mirror of
https://github.com/versia-pub/docs.git
synced 2026-03-13 02:49:16 +01:00
feat: ✨ Initialize rewrite
This commit is contained in:
parent
47ce9bd9f8
commit
f39d34b769
143 changed files with 7257 additions and 4032 deletions
182
components/MobileNavigation.tsx
Normal file
182
components/MobileNavigation.tsx
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
"use client";
|
||||
|
||||
import {
|
||||
Dialog,
|
||||
DialogPanel,
|
||||
Transition,
|
||||
TransitionChild,
|
||||
} from "@headlessui/react";
|
||||
import { motion } from "framer-motion";
|
||||
import { usePathname, useSearchParams } from "next/navigation";
|
||||
import {
|
||||
type ComponentPropsWithoutRef,
|
||||
type MouseEvent,
|
||||
Suspense,
|
||||
createContext,
|
||||
useContext,
|
||||
useEffect,
|
||||
useRef,
|
||||
} from "react";
|
||||
import { create } from "zustand";
|
||||
|
||||
import { Header } from "./Header";
|
||||
import { Navigation } from "./Navigation";
|
||||
|
||||
function MenuIcon(props: ComponentPropsWithoutRef<"svg">) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 10 9"
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<path d="M.5 1h9M.5 8h9M.5 4.5h9" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function XIcon(props: ComponentPropsWithoutRef<"svg">) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 10 9"
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<path d="m1.5 1 7 7M8.5 1l-7 7" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
const IsInsideMobileNavigationContext = createContext(false);
|
||||
|
||||
function MobileNavigationDialog({
|
||||
isOpen,
|
||||
close,
|
||||
}: {
|
||||
isOpen: boolean;
|
||||
close: () => void;
|
||||
}) {
|
||||
const pathname = usePathname();
|
||||
const searchParams = useSearchParams();
|
||||
const initialPathname = useRef(pathname).current;
|
||||
const initialSearchParams = useRef(searchParams).current;
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
pathname !== initialPathname ||
|
||||
searchParams !== initialSearchParams
|
||||
) {
|
||||
close();
|
||||
}
|
||||
}, [pathname, searchParams, close, initialPathname, initialSearchParams]);
|
||||
|
||||
function onClickDialog(event: MouseEvent<HTMLDivElement>) {
|
||||
if (!(event.target instanceof HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const link = event.target.closest("a");
|
||||
if (
|
||||
link &&
|
||||
link.pathname + link.search + link.hash ===
|
||||
window.location.pathname +
|
||||
window.location.search +
|
||||
window.location.hash
|
||||
) {
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Transition show={isOpen}>
|
||||
<Dialog
|
||||
onClickCapture={onClickDialog}
|
||||
onClose={close}
|
||||
className="fixed inset-0 z-50 lg:hidden"
|
||||
>
|
||||
<TransitionChild
|
||||
enter="duration-300 ease-out"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="duration-200 ease-in"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 top-14 bg-zinc-400/20 backdrop-blur-sm dark:bg-black/40" />
|
||||
</TransitionChild>
|
||||
|
||||
<DialogPanel>
|
||||
<TransitionChild
|
||||
enter="duration-300 ease-out"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="duration-200 ease-in"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<Header />
|
||||
</TransitionChild>
|
||||
|
||||
<TransitionChild
|
||||
enter="duration-500 ease-in-out"
|
||||
enterFrom="-translate-x-full"
|
||||
enterTo="translate-x-0"
|
||||
leave="duration-500 ease-in-out"
|
||||
leaveFrom="translate-x-0"
|
||||
leaveTo="-translate-x-full"
|
||||
>
|
||||
<motion.div
|
||||
layoutScroll={true}
|
||||
className="fixed bottom-0 left-0 top-14 w-full overflow-y-auto bg-white px-4 pb-4 pt-6 shadow-lg shadow-zinc-900/10 ring-1 ring-zinc-900/7.5 min-[416px]:max-w-sm sm:px-6 sm:pb-10 dark:bg-zinc-900 dark:ring-zinc-800"
|
||||
>
|
||||
<Navigation />
|
||||
</motion.div>
|
||||
</TransitionChild>
|
||||
</DialogPanel>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
);
|
||||
}
|
||||
|
||||
export function useIsInsideMobileNavigation() {
|
||||
return useContext(IsInsideMobileNavigationContext);
|
||||
}
|
||||
|
||||
export const useMobileNavigationStore = create<{
|
||||
isOpen: boolean;
|
||||
open: () => void;
|
||||
close: () => void;
|
||||
toggle: () => void;
|
||||
}>()((set) => ({
|
||||
isOpen: false,
|
||||
open: () => set({ isOpen: true }),
|
||||
close: () => set({ isOpen: false }),
|
||||
toggle: () => set((state) => ({ isOpen: !state.isOpen })),
|
||||
}));
|
||||
|
||||
export function MobileNavigation() {
|
||||
const isInsideMobileNavigation = useIsInsideMobileNavigation();
|
||||
const { isOpen, toggle, close } = useMobileNavigationStore();
|
||||
const ToggleIcon = isOpen ? XIcon : MenuIcon;
|
||||
|
||||
return (
|
||||
<IsInsideMobileNavigationContext.Provider value={true}>
|
||||
<button
|
||||
type="button"
|
||||
className="flex h-6 w-6 items-center justify-center rounded-md transition hover:bg-zinc-900/5 dark:hover:bg-white/5"
|
||||
aria-label="Toggle navigation"
|
||||
onClick={toggle}
|
||||
>
|
||||
<ToggleIcon className="w-2.5 stroke-zinc-900 dark:stroke-white" />
|
||||
</button>
|
||||
{!isInsideMobileNavigation && (
|
||||
<Suspense fallback={null}>
|
||||
<MobileNavigationDialog isOpen={isOpen} close={close} />
|
||||
</Suspense>
|
||||
)}
|
||||
</IsInsideMobileNavigationContext.Provider>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue