UNPKG

fumadocs-ui

Version:

The Radix UI version of Fumadocs UI

183 lines (182 loc) 7.17 kB
"use client"; import { cn } from "../../../../utils/cn.js"; import { TOCProvider as TOCProvider$1, TOCScrollArea, useItems, useTOCItems } from "../../../../components/toc/index.js"; import { default_exports } from "../../../../components/toc/default.js"; import { clerk_exports } from "../../../../components/toc/clerk.js"; import { useTreePath } from "../../../../contexts/tree.js"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "../../../../components/ui/collapsible.js"; import { useDocsLayout } from "../../client.js"; import "../../index.js"; import { jsx, jsxs } from "react/jsx-runtime"; import { ChevronDown, Text } from "lucide-react"; import { createContext, use, useEffect, useEffectEvent, useMemo, useRef, useState } from "react"; import { useTranslations } from "@fuma-translate/react"; //#region src/layouts/docs/page/slots/toc.tsx function TOCProvider(props) { return /* @__PURE__ */ jsx(TOCProvider$1, { ...props }); } function TOC({ container, header, footer, style = "normal", list }) { const items = useTOCItems(); const t = useTranslations({ note: "table of contents" }); const { TOCItems, TOCEmpty, TOCItem } = style === "clerk" ? clerk_exports : default_exports; if (items.length === 0 && !header && !footer) return /* @__PURE__ */ jsx("div", { id: "nd-toc-placeholder", className: "hidden xl:layout:[--fd-toc-width:268px]" }); return /* @__PURE__ */ jsxs("div", { id: "nd-toc", ...container, className: cn("sticky top-(--fd-docs-row-1) h-[calc(var(--fd-docs-height)-var(--fd-docs-row-1))] flex flex-col [grid-area:toc] w-(--fd-toc-width) pt-12 pe-4 pb-2 xl:layout:[--fd-toc-width:268px] max-xl:hidden", container?.className), children: [ header, /* @__PURE__ */ jsxs("h3", { id: "toc-title", className: "inline-flex items-center gap-1.5 text-sm text-fd-muted-foreground", children: [/* @__PURE__ */ jsx(Text, { className: "size-4" }), t("On this page")] }), /* @__PURE__ */ jsx(TOCScrollArea, { children: /* @__PURE__ */ jsxs(TOCItems, { ...list, children: [items.length === 0 && /* @__PURE__ */ jsx(TOCEmpty, {}), items.map((item) => /* @__PURE__ */ jsx(TOCItem, { item }, item.url))] }) }), footer ] }); } const TocPopoverContext = createContext(null); function TOCPopover({ container, trigger, content, header, footer, style = "normal", list }) { const items = useTOCItems(); const ref = useRef(null); const [open, setOpen] = useState(false); const { isNavTransparent } = useDocsLayout(); const { TOCItems, TOCItem, TOCEmpty } = style === "clerk" ? clerk_exports : default_exports; const onClickOutside = useEffectEvent((e) => { if (!open || !(e.target instanceof HTMLElement)) return; if (ref.current && !ref.current.contains(e.target)) setOpen(false); }); const onClickItem = () => { setOpen(false); }; useEffect(() => { window.addEventListener("click", onClickOutside); return () => { window.removeEventListener("click", onClickOutside); }; }, []); return /* @__PURE__ */ jsx(TocPopoverContext, { value: useMemo(() => ({ open, setOpen }), [setOpen, open]), children: /* @__PURE__ */ jsx(Collapsible, { open, onOpenChange: setOpen, "data-toc-popover": "", ...container, 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)]", container?.className), children: /* @__PURE__ */ jsxs("header", { ref, className: cn("border-b backdrop-blur-sm transition-colors", (!isNavTransparent || open) && "bg-fd-background/80", open && "shadow-lg"), children: [/* @__PURE__ */ jsx(PageTOCPopoverTrigger, { ...trigger }), /* @__PURE__ */ jsxs(PageTOCPopoverContent, { ...content, children: [ header, /* @__PURE__ */ jsx(TOCScrollArea, { children: /* @__PURE__ */ jsxs(TOCItems, { ...list, children: [items.length === 0 && /* @__PURE__ */ jsx(TOCEmpty, {}), items.map((item) => /* @__PURE__ */ jsx(TOCItem, { item, onClick: onClickItem }, item.url))] }) }), footer ] })] }) }) }); } function PageTOCPopoverTrigger({ className, ...props }) { const t = useTranslations({ note: "table of contents" }); const { open } = use(TocPopoverContext); const items = useItems(); const selectedIdx = items.findIndex((item) => item.active); const path = useTreePath().at(-1); const showItem = selectedIdx !== -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: (items.findLastIndex((item) => item.active) + 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 ?? t("On this page") }), /* @__PURE__ */ jsx("span", { className: cn("truncate transition-[opacity,translate]", !showItem && "opacity-0 translate-y-full pointer-events-none"), children: items[selectedIdx]?.original.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 = 1.5, size = 18, min = 0, max = 100, style, ...restSvgProps }) { const normalizedValue = clamp(value, min, max); const radius = size / 2 - strokeWidth; 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, style: { width: size, height: size, ...style }, ...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, children: /* @__PURE__ */ jsx("div", { className: "flex flex-col px-4 max-h-[50vh] md:px-6", children: props.children }) }); } //#endregion export { TOC, TOCPopover, TOCProvider };