UNPKG

fumadocs-ui

Version:

The Radix UI version of Fumadocs UI

328 lines (327 loc) 14.6 kB
"use client"; import { cn } from "../../../utils/cn.js"; import { buttonVariants } from "../../../components/ui/button.js"; import { mergeRefs } from "../../../utils/merge-refs.js"; import { Popover, PopoverContent, PopoverTrigger } from "../../../components/ui/popover.js"; import { SearchTrigger } from "../../shared/slots/search-trigger.js"; import { LinkItem } from "../../shared/client.js"; import { isLayoutTabActive } from "../../shared/index.js"; import { SidebarCollapseTrigger as SidebarCollapseTrigger$1, SidebarContent as SidebarContent$1, SidebarDrawerContent, SidebarDrawerOverlay, 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, SidebarTrigger as SidebarTrigger$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 { useDocsLayout } from "../client.js"; import Link from "fumadocs-core/link"; import { usePathname } from "fumadocs-core/framework"; import { Fragment, jsx, jsxs } from "react/jsx-runtime"; import { Check, ChevronDown, ChevronsUpDown, Languages, SidebarIcon } from "lucide-react"; import { cva } from "class-variance-authority"; import { useMemo, useRef, useState } from "react"; //#region src/layouts/docs/slots/sidebar.tsx 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:inset-s-2.5" } } }); const { useSidebar } = base_exports; function SidebarProvider(props) { return /* @__PURE__ */ jsx(SidebarProvider$1, { ...props }); } function Sidebar({ footer, banner, collapsible = true, components, ...rest }) { const { menuItems, slots, props: { tabs, nav, tabMode } } = useDocsLayout(); const iconLinks = menuItems.filter((item) => item.type === "icon"); const viewport = /* @__PURE__ */ jsxs(SidebarViewport, { 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 })] }); return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(SidebarContent, { ...rest, children: [ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 p-4 pb-2", children: [ /* @__PURE__ */ jsxs("div", { className: "flex", children: [ slots.navTitle && /* @__PURE__ */ jsx(slots.navTitle, { className: "inline-flex text-[0.9375rem] items-center gap-2.5 font-medium me-auto" }), nav?.children, collapsible && /* @__PURE__ */ jsx(SidebarCollapseTrigger, { className: cn(buttonVariants({ color: "ghost", size: "icon-sm", className: "mb-auto text-fd-muted-foreground" })), children: /* @__PURE__ */ jsx(SidebarIcon, {}) }) ] }), slots.searchTrigger && /* @__PURE__ */ jsx(slots.searchTrigger.full, { hideIfDisabled: true }), tabs.length > 0 && tabMode === "auto" && /* @__PURE__ */ jsx(SidebarTabsDropdown, { tabs }), banner ] }), viewport, (slots.languageSelect || iconLinks.length > 0 || slots.themeSwitch || footer) && /* @__PURE__ */ jsxs("div", { className: "flex flex-col p-4 pt-2", children: [ slots.languageSelect && /* @__PURE__ */ jsxs(slots.languageSelect.root, { variant: "secondary", className: "text-fd-muted-foreground text-start justify-start bg-fd-secondary/50 mb-2", children: [ /* @__PURE__ */ jsx(Languages, { className: "size-4.5" }), /* @__PURE__ */ jsx(slots.languageSelect.text, {}), /* @__PURE__ */ jsx(ChevronDown, { className: "ms-auto size-3.5" }) ] }), /* @__PURE__ */ jsxs("div", { className: "flex text-fd-muted-foreground items-center border bg-fd-secondary/50 p-0.5 pe-0 rounded-lg empty:hidden", children: [iconLinks.map((item, i) => /* @__PURE__ */ jsx(LinkItem, { item, className: cn(buttonVariants({ size: "icon-sm", color: "ghost" })), "aria-label": item.label, children: item.icon }, i)), slots.themeSwitch && /* @__PURE__ */ jsx(slots.themeSwitch, { className: "px-1 py-0 border-y-0 border-e-0 rounded-none ms-auto *:rounded-md" })] }), footer ] }) ] }), /* @__PURE__ */ jsxs(SidebarDrawer, { children: [ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 p-4 pb-2", children: [ /* @__PURE__ */ jsxs("div", { className: "flex text-fd-muted-foreground items-center gap-1.5", children: [ /* @__PURE__ */ jsx("div", { className: "flex flex-1", children: iconLinks.map((item, i) => /* @__PURE__ */ jsx(LinkItem, { item, className: cn(buttonVariants({ size: "icon-sm", color: "ghost", className: "p-2" })), "aria-label": item.label, children: item.icon }, i)) }), slots.languageSelect && /* @__PURE__ */ jsxs(slots.languageSelect.root, { children: [/* @__PURE__ */ jsx(Languages, { className: "size-4.5" }), /* @__PURE__ */ jsx(slots.languageSelect.text, {})] }), slots.themeSwitch && /* @__PURE__ */ jsx(slots.themeSwitch, { className: "p-0" }), /* @__PURE__ */ jsx(SidebarTrigger, { className: cn(buttonVariants({ color: "ghost", size: "icon-sm", className: "p-2" })), children: /* @__PURE__ */ jsx(SidebarIcon, {}) }) ] }), tabs.length > 0 && /* @__PURE__ */ jsx(SidebarTabsDropdown, { tabs }), banner ] }), viewport, /* @__PURE__ */ jsx("div", { className: "flex flex-col border-t p-4 pt-2 empty:hidden", children: footer }) ] })] }); } function SidebarFolder(props) { return /* @__PURE__ */ jsx(SidebarFolder$1, { ...props }); } function SidebarCollapseTrigger(props) { return /* @__PURE__ */ jsx(SidebarCollapseTrigger$1, { ...props }); } function SidebarTrigger(props) { return /* @__PURE__ */ jsx(SidebarTrigger$1, { ...props }); } function SidebarContent({ ref: refProp, className, children, ...props }) { const ref = useRef(null); return /* @__PURE__ */ jsx(SidebarContent$1, { children: ({ collapsed, hovered, ref: asideRef, ...rest }) => /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", { "data-sidebar-placeholder": "", className: "sticky top-(--fd-docs-row-1) z-20 [grid-area:sidebar] pointer-events-none *:pointer-events-auto h-[calc(var(--fd-docs-height)-var(--fd-docs-row-1))] md:layout:[--fd-sidebar-width:268px] max-md:hidden", children: [collapsed && /* @__PURE__ */ jsx("div", { className: "absolute inset-s-0 inset-y-0 w-4", ...rest }), /* @__PURE__ */ jsx("aside", { id: "nd-sidebar", ref: mergeRefs(ref, refProp, asideRef), "data-collapsed": collapsed, "data-hovered": collapsed && hovered, className: cn("absolute flex flex-col w-full inset-s-0 inset-y-0 items-end bg-fd-card text-sm border-e duration-250 *:w-(--fd-sidebar-width)", collapsed && ["inset-y-2 rounded-xl transition-transform border w-(--fd-sidebar-width)", hovered ? "shadow-lg translate-x-2 rtl:-translate-x-2" : "-translate-x-(--fd-sidebar-width) rtl:translate-x-full"], ref.current && ref.current.getAttribute("data-collapsed") === "true" !== collapsed && "transition-[width,inset-block,translate,background-color]", className), ...props, ...rest, children })] }), /* @__PURE__ */ jsxs("div", { "data-sidebar-panel": "", className: cn("fixed flex top-[calc(--spacing(4)+var(--fd-docs-row-3))] inset-s-4 shadow-lg transition-opacity rounded-xl p-0.5 border bg-fd-muted text-fd-muted-foreground z-10", (!collapsed || hovered) && "pointer-events-none opacity-0"), children: [/* @__PURE__ */ jsx(SidebarCollapseTrigger$1, { className: cn(buttonVariants({ color: "ghost", size: "icon-sm", className: "rounded-lg" })), children: /* @__PURE__ */ jsx(SidebarIcon, {}) }), /* @__PURE__ */ jsx(SearchTrigger, { className: "rounded-lg", hideIfDisabled: true })] })] }) }); } function SidebarDrawer({ children, className, ...props }) { return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(SidebarDrawerOverlay, { className: "fixed z-40 inset-0 backdrop-blur-xs data-[state=open]:animate-fd-fade-in data-[state=closed]:animate-fd-fade-out" }), /* @__PURE__ */ jsx(SidebarDrawerContent, { className: cn("fixed text-[0.9375rem] flex flex-col shadow-lg border-s inset-e-0 inset-y-0 w-[85%] max-w-[380px] z-40 bg-fd-background data-[state=open]:animate-fd-sidebar-in data-[state=closed]:animate-fd-sidebar-out", className), ...props, children })] }); } function SidebarSeparator({ className, style, children, ...props }) { const depth = useFolderDepth(); return /* @__PURE__ */ jsx(SidebarSeparator$1, { className: cn("inline-flex items-center gap-2 mb-1 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(SidebarItem$1, { 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(SidebarFolderTrigger$1, { 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(SidebarFolderLink$1, { 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(); return /* @__PURE__ */ jsx(SidebarFolderContent$1, { className: cn("relative", depth === 1 && "before:content-[''] before:absolute before:w-px before:inset-y-1 before:bg-fd-border before:inset-s-2.5", className), ...props, children: /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-0.5 pt-0.5", children }) }); } function SidebarTabsDropdown({ tabs, placeholder, ...props }) { const [open, setOpen] = useState(false); const { closeOnRedirect } = useSidebar(); const pathname = usePathname(); const selected = useMemo(() => { return tabs.findLast((item) => isLayoutTabActive(item, pathname)); }, [tabs, pathname]); const onClick = () => { closeOnRedirect.current = false; setOpen(false); }; const item = selected ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", { className: "size-9 shrink-0 empty:hidden md:size-5", children: selected.icon }), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: selected.title }), /* @__PURE__ */ jsx("p", { className: "text-sm text-fd-muted-foreground empty:hidden md:hidden", children: selected.description })] })] }) : placeholder; return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: setOpen, children: [item && /* @__PURE__ */ jsxs(PopoverTrigger, { ...props, className: cn("flex items-center gap-2 rounded-lg p-2 border bg-fd-secondary/50 text-start text-fd-secondary-foreground transition-colors hover:bg-fd-accent data-[state=open]:bg-fd-accent data-[state=open]:text-fd-accent-foreground", props.className), children: [item, /* @__PURE__ */ jsx(ChevronsUpDown, { className: "shrink-0 ms-auto size-4 text-fd-muted-foreground" })] }), /* @__PURE__ */ jsx(PopoverContent, { className: "flex flex-col gap-1 w-(--radix-popover-trigger-width) p-1 fd-scroll-container", children: tabs.map((item) => { const isActive = selected && item.url === selected.url; if (!isActive && item.unlisted) return; return /* @__PURE__ */ jsxs(Link, { href: item.url, onClick, ...item.props, className: cn("flex items-center gap-2 rounded-lg p-1.5 hover:bg-fd-accent hover:text-fd-accent-foreground", item.props?.className), children: [ /* @__PURE__ */ jsx("div", { className: "shrink-0 size-9 md:mb-auto md:size-5 empty:hidden", children: item.icon }), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("p", { className: "text-sm font-medium leading-none", children: item.title }), /* @__PURE__ */ jsx("p", { className: "text-[0.8125rem] text-fd-muted-foreground mt-1 empty:hidden", children: item.description })] }), /* @__PURE__ */ jsx(Check, { className: cn("shrink-0 ms-auto size-3.5 text-fd-primary", !isActive && "invisible") }) ] }, item.url); }) })] }); } function getItemOffset(depth) { return `calc(${2 + 3 * depth} * var(--spacing))`; } const SidebarPageTree = createPageTreeRenderer({ SidebarFolder, SidebarFolderContent, SidebarFolderLink, SidebarFolderTrigger, SidebarItem, SidebarSeparator }); const SidebarLinkItem = createLinkItemRenderer({ SidebarFolder, SidebarFolderContent, SidebarFolderLink, SidebarFolderTrigger, SidebarItem }); //#endregion export { Sidebar, SidebarProvider, SidebarTrigger, useSidebar };