UNPKG

fumadocs-ui

Version:

The framework for building a documentation website in Next.js

92 lines (91 loc) 10.4 kB
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime"; import { Fragment, useMemo, } from 'react'; import { getLinks } from '../layouts/shared.js'; import { Sidebar, SidebarCollapseTrigger, SidebarContent, SidebarContentMobile, SidebarFooter, SidebarHeader, SidebarPageTree, SidebarTrigger, SidebarViewport, } from '../components/layout/sidebar.js'; import { TreeContextProvider } from '../contexts/tree.js'; import { cn } from '../utils/cn.js'; import { buttonVariants } from '../components/ui/button.js'; import { ChevronDown, Languages, Sidebar as SidebarIcon, X, } from '../icons.js'; import { BaseLinkItem } from '../layouts/links.js'; import { LanguageToggle } from '../components/layout/language-toggle.js'; import { ThemeToggle } from '../components/layout/theme-toggle.js'; import { Popover, PopoverContent, PopoverTrigger, } from '../components/ui/popover.js'; import { getSidebarTabsFromOptions, SidebarLinkItem, } from '../layouts/docs/shared.js'; import { LayoutBody, LayoutTabs, Navbar, NavbarSidebarTrigger, } from './notebook-client.js'; import { NavProvider } from '../contexts/layout.js'; import { RootToggle } from '../components/layout/root-toggle.js'; import Link from 'fumadocs-core/link'; import { LargeSearchToggle, SearchToggle, } from '../components/layout/search-toggle.js'; import { HideIfEmpty } from 'fumadocs-core/hide-if-empty'; export function DocsLayout(props) { const { tabMode = 'sidebar', nav: { transparentMode, ...nav } = {}, sidebar: { tabs: tabOptions, ...sidebarProps } = {}, i18n = false, disableThemeSwitch = false, themeSwitch = { enabled: !disableThemeSwitch }, } = props; const navMode = nav.mode ?? 'auto'; const links = getLinks(props.links ?? [], props.githubUrl); const tabs = useMemo(() => getSidebarTabsFromOptions(tabOptions, props.tree) ?? [], [tabOptions, props.tree]); const variables = cn('[--fd-nav-height:56px] md:[--fd-sidebar-width:286px] md:[--fd-nav-height:64px] xl:[--fd-toc-width:286px]', tabs.length > 0 && tabMode === 'navbar' && 'lg:[--fd-nav-height:104px]'); function sidebar() { const { banner, footer, components, collapsible = true, prefetch, defaultOpenLevel, ...rest } = sidebarProps; const iconLinks = links.filter((item) => item.type === 'icon'); const rootToggle = (_jsxs(_Fragment, { children: [tabMode === 'sidebar' && tabs.length > 0 && (_jsx(RootToggle, { className: "mb-2", options: tabs })), tabMode === 'navbar' && tabs.length > 0 && (_jsx(RootToggle, { options: tabs, className: "lg:hidden" }))] })); const sidebarNav = (_jsxs("div", { className: "flex justify-between", children: [_jsx(Link, { href: nav.url ?? '/', className: "inline-flex items-center gap-2.5 font-medium", children: nav.title }), collapsible && (_jsx(SidebarCollapseTrigger, { className: cn(buttonVariants({ color: 'ghost', size: 'icon-sm', className: 'mt-px mb-auto text-fd-muted-foreground', })), children: _jsx(SidebarIcon, {}) }))] })); const viewport = (_jsxs(SidebarViewport, { children: [links .filter((item) => item.type !== 'icon') .map((item, i, arr) => (_jsx(SidebarLinkItem, { item: item, className: cn('lg:hidden', i === arr.length - 1 && 'mb-4') }, i))), _jsx(SidebarPageTree, { components: components })] })); const content = (_jsxs(SidebarContent, { ...rest, className: cn(navMode === 'top' ? 'border-e-0 bg-transparent' : '[--fd-nav-height:0px]', rest.className), children: [_jsxs(HideIfEmpty, { as: SidebarHeader, children: [navMode === 'auto' && sidebarNav, nav.children, banner, rootToggle] }), viewport, _jsxs(HideIfEmpty, { as: SidebarFooter, className: "flex flex-row text-fd-muted-foreground items-center", children: [iconLinks.map((item, i) => (_jsx(BaseLinkItem, { item: item, className: cn(buttonVariants({ size: 'icon-sm', color: 'ghost', className: 'lg:hidden', })), "aria-label": item.label, children: item.icon }, i))), footer] })] })); const mobile = (_jsxs(SidebarContentMobile, { ...rest, children: [_jsxs(SidebarHeader, { children: [_jsx(SidebarTrigger, { className: cn(buttonVariants({ size: 'icon-sm', color: 'ghost', className: 'ms-auto text-fd-muted-foreground', })), children: _jsx(X, {}) }), banner, rootToggle] }), viewport, _jsxs(HideIfEmpty, { as: SidebarFooter, className: "flex flex-row items-center justify-end", children: [iconLinks.map((item, i) => (_jsx(BaseLinkItem, { item: item, className: cn(buttonVariants({ size: 'icon-sm', color: 'ghost', }), 'text-fd-muted-foreground lg:hidden', i === iconLinks.length - 1 && 'me-auto'), "aria-label": item.label, children: item.icon }, i))), i18n ? (_jsx(LanguageToggle, { children: _jsx(Languages, { className: "size-4.5 text-fd-muted-foreground" }) })) : null, themeSwitch.enabled !== false && (themeSwitch.component ?? (_jsx(ThemeToggle, { mode: themeSwitch.mode ?? 'light-dark-system' }))), footer] })] })); return (_jsx(Sidebar, { defaultOpenLevel: defaultOpenLevel, prefetch: prefetch, Content: content, Mobile: mobile })); } return (_jsx(TreeContextProvider, { tree: props.tree, children: _jsx(NavProvider, { transparentMode: transparentMode, children: _jsxs(LayoutBody, { ...props.containerProps, className: cn(variables, props.containerProps?.className), children: [sidebar(), _jsx(DocsNavbar, { ...props, links: links, tabs: tabMode == 'navbar' ? tabs : [] }), props.children] }) }) })); } function DocsNavbar({ links, tabs, searchToggle = {}, themeSwitch = {}, ...props }) { const navMode = props.nav?.mode ?? 'auto'; const sidebarCollapsible = props.sidebar?.collapsible ?? true; const nav = (_jsx(Link, { href: props.nav?.url ?? '/', className: cn('inline-flex items-center gap-2.5 font-semibold empty:hidden', navMode === 'auto' && 'md:hidden'), children: props.nav?.title })); return (_jsxs(Navbar, { mode: navMode, children: [_jsxs("div", { className: cn('flex border-b px-4 flex-1', navMode === 'auto' && 'md:px-6'), children: [_jsxs("div", { className: cn('flex flex-row items-center', navMode === 'top' && 'flex-1 pe-4'), children: [sidebarCollapsible && navMode === 'auto' ? (_jsx(SidebarCollapseTrigger, { className: cn(buttonVariants({ color: 'ghost', size: 'icon-sm', }), 'text-fd-muted-foreground -ms-1.5 me-2 data-[collapsed=false]:hidden max-md:hidden'), children: _jsx(SidebarIcon, {}) })) : null, nav] }), searchToggle.enabled !== false && (searchToggle.components?.lg ? (_jsx("div", { className: cn('w-full my-auto max-md:hidden', navMode === 'top' ? 'rounded-xl max-w-sm' : 'max-w-[240px]'), children: searchToggle.components?.lg })) : (_jsx(LargeSearchToggle, { hideIfDisabled: true, className: cn('w-full my-auto max-md:hidden', navMode === 'top' ? 'rounded-xl max-w-sm ps-2.5' : 'max-w-[240px]') }))), _jsxs("div", { className: "flex flex-1 flex-row items-center justify-end", children: [_jsx("div", { className: "flex flex-row items-center gap-6 px-4 empty:hidden max-lg:hidden", children: links .filter((item) => item.type !== 'icon') .map((item, i) => (_jsx(NavbarLinkItem, { item: item, className: "text-sm text-fd-muted-foreground transition-colors hover:text-fd-accent-foreground data-[active=true]:text-fd-primary" }, i))) }), props.nav?.children, searchToggle.enabled !== false && (searchToggle.components?.sm ?? (_jsx(SearchToggle, { hideIfDisabled: true, className: "p-2 md:hidden" }))), _jsx(NavbarSidebarTrigger, { className: "p-2 -me-1.5 md:hidden" }), links .filter((item) => item.type === 'icon') .map((item, i) => (_jsx(BaseLinkItem, { item: item, className: cn(buttonVariants({ size: 'icon-sm', color: 'ghost' }), 'text-fd-muted-foreground max-lg:hidden'), "aria-label": item.label, children: item.icon }, i))), props.i18n ? (_jsx(LanguageToggle, { className: "max-md:hidden", children: _jsx(Languages, { className: "size-4.5 text-fd-muted-foreground" }) })) : null, themeSwitch.enabled !== false && (themeSwitch.component ?? (_jsx(ThemeToggle, { className: "ms-2 max-md:hidden", mode: themeSwitch.mode ?? 'light-dark-system' }))), sidebarCollapsible && navMode === 'top' ? (_jsx(SidebarCollapseTrigger, { className: cn(buttonVariants({ color: 'secondary', size: 'icon-sm', }), 'ms-2 text-fd-muted-foreground rounded-full max-md:hidden'), children: _jsx(SidebarIcon, {}) })) : null] })] }), tabs.length > 0 && (_jsx(LayoutTabs, { className: cn('border-b h-10 max-lg:hidden', navMode === 'top' ? 'px-4' : 'px-6'), options: tabs }))] })); } function NavbarLinkItem({ item, ...props }) { if (item.type === 'menu') { return (_jsxs(Popover, { children: [_jsxs(PopoverTrigger, { ...props, className: cn('inline-flex items-center gap-1.5', props.className), children: [item.text, _jsx(ChevronDown, { className: "size-3" })] }), _jsx(PopoverContent, { className: "flex flex-col", children: item.items.map((child, i) => { if (child.type === 'custom') return _jsx(Fragment, { children: child.children }, i); return (_jsxs(BaseLinkItem, { item: child, className: "inline-flex items-center gap-2 rounded-md p-2 text-start hover:bg-fd-accent hover:text-fd-accent-foreground data-[active=true]:text-fd-primary [&_svg]:size-4", children: [child.icon, child.text] }, i)); }) })] })); } if (item.type === 'custom') return item.children; return (_jsx(BaseLinkItem, { item: item, ...props, children: item.text })); } export { Navbar, NavbarSidebarTrigger };