fumadocs-ui
Version:
The framework for building a documentation website in Next.js
92 lines (91 loc) • 10.4 kB
JavaScript
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 };