UNPKG

fumadocs-ui

Version:

The Radix UI version of Fumadocs UI

262 lines (259 loc) 8.04 kB
'use client'; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "../ui/collapsible.js"; import { ScrollArea, ScrollViewport } from "../ui/scroll-area.js"; import Link from "fumadocs-core/link"; import { usePathname } from "fumadocs-core/framework"; import { cn } from "@fumadocs/ui/cn"; import { jsx, jsxs } from "react/jsx-runtime"; import { ChevronDown, ExternalLink } from "lucide-react"; import { createContext, use, useEffect, useMemo, useRef, useState } from "react"; import { isActive } from "@fumadocs/ui/urls"; import { useOnChange } from "fumadocs-core/utils/use-on-change"; import { useMediaQuery } from "fumadocs-core/utils/use-media-query"; import { Presence } from "@radix-ui/react-presence"; import scrollIntoView from "scroll-into-view-if-needed"; //#region src/components/sidebar/base.tsx const SidebarContext = createContext(null); const FolderContext = createContext(null); function SidebarProvider({ defaultOpenLevel = 0, prefetch = true, children }) { const closeOnRedirect = useRef(true); const [open, setOpen] = useState(false); const [collapsed, setCollapsed] = useState(false); const pathname = usePathname(); const mode = useMediaQuery("(width < 768px)") ? "drawer" : "full"; useOnChange(pathname, () => { if (closeOnRedirect.current) setOpen(false); closeOnRedirect.current = true; }); return /* @__PURE__ */ jsx(SidebarContext, { value: useMemo(() => ({ open, setOpen, collapsed, setCollapsed, closeOnRedirect, defaultOpenLevel, prefetch, mode }), [ open, collapsed, defaultOpenLevel, prefetch, mode ]), children }); } function useSidebar() { const ctx = use(SidebarContext); if (!ctx) throw new Error("Missing SidebarContext, make sure you have wrapped the component in <DocsLayout /> and the context is available."); return ctx; } function useFolder() { return use(FolderContext); } function useFolderDepth() { return use(FolderContext)?.depth ?? 0; } function SidebarContent({ children }) { const { collapsed, mode } = useSidebar(); const [hover, setHover] = useState(false); const ref = useRef(null); const timerRef = useRef(0); useOnChange(collapsed, () => { if (collapsed) setHover(false); }); if (mode !== "full") return; function shouldIgnoreHover(e) { const element = ref.current; if (!element) return true; return !collapsed || e.pointerType === "touch" || element.getAnimations().length > 0; } return children({ ref, collapsed, hovered: hover, onPointerEnter(e) { if (shouldIgnoreHover(e)) return; window.clearTimeout(timerRef.current); setHover(true); }, onPointerLeave(e) { if (shouldIgnoreHover(e)) return; window.clearTimeout(timerRef.current); timerRef.current = window.setTimeout(() => setHover(false), Math.min(e.clientX, document.body.clientWidth - e.clientX) > 100 ? 0 : 500); } }); } function SidebarDrawerOverlay(props) { const { open, setOpen, mode } = useSidebar(); if (mode !== "drawer") return; return /* @__PURE__ */ jsx(Presence, { present: open, children: /* @__PURE__ */ jsx("div", { "data-state": open ? "open" : "closed", onClick: () => setOpen(false), ...props }) }); } function SidebarDrawerContent({ className, children, ...props }) { const { open, mode } = useSidebar(); const state = open ? "open" : "closed"; if (mode !== "drawer") return; return /* @__PURE__ */ jsx(Presence, { present: open, children: ({ present }) => /* @__PURE__ */ jsx("aside", { id: "nd-sidebar-mobile", "data-state": state, className: cn(!present && "invisible", className), ...props, children }) }); } function SidebarViewport(props) { return /* @__PURE__ */ jsx(ScrollArea, { ...props, className: cn("min-h-0 flex-1", props.className), children: /* @__PURE__ */ jsx(ScrollViewport, { className: "p-4 overscroll-contain", style: { maskImage: "linear-gradient(to bottom, transparent, white 12px, white calc(100% - 12px), transparent)" }, children: props.children }) }); } function SidebarSeparator(props) { const depth = useFolderDepth(); return /* @__PURE__ */ jsx("p", { ...props, className: cn("inline-flex items-center gap-2 mb-1.5 px-2 mt-6 empty:mb-0", depth === 0 && "first:mt-0", props.className), children: props.children }); } function SidebarItem({ icon, children, ...props }) { const pathname = usePathname(); const ref = useRef(null); const { prefetch } = useSidebar(); const active = props.href !== void 0 && isActive(props.href, pathname, false); useAutoScroll(active, ref); return /* @__PURE__ */ jsxs(Link, { ref, "data-active": active, prefetch, ...props, children: [icon ?? (props.external ? /* @__PURE__ */ jsx(ExternalLink, {}) : null), children] }); } function SidebarFolder({ defaultOpen: defaultOpenProp, collapsible = true, active = false, children, ...props }) { const { defaultOpenLevel } = useSidebar(); const depth = useFolderDepth() + 1; const defaultOpen = collapsible === false || active || (defaultOpenProp ?? defaultOpenLevel >= depth); const [open, setOpen] = useState(defaultOpen); useOnChange(defaultOpen, (v) => { if (v) setOpen(v); }); return /* @__PURE__ */ jsx(Collapsible, { open, onOpenChange: setOpen, disabled: !collapsible, ...props, children: /* @__PURE__ */ jsx(FolderContext, { value: useMemo(() => ({ open, setOpen, depth, collapsible }), [ collapsible, depth, open ]), children }) }); } function SidebarFolderTrigger({ children, ...props }) { const { open, collapsible } = use(FolderContext); if (collapsible) return /* @__PURE__ */ jsxs(CollapsibleTrigger, { ...props, children: [children, /* @__PURE__ */ jsx(ChevronDown, { "data-icon": true, className: cn("ms-auto transition-transform", !open && "-rotate-90") })] }); return /* @__PURE__ */ jsx("div", { ...props, children }); } function SidebarFolderLink({ children, ...props }) { const ref = useRef(null); const { open, setOpen, collapsible } = use(FolderContext); const { prefetch } = useSidebar(); const pathname = usePathname(); const active = props.href !== void 0 && isActive(props.href, pathname, false); useAutoScroll(active, ref); return /* @__PURE__ */ jsxs(Link, { ref, "data-active": active, onClick: (e) => { if (!collapsible) return; if (e.target instanceof Element && e.target.matches("[data-icon], [data-icon] *")) { setOpen(!open); e.preventDefault(); } else setOpen(active ? !open : true); }, prefetch, ...props, children: [children, collapsible && /* @__PURE__ */ jsx(ChevronDown, { "data-icon": true, className: cn("ms-auto transition-transform", !open && "-rotate-90") })] }); } function SidebarFolderContent(props) { return /* @__PURE__ */ jsx(CollapsibleContent, { ...props, children: props.children }); } function SidebarTrigger({ children, ...props }) { const { setOpen } = useSidebar(); return /* @__PURE__ */ jsx("button", { "aria-label": "Open Sidebar", onClick: () => setOpen((prev) => !prev), ...props, children }); } function SidebarCollapseTrigger(props) { const { collapsed, setCollapsed } = useSidebar(); return /* @__PURE__ */ jsx("button", { type: "button", "aria-label": "Collapse Sidebar", "data-collapsed": collapsed, onClick: () => { setCollapsed((prev) => !prev); }, ...props, children: props.children }); } function useAutoScroll(active, ref) { const { mode } = useSidebar(); useEffect(() => { if (active && ref.current) scrollIntoView(ref.current, { boundary: document.getElementById(mode === "drawer" ? "nd-sidebar-mobile" : "nd-sidebar"), scrollMode: "if-needed" }); }, [ active, mode, ref ]); } //#endregion export { SidebarCollapseTrigger, SidebarContent, SidebarDrawerContent, SidebarDrawerOverlay, SidebarFolder, SidebarFolderContent, SidebarFolderLink, SidebarFolderTrigger, SidebarItem, SidebarProvider, SidebarSeparator, SidebarTrigger, SidebarViewport, useFolder, useFolderDepth, useSidebar }; //# sourceMappingURL=base.js.map