UI: Support custom renderer for title in layouts

This commit is contained in:
Fuma Nama
2025-12-21 12:47:26 +08:00
parent 389e68b33e
commit 7c78045ec3
9 changed files with 92 additions and 74 deletions
+6
View File
@@ -0,0 +1,6 @@
---
'fumadocs-ui': patch
'@fumadocs/base-ui': patch
---
Support custom renderer for `title` in layouts
+12 -14
View File
@@ -18,7 +18,11 @@ import {
SidebarTrigger,
SidebarViewport,
} from './sidebar';
import { type BaseLayoutProps, resolveLinkItems } from '@/layouts/shared';
import {
type BaseLayoutProps,
renderTitleNav,
resolveLinkItems,
} from '@/layouts/shared';
import { LinkItem } from '@fumadocs/ui/link-item';
import {
LanguageToggle,
@@ -32,7 +36,6 @@ import {
} from './client';
import { TreeContextProvider } from '@/contexts/tree';
import { ThemeToggle } from '../shared/theme-toggle';
import Link from 'fumadocs-core/link';
import {
LargeSearchToggle,
SearchToggle,
@@ -147,12 +150,10 @@ export function DocsLayout({
<SidebarContent {...rest}>
<div className="flex flex-col gap-3 p-4 pb-2">
<div className="flex">
<Link
href={nav.url ?? '/'}
className="inline-flex text-[0.9375rem] items-center gap-2.5 font-medium me-auto"
>
{nav.title}
</Link>
{renderTitleNav(nav, {
className:
'inline-flex text-[0.9375rem] items-center gap-2.5 font-medium me-auto',
})}
{nav.children}
{collapsible && (
<SidebarCollapseTrigger
@@ -279,12 +280,9 @@ export function DocsLayout({
id="nd-subnav"
className="[grid-area:header] sticky top-(--fd-docs-row-1) z-30 flex items-center ps-4 pe-2.5 border-b transition-colors backdrop-blur-sm h-(--fd-header-height) md:hidden max-md:layout:[--fd-header-height:--spacing(14)] data-[transparent=false]:bg-fd-background/80"
>
<Link
href={nav.url ?? '/'}
className="inline-flex items-center gap-2.5 font-semibold"
>
{nav.title}
</Link>
{renderTitleNav(nav, {
className: 'inline-flex items-center gap-2.5 font-semibold',
})}
<div className="flex-1">{nav.children}</div>
{searchToggle.enabled !== false &&
(searchToggle.components?.sm ?? (
+4 -6
View File
@@ -15,6 +15,7 @@ import { cn } from '@fumadocs/ui/cn';
import {
type LinkItemType,
type NavOptions,
renderTitleNav,
resolveLinkItems,
} from '@/layouts/shared';
import { LinkItem } from '@fumadocs/ui/link-item';
@@ -107,12 +108,9 @@ export function Header({
className={cn(s.open && 'shadow-lg rounded-b-2xl')}
>
<NavigationMenuList className="flex h-14 w-full items-center px-4">
<Link
href={nav.url ?? '/'}
className="inline-flex items-center gap-2.5 font-semibold"
>
{nav.title}
</Link>
{renderTitleNav(nav, {
className: 'inline-flex items-center gap-2.5 font-semibold',
})}
{nav.children}
<ul className="flex flex-row items-center gap-2 px-6 max-sm:hidden">
{navItems
+12 -15
View File
@@ -5,7 +5,11 @@ import {
type ReactNode,
useMemo,
} from 'react';
import { type BaseLayoutProps, resolveLinkItems } from '@/layouts/shared';
import {
type BaseLayoutProps,
renderTitleNav,
resolveLinkItems,
} from '@/layouts/shared';
import {
Sidebar,
SidebarCollapseTrigger,
@@ -30,7 +34,6 @@ import {
LayoutHeaderTabs,
NavbarLinkItem,
} from '@/layouts/notebook/client';
import Link from 'fumadocs-core/link';
import {
LargeSearchToggle,
SearchToggle,
@@ -177,12 +180,9 @@ export function DocsLayout(props: DocsLayoutProps) {
<Header>
{navMode === 'auto' && (
<div className="flex justify-between">
<Link
href={nav.url ?? '/'}
className="inline-flex items-center gap-2.5 font-medium"
>
{nav.title}
</Link>
{renderTitleNav(nav, {
className: 'inline-flex items-center gap-2.5 font-medium',
})}
{collapsible && (
<SidebarCollapseTrigger
className={cn(
@@ -349,15 +349,12 @@ function DocsNavbar({
<SidebarIcon />
</SidebarCollapseTrigger>
)}
<Link
href={nav.url ?? '/'}
className={cn(
{renderTitleNav(nav, {
className: cn(
'inline-flex items-center gap-2.5 font-semibold',
navMode === 'auto' && 'md:hidden',
)}
>
{nav.title}
</Link>
),
})}
</div>
{searchToggle.enabled !== false &&
(searchToggle.components?.lg ? (
+15 -2
View File
@@ -1,12 +1,13 @@
import type { ReactNode } from 'react';
import type { ComponentProps, ReactNode } from 'react';
import type { I18nConfig } from 'fumadocs-core/i18n';
import type { LinkItemType } from '@fumadocs/ui/link-item';
import Link from 'fumadocs-core/link';
export interface NavOptions {
enabled: boolean;
component: ReactNode;
title?: ReactNode;
title?: ReactNode | ((props: ComponentProps<'a'>) => ReactNode);
/**
* Redirect url of title
@@ -86,4 +87,16 @@ export function resolveLinkItems({
return result;
}
export function renderTitleNav(
{ title, url = '/' }: Partial<NavOptions>,
props: ComponentProps<'a'>,
) {
if (typeof title === 'function') return title({ href: url, ...props });
return (
<Link href={url} {...props}>
{title}
</Link>
);
}
export type * from '@fumadocs/ui/link-item';
+12 -14
View File
@@ -18,7 +18,11 @@ import {
SidebarTrigger,
SidebarViewport,
} from './sidebar';
import { type BaseLayoutProps, resolveLinkItems } from '@/layouts/shared';
import {
type BaseLayoutProps,
renderTitleNav,
resolveLinkItems,
} from '@/layouts/shared';
import { LinkItem } from '@fumadocs/ui/link-item';
import {
LanguageToggle,
@@ -32,7 +36,6 @@ import {
} from './client';
import { TreeContextProvider } from '@/contexts/tree';
import { ThemeToggle } from '../shared/theme-toggle';
import Link from 'fumadocs-core/link';
import {
LargeSearchToggle,
SearchToggle,
@@ -147,12 +150,10 @@ export function DocsLayout({
<SidebarContent {...rest}>
<div className="flex flex-col gap-3 p-4 pb-2">
<div className="flex">
<Link
href={nav.url ?? '/'}
className="inline-flex text-[0.9375rem] items-center gap-2.5 font-medium me-auto"
>
{nav.title}
</Link>
{renderTitleNav(nav, {
className:
'inline-flex text-[0.9375rem] items-center gap-2.5 font-medium me-auto',
})}
{nav.children}
{collapsible && (
<SidebarCollapseTrigger
@@ -279,12 +280,9 @@ export function DocsLayout({
id="nd-subnav"
className="[grid-area:header] sticky top-(--fd-docs-row-1) z-30 flex items-center ps-4 pe-2.5 border-b transition-colors backdrop-blur-sm h-(--fd-header-height) md:hidden max-md:layout:[--fd-header-height:--spacing(14)] data-[transparent=false]:bg-fd-background/80"
>
<Link
href={nav.url ?? '/'}
className="inline-flex items-center gap-2.5 font-semibold"
>
{nav.title}
</Link>
{renderTitleNav(nav, {
className: 'inline-flex items-center gap-2.5 font-semibold',
})}
<div className="flex-1">{nav.children}</div>
{searchToggle.enabled !== false &&
(searchToggle.components?.sm ?? (
@@ -6,6 +6,7 @@ import { cn } from '@fumadocs/ui/cn';
import {
type LinkItemType,
type NavOptions,
renderTitleNav,
resolveLinkItems,
} from '@/layouts/shared';
import { LinkItem } from '@fumadocs/ui/link-item';
@@ -82,12 +83,9 @@ export function Header({
return (
<HeaderNavigationMenu transparentMode={nav.transparentMode}>
<Link
href={nav.url ?? '/'}
className="inline-flex items-center gap-2.5 font-semibold"
>
{nav.title}
</Link>
{renderTitleNav(nav, {
className: 'inline-flex items-center gap-2.5 font-semibold',
})}
{nav.children}
<ul className="flex flex-row items-center gap-2 px-6 max-sm:hidden">
{navItems
@@ -5,7 +5,11 @@ import {
type ReactNode,
useMemo,
} from 'react';
import { type BaseLayoutProps, resolveLinkItems } from '@/layouts/shared';
import {
type BaseLayoutProps,
renderTitleNav,
resolveLinkItems,
} from '@/layouts/shared';
import {
Sidebar,
SidebarCollapseTrigger,
@@ -30,7 +34,6 @@ import {
LayoutHeaderTabs,
NavbarLinkItem,
} from '@/layouts/notebook/client';
import Link from 'fumadocs-core/link';
import {
LargeSearchToggle,
SearchToggle,
@@ -177,12 +180,9 @@ export function DocsLayout(props: DocsLayoutProps) {
<Header>
{navMode === 'auto' && (
<div className="flex justify-between">
<Link
href={nav.url ?? '/'}
className="inline-flex items-center gap-2.5 font-medium"
>
{nav.title}
</Link>
{renderTitleNav(nav, {
className: 'inline-flex items-center gap-2.5 font-medium',
})}
{collapsible && (
<SidebarCollapseTrigger
className={cn(
@@ -349,15 +349,12 @@ function DocsNavbar({
<SidebarIcon />
</SidebarCollapseTrigger>
)}
<Link
href={nav.url ?? '/'}
className={cn(
{renderTitleNav(nav, {
className: cn(
'inline-flex items-center gap-2.5 font-semibold',
navMode === 'auto' && 'md:hidden',
)}
>
{nav.title}
</Link>
),
})}
</div>
{searchToggle.enabled !== false &&
(searchToggle.components?.lg ? (
+15 -2
View File
@@ -1,12 +1,13 @@
import type { ReactNode } from 'react';
import type { ComponentProps, ReactNode } from 'react';
import type { I18nConfig } from 'fumadocs-core/i18n';
import type { LinkItemType } from '@fumadocs/ui/link-item';
import Link from 'fumadocs-core/link';
export interface NavOptions {
enabled: boolean;
component: ReactNode;
title?: ReactNode;
title?: ReactNode | ((props: ComponentProps<'a'>) => ReactNode);
/**
* Redirect url of title
@@ -86,4 +87,16 @@ export function resolveLinkItems({
return result;
}
export function renderTitleNav(
{ title, url = '/' }: Partial<NavOptions>,
props: ComponentProps<'a'>,
) {
if (typeof title === 'function') return title({ href: url, ...props });
return (
<Link href={url} {...props}>
{title}
</Link>
);
}
export type * from '@fumadocs/ui/link-item';