UI: No longer expose layout components

This commit is contained in:
Fuma Nama
2025-11-23 11:16:07 +08:00
parent 6635eb5ab8
commit 80579fd71f
10 changed files with 106 additions and 141 deletions
+11
View File
@@ -0,0 +1,11 @@
---
'fumadocs-ui': minor
---
**No longer expose layout components**
This includes Root Toggle, Language Toggle, Theme Toggle etc.
It allows Fumadocs UI to change these components without introducing breaking changes over existing customizations.
If you're using the removed components, consider overriding the layout components with yours, or use Fumadocs CLI add/customize command.
-25
View File
@@ -3,7 +3,6 @@ import { Heading } from 'fumadocs-ui/components/heading';
import { Card } from 'fumadocs-ui/components/card';
import { Callout } from 'fumadocs-ui/components/callout';
import { Accordion, Accordions } from 'fumadocs-ui/components/accordion';
import { RootToggle } from 'fumadocs-ui/components/layout/root-toggle';
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
import { Step, Steps } from 'fumadocs-ui/components/steps';
import { TypeTable } from 'fumadocs-ui/components/type-table';
@@ -200,30 +199,6 @@ export function steps(): ReactNode {
);
}
export function rootToggle(): ReactNode {
return (
<Wrapper>
<div className="not-prose mx-auto grid max-w-[240px] rounded-lg bg-fd-background">
<RootToggle
className="p-3"
options={[
{
title: 'Hello World',
description: 'The example item of root toggle',
url: '/docs/ui',
},
{
title: 'Other page',
description: 'The example item of root toggle',
url: '/docs/headless',
},
]}
/>
</div>
</Wrapper>
);
}
export function dynamicCodeBlock() {
return (
<Wrapper>
@@ -57,7 +57,7 @@ export default function Layout({ children }: { children: ReactNode }) {
Sidebar Tabs are folders with tab-like behaviours, only the content of opened tab will be visible.
<div className='flex justify-center items-center *:max-w-[360px] bg-gradient-to-br from-fd-primary/10 rounded-xl border'>
<div className='flex justify-center items-center *:max-w-[360px] bg-linear-to-br from-fd-primary/10 rounded-xl border'>
![Sidebar Tabs](/docs/sidebar-tabs.png)
+51 -61
View File
@@ -110,24 +110,44 @@ export const registry: Registry = {
{
type: 'components',
path: 'layouts/shared/index.tsx',
target: 'components/layout/shared/index.tsx',
target: 'components/layout/shared.tsx',
},
{
type: 'components',
path: 'layouts/shared/client.tsx',
target: 'components/layout/shared/client.tsx',
path: 'layouts/shared/sidebar-tab.tsx',
target: 'components/layout/sidebar-tab.tsx',
},
{
type: 'components',
path: 'components/layout/search-toggle.tsx',
path: 'layouts/shared/language-toggle.tsx',
target: 'components/layout/language-toggle.tsx',
},
{
type: 'components',
path: 'components/layout/theme-toggle.tsx',
path: 'layouts/shared/link-item.tsx',
target: 'components/layout/link-item.tsx',
},
{
type: 'components',
path: 'components/layout/sidebar.tsx',
path: 'layouts/shared/search-toggle.tsx',
target: 'components/layout/search-toggle.tsx',
},
{
type: 'components',
path: 'layouts/shared/theme-toggle.tsx',
target: 'components/layout/theme-toggle.tsx',
},
{
type: 'components',
path: 'components/toc/clerk.tsx',
},
{
type: 'components',
path: 'components/toc/default.tsx',
},
{
type: 'components',
path: 'components/toc/index.tsx',
},
],
},
@@ -144,6 +164,21 @@ export const registry: Registry = {
path: 'layouts/docs/client.tsx',
target: 'components/layout/docs/client.tsx',
},
{
type: 'block',
path: 'layouts/docs/sidebar.tsx',
target: 'components/layout/docs/sidebar.tsx',
},
{
type: 'block',
path: 'layouts/docs/page/index.tsx',
target: 'components/layout/docs/page/index.tsx',
},
{
type: 'block',
path: 'layouts/docs/page/client.tsx',
target: 'components/layout/docs/page/client.tsx',
},
],
unlisted: true,
},
@@ -153,45 +188,27 @@ export const registry: Registry = {
{
type: 'block',
path: 'layouts/notebook/index.tsx',
target: 'components/layout/docs/index.tsx',
target: 'components/layout/notebook/index.tsx',
},
{
type: 'block',
path: 'layouts/notebook/client.tsx',
target: 'components/layout/docs/client.tsx',
},
],
unlisted: true,
},
{
name: 'layouts/page',
files: [
{
type: 'components',
path: 'layouts/docs/page.tsx',
target: 'components/layout/docs/page.tsx',
target: 'components/layout/notebook/client.tsx',
},
{
type: 'block',
path: 'layouts/docs/page-client.tsx',
target: 'components/layout/docs/page-client.tsx',
path: 'layouts/notebook/sidebar.tsx',
target: 'components/layout/notebook/sidebar.tsx',
},
{
type: 'components',
path: 'page.tsx',
target: 'components/layout/page.tsx',
type: 'block',
path: 'layouts/notebook/page/index.tsx',
target: 'components/layout/notebook/page/index.tsx',
},
{
type: 'ui',
path: 'components/layout/toc.tsx',
},
{
type: 'ui',
path: 'components/layout/toc-clerk.tsx',
},
{
type: 'ui',
path: 'components/layout/toc-thumb.tsx',
type: 'block',
path: 'layouts/notebook/page/client.tsx',
target: 'components/layout/notebook/page/client.tsx',
},
],
unlisted: true,
@@ -204,13 +221,6 @@ export const registry: Registry = {
path: 'layouts/home/index.tsx',
target: 'components/layout/home/index.tsx',
},
/* optional
{
type: 'components',
path: 'layouts/home/navbar.tsx',
target: 'components/layout/home/navbar.tsx',
},
*/
{
type: 'components',
path: 'layouts/home/client.tsx',
@@ -219,26 +229,6 @@ export const registry: Registry = {
],
unlisted: true,
},
{
name: 'root-toggle',
description: 'the UI of Sidebar Tabs',
files: [
{
type: 'components',
path: 'components/layout/root-toggle.tsx',
},
],
},
{
name: 'language-toggle',
description: 'Language Select',
files: [
{
type: 'components',
path: 'components/layout/language-toggle.tsx',
},
],
},
{
name: 'accordion',
files: [
+6 -3
View File
@@ -8,10 +8,10 @@ import { useSidebar } from '@/contexts/sidebar';
import { useNav } from '@/contexts/layout';
import { SidebarCollapseTrigger } from './sidebar';
import { SearchToggle } from '../shared/search-toggle';
import type { Option } from '@/layouts/shared/root-toggle';
import { usePathname } from 'fumadocs-core/framework';
import { isTabActive } from '@/utils/is-active';
import Link from 'fumadocs-core/link';
import type { SidebarTab } from '@/utils/get-sidebar-tabs';
export function LayoutHeader(props: ComponentProps<'header'>) {
const { isTransparent } = useNav();
@@ -37,7 +37,10 @@ export function LayoutBody({
return (
<div
id="nd-docs-layout"
className={cn('grid transition-[grid-template-columns]', className)}
className={cn(
'grid transition-[grid-template-columns] overflow-x-clip',
className,
)}
style={{
gridTemplate: `"sidebar header header"
"sidebar toc-popover toc-popover"
@@ -81,7 +84,7 @@ export function LayoutTabs({
options,
...props
}: ComponentProps<'div'> & {
options: Option[];
options: SidebarTab[];
}) {
const pathname = usePathname();
const selected = useMemo(() => {
+16 -13
View File
@@ -14,15 +14,16 @@ import {
type SidebarComponents,
SidebarContent,
SidebarContentMobile,
SidebarFooter,
SidebarHeader,
SidebarLinkItem,
SidebarPageTree,
type SidebarProps,
SidebarTrigger,
SidebarViewport,
} from './sidebar';
import { type Option, RootToggle } from '@/layouts/shared/root-toggle';
import {
type SidebarTabWithProps,
SidebarTabTrigger,
} from '@/layouts/shared/sidebar-tab';
import { type BaseLayoutProps, resolveLinkItems } from '@/layouts/shared';
import { LinkItem } from '@/layouts/shared/link-item';
import {
@@ -71,7 +72,7 @@ interface SidebarOptions
/**
* Root Toggle options
*/
tabs?: Option[] | GetSidebarTabsOptions | false;
tabs?: SidebarTabWithProps[] | GetSidebarTabsOptions | false;
banner?: ReactNode;
footer?: ReactNode;
@@ -145,7 +146,7 @@ export function DocsLayout({
const mobile = (
<SidebarContentMobile {...rest}>
<SidebarHeader>
<div className="flex flex-col gap-3 p-4 pb-2">
<div className="flex text-fd-muted-foreground items-center gap-1.5">
<div className="flex flex-1">
{iconLinks.map((item, i) => (
@@ -187,17 +188,19 @@ export function DocsLayout({
<SidebarIcon />
</SidebarTrigger>
</div>
{tabs.length > 0 && <RootToggle options={tabs} />}
{tabs.length > 0 && <SidebarTabTrigger options={tabs} />}
{banner}
</SidebarHeader>
</div>
{viewport}
<SidebarFooter className="empty:hidden">{footer}</SidebarFooter>
<div className="flex flex-col border-t p-4 pt-2 empty:hidden">
{footer}
</div>
</SidebarContentMobile>
);
const content = (
<SidebarContent {...rest}>
<SidebarHeader>
<div className="flex flex-col gap-3 p-4 pb-2">
<div className="flex">
<Link
href={nav.url ?? '/'}
@@ -225,16 +228,16 @@ export function DocsLayout({
<LargeSearchToggle hideIfDisabled />
))}
{tabs.length > 0 && tabMode === 'auto' && (
<RootToggle options={tabs} />
<SidebarTabTrigger options={tabs} />
)}
{banner}
</SidebarHeader>
</div>
{viewport}
{(i18n ||
iconLinks.length > 0 ||
themeSwitch?.enabled !== false ||
footer) && (
<SidebarFooter>
<div className="flex flex-col border-t p-4 pt-2 empty:hidden">
<div className="flex text-fd-muted-foreground items-center empty:hidden">
{i18n && (
<LanguageToggle>
@@ -262,7 +265,7 @@ export function DocsLayout({
))}
</div>
{footer}
</SidebarFooter>
</div>
)}
</SidebarContent>
);
+7 -27
View File
@@ -116,12 +116,14 @@ export function SidebarContent(props: ComponentProps<'aside'>) {
const ignoreHoverUntil = useRef(0);
useOnChange(collapsed, () => {
setHover(true);
ignoreHoverUntil.current = Date.now() + 200;
if (collapsed) {
setHover(true);
ignoreHoverUntil.current = Date.now() + 200;
setTimeout(() => {
setHover(false);
}, 200);
setTimeout(() => {
setHover(false);
}, 200);
}
});
return (
@@ -213,28 +215,6 @@ export function SidebarContentMobile({
);
}
export function SidebarHeader(props: ComponentProps<'div'>) {
return (
<div
{...props}
className={cn('flex flex-col gap-3 p-4 pb-2', props.className)}
>
{props.children}
</div>
);
}
export function SidebarFooter(props: ComponentProps<'div'>) {
return (
<div
{...props}
className={cn('flex flex-col border-t p-4 pt-2', props.className)}
>
{props.children}
</div>
);
}
export function SidebarViewport(props: ScrollAreaProps) {
return (
<ScrollArea {...props} className={cn('min-h-0 flex-1', props.className)}>
+3 -3
View File
@@ -8,7 +8,7 @@ import { Sidebar as SidebarIcon } from 'lucide-react';
import Link from 'fumadocs-core/link';
import { usePathname } from 'fumadocs-core/framework';
import { isTabActive } from '@/utils/is-active';
import type { Option } from '@/layouts/shared/root-toggle';
import type { SidebarTabWithProps } from '@/layouts/shared/sidebar-tab';
export function Navbar({
mode,
@@ -86,7 +86,7 @@ export function LayoutTabs({
options,
...props
}: ComponentProps<'div'> & {
options: Option[];
options: SidebarTabWithProps[];
}) {
const pathname = usePathname();
const selected = useMemo(() => {
@@ -116,7 +116,7 @@ function LayoutTab({
option: { title, url, unlisted, props },
selected = false,
}: {
option: Option;
option: SidebarTabWithProps;
selected?: boolean;
}) {
return (
+8 -5
View File
@@ -49,7 +49,10 @@ import {
NavbarSidebarTrigger,
} from '@/layouts/notebook/client';
import { NavProvider } from '@/contexts/layout';
import { type Option, RootToggle } from '@/layouts/shared/root-toggle';
import {
type SidebarTabWithProps,
SidebarTabTrigger,
} from '@/layouts/shared/sidebar-tab';
import Link from 'fumadocs-core/link';
import {
LargeSearchToggle,
@@ -82,7 +85,7 @@ interface SidebarOptions
/**
* Root Toggle options
*/
tabs?: Option[] | GetSidebarTabsOptions | false;
tabs?: SidebarTabWithProps[] | GetSidebarTabsOptions | false;
banner?: ReactNode | FC<ComponentProps<typeof SidebarHeader>>;
footer?: ReactNode | FC<ComponentProps<typeof SidebarFooter>>;
@@ -155,10 +158,10 @@ export function DocsLayout(props: DocsLayoutProps) {
const rootToggle = (
<>
{tabMode === 'sidebar' && tabs.length > 0 && (
<RootToggle className="mb-2" options={tabs} />
<SidebarTabTrigger className="mb-2" options={tabs} />
)}
{tabMode === 'navbar' && tabs.length > 0 && (
<RootToggle options={tabs} className="lg:hidden" />
<SidebarTabTrigger options={tabs} className="lg:hidden" />
)}
</>
);
@@ -340,7 +343,7 @@ function DocsNavbar({
i18n,
}: DocsLayoutProps & {
links: LinkItemType[];
tabs: Option[];
tabs: SidebarTabWithProps[];
}) {
const navMode = nav.mode ?? 'auto';
@@ -13,17 +13,17 @@ import {
} from '@/components/ui/popover';
import type { SidebarTab } from '@/utils/get-sidebar-tabs';
export interface Option extends SidebarTab {
export interface SidebarTabWithProps extends SidebarTab {
props?: ComponentProps<'a'>;
}
export function RootToggle({
export function SidebarTabTrigger({
options,
placeholder,
...props
}: {
placeholder?: ReactNode;
options: Option[];
options: SidebarTabWithProps[];
} & ComponentProps<'button'>) {
const [open, setOpen] = useState(false);
const { closeOnRedirect } = useSidebar();