UNPKG

fumadocs-ui

Version:

The Radix UI version of Fumadocs UI

227 lines (224 loc) 8.38 kB
'use client'; import { i18n_exports } from "../../../contexts/i18n.js"; import { tree_exports } from "../../../contexts/tree.js"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "../../../components/ui/collapsible.js"; import { toc_exports } from "../../../components/toc/index.js"; import { LayoutContext } from "../client.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, ChevronLeft, ChevronRight } from "lucide-react"; import { Fragment as Fragment$1, createContext, use, useEffect, useEffectEvent, useMemo, useRef, useState } from "react"; import { getBreadcrumbItemsFromPath } from "fumadocs-core/breadcrumb"; import { isActive } from "@fumadocs/ui/urls"; import { useActiveAnchor } from "fumadocs-core/toc"; import { useFooterItems } from "@fumadocs/ui/hooks/use-footer-items"; //#region src/layouts/docs/page/client.tsx const TocPopoverContext = createContext(null); function PageTOCPopover({ className, children, ...rest }) { const ref = useRef(null); const [open, setOpen] = useState(false); const { isNavTransparent } = use(LayoutContext); const onClick = useEffectEvent((e) => { if (!open) return; if (ref.current && !ref.current.contains(e.target)) setOpen(false); }); useEffect(() => { window.addEventListener("click", onClick); return () => { window.removeEventListener("click", onClick); }; }, []); return /* @__PURE__ */ jsx(TocPopoverContext, { value: useMemo(() => ({ open, setOpen }), [setOpen, open]), children: /* @__PURE__ */ jsx(Collapsible, { open, onOpenChange: setOpen, "data-toc-popover": "", className: cn("sticky top-(--fd-docs-row-2) z-10 [grid-area:toc-popover] h-(--fd-toc-popover-height) xl:hidden max-xl:layout:[--fd-toc-popover-height:--spacing(10)]", className), ...rest, children: /* @__PURE__ */ jsx("header", { ref, className: cn("border-b backdrop-blur-sm transition-colors", (!isNavTransparent || open) && "bg-fd-background/80", open && "shadow-lg"), children }) }) }); } function PageTOCPopoverTrigger({ className, ...props }) { const { text } = (0, i18n_exports.useI18n)(); const { open } = use(TocPopoverContext); const items = (0, toc_exports.useTOCItems)(); const active = useActiveAnchor(); const selected = useMemo(() => items.findIndex((item) => active === item.url.slice(1)), [items, active]); const path = (0, tree_exports.useTreePath)().at(-1); const showItem = selected !== -1 && !open; return /* @__PURE__ */ jsxs(CollapsibleTrigger, { className: cn("flex w-full h-10 items-center text-sm text-fd-muted-foreground gap-2.5 px-4 py-2.5 text-start focus-visible:outline-none [&_svg]:size-4 md:px-6", className), "data-toc-popover-trigger": "", ...props, children: [ /* @__PURE__ */ jsx(ProgressCircle, { value: (selected + 1) / Math.max(1, items.length), max: 1, className: cn("shrink-0", open && "text-fd-primary") }), /* @__PURE__ */ jsxs("span", { className: "grid flex-1 *:my-auto *:row-start-1 *:col-start-1", children: [/* @__PURE__ */ jsx("span", { className: cn("truncate transition-[opacity,translate,color]", open && "text-fd-foreground", showItem && "opacity-0 -translate-y-full pointer-events-none"), children: path?.name ?? text.toc }), /* @__PURE__ */ jsx("span", { className: cn("truncate transition-[opacity,translate]", !showItem && "opacity-0 translate-y-full pointer-events-none"), children: items[selected]?.title })] }), /* @__PURE__ */ jsx(ChevronDown, { className: cn("shrink-0 transition-transform mx-0.5", open && "rotate-180") }) ] }); } function clamp(input, min, max) { if (input < min) return min; if (input > max) return max; return input; } function ProgressCircle({ value, strokeWidth = 2, size = 24, min = 0, max = 100, ...restSvgProps }) { const normalizedValue = clamp(value, min, max); const radius = (size - strokeWidth) / 2; const circumference = 2 * Math.PI * radius; const progress = normalizedValue / max * circumference; const circleProps = { cx: size / 2, cy: size / 2, r: radius, fill: "none", strokeWidth }; return /* @__PURE__ */ jsxs("svg", { role: "progressbar", viewBox: `0 0 ${size} ${size}`, "aria-valuenow": normalizedValue, "aria-valuemin": min, "aria-valuemax": max, ...restSvgProps, children: [/* @__PURE__ */ jsx("circle", { ...circleProps, className: "stroke-current/25" }), /* @__PURE__ */ jsx("circle", { ...circleProps, stroke: "currentColor", strokeDasharray: circumference, strokeDashoffset: circumference - progress, strokeLinecap: "round", transform: `rotate(-90 ${size / 2} ${size / 2})`, className: "transition-all" })] }); } function PageTOCPopoverContent(props) { return /* @__PURE__ */ jsx(CollapsibleContent, { "data-toc-popover-content": "", ...props, className: cn("flex flex-col px-4 max-h-[50vh] md:px-6", props.className), children: props.children }); } function PageLastUpdate({ date: value, ...props }) { const { text } = (0, i18n_exports.useI18n)(); const [date, setDate] = useState(""); useEffect(() => { setDate(value.toLocaleDateString()); }, [value]); return /* @__PURE__ */ jsxs("p", { ...props, className: cn("text-sm text-fd-muted-foreground", props.className), children: [ text.lastUpdate, " ", date ] }); } function PageFooter({ items, ...props }) { const footerList = useFooterItems(); const pathname = usePathname(); const { previous, next } = useMemo(() => { if (items) return items; const idx = footerList.findIndex((item) => isActive(item.url, pathname, false)); if (idx === -1) return {}; return { previous: footerList[idx - 1], next: footerList[idx + 1] }; }, [ footerList, items, pathname ]); return /* @__PURE__ */ jsxs("div", { ...props, className: cn("@container grid gap-4", previous && next ? "grid-cols-2" : "grid-cols-1", props.className), children: [previous ? /* @__PURE__ */ jsx(FooterItem, { item: previous, index: 0 }) : null, next ? /* @__PURE__ */ jsx(FooterItem, { item: next, index: 1 }) : null] }); } function FooterItem({ item, index }) { const { text } = (0, i18n_exports.useI18n)(); const Icon = index === 0 ? ChevronLeft : ChevronRight; return /* @__PURE__ */ jsxs(Link, { href: item.url, className: cn("flex flex-col gap-2 rounded-lg border p-4 text-sm transition-colors hover:bg-fd-accent/80 hover:text-fd-accent-foreground @max-lg:col-span-full", index === 1 && "text-end"), children: [/* @__PURE__ */ jsxs("div", { className: cn("inline-flex items-center gap-1.5 font-medium", index === 1 && "flex-row-reverse"), children: [/* @__PURE__ */ jsx(Icon, { className: "-mx-1 size-4 shrink-0 rtl:rotate-180" }), /* @__PURE__ */ jsx("p", { children: item.name })] }), /* @__PURE__ */ jsx("p", { className: "text-fd-muted-foreground truncate", children: item.description ?? (index === 0 ? text.previousPage : text.nextPage) })] }); } function PageBreadcrumb({ includeRoot, includeSeparator, includePage, ...props }) { const path = (0, tree_exports.useTreePath)(); const { root } = (0, tree_exports.useTreeContext)(); const items = useMemo(() => { return getBreadcrumbItemsFromPath(root, path, { includePage, includeSeparator, includeRoot }); }, [ includePage, includeRoot, includeSeparator, path, root ]); if (items.length === 0) return null; return /* @__PURE__ */ jsx("div", { ...props, className: cn("flex items-center gap-1.5 text-sm text-fd-muted-foreground", props.className), children: items.map((item, i) => { const className = cn("truncate", i === items.length - 1 && "text-fd-primary font-medium"); return /* @__PURE__ */ jsxs(Fragment$1, { children: [i !== 0 && /* @__PURE__ */ jsx(ChevronRight, { className: "size-3.5 shrink-0" }), item.url ? /* @__PURE__ */ jsx(Link, { href: item.url, className: cn(className, "transition-opacity hover:opacity-80"), children: item.name }) : /* @__PURE__ */ jsx("span", { className, children: item.name })] }, i); }) }); } //#endregion export { PageBreadcrumb, PageFooter, PageLastUpdate, PageTOCPopover, PageTOCPopoverContent, PageTOCPopoverTrigger }; //# sourceMappingURL=client.js.map