UNPKG

fumadocs-ui

Version:

The Radix UI version of Fumadocs UI

278 lines (277 loc) 8.97 kB
"use client"; import { __exportAll } from "../../_virtual/_rolldown/runtime.js"; import { cn } from "../../utils/cn.js"; 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 { jsx, jsxs } from "react/jsx-runtime"; import { ChevronDown, ExternalLink } from "lucide-react"; import { createContext, use, useEffect, useMemo, useRef, useState } from "react"; import { useTranslations } from "@fuma-translate/react"; 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 var base_exports = /* @__PURE__ */ __exportAll({ SidebarCollapseTrigger: () => SidebarCollapseTrigger, SidebarContent: () => SidebarContent, SidebarDrawerContent: () => SidebarDrawerContent, SidebarDrawerOverlay: () => SidebarDrawerOverlay, SidebarFolder: () => SidebarFolder, SidebarFolderContent: () => SidebarFolderContent, SidebarFolderLink: () => SidebarFolderLink, SidebarFolderTrigger: () => SidebarFolderTrigger, SidebarItem: () => SidebarItem, SidebarProvider: () => SidebarProvider, SidebarSeparator: () => SidebarSeparator, SidebarTrigger: () => SidebarTrigger, SidebarViewport: () => SidebarViewport, useAutoScroll: () => useAutoScroll, useFolder: () => useFolder, useFolderDepth: () => useFolderDepth, useSidebar: () => useSidebar }); const SidebarContext = createContext(null); const FolderContext = createContext(null); function SidebarProvider({ defaultOpenLevel = 0, prefetch, 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({ mode: allowedMode = "full", 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 (allowedMode !== true && allowedMode !== mode) 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 SidebarViewport({ area, viewport, children }) { return /* @__PURE__ */ jsx(ScrollArea, { ...area, className: cn("min-h-0 flex-1", area?.className), children: /* @__PURE__ */ jsx(ScrollViewport, { ...viewport, className: cn("*:flex! *:flex-col! *:gap-0.5! p-4 overscroll-contain mask-[linear-gradient(to_bottom,transparent,white_12px,white_calc(100%-12px),transparent)]", viewport?.className), children }) }); } 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 SidebarSeparator(props) { return /* @__PURE__ */ jsx("p", { ...props }); } function SidebarItem({ icon, active = false, children, ...props }) { const ref = useRef(null); const { prefetch } = useSidebar(); 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 rtl:rotate-90") })] }); return /* @__PURE__ */ jsx("div", { ...props, children }); } function SidebarFolderLink({ children, active = false, ...props }) { const ref = useRef(null); const { open, setOpen, collapsible } = use(FolderContext); const { prefetch } = useSidebar(); 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 rtl:rotate-90") })] }); } function SidebarFolderContent(props) { return /* @__PURE__ */ jsx(CollapsibleContent, { ...props, children: props.children }); } function SidebarTrigger({ children, ...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-mobile", onClick: () => setOpen((prev) => !prev), ...props, children }); } function SidebarCollapseTrigger(props) { const { collapsed, setCollapsed } = useSidebar(); const t = useTranslations({ note: "sidebar" }); return /* @__PURE__ */ jsx("button", { type: "button", "aria-label": t("Collapse Sidebar", { note: "aria-label" }), "data-collapsed": collapsed, onClick: () => { setCollapsed((prev) => !prev); }, ...props, children: props.children }); } /** * scroll to the element if `active` is true */ 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, base_exports, useAutoScroll, useFolder, useFolderDepth, useSidebar };