fumadocs-ui
Version:
The Radix UI version of Fumadocs UI
244 lines (243 loc) • 8.34 kB
JavaScript
"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 };