UI: Support tabMode on <DocsLayout />

This commit is contained in:
Fuma Nama
2025-09-29 22:57:26 +08:00
parent 1d56767826
commit 90cf1fef5e
9 changed files with 91 additions and 17 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'fumadocs-ui': patch
---
Support `tabMode` on `<DocsLayout />`
+5
View File
@@ -0,0 +1,5 @@
---
'fumadocs-core': patch
---
Support Negotiation API
+1
View File
@@ -0,0 +1 @@
export * from './vite';
+1 -1
View File
@@ -1,4 +1,4 @@
import { PluginOption } from 'vite';
import type { PluginOption } from 'vite';
import rsc from '@vitejs/plugin-rsc';
import tailwindcss from '@tailwindcss/vite';
import { unstable_reactRouterRSC as reactRouterRSC } from '@react-router/dev/vite';
+1 -1
View File
@@ -4,5 +4,5 @@ export default defineConfig({
dts: true,
target: 'es2022',
format: 'esm',
entry: [],
entry: ['src/index.ts'],
});
+58 -1
View File
@@ -1,13 +1,17 @@
'use client';
import { Sidebar as SidebarIcon } from 'lucide-react';
import { type ComponentProps } from 'react';
import { type ComponentProps, useMemo } from 'react';
import { cn } from '@/utils/cn';
import { buttonVariants } from '@/components/ui/button';
import { useSidebar } from '@/contexts/sidebar';
import { useNav } from '@/contexts/layout';
import { SidebarCollapseTrigger } from '@/components/layout/sidebar';
import { SearchToggle } from '@/components/layout/search-toggle';
import type { Option } from '@/components/layout/root-toggle';
import { usePathname } from 'fumadocs-core/framework';
import { isTabActive } from '@/utils/is-active';
import Link from 'fumadocs-core/link';
export function Navbar(props: ComponentProps<'header'>) {
const { isTransparent } = useNav();
@@ -79,3 +83,56 @@ export function CollapsibleControl() {
</div>
);
}
export function LayoutTabs({
options,
...props
}: ComponentProps<'div'> & {
options: Option[];
}) {
const pathname = usePathname();
const selected = useMemo(() => {
return options.findLast((option) => isTabActive(option, pathname));
}, [options, pathname]);
return (
<div
{...props}
className={cn(
'flex flex-row items-end gap-6 overflow-auto',
props.className,
)}
>
{options.map((option) => (
<LayoutTab
key={option.url}
selected={selected === option}
option={option}
/>
))}
</div>
);
}
function LayoutTab({
option: { title, url, unlisted, props },
selected = false,
}: {
option: Option;
selected?: boolean;
}) {
return (
<Link
href={url}
{...props}
className={cn(
'inline-flex border-b-2 border-transparent transition-colors items-center pb-1.5 font-medium gap-2 text-fd-muted-foreground text-sm text-nowrap hover:text-fd-accent-foreground',
unlisted && !selected && 'hidden',
selected && 'border-fd-primary text-fd-primary',
props?.className,
)}
>
{title}
</Link>
);
}
+19 -5
View File
@@ -38,7 +38,12 @@ import {
LanguageToggle,
LanguageToggleText,
} from '@/components/layout/language-toggle';
import { CollapsibleControl, LayoutBody, Navbar } from '@/layouts/docs/client';
import {
CollapsibleControl,
LayoutBody,
LayoutTabs,
Navbar,
} from '@/layouts/docs/client';
import { TreeContextProvider } from '@/contexts/tree';
import { ThemeToggle } from '@/components/layout/theme-toggle';
import { NavProvider } from '@/contexts/layout';
@@ -58,6 +63,8 @@ export interface DocsLayoutProps extends BaseLayoutProps {
sidebar?: SidebarOptions;
tabMode?: 'top' | 'auto';
/**
* Props for the `div` container
*/
@@ -95,8 +102,8 @@ export function DocsLayout({
...sidebarProps
} = {},
searchToggle = {},
disableThemeSwitch = false,
themeSwitch = { enabled: !disableThemeSwitch },
themeSwitch = {},
tabMode = 'auto',
i18n = false,
children,
...props
@@ -231,8 +238,9 @@ export function DocsLayout({
(searchToggle.components?.lg ?? (
<LargeSearchToggle hideIfDisabled />
))}
{tabs.length > 0 && <RootToggle options={tabs} />}
{tabs.length > 0 && tabMode === 'auto' && (
<RootToggle options={tabs} />
)}
{banner}
</SidebarHeader>
{viewport}
@@ -321,6 +329,12 @@ export function DocsLayout({
)}
>
{sidebarEnabled && sidebar()}
{tabMode === 'top' && tabs.length > 0 && (
<LayoutTabs
options={tabs}
className="sticky top-[calc(var(--fd-nav-height)+var(--fd-tocnav-height))] z-10 bg-fd-background border-b px-6 pt-3 xl:px-8 max-md:hidden"
/>
)}
{children}
</LayoutBody>
</NavProvider>
+1 -2
View File
@@ -106,8 +106,7 @@ export function DocsLayout(props: DocsLayoutProps) {
nav: { transparentMode, ...nav } = {},
sidebar: { tabs: tabOptions, ...sidebarProps } = {},
i18n = false,
disableThemeSwitch = false,
themeSwitch = { enabled: !disableThemeSwitch },
themeSwitch = {},
} = props;
const navMode = nav.mode ?? 'auto';
-7
View File
@@ -32,13 +32,6 @@ export interface BaseLayoutProps {
}>;
}>;
/**
* Remove theme switcher component
*
* @deprecated Use `themeSwitch.enabled` instead.
*/
disableThemeSwitch?: boolean;
/**
* I18n options
*