UNPKG

fumadocs-ui

Version:

The Radix UI version of Fumadocs UI

244 lines (243 loc) 8.34 kB
"use client"; import { cn } from "../../../utils/cn.js"; import { mergeRefs } from "../../../utils/merge-refs.js"; import { SidebarFolder as SidebarFolder$1, SidebarFolderContent as SidebarFolderContent$1, SidebarFolderLink as SidebarFolderLink$1, SidebarFolderTrigger as SidebarFolderTrigger$1, SidebarItem as SidebarItem$1, SidebarProvider as SidebarProvider$1, SidebarSeparator as SidebarSeparator$1, SidebarViewport, base_exports, useFolder, useFolderDepth } from "../../../components/sidebar/base.js"; import { createPageTreeRenderer } from "../../../components/sidebar/page-tree.js"; import { createLinkItemRenderer } from "../../../components/sidebar/link-item.js"; import { useFluxLayout } from "../index.js"; import { jsx, jsxs } from "react/jsx-runtime"; import { SidebarIcon, XIcon } from "lucide-react"; import { cva } from "class-variance-authority"; import { useEffect, useEffectEvent, useRef, useState } from "react"; import { useTranslations } from "@fuma-translate/react"; import { AnimatePresence, motion } from "motion/react"; import { RemoveScroll } from "react-remove-scroll"; //#region src/layouts/flux/slots/sidebar.tsx const MotionSidebarItem = motion.create(SidebarItem$1); const MotionSidebarFolderTrigger = motion.create(SidebarFolderTrigger$1); const MotionSidebarFolderLink = motion.create(SidebarFolderLink$1); const MotionSidebarFolderContent = motion.create(SidebarFolderContent$1); const itemVariants = cva("relative flex flex-row items-center gap-2 rounded-lg p-2 text-start text-fd-muted-foreground wrap-anywhere [&_svg]:size-4 [&_svg]:shrink-0", { variants: { variant: { link: "transition-colors hover:bg-fd-accent/50 hover:text-fd-accent-foreground/80 hover:transition-none data-[active=true]:bg-fd-primary/10 data-[active=true]:text-fd-primary data-[active=true]:hover:transition-colors", button: "transition-colors hover:bg-fd-accent/50 hover:text-fd-accent-foreground/80 hover:transition-none" }, highlight: { true: "data-[active=true]:before:content-[''] data-[active=true]:before:bg-fd-primary data-[active=true]:before:absolute data-[active=true]:before:w-px data-[active=true]:before:inset-y-2.5 data-[active=true]:before:start-2.5" } } }); function getItemOffset(depth) { return `calc(${2 + 3 * depth} * var(--spacing))`; } const { useSidebar } = base_exports; function SidebarProvider(props) { return /* @__PURE__ */ jsx(SidebarProvider$1, { ...props }); } function Sidebar({ footer, banner, components, ...rest }) { const { menuItems } = useFluxLayout(); return /* @__PURE__ */ jsxs(SidebarContent, { ...rest, children: [ /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-3 p-4 pb-2 empty:hidden", children: banner }), /* @__PURE__ */ jsxs(SidebarViewport, { viewport: { className: "*:gap-0!" }, children: [menuItems.filter((v) => v.type !== "icon").map((item, i, list) => /* @__PURE__ */ jsx(SidebarLinkItem, { item, className: cn(i === list.length - 1 && "mb-4") }, i)), /* @__PURE__ */ jsx(SidebarPageTree, { ...components })] }), footer ] }); } function SidebarTrigger(props) { const { open, setOpen } = useSidebar(); const t = useTranslations({ note: "sidebar" }); return /* @__PURE__ */ jsx("button", { type: "button", "aria-label": open ? t("Close Sidebar", { note: "aria-label" }) : t("Open Sidebar", { note: "aria-label" }), "aria-expanded": open, "aria-controls": "nd-sidebar", onClick: () => setOpen((prev) => !prev), ...props, children: /* @__PURE__ */ jsx(AnimatePresence, { mode: "wait", children: /* @__PURE__ */ jsx(motion.span, { transition: { duration: .2 }, initial: { y: "100%", opacity: 0 }, animate: { y: 0, opacity: 1 }, exit: { y: "100%", opacity: 0 }, children: open ? /* @__PURE__ */ jsx(XIcon, {}) : /* @__PURE__ */ jsx(SidebarIcon, {}) }, open ? "open" : "closed") }) }); } function SidebarContent({ ref: refProp, className, children, ...props }) { const ref = useRef(null); const [blockScroll, setBlockScroll] = useState(false); const { open, setOpen } = useSidebar(); const listener = useEffectEvent((e) => { if (open && e.key === "Escape") { setOpen(false); e.preventDefault(); } }); useEffect(() => { window.addEventListener("keydown", listener); return () => { window.removeEventListener("keydown", listener); }; }, []); if (open && !blockScroll) setBlockScroll(true); return /* @__PURE__ */ jsx(RemoveScroll, { enabled: blockScroll, children: /* @__PURE__ */ jsx(motion.div, { className: cn("fixed inset-0 py-10 z-30 backdrop-blur-md bg-fd-background/60", !open && "pointer-events-none"), initial: "hide", variants: { show: { opacity: 1 }, hide: { opacity: 0 } }, animate: open ? "show" : "hide", exit: "hide", onClick: () => { setOpen(false); }, onAnimationComplete: (definition) => { if (definition === "hide") setBlockScroll(false); }, children: /* @__PURE__ */ jsx(motion.div, { className: "absolute top-0 min-h-0 inset-x-0 bottom-26 overflow-y-auto fd-scroll-container pr-(--removed-body-scroll-bar-size,0) py-16 mask-[linear-gradient(to_bottom,transparent,white_--spacing(14),white_calc(100%---spacing(14)),transparent)] lg:text-sm", variants: { show: { y: 0, opacity: 1 }, hide: { y: "80%", opacity: 0 } }, transition: { duration: .4, ease: [ .16, 1, .3, 1 ] }, children: /* @__PURE__ */ jsx(motion.aside, { id: "nd-sidebar", ref: mergeRefs(ref, refProp), className: cn("mx-auto sm:max-w-[400px]", className), onClick: (e) => e.stopPropagation(), ...props, children }) }) }) }); } function SidebarFolder(props) { return /* @__PURE__ */ jsx(SidebarFolder$1, { ...props }); } function SidebarSeparator({ className, style, children, ...props }) { const depth = useFolderDepth(); return /* @__PURE__ */ jsx(SidebarSeparator$1, { className: cn("inline-flex items-center gap-2 mb-1.5 px-2 mt-6 empty:mb-0 [&_svg]:size-4 [&_svg]:shrink-0", depth === 0 && "first:mt-0", className), style: { paddingInlineStart: getItemOffset(depth), ...style }, ...props, children }); } function SidebarItem({ className, style, children, ...props }) { const depth = useFolderDepth(); return /* @__PURE__ */ jsx(MotionSidebarItem, { className: cn(itemVariants({ variant: "link", highlight: depth >= 1 }), className), style: { paddingInlineStart: getItemOffset(depth), ...style }, ...props, children }); } function SidebarFolderTrigger({ className, style, ...props }) { const { depth, collapsible } = useFolder(); return /* @__PURE__ */ jsx(MotionSidebarFolderTrigger, { className: cn(itemVariants({ variant: collapsible ? "button" : null }), "w-full", className), style: { paddingInlineStart: getItemOffset(depth - 1), ...style }, ...props, children: props.children }); } function SidebarFolderLink({ className, style, ...props }) { const depth = useFolderDepth(); return /* @__PURE__ */ jsx(MotionSidebarFolderLink, { className: cn(itemVariants({ variant: "link", highlight: depth > 1 }), "w-full", className), style: { paddingInlineStart: getItemOffset(depth - 1), ...style }, ...props, children: props.children }); } function SidebarFolderContent({ className, children, ...props }) { const depth = useFolderDepth(); const { open } = useFolder(); return /* @__PURE__ */ jsx(MotionSidebarFolderContent, { className: cn("relative", depth === 1 && "before:content-[''] before:absolute before:w-px before:inset-y-1 before:bg-fd-border before:start-2.5", className), ...props, children: /* @__PURE__ */ jsx(motion.div, { initial: "hide", animate: open ? "show" : "hide", exit: "hide", variants: { show: { opacity: 1 }, hide: { opacity: 0 } }, children }) }); } const SidebarPageTree = createPageTreeRenderer({ SidebarFolder, SidebarFolderContent, SidebarSeparator, SidebarFolderLink, SidebarFolderTrigger, SidebarItem }); const SidebarLinkItem = createLinkItemRenderer({ SidebarFolder, SidebarFolderContent, SidebarFolderLink, SidebarFolderTrigger, SidebarItem }); //#endregion export { Sidebar, SidebarProvider, SidebarTrigger, useSidebar };