UNPKG

fumadocs-ui

Version:

The Radix UI version of Fumadocs UI

147 lines (146 loc) 6.05 kB
"use client"; import { cn } from "../../utils/cn.js"; import { buttonVariants } from "../../components/ui/button.js"; import { TreeContextProvider } from "../../contexts/tree.js"; import { useSearchContext } from "../../contexts/search.js"; import { LinkItem, baseSlots } from "../shared/client.js"; import { getLayoutTabs, useLinkItems } from "../shared/index.js"; import { TabDropdown } from "./slots/tab-dropdown.js"; import { Sidebar as Sidebar$1, SidebarProvider, SidebarTrigger, useSidebar } from "./slots/sidebar.js"; import { Container } from "./slots/container.js"; import { Fragment, jsx, jsxs } from "react/jsx-runtime"; import { Languages } from "lucide-react"; import { createContext, use, useMemo } from "react"; import { motion } from "motion/react"; import { RemoveScroll } from "react-remove-scroll"; //#region src/layouts/flux/index.tsx const LayoutContext = createContext(null); function useFluxLayout() { const context = use(LayoutContext); if (!context) throw new Error("Please use Flux layout components under <DocsLayout /> (`fumadocs-ui/layouts/flux`)."); return context; } const { useProvider } = baseSlots({ useProps() { return useFluxLayout().props; } }); function DocsLayout(props) { const { tree, nav = {}, sidebar: { enabled: sidebarEnabled = true, tabs: _tabs, defaultOpenLevel, prefetch, ...sidebarProps } = {}, tabs: defaultTabs = _tabs, children, containerProps, renderNavigationPanel = (props) => /* @__PURE__ */ jsx(NavigationPanel, { ...props }), slots: defaultSlots = {} } = props; const linkItems = useLinkItems(props); const { baseSlots, baseProps } = useProvider(props); const tabs = useMemo(() => { if (Array.isArray(defaultTabs)) return defaultTabs; if (typeof defaultTabs === "object") return getLayoutTabs(tree, defaultTabs); if (defaultTabs !== false) return getLayoutTabs(tree); return []; }, [tree, defaultTabs]); const slots = { ...baseSlots, container: defaultSlots.container ?? Container, tabDropdown: defaultSlots.tabDropdown ?? TabDropdown, sidebar: defaultSlots.sidebar ?? { root: Sidebar$1, provider: SidebarProvider, trigger: SidebarTrigger, useSidebar } }; return /* @__PURE__ */ jsx(LayoutContext, { value: { props: baseProps, slots, ...linkItems }, children: /* @__PURE__ */ jsxs(TreeContextProvider, { tree, children: [/* @__PURE__ */ jsx(slots.sidebar.provider, { defaultOpenLevel, prefetch, children: /* @__PURE__ */ jsxs(slots.container, { ...containerProps, children: [sidebarEnabled && /* @__PURE__ */ jsx(slots.sidebar.root, { ...sidebarProps }), children] }) }), renderNavigationPanel({ head: /* @__PURE__ */ jsxs(Fragment, { children: [slots.navTitle && /* @__PURE__ */ jsx(slots.navTitle, { className: "inline-flex items-center gap-2.5 text-sm font-semibold" }), nav.children] }), tabDropdown: slots.tabDropdown && tabs.length > 0 && /* @__PURE__ */ jsx(slots.tabDropdown, { className: "flex-1", tabs }), tool: /* @__PURE__ */ jsxs(Fragment, { children: [ slots.languageSelect && /* @__PURE__ */ jsx(slots.languageSelect.root, { children: /* @__PURE__ */ jsx(Languages, { className: "size-4.5" }) }), slots.searchTrigger && /* @__PURE__ */ jsx(slots.searchTrigger.sm, { hideIfDisabled: true, className: "rounded-lg" }), slots.sidebar && /* @__PURE__ */ jsx(slots.sidebar.trigger, { className: cn(buttonVariants({ variant: "ghost", size: "icon-sm", className: "overflow-hidden" })) }), slots.themeSwitch && /* @__PURE__ */ jsx(slots.themeSwitch, { className: "p-1 h-full ms-1 rounded-xl bg-fd-muted *:rounded-lg" }) ] }), link: linkItems.menuItems.filter((item) => item.type === "icon").map((item, i) => /* @__PURE__ */ jsx(LinkItem, { item, className: cn(buttonVariants({ size: "icon-sm", color: "ghost" })), "aria-label": item.label, children: item.icon }, i)) })] }) }); } function NavigationPanel({ head, tabDropdown, tool, link, children = (v) => v, ...props }) { const { open } = useSearchContext(); return /* @__PURE__ */ jsx(motion.div, { ...props, className: cn("fixed left-1/2 w-[calc(100%-var(--removed-body-scroll-bar-size,0px))] translate-x-[calc(-50%-var(--removed-body-scroll-bar-size,0px)/2)] bottom-0 z-40 bg-fd-popover text-fd-popover-foreground border-t shadow-lg sm:bottom-6 sm:rounded-2xl sm:border sm:max-w-[380px]", props.className), animate: props.animate ?? { scale: open ? .9 : 1, translateY: open ? 20 : 0, opacity: open ? .8 : 1 }, children: children(/* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", { className: "flex flex-row items-center ps-2.5 p-1 gap-2 min-h-11", children: [head, /* @__PURE__ */ jsx("div", { id: "flux-layout-slot", className: "flex-1" })] }), /* @__PURE__ */ jsxs("div", { className: "flex flex-row gap-1.5 overflow-x-auto overflow-y-hidden p-2 sm:p-1", children: [ /* @__PURE__ */ jsx("div", { className: "flex flex-row items-center gap-2 min-w-0 flex-1", children: tabDropdown }), /* @__PURE__ */ jsx("div", { className: "flex flex-row items-center text-fd-muted-foreground border-x px-0.5 empty:hidden", children: link }), /* @__PURE__ */ jsx("div", { className: "flex flex-row items-center text-fd-muted-foreground empty:hidden", children: tool }) ] })] })) }); } function NavigationPanelOverlay({ enabled = false, className, ...props }) { return /* @__PURE__ */ jsx(RemoveScroll, { enabled, children: /* @__PURE__ */ jsx(motion.div, { className: cn("fixed inset-0 z-30 pr-(--removed-body-scroll-bar-size,0) backdrop-blur-md bg-fd-background/60", !enabled && "pointer-events-none", className), initial: "hide", variants: { show: { opacity: 1 }, hide: { opacity: 0 } }, animate: enabled ? "show" : "hide", exit: "hide", ...props }) }); } //#endregion export { DocsLayout, NavigationPanel, NavigationPanelOverlay, useFluxLayout };