Initial commit
This commit is contained in:
commit
bcca176137
88 files changed
+8725
No files matched your search
@@ -0,0 +1,29 @@
|
||||
"use client";
|
||||
|
||||
import { useLayoutEffect } from "react";
|
||||
|
||||
type Mode = "scroll" | "offset";
|
||||
|
||||
export function useHeightVar(
|
||||
ref: React.RefObject<HTMLElement | null>,
|
||||
cssVarName: string,
|
||||
mode: Mode = "scroll",
|
||||
trigger?: unknown,
|
||||
) {
|
||||
useLayoutEffect(() => {
|
||||
const el = ref.current;
|
||||
if (!el) return;
|
||||
|
||||
const measure = () => {
|
||||
const h = mode === "scroll" ? el.scrollHeight : el.offsetHeight;
|
||||
el.style.setProperty(cssVarName, `${h}px`);
|
||||
};
|
||||
|
||||
measure();
|
||||
|
||||
const ro = new ResizeObserver(measure);
|
||||
ro.observe(el);
|
||||
|
||||
return () => ro.disconnect();
|
||||
}, [ref, cssVarName, mode, trigger]);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export function useIsDesktop(breakpoint = 1024) {
|
||||
const [isDesktop, setIsDesktop] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const mql = window.matchMedia(`(min-width: ${breakpoint}px)`);
|
||||
|
||||
const update = () => setIsDesktop(mql.matches);
|
||||
|
||||
update();
|
||||
|
||||
if (mql.addEventListener) {
|
||||
mql.addEventListener("change", update);
|
||||
return () => mql.removeEventListener("change", update);
|
||||
}
|
||||
|
||||
mql.addListener(update);
|
||||
return () => mql.removeListener(update);
|
||||
}, [breakpoint]);
|
||||
|
||||
return isDesktop;
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
|
||||
type Options = {
|
||||
supportsHover: boolean;
|
||||
desktopBp?: number;
|
||||
};
|
||||
|
||||
export function useNavToggleMenu({ desktopBp = 1024 }: Options) {
|
||||
const [menuOpen, setMenuOpen] = useState(false);
|
||||
const [openSubmenuId, setOpenSubmenuId] = useState<string | null>(null);
|
||||
const [noTransition, setNoTransition] = useState(false);
|
||||
|
||||
const rafRef = useRef<number | null>(null);
|
||||
|
||||
const clearRaf = useCallback(() => {
|
||||
if (rafRef.current != null) {
|
||||
cancelAnimationFrame(rafRef.current);
|
||||
rafRef.current = null;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const closeMenu = useCallback(() => {
|
||||
setMenuOpen(false);
|
||||
setOpenSubmenuId(null);
|
||||
}, []);
|
||||
|
||||
const closeInstantNoAnim = useCallback(() => {
|
||||
clearRaf();
|
||||
setNoTransition(true);
|
||||
closeMenu();
|
||||
|
||||
rafRef.current = requestAnimationFrame(() => {
|
||||
setNoTransition(false);
|
||||
rafRef.current = null;
|
||||
});
|
||||
}, [clearRaf, closeMenu]);
|
||||
|
||||
const toggleMenu = useCallback(() => {
|
||||
clearRaf();
|
||||
setNoTransition(false);
|
||||
|
||||
setMenuOpen((prev) => {
|
||||
const next = !prev;
|
||||
if (!next) {
|
||||
setOpenSubmenuId(null);
|
||||
}
|
||||
return next;
|
||||
});
|
||||
}, [clearRaf]);
|
||||
|
||||
const toggleSubmenu = useCallback((id: string) => {
|
||||
setOpenSubmenuId((cur) => (cur === id ? null : id));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const mql = window.matchMedia(`(min-width: ${desktopBp}px)`);
|
||||
|
||||
const onChange = () => {
|
||||
clearRaf();
|
||||
setNoTransition(true);
|
||||
closeMenu();
|
||||
requestAnimationFrame(() => setNoTransition(false));
|
||||
};
|
||||
|
||||
if (mql.addEventListener) {
|
||||
mql.addEventListener("change", onChange);
|
||||
} else {
|
||||
mql.addListener(onChange);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (mql.removeEventListener) {
|
||||
mql.removeEventListener("change", onChange);
|
||||
} else {
|
||||
mql.removeListener(onChange);
|
||||
}
|
||||
};
|
||||
}, [desktopBp, clearRaf, closeMenu]);
|
||||
|
||||
useEffect(() => clearRaf, [clearRaf]);
|
||||
|
||||
return {
|
||||
menuOpen,
|
||||
openSubmenuId,
|
||||
noTransition,
|
||||
setMenuOpen,
|
||||
setOpenSubmenuId,
|
||||
toggleMenu,
|
||||
toggleSubmenu,
|
||||
closeMenu,
|
||||
closeInstantNoAnim,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export function useSupportsHover() {
|
||||
const [supportsHover, setSupportsHover] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const mql = window.matchMedia("(hover: hover) and (pointer: fine)");
|
||||
|
||||
const update = () => setSupportsHover(mql.matches);
|
||||
|
||||
update();
|
||||
|
||||
if (mql.addEventListener) {
|
||||
mql.addEventListener("change", update);
|
||||
return () => mql.removeEventListener("change", update);
|
||||
}
|
||||
|
||||
mql.addListener(update);
|
||||
return () => mql.removeListener(update);
|
||||
}, []);
|
||||
|
||||
return supportsHover;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
|
||||
type Options = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export function useToggleBodyOverflow(locked: boolean, options: Options = {}) {
|
||||
const { className = "has-menu-open" } = options;
|
||||
|
||||
useEffect(() => {
|
||||
const root = document.documentElement;
|
||||
|
||||
root.classList.toggle(className, locked);
|
||||
|
||||
const prevOverflow = document.body.style.overflow;
|
||||
document.body.style.overflow = locked ? "hidden" : "";
|
||||
|
||||
return () => {
|
||||
root.classList.remove(className);
|
||||
document.body.style.overflow = prevOverflow;
|
||||
};
|
||||
}, [locked, className]);
|
||||
}
|
||||
Reference in new issue
Block a user