fumadocs-ui
Version:
The Radix UI version of Fumadocs UI
299 lines (298 loc) • 13.7 kB
JavaScript
"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 { 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 { useNotebookLayout } 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, ChevronsUpDown, Languages, SidebarIcon, X } from "lucide-react";
import { cva } from "class-variance-authority";
import { createElement, useMemo, useRef, useState } from "react";
//#region src/layouts/notebook/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" }
} });
function getItemOffset(depth) {
return `calc(${2 + 3 * depth} * var(--spacing))`;
}
const { useSidebar } = base_exports;
function SidebarProvider(props) {
return /* @__PURE__ */ jsx(SidebarProvider$1, { ...props });
}
function SidebarTrigger(props) {
return /* @__PURE__ */ jsx(SidebarTrigger$1, { ...props });
}
function SidebarCollapseTrigger(props) {
return /* @__PURE__ */ jsx(SidebarCollapseTrigger$1, { ...props });
}
function SidebarContent({ ref: refProp, className, children, ...props }) {
const { props: { nav } } = useNotebookLayout();
const navMode = nav?.mode ?? "auto";
const ref = useRef(null);
return /* @__PURE__ */ jsx(SidebarContent$1, { children: ({ collapsed, hovered, ref: asideRef, ...rest }) => /* @__PURE__ */ jsxs("div", {
"data-sidebar-placeholder": "",
className: cn("sticky z-20 [grid-area:sidebar] pointer-events-none *:pointer-events-auto md:layout:[--fd-sidebar-width:268px] max-md:hidden", navMode === "auto" ? "top-(--fd-docs-row-1) h-[calc(var(--fd-docs-height)-var(--fd-docs-row-1))]" : "top-(--fd-docs-row-2) h-[calc(var(--fd-docs-height)-var(--fd-docs-row-2))]"),
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 text-sm duration-250 *:w-(--fd-sidebar-width)", navMode === "auto" && "bg-fd-card border-e", collapsed && ["inset-y-2 rounded-xl bg-fd-card 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
})]
}) });
}
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 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(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 }) {
return /* @__PURE__ */ jsx(SidebarFolderContent$1, {
className: cn("relative", useFolderDepth() === 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
})
});
}
const SidebarPageTree = createPageTreeRenderer({
SidebarFolder,
SidebarFolderContent,
SidebarFolderLink,
SidebarFolderTrigger,
SidebarItem,
SidebarSeparator
});
const SidebarLinkItem = createLinkItemRenderer({
SidebarFolder,
SidebarFolderContent,
SidebarFolderLink,
SidebarFolderTrigger,
SidebarItem
});
function Sidebar({ banner, footer, components, collapsible = true, ...rest }) {
const { menuItems, slots, props: { nav, tabs, tabMode } } = useNotebookLayout();
const navMode = nav?.mode ?? "auto";
const iconLinks = menuItems.filter((item) => item.type === "icon");
function renderHeader(props) {
if (typeof banner === "function") return createElement(banner, props);
return /* @__PURE__ */ jsxs("div", {
...props,
className: cn("flex flex-col gap-3 p-4 pb-2 empty:hidden", props.className),
children: [props.children, banner]
});
}
function renderFooter(props) {
if (typeof footer === "function") return createElement(footer, props);
return /* @__PURE__ */ jsxs("div", {
...props,
children: [props.children, footer]
});
}
const viewport = /* @__PURE__ */ jsxs(SidebarViewport, { children: [menuItems.filter((item) => item.type !== "icon").map((item, i, arr) => /* @__PURE__ */ jsx(SidebarLinkItem, {
item,
className: cn("lg:hidden", i === arr.length - 1 && "mb-3")
}, i)), /* @__PURE__ */ jsx(SidebarPageTree, { ...components })] });
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(SidebarContent, {
...rest,
children: [
renderHeader({ children: /* @__PURE__ */ jsxs(Fragment, { children: [navMode === "auto" && /* @__PURE__ */ jsxs("div", {
className: "flex justify-between",
children: [
slots.navTitle && /* @__PURE__ */ jsx(slots.navTitle, { className: "inline-flex items-center gap-2.5 font-medium" }),
nav?.children,
collapsible && /* @__PURE__ */ jsx(SidebarCollapseTrigger, {
className: cn(buttonVariants({
color: "ghost",
size: "icon-sm",
className: "mt-px mb-auto text-fd-muted-foreground"
})),
children: /* @__PURE__ */ jsx(SidebarIcon, {})
})
]
}), tabs.length > 0 && /* @__PURE__ */ jsx(SidebarTabsDropdown, {
options: tabs,
className: cn(tabMode === "navbar" && "lg:hidden")
})] }) }),
viewport,
renderFooter({
className: cn("hidden flex-row text-fd-muted-foreground items-center border-t px-4 py-2.5", iconLinks.length > 0 && "max-lg:flex"),
children: iconLinks.map((item, i) => /* @__PURE__ */ jsx(LinkItem, {
item,
className: cn(buttonVariants({
size: "icon-sm",
color: "ghost",
className: "lg:hidden"
})),
"aria-label": item.label,
children: item.icon
}, i))
})
]
}), /* @__PURE__ */ jsxs(SidebarDrawer, {
...rest,
children: [
renderHeader({ children: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(SidebarTrigger, {
className: cn(buttonVariants({
size: "icon-sm",
color: "ghost",
className: "ms-auto text-fd-muted-foreground"
})),
children: /* @__PURE__ */ jsx(X, {})
}), tabs.length > 0 && /* @__PURE__ */ jsx(SidebarTabsDropdown, { options: tabs })] }) }),
viewport,
renderFooter({
className: cn("hidden flex-row text-fd-muted-foreground items-center border-t p-4 pt-2 justify-end", (slots.languageSelect || slots.themeSwitch) && "flex", iconLinks.length > 0 && "max-lg:flex"),
children: /* @__PURE__ */ jsxs(Fragment, { children: [
iconLinks.map((item, i) => /* @__PURE__ */ jsx(LinkItem, {
item,
className: cn(buttonVariants({
size: "icon-sm",
color: "ghost"
}), "text-fd-muted-foreground lg:hidden", i === iconLinks.length - 1 && "me-auto"),
"aria-label": item.label,
children: item.icon
}, i)),
slots.languageSelect && /* @__PURE__ */ jsx(slots.languageSelect.root, { children: /* @__PURE__ */ jsx(Languages, { className: "size-4.5 text-fd-muted-foreground" }) }),
slots.themeSwitch && /* @__PURE__ */ jsx(slots.themeSwitch, {})
] })
})
]
})] });
}
function SidebarTabsDropdown({ options, placeholder, ...props }) {
const [open, setOpen] = useState(false);
const { closeOnRedirect } = useSidebar();
const pathname = usePathname();
const selected = useMemo(() => {
return options.findLast((item) => isLayoutTabActive(item, pathname));
}, [options, 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: options.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);
})
})]
});
}
//#endregion
export { Sidebar, SidebarCollapseTrigger, SidebarProvider, SidebarTrigger, useSidebar };