UNPKG

fumadocs-ui

Version:

The Radix UI version of Fumadocs UI

227 lines (226 loc) 7.13 kB
"use client"; import { __exportAll } from "../../_virtual/_rolldown/runtime.js"; import { cn } from "../../utils/cn.js"; import { mergeRefs } from "../../utils/merge-refs.js"; import { useTOCItems } from "./index.js"; import { jsx, jsxs } from "react/jsx-runtime"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useTranslations } from "@fuma-translate/react"; import * as Primitive from "fumadocs-core/toc"; //#region src/components/toc/clerk.tsx var clerk_exports = /* @__PURE__ */ __exportAll({ TOCEmpty: () => TOCEmpty, TOCItem: () => TOCItem, TOCItems: () => TOCItems }); function TOCItems({ ref, className, children, ...props }) { const containerRef = useRef(null); const items = useTOCItems(); const [svg, setSvg] = useState(null); const onPrint = useCallback(() => { const container = containerRef.current; if (!container || container.clientHeight === 0) return; if (items.length === 0) { setSvg(null); return; } let w = 0; let h = 0; let d = ""; const positions = []; const output = []; for (let i = 0; i < items.length; i++) { const item = items[i]; const element = container.querySelector(`a[href="${item.url}"]`); if (!element) continue; const styles = getComputedStyle(element); const x = getLineOffset(item.depth) + .5; const top = element.offsetTop + parseFloat(styles.paddingTop); const bottom = element.offsetTop + element.clientHeight - parseFloat(styles.paddingBottom); w = Math.max(x + 8, w); h = Math.max(h, bottom); if (i === 0) d += ` M${x} ${top} L${x} ${bottom}`; else { const [, upperBottom, upperX] = i > 0 ? positions[i - 1] : [ 0, 0, 0 ]; d += ` L ${upperX} ${upperBottom} ${x} ${top} L${x} ${bottom}`; } if (item._step !== void 0) output.push(/* @__PURE__ */ jsxs("g", { transform: `translate(${x}, ${(top + bottom) / 2})`, children: [/* @__PURE__ */ jsx("circle", { cx: "0", cy: "0", r: "8", className: "fill-fd-primary" }), /* @__PURE__ */ jsx("text", { cx: "0", cy: "0", textAnchor: "middle", alignmentBaseline: "central", dominantBaseline: "middle", className: "fill-fd-primary-foreground font-medium text-xs leading-none font-mono rtl:-scale-x-100", children: item._step })] }, i)); positions.push([ top, bottom, x ]); } output.unshift(/* @__PURE__ */ jsx("path", { d, className: "stroke-fd-primary", strokeWidth: "1", fill: "none" }, "path")); setSvg({ content: output, width: w, height: h, d, itemLineLengths: [], positions }); }, [items]); useEffect(() => { const container = containerRef.current; if (!container) return; const observer = new ResizeObserver(onPrint); observer.observe(container); onPrint(); return () => { observer.unobserve(container); }; }, [onPrint]); return /* @__PURE__ */ jsxs("div", { ref: mergeRefs(containerRef, ref), className: cn("relative flex flex-col", className), ...props, children: [svg && /* @__PURE__ */ jsx(ThumbTrack, { computed: svg }), children] }); } function TOCEmpty() { return /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-fd-card p-3 text-xs text-fd-muted-foreground", children: useTranslations({ note: "table of contents" })("No Headings") }); } function ThumbTrack({ computed }) { const ref = useRef(null); const tocInfo = Primitive.useTOC(); function calculate(items) { const out = {}; const startIdx = items.findIndex((item) => item.active); if (startIdx === -1) return out; const endIdx = items.findLastIndex((item) => item.active); out["--track-top"] = `${computed.positions[startIdx][0]}px`; out["--track-bottom"] = `${computed.positions[endIdx][1]}px`; return out; } Primitive.useTOCListener((items) => { const element = ref.current; if (!element) return; for (const [k, v] of Object.entries(calculate(items))) element.style.setProperty(k, v); }); return /* @__PURE__ */ jsx("div", { ref, className: "absolute top-0 inset-s-0 origin-center rtl:-scale-x-100", style: { width: computed.width, height: computed.height, ...calculate(tocInfo.get()) }, children: /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: `0 0 ${computed.width} ${computed.height}`, className: "absolute transition-[clip-path]", style: { width: computed.width, height: computed.height, clipPath: `polygon(0 var(--track-top,0), 100% var(--track-top,0), 100% var(--track-bottom,0), 0 var(--track-bottom,0))` }, children: computed.content }) }); } const BASE = 8; function getItemOffset(depth) { if (depth <= 2) return 20; if (depth === 3) return 32; return 44; } function getLineOffset(depth) { if (depth <= 2) return BASE; if (depth === 3) return 20; return 32; } function TOCItem({ item, ...props }) { const items = useTOCItems(); const { isFirst, isLast, svg } = useMemo(() => { const index = items.indexOf(item); const isFirst = index === 0; const isLast = index === items.length - 1; const l1 = getLineOffset(item.depth); const l0 = isFirst ? l1 : getLineOffset(items[index - 1].depth); const l2 = isLast ? l1 : getLineOffset(items[index + 1].depth); return { isFirst, isLast, svg: /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", className: cn("absolute -top-1.5 inset-s-0 bottom-0 h-[calc(100%+--spacing(1.5))] -z-1 rtl:-scale-x-100", l1 !== l2 && "h-full bottom-1.5"), style: { width: Math.max(l0, l1) + 9 }, children: [ l0 !== l1 && /* @__PURE__ */ jsx("path", { d: `M ${l0 + .5} 0 L ${l0 + .5} 0 ${l1 + .5} 12`, stroke: "black", strokeWidth: "1", fill: "none", className: "stroke-fd-foreground/10" }), /* @__PURE__ */ jsx("line", { x1: l1 + .5, y1: l0 === l1 ? "6" : "12", x2: l1 + .5, y2: "100%", strokeWidth: "1", className: "stroke-fd-foreground/10" }), item._step !== void 0 && /* @__PURE__ */ jsxs("g", { transform: `translate(${l1 + .5}, ${l1 === l2 ? "3" : "6"})`, children: [/* @__PURE__ */ jsx("circle", { cx: "0", cy: "50%", r: "8", className: "fill-fd-muted" }), /* @__PURE__ */ jsx("text", { x: "0", y: "50%", textAnchor: "middle", alignmentBaseline: "central", dominantBaseline: "middle", className: "fill-fd-muted-foreground font-medium text-xs leading-none font-mono rtl:-scale-x-100", children: item._step })] }) ] }) }; }, [items, item]); return /* @__PURE__ */ jsxs(Primitive.TOCItem, { href: item.url, ...props, className: cn("prose relative py-1.5 text-sm scroll-m-4 text-fd-muted-foreground hover:text-fd-accent-foreground transition-colors wrap-anywhere data-[active=true]:text-fd-primary", isFirst && "pt-0", isLast && "pb-0", props.className), style: { paddingInlineStart: getItemOffset(item.depth), ...props.style }, children: [svg, item.title] }); } //#endregion export { TOCEmpty, TOCItem, TOCItems, clerk_exports };