UI: Introduce --fd-nav-height CSS variable for custom navbar

This commit is contained in:
fuma-nama
2024-10-08 19:15:59 +08:00
parent 139361399c
commit 64f0653310
15 changed files with 295 additions and 207 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'fumadocs-ui': minor
---
Introduce `--fd-nav-height` CSS variable for custom navbar
+36 -24
View File
@@ -36,20 +36,47 @@ See [Root Provider](/docs/ui/blocks/root-provider#theme-provider) to learn more.
### RTL Layout ### RTL Layout
RTL (Right-to-left) layout is supported. To enable RTL, pass the `dir` prop to body. RTL (Right-to-left) layout is supported.
To enable RTL, set the `dir` prop to `rtl` in body and root provider (required for Radix UI).
```tsx ```tsx
<body dir="rtl"></body> import { RootProvider } from 'fumadocs-ui/provider';
import type { ReactNode } from 'react';
export default function RootLayout({
children,
}: {
children: ReactNode;
}): ReactNode {
return (
<html lang="en" suppressHydrationWarning>
<body dir="rtl">
<RootProvider dir="rtl">{children}</RootProvider>
</body>
</html>
);
}
``` ```
Also pass the same prop to root provider to configure Radix UI RTL layout. ## CSS Variables
```tsx Fumadocs UI provides some CSS variables for customising the layout.
import { RootProvider } from '@fumadocs-ui/provider';
<RootProvider dir="rtl"></RootProvider>; ### Navbar Height
When the default navbar is replaced, the layout may overlap with your new navbar.
You should set the `--fd-nav-height` variable to the height of your navbar.
```css
:root {
--fd-nav-height: 40px !important;
}
``` ```
> You can use it with CSS media queries.
### Layout Width ### Layout Width
Customise the max width of docs layout with CSS Variables. Customise the max width of docs layout with CSS Variables.
@@ -113,7 +140,9 @@ We use the [Tailwind CSS Typography](https://tailwindcss.com/docs/typography-plu
The Tailwind CSS plugin overrides the default configuration for the Typography plugin. You can use the `prose` utility directly. The Tailwind CSS plugin overrides the default configuration for the Typography plugin. You can use the `prose` utility directly.
```tsx ```tsx
<div className="prose">...</div> <div className="prose">
<h1>Good Heading</h1>
</div>
``` ```
### Colors ### Colors
@@ -135,20 +164,3 @@ export default {
], ],
}; };
``` ```
### Layout Width
Customise the max width of docs layout with Tailwind CSS plugin.
```js
import { createPreset } from 'fumadocs-ui/tailwind-plugin';
/** @type {import('tailwindcss').Config} */
export default {
presets: [
createPreset({
layoutWidth: '1400px',
}),
],
};
```
+15 -4
View File
@@ -1,4 +1,15 @@
import { toRuntime } from "fumadocs-mdx" import { toRuntime } from 'fumadocs-mdx';
import * as docs_0 from "./index.mdx?collection=docs&hash=hash" import * as docs_0 from './index.mdx?collection=docs&hash=hash';
import * as docs_1 from "./folder/test.mdx?collection=docs&hash=hash" import * as docs_1 from './folder/test.mdx?collection=docs&hash=hash';
export const docs = [toRuntime("doc", docs_0, {"path":"index.mdx","absolutePath":"/Users/xred/dev/fumadocs/packages/mdx/test/fixtures/index.mdx"}),toRuntime("doc", docs_1, {"path":"folder/test.mdx","absolutePath":"/Users/xred/dev/fumadocs/packages/mdx/test/fixtures/folder/test.mdx"})]; export const docs = [
toRuntime('doc', docs_0, {
path: 'index.mdx',
absolutePath:
'/Users/xred/dev/fumadocs/packages/mdx/test/fixtures/index.mdx',
}),
toRuntime('doc', docs_1, {
path: 'folder/test.mdx',
absolutePath:
'/Users/xred/dev/fumadocs/packages/mdx/test/fixtures/folder/test.mdx',
}),
];
+2 -1
View File
@@ -87,7 +87,8 @@ export function APIPlayground({
); );
const headers = new Headers(); const headers = new Headers();
if (bodyType !== 'form-data') headers.append('Content-Type', 'application/json'); if (bodyType !== 'form-data')
headers.append('Content-Type', 'application/json');
if (input.authorization) { if (input.authorization) {
headers.append('Authorization', input.authorization); headers.append('Authorization', input.authorization);
+118 -70
View File
@@ -2,7 +2,7 @@ import Link from 'fumadocs-core/link';
import { ChevronDown } from 'lucide-react'; import { ChevronDown } from 'lucide-react';
import { usePathname } from 'next/navigation'; import { usePathname } from 'next/navigation';
import { cva } from 'class-variance-authority'; import { cva } from 'class-variance-authority';
import { type ReactNode, useState } from 'react'; import { HTMLAttributes, type ReactNode, useState } from 'react';
import { useOnChange } from 'fumadocs-core/utils/use-on-change'; import { useOnChange } from 'fumadocs-core/utils/use-on-change';
import { cn } from '@/utils/cn'; import { cn } from '@/utils/cn';
import { isActive } from '@/utils/shared'; import { isActive } from '@/utils/shared';
@@ -19,19 +19,8 @@ import {
} from '@/components/ui/popover'; } from '@/components/ui/popover';
import { itemVariants } from '@/components/layout/variants'; import { itemVariants } from '@/components/layout/variants';
const linkItemVariants = cva( const navItemVariants = cva(
'-m-2 inline-flex items-center gap-1 p-2 text-fd-muted-foreground transition-colors [&_svg]:size-4', '-m-2 inline-flex items-center gap-1 p-2 text-fd-muted-foreground transition-colors hover:text-fd-accent-foreground data-[active=true]:!text-fd-primary [&_svg]:size-4',
{
variants: {
active: {
true: 'text-fd-primary',
false: 'hover:text-fd-accent-foreground',
},
},
defaultVariants: {
active: false,
},
},
); );
interface BaseItem { interface BaseItem {
@@ -54,13 +43,13 @@ interface BaseLinkItem extends BaseItem {
external?: boolean; external?: boolean;
} }
export type LinkItemType = interface MainItem extends BaseLinkItem {
| (BaseLinkItem & {
type?: 'main'; type?: 'main';
icon?: ReactNode; icon?: ReactNode;
text: ReactNode; text: ReactNode;
}) }
| (BaseLinkItem & {
interface IconItem extends BaseLinkItem {
type: 'icon'; type: 'icon';
/** /**
* `aria-label` of icon button * `aria-label` of icon button
@@ -72,8 +61,9 @@ export type LinkItemType =
* @defaultValue true * @defaultValue true
*/ */
secondary?: boolean; secondary?: boolean;
}) }
| (BaseLinkItem & {
interface ButtonItem extends BaseLinkItem {
type: 'button'; type: 'button';
icon?: ReactNode; icon?: ReactNode;
text: ReactNode; text: ReactNode;
@@ -81,8 +71,9 @@ export type LinkItemType =
* @defaultValue false * @defaultValue false
*/ */
secondary?: boolean; secondary?: boolean;
}) }
| (BaseItem & {
interface MenuItem extends BaseItem {
type: 'menu'; type: 'menu';
icon?: ReactNode; icon?: ReactNode;
text: ReactNode; text: ReactNode;
@@ -91,49 +82,39 @@ export type LinkItemType =
* @defaultValue false * @defaultValue false
*/ */
secondary?: boolean; secondary?: boolean;
}) }
| (BaseItem & {
interface CustomItem extends BaseItem {
type: 'custom'; type: 'custom';
/** /**
* @defaultValue false * @defaultValue false
*/ */
secondary?: boolean; secondary?: boolean;
children: ReactNode; children: ReactNode;
}); }
export type LinkItemType =
| MainItem
| IconItem
| ButtonItem
| MenuItem
| CustomItem;
interface LinkItemProps extends React.HTMLAttributes<HTMLElement> { interface LinkItemProps extends React.HTMLAttributes<HTMLElement> {
item: LinkItemType; item: LinkItemType;
on?: 'menu' | 'nav';
} }
export function LinkItem({ export function MenuItem({
item, item,
on = 'nav',
className, className,
...props ...props
}: LinkItemProps): React.ReactNode { }: LinkItemProps): React.ReactNode {
const pathname = usePathname();
if (item.on && item.on !== 'all' && item.on !== on) return null;
if (item.type === 'custom') if (item.type === 'custom')
return <div className={cn('grid', className)}>{item.children}</div>;
if (item.type === 'menu' && on === 'nav') {
return ( return (
<LinksMenu <div className={cn('grid', className)} {...props}>
items={item.items.map((child, i) => ( {item.children}
<LinkItem key={i} item={child} on="menu" /> </div>
))}
className={cn(linkItemVariants({ className }))}
{...props}
>
{item.icon}
{item.text}
<ChevronDown className="ms-auto !size-3.5" />
</LinksMenu>
); );
}
if (item.type === 'menu') { if (item.type === 'menu') {
return ( return (
@@ -149,7 +130,7 @@ export function LinkItem({
<CollapsibleContent> <CollapsibleContent>
<div className="ms-2 flex flex-col border-s py-2 ps-2"> <div className="ms-2 flex flex-col border-s py-2 ps-2">
{item.items.map((child, i) => ( {item.items.map((child, i) => (
<LinkItem key={i} item={child} on="menu" /> <MenuItem key={i} item={child} />
))} ))}
</div> </div>
</CollapsibleContent> </CollapsibleContent>
@@ -158,62 +139,129 @@ export function LinkItem({
} }
if (item.type === 'button') { if (item.type === 'button') {
return <ButtonItem item={item} />;
}
return ( return (
<Link <BaseLinkItem
href={item.url} item={item}
external={item.external} className={cn(itemVariants(), className)}
className={cn( {...props}
buttonVariants({ color: 'secondary' }),
'gap-1.5 [&_svg]:size-4',
className,
)}
> >
{item.icon} {item.icon}
{item.text} {item.text}
</Link> </BaseLinkItem>
);
}
export function LinkItem({
item,
className,
...props
}: LinkItemProps): React.ReactNode {
if (item.type === 'custom')
return (
<div className={cn('grid', className)} {...props}>
{item.children}
</div>
);
if (item.type === 'menu') {
return (
<LinksMenu
items={item.items.map((child, i) => (
<MenuItem key={i} item={child} />
))}
className={cn(navItemVariants({ className }))}
{...props}
>
{item.icon}
{item.text}
<ChevronDown className="ms-auto !size-3.5" />
</LinksMenu>
); );
} }
if (item.type === 'button') {
return <ButtonItem item={item} {...props} />;
}
if (item.type === 'icon') {
return <IconItem item={item} {...props} />;
}
return (
<BaseLinkItem
item={item}
className={cn(navItemVariants(), className)}
{...props}
>
{item.text}
</BaseLinkItem>
);
}
function BaseLinkItem({
item,
...props
}: { item: BaseLinkItem } & HTMLAttributes<HTMLAnchorElement>): ReactNode {
const pathname = usePathname();
const activeType = item.active ?? 'url'; const activeType = item.active ?? 'url';
const active = const active =
activeType !== 'none' && activeType !== 'none' &&
isActive(item.url, pathname, activeType === 'nested-url'); isActive(item.url, pathname, activeType === 'nested-url');
if (item.type === 'icon' && on === 'nav') { return (
<Link
href={item.url}
external={item.external}
data-active={active}
{...props}
>
{props.children}
</Link>
);
}
export function IconItem({
item,
...props
}: { item: IconItem } & HTMLAttributes<HTMLAnchorElement>): ReactNode {
return ( return (
<Link <Link
aria-label={item.label} aria-label={item.label}
href={item.url} href={item.url}
external={item.external} external={item.external}
{...props}
className={cn( className={cn(
buttonVariants({ buttonVariants({
size: 'icon', size: 'icon',
color: 'ghost', color: 'ghost',
className,
}), }),
props.className,
)} )}
{...props}
> >
{item.icon} {item.icon}
</Link> </Link>
); );
} }
function ButtonItem({
item,
...props
}: { item: ButtonItem } & HTMLAttributes<HTMLAnchorElement>): ReactNode {
return ( return (
<Link <Link
href={item.url} href={item.url}
external={item.external} external={item.external}
className={cn(
on === 'nav'
? linkItemVariants({
active,
})
: itemVariants({ active }),
className,
)}
{...props} {...props}
className={cn(
buttonVariants({ color: 'secondary' }),
'gap-1.5 [&_svg]:size-4',
props.className,
)}
> >
{on === 'menu' ? item.icon : null} {item.icon}
{item.text} {item.text}
</Link> </Link>
); );
+1 -1
View File
@@ -52,7 +52,7 @@ export function NavBox({
<header <header
{...props} {...props}
className={cn( className={cn(
'sticky top-fd-sidebar-top z-40 border-b transition-colors', 'sticky top-[var(--fd-banner-height)] z-40 border-b transition-colors',
transparent transparent
? 'border-transparent' ? 'border-transparent'
: 'border-fd-foreground/10 bg-fd-background/80 backdrop-blur-md', : 'border-fd-foreground/10 bg-fd-background/80 backdrop-blur-md',
+22 -16
View File
@@ -16,7 +16,7 @@ import { cn } from '@/utils/cn';
import { useTreeContext } from '@/contexts/tree'; import { useTreeContext } from '@/contexts/tree';
import { ScrollArea, ScrollViewport } from '@/components/ui/scroll-area'; import { ScrollArea, ScrollViewport } from '@/components/ui/scroll-area';
import { hasActive, isActive } from '@/utils/shared'; import { hasActive, isActive } from '@/utils/shared';
import { LinkItem, type LinkItemType } from '@/components/layout/link-item'; import { type LinkItemType, MenuItem } from '@/components/layout/link-item';
import { LargeSearchToggle } from '@/components/layout/search-toggle'; import { LargeSearchToggle } from '@/components/layout/search-toggle';
import { useSearchContext } from '@/contexts/search'; import { useSearchContext } from '@/contexts/search';
import { itemVariants } from '@/components/layout/variants'; import { itemVariants } from '@/components/layout/variants';
@@ -113,13 +113,15 @@ export function Sidebar({
blockScrollingWidth={768} // md blockScrollingWidth={768} // md
{...props.aside} {...props.aside}
className={cn( className={cn(
'fixed z-30 flex flex-col bg-fd-card text-sm md:sticky md:top-fd-sidebar-top md:h-fd-sidebar-height md:w-[var(--fd-c-sidebar)] md:min-w-[var(--fd-sidebar-width)] md:border-e md:ps-[var(--fd-sidebar-offset)]', 'fixed top-fd-layout-top z-30 flex flex-col bg-fd-card text-sm md:sticky md:h-[var(--fd-sidebar-height)] md:w-[var(--fd-c-sidebar)] md:min-w-[var(--fd-sidebar-width)] md:border-e md:ps-[var(--fd-sidebar-offset)]',
'max-md:inset-0 max-md:top-fd-sidebar-top max-md:bg-fd-background/80 max-md:pt-[var(--fd-nav-height)] max-md:text-[15px] max-md:backdrop-blur-md max-md:data-[open=false]:invisible', 'max-md:inset-x-0 max-md:bottom-0 max-md:bg-fd-background/80 max-md:pt-[var(--fd-nav-height)] max-md:text-[15px] max-md:backdrop-blur-md max-md:data-[open=false]:invisible',
props.aside?.className, props.aside?.className,
)} )}
style={ style={
{ {
...props.aside?.style, ...props.aside?.style,
'--fd-sidebar-height':
'calc(100dvh - var(--fd-banner-height) - var(--fd-nav-height))',
'--fd-sidebar-offset': '--fd-sidebar-offset':
'calc(var(--fd-c-sidebar) - var(--fd-sidebar-width))', 'calc(var(--fd-c-sidebar) - var(--fd-sidebar-width))',
} as object } as object
@@ -140,7 +142,15 @@ export function Sidebar({
) : null} ) : null}
</div> </div>
) : null} ) : null}
<ViewportContent items={items} /> <ViewportContent>
{items.length > 0 ? (
<div className="flex flex-col px-4 pt-6 md:hidden">
{items.map((item, i) => (
<MenuItem key={i} item={item} />
))}
</div>
) : null}
</ViewportContent>
{props.footer ? ( {props.footer ? (
<div <div
{...props.footerProps} {...props.footerProps}
@@ -158,9 +168,9 @@ export function Sidebar({
} }
function ViewportContent({ function ViewportContent({
items, children,
}: { }: {
items: LinkItemType[]; children: React.ReactNode;
}): React.ReactElement { }): React.ReactElement {
const { root } = useTreeContext(); const { root } = useTreeContext();
@@ -171,13 +181,7 @@ function ViewportContent({
maskImage: 'linear-gradient(to bottom, transparent 2px, white 24px)', maskImage: 'linear-gradient(to bottom, transparent 2px, white 24px)',
}} }}
> >
{items.length > 0 ? ( {children}
<div className="flex flex-col px-4 pt-6 md:hidden">
{items.map((item, i) => (
<LinkItem key={i} item={item} on="menu" />
))}
</div>
) : null}
<NodeList items={root.children} className="px-4 py-6 md:px-3" /> <NodeList items={root.children} className="px-4 py-6 md:px-3" />
</ScrollViewport> </ScrollViewport>
</ScrollArea> </ScrollArea>
@@ -227,7 +231,8 @@ function PageNode({
<Link <Link
href={url} href={url}
external={external} external={external}
className={cn(itemVariants({ active }))} data-active={active}
className={cn(itemVariants())}
prefetch={prefetch} prefetch={prefetch}
> >
{icon ?? (external ? <ExternalLinkIcon /> : null)} {icon ?? (external ? <ExternalLinkIcon /> : null)}
@@ -289,7 +294,8 @@ function FolderNode({
<Collapsible open={open} onOpenChange={setOpen}> <Collapsible open={open} onOpenChange={setOpen}>
{item.index ? ( {item.index ? (
<Link <Link
className={cn(itemVariants({ active }))} data-active={active}
className={cn(itemVariants())}
href={item.index.url} href={item.index.url}
onClick={onClick} onClick={onClick}
prefetch={prefetch} prefetch={prefetch}
@@ -297,7 +303,7 @@ function FolderNode({
{content} {content}
</Link> </Link>
) : ( ) : (
<CollapsibleTrigger className={cn(itemVariants({ active }))}> <CollapsibleTrigger data-active={active} className={cn(itemVariants())}>
{content} {content}
</CollapsibleTrigger> </CollapsibleTrigger>
)} )}
+1 -1
View File
@@ -32,7 +32,7 @@ export function Toc({ header, footer, children }: TOCProps): ReactElement {
return ( return (
<div <div
data-toc="" data-toc=""
className="sticky top-fd-toc-top flex w-[var(--fd-c-toc)] min-w-[var(--fd-toc-width)] flex-col gap-3 pb-2 pe-[var(--fd-toc-offset)] pt-12 max-lg:hidden md:h-fd-toc-height" className="sticky top-fd-layout-top flex h-fd-toc-height w-[var(--fd-c-toc)] min-w-[var(--fd-toc-width)] flex-col gap-3 pb-2 pe-[var(--fd-toc-offset)] pt-12 max-lg:hidden"
style={ style={
{ {
'--fd-toc-offset': '--fd-toc-offset':
+3 -13
View File
@@ -1,17 +1,7 @@
import { cva } from 'class-variance-authority'; import { cva } from 'class-variance-authority';
export const itemVariants = cva( export const itemVariants = cva([
'flex w-full flex-row items-center gap-2 rounded-md px-2 py-1.5 text-fd-muted-foreground transition-colors duration-100 [overflow-wrap:anywhere] [&_svg]:size-4', 'flex w-full flex-row items-center gap-2 rounded-md px-2 py-1.5 text-fd-muted-foreground transition-colors duration-100 [overflow-wrap:anywhere] [&_svg]:size-4',
{
variants: {
active: {
true: 'bg-fd-primary/10 font-medium text-fd-primary',
false:
'hover:bg-fd-accent/50 hover:text-fd-accent-foreground/80 hover:transition-none', 'hover:bg-fd-accent/50 hover:text-fd-accent-foreground/80 hover:transition-none',
}, 'data-[active=true]:!bg-fd-primary/10 data-[active=true]:font-medium data-[active=true]:!text-fd-primary data-[active=true]:hover:transition-colors',
}, ]);
defaultVariants: {
active: false,
},
},
);
+7 -5
View File
@@ -25,10 +25,7 @@ export function SubNav({
<NavBox <NavBox
id="nd-subnav" id="nd-subnav"
{...props} {...props}
className={cn( className={cn('flex flex-row items-center px-4', props.className)}
'flex h-[var(--fd-nav-height)] flex-row items-center px-4',
props.className,
)}
> >
<Title url={url} title={title} /> <Title url={url} title={title} />
<div className="flex flex-1 flex-row items-center gap-1"> <div className="flex flex-1 flex-row items-center gap-1">
@@ -76,7 +73,12 @@ export function SidebarCollapseTrigger(
); );
} }
export { LinksMenu, LinkItem } from '@/components/layout/link-item'; export {
LinksMenu,
LinkItem,
IconItem,
MenuItem,
} from '@/components/layout/link-item';
export { RootToggle } from '@/components/layout/root-toggle'; export { RootToggle } from '@/components/layout/root-toggle';
export { Sidebar } from '@/components/layout/sidebar'; export { Sidebar } from '@/components/layout/sidebar';
export { TreeContextProvider } from '@/contexts/tree'; export { TreeContextProvider } from '@/contexts/tree';
+14 -8
View File
@@ -21,9 +21,10 @@ declare const {
LanguageToggleText, LanguageToggleText,
LinksMenu, LinksMenu,
RootToggle, RootToggle,
LinkItem,
DynamicSidebar, DynamicSidebar,
Sidebar, Sidebar,
IconItem,
MenuItem,
}: typeof import('./docs.client'); }: typeof import('./docs.client');
export type { LinkItemType }; export type { LinkItemType };
@@ -85,7 +86,7 @@ export function DocsLayout({
<LinksMenu <LinksMenu
key="links" key="links"
items={links.map((item, i) => ( items={links.map((item, i) => (
<LinkItem key={i} item={item} on="menu" /> <MenuItem key={i} item={item} />
))} ))}
className={cn( className={cn(
buttonVariants({ buttonVariants({
@@ -121,11 +122,10 @@ export function DocsLayout({
<Fragment key="links"> <Fragment key="links">
{links {links
.filter((v) => v.type === 'icon') .filter((v) => v.type === 'icon')
.map((v, i) => ( .map((item, i) => (
<LinkItem <IconItem
key={i} key={i}
item={v} item={item}
on="nav"
className="text-fd-muted-foreground md:hidden" className="text-fd-muted-foreground md:hidden"
/> />
))} ))}
@@ -147,11 +147,17 @@ export function DocsLayout({
return ( return (
<TreeContextProvider tree={props.tree}> <TreeContextProvider tree={props.tree}>
{replaceOrDefault(nav, <SubNav className="md:hidden" {...nav} />)} {replaceOrDefault(nav, <SubNav className="h-14 md:hidden" {...nav} />)}
<main <main
id="nd-docs-layout" id="nd-docs-layout"
{...props.containerProps} {...props.containerProps}
className={cn('flex flex-1 flex-row', props.containerProps?.className)} className={cn(
'flex flex-1 flex-row',
!nav.component &&
nav.enabled !== false &&
'[--fd-nav-height:3.5rem] md:[--fd-nav-height:0px]',
props.containerProps?.className,
)}
> >
{replaceOrDefault( {replaceOrDefault(
{ enabled: sidebarEnabled, component: sidebarReplace }, { enabled: sidebarEnabled, component: sidebarReplace },
+13 -5
View File
@@ -1,11 +1,13 @@
'use client'; 'use client';
import { ChevronDown, Languages, MoreVertical } from 'lucide-react'; import { ChevronDown, Languages, MoreVertical } from 'lucide-react';
import { useMemo } from 'react';
import { useSearchContext } from '@/contexts/search'; import { useSearchContext } from '@/contexts/search';
import { import {
LinkItem, LinkItem,
type LinkItemType, type LinkItemType,
LinksMenu, LinksMenu,
MenuItem,
} from '@/components/layout/link-item'; } from '@/components/layout/link-item';
import { import {
LargeSearchToggle, LargeSearchToggle,
@@ -31,6 +33,13 @@ export function Nav({
items: LinkItemType[]; items: LinkItemType[];
}): React.ReactElement { }): React.ReactElement {
const search = useSearchContext(); const search = useSearchContext();
const [navItems, menuItems] = useMemo(
() => [
items.filter((item) => ['nav', 'all'].includes(item.on ?? 'all')),
items.filter((item) => ['menu', 'all'].includes(item.on ?? 'all')),
],
[items],
);
return ( return (
<NavBox <NavBox
@@ -41,7 +50,7 @@ export function Nav({
<nav className="mx-auto flex size-full max-w-fd-container flex-row items-center gap-6 px-4"> <nav className="mx-auto flex size-full max-w-fd-container flex-row items-center gap-6 px-4">
<Title title={props.title} url={props.url} /> <Title title={props.title} url={props.url} />
{props.children} {props.children}
{items {navItems
.filter((item) => !isSecondary(item)) .filter((item) => !isSecondary(item))
.map((item, i) => ( .map((item, i) => (
<LinkItem key={i} item={item} className="text-sm max-sm:hidden" /> <LinkItem key={i} item={item} className="text-sm max-sm:hidden" />
@@ -62,18 +71,17 @@ export function Nav({
</LanguageToggle> </LanguageToggle>
) : null} ) : null}
{items.filter(isSecondary).map((item, i) => ( {navItems.filter(isSecondary).map((item, i) => (
<LinkItem key={i} item={item} className="max-lg:hidden" /> <LinkItem key={i} item={item} className="max-lg:hidden" />
))} ))}
<LinksMenu <LinksMenu
items={ items={
<> <>
{items.map((item, i) => ( {menuItems.map((item, i) => (
<LinkItem <MenuItem
key={i} key={i}
item={item} item={item}
on="menu"
className={cn(!isSecondary(item) && 'sm:hidden')} className={cn(!isSecondary(item) && 'sm:hidden')}
/> />
))} ))}
+1
View File
@@ -22,6 +22,7 @@ export function HomeLayout({
disableThemeSwitch={props.disableThemeSwitch} disableThemeSwitch={props.disableThemeSwitch}
{...nav} {...nav}
> >
<style>{`:root { --fd-nav-height 3.5rem; }`}</style>
{nav.children} {nav.children}
</Nav>, </Nav>,
)} )}
+1 -1
View File
@@ -106,7 +106,7 @@ export function DocsPage({
<div <div
id="nd-tocnav" id="nd-tocnav"
className={cn( className={cn(
'sticky top-fd-toc-top z-10 border-b bg-fd-background/60 text-sm backdrop-blur-md md:top-[calc(4px+var(--fd-banner-height)+var(--fd-nav-height))] md:mx-3 md:rounded-full md:border md:shadow-md', 'sticky top-fd-layout-top z-10 border-b bg-fd-background/60 text-sm backdrop-blur-md md:top-[calc(4px+var(--fd-banner-height)+var(--fd-nav-height))] md:mx-3 md:rounded-full md:border md:shadow-md',
tocPopoverOptions.enabled !== true && 'lg:hidden', tocPopoverOptions.enabled !== true && 'lg:hidden',
)} )}
> >
+5 -7
View File
@@ -126,9 +126,9 @@ export const docsUi = plugin.withOptions<DocsUIOptions>(
'--fd-sidebar-width': '0px', '--fd-sidebar-width': '0px',
'--fd-toc-width': '0px', '--fd-toc-width': '0px',
'--fd-layout-width': layoutWidth, '--fd-layout-width': layoutWidth,
'--fd-nav-height': '56px',
'--fd-banner-height': '0px', '--fd-banner-height': '0px',
'--fd-nav-height': '0px',
// computed // computed
'--fd-doc': '--fd-doc':
'calc(min(100vw, var(--fd-layout-width)) - var(--fd-toc-width) - var(--fd-sidebar-width))', 'calc(min(100vw, var(--fd-layout-width)) - var(--fd-toc-width) - var(--fd-sidebar-width))',
@@ -150,7 +150,6 @@ export const docsUi = plugin.withOptions<DocsUIOptions>(
'@screen md': { '@screen md': {
':root': { ':root': {
'--fd-sidebar-width': '260px', '--fd-sidebar-width': '260px',
'--fd-nav-height': '0px',
}, },
}, },
'@screen lg': { '@screen lg': {
@@ -202,7 +201,7 @@ export const docsUi = plugin.withOptions<DocsUIOptions>(
'.shiki code .diff': { '.shiki code .diff': {
margin: '0 -16px', margin: '0 -16px',
padding: '0 16px', padding: '0 16px',
display: 'inline-block', position: 'relative',
}, },
'.shiki .highlighted': { '.shiki .highlighted': {
margin: '0 -16px', margin: '0 -16px',
@@ -278,11 +277,10 @@ export const docsUi = plugin.withOptions<DocsUIOptions>(
height: { height: {
'fd-toc-height': 'fd-toc-height':
'calc(100dvh - var(--fd-banner-height) - var(--fd-nav-height))', 'calc(100dvh - var(--fd-banner-height) - var(--fd-nav-height))',
'fd-sidebar-height': 'calc(100dvh - var(--fd-banner-height))',
}, },
spacing: { spacing: {
'fd-toc-top': 'calc(var(--fd-banner-height) + var(--fd-nav-height))', 'fd-layout-top':
'fd-sidebar-top': 'var(--fd-banner-height)', 'calc(var(--fd-banner-height) + var(--fd-nav-height))',
}, },
margin: { margin: {
// the offset given to docs content when the sidebar is collapsed // the offset given to docs content when the sidebar is collapsed