fumadocs-ui
Version:
The Radix UI version of Fumadocs UI
183 lines (182 loc) • 7.17 kB
JavaScript
"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 };