fumadocs-ui
Version:
The Radix UI version of Fumadocs UI
227 lines (224 loc) • 8.38 kB
JavaScript
'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