mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-26 06:31:49 +00:00
UI: Introduce --fd-nav-height CSS variable for custom navbar
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'fumadocs-ui': minor
|
||||
---
|
||||
|
||||
Introduce `--fd-nav-height` CSS variable for custom navbar
|
||||
@@ -36,20 +36,47 @@ See [Root Provider](/docs/ui/blocks/root-provider#theme-provider) to learn more.
|
||||
|
||||
### 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
|
||||
<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
|
||||
import { RootProvider } from '@fumadocs-ui/provider';
|
||||
Fumadocs UI provides some CSS variables for customising the layout.
|
||||
|
||||
<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
|
||||
|
||||
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.
|
||||
|
||||
```tsx
|
||||
<div className="prose">...</div>
|
||||
<div className="prose">
|
||||
<h1>Good Heading</h1>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 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
@@ -1,4 +1,15 @@
|
||||
import { toRuntime } from "fumadocs-mdx"
|
||||
import * as docs_0 from "./index.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"})];
|
||||
import { toRuntime } from 'fumadocs-mdx';
|
||||
import * as docs_0 from './index.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',
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -87,7 +87,8 @@ export function APIPlayground({
|
||||
);
|
||||
|
||||
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) {
|
||||
headers.append('Authorization', input.authorization);
|
||||
|
||||
@@ -2,7 +2,7 @@ import Link from 'fumadocs-core/link';
|
||||
import { ChevronDown } from 'lucide-react';
|
||||
import { usePathname } from 'next/navigation';
|
||||
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 { cn } from '@/utils/cn';
|
||||
import { isActive } from '@/utils/shared';
|
||||
@@ -19,19 +19,8 @@ import {
|
||||
} from '@/components/ui/popover';
|
||||
import { itemVariants } from '@/components/layout/variants';
|
||||
|
||||
const linkItemVariants = cva(
|
||||
'-m-2 inline-flex items-center gap-1 p-2 text-fd-muted-foreground transition-colors [&_svg]:size-4',
|
||||
{
|
||||
variants: {
|
||||
active: {
|
||||
true: 'text-fd-primary',
|
||||
false: 'hover:text-fd-accent-foreground',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
active: false,
|
||||
},
|
||||
},
|
||||
const navItemVariants = cva(
|
||||
'-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',
|
||||
);
|
||||
|
||||
interface BaseItem {
|
||||
@@ -54,86 +43,78 @@ interface BaseLinkItem extends BaseItem {
|
||||
external?: boolean;
|
||||
}
|
||||
|
||||
interface MainItem extends BaseLinkItem {
|
||||
type?: 'main';
|
||||
icon?: ReactNode;
|
||||
text: ReactNode;
|
||||
}
|
||||
|
||||
interface IconItem extends BaseLinkItem {
|
||||
type: 'icon';
|
||||
/**
|
||||
* `aria-label` of icon button
|
||||
*/
|
||||
label?: string;
|
||||
icon: ReactNode;
|
||||
text: ReactNode;
|
||||
/**
|
||||
* @defaultValue true
|
||||
*/
|
||||
secondary?: boolean;
|
||||
}
|
||||
|
||||
interface ButtonItem extends BaseLinkItem {
|
||||
type: 'button';
|
||||
icon?: ReactNode;
|
||||
text: ReactNode;
|
||||
/**
|
||||
* @defaultValue false
|
||||
*/
|
||||
secondary?: boolean;
|
||||
}
|
||||
|
||||
interface MenuItem extends BaseItem {
|
||||
type: 'menu';
|
||||
icon?: ReactNode;
|
||||
text: ReactNode;
|
||||
items: LinkItemType[];
|
||||
/**
|
||||
* @defaultValue false
|
||||
*/
|
||||
secondary?: boolean;
|
||||
}
|
||||
|
||||
interface CustomItem extends BaseItem {
|
||||
type: 'custom';
|
||||
/**
|
||||
* @defaultValue false
|
||||
*/
|
||||
secondary?: boolean;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export type LinkItemType =
|
||||
| (BaseLinkItem & {
|
||||
type?: 'main';
|
||||
icon?: ReactNode;
|
||||
text: ReactNode;
|
||||
})
|
||||
| (BaseLinkItem & {
|
||||
type: 'icon';
|
||||
/**
|
||||
* `aria-label` of icon button
|
||||
*/
|
||||
label?: string;
|
||||
icon: ReactNode;
|
||||
text: ReactNode;
|
||||
/**
|
||||
* @defaultValue true
|
||||
*/
|
||||
secondary?: boolean;
|
||||
})
|
||||
| (BaseLinkItem & {
|
||||
type: 'button';
|
||||
icon?: ReactNode;
|
||||
text: ReactNode;
|
||||
/**
|
||||
* @defaultValue false
|
||||
*/
|
||||
secondary?: boolean;
|
||||
})
|
||||
| (BaseItem & {
|
||||
type: 'menu';
|
||||
icon?: ReactNode;
|
||||
text: ReactNode;
|
||||
items: LinkItemType[];
|
||||
/**
|
||||
* @defaultValue false
|
||||
*/
|
||||
secondary?: boolean;
|
||||
})
|
||||
| (BaseItem & {
|
||||
type: 'custom';
|
||||
/**
|
||||
* @defaultValue false
|
||||
*/
|
||||
secondary?: boolean;
|
||||
children: ReactNode;
|
||||
});
|
||||
| MainItem
|
||||
| IconItem
|
||||
| ButtonItem
|
||||
| MenuItem
|
||||
| CustomItem;
|
||||
|
||||
interface LinkItemProps extends React.HTMLAttributes<HTMLElement> {
|
||||
item: LinkItemType;
|
||||
on?: 'menu' | 'nav';
|
||||
}
|
||||
|
||||
export function LinkItem({
|
||||
export function MenuItem({
|
||||
item,
|
||||
on = 'nav',
|
||||
className,
|
||||
...props
|
||||
}: LinkItemProps): React.ReactNode {
|
||||
const pathname = usePathname();
|
||||
|
||||
if (item.on && item.on !== 'all' && item.on !== on) return null;
|
||||
|
||||
if (item.type === 'custom')
|
||||
return <div className={cn('grid', className)}>{item.children}</div>;
|
||||
|
||||
if (item.type === 'menu' && on === 'nav') {
|
||||
return (
|
||||
<LinksMenu
|
||||
items={item.items.map((child, i) => (
|
||||
<LinkItem key={i} item={child} on="menu" />
|
||||
))}
|
||||
className={cn(linkItemVariants({ className }))}
|
||||
{...props}
|
||||
>
|
||||
{item.icon}
|
||||
{item.text}
|
||||
<ChevronDown className="ms-auto !size-3.5" />
|
||||
</LinksMenu>
|
||||
<div className={cn('grid', className)} {...props}>
|
||||
{item.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (item.type === 'menu') {
|
||||
return (
|
||||
@@ -149,7 +130,7 @@ export function LinkItem({
|
||||
<CollapsibleContent>
|
||||
<div className="ms-2 flex flex-col border-s py-2 ps-2">
|
||||
{item.items.map((child, i) => (
|
||||
<LinkItem key={i} item={child} on="menu" />
|
||||
<MenuItem key={i} item={child} />
|
||||
))}
|
||||
</div>
|
||||
</CollapsibleContent>
|
||||
@@ -158,62 +139,129 @@ export function LinkItem({
|
||||
}
|
||||
|
||||
if (item.type === 'button') {
|
||||
return <ButtonItem item={item} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<BaseLinkItem
|
||||
item={item}
|
||||
className={cn(itemVariants(), className)}
|
||||
{...props}
|
||||
>
|
||||
{item.icon}
|
||||
{item.text}
|
||||
</BaseLinkItem>
|
||||
);
|
||||
}
|
||||
|
||||
export function LinkItem({
|
||||
item,
|
||||
className,
|
||||
...props
|
||||
}: LinkItemProps): React.ReactNode {
|
||||
if (item.type === 'custom')
|
||||
return (
|
||||
<Link
|
||||
href={item.url}
|
||||
external={item.external}
|
||||
className={cn(
|
||||
buttonVariants({ color: 'secondary' }),
|
||||
'gap-1.5 [&_svg]:size-4',
|
||||
className,
|
||||
)}
|
||||
<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}
|
||||
</Link>
|
||||
<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 active =
|
||||
activeType !== 'none' &&
|
||||
isActive(item.url, pathname, activeType === 'nested-url');
|
||||
|
||||
if (item.type === 'icon' && on === 'nav') {
|
||||
return (
|
||||
<Link
|
||||
aria-label={item.label}
|
||||
href={item.url}
|
||||
external={item.external}
|
||||
className={cn(
|
||||
buttonVariants({
|
||||
size: 'icon',
|
||||
color: 'ghost',
|
||||
className,
|
||||
}),
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{item.icon}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={item.url}
|
||||
external={item.external}
|
||||
className={cn(
|
||||
on === 'nav'
|
||||
? linkItemVariants({
|
||||
active,
|
||||
})
|
||||
: itemVariants({ active }),
|
||||
className,
|
||||
)}
|
||||
data-active={active}
|
||||
{...props}
|
||||
>
|
||||
{on === 'menu' ? item.icon : null}
|
||||
{props.children}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
export function IconItem({
|
||||
item,
|
||||
...props
|
||||
}: { item: IconItem } & HTMLAttributes<HTMLAnchorElement>): ReactNode {
|
||||
return (
|
||||
<Link
|
||||
aria-label={item.label}
|
||||
href={item.url}
|
||||
external={item.external}
|
||||
{...props}
|
||||
className={cn(
|
||||
buttonVariants({
|
||||
size: 'icon',
|
||||
color: 'ghost',
|
||||
}),
|
||||
props.className,
|
||||
)}
|
||||
>
|
||||
{item.icon}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
function ButtonItem({
|
||||
item,
|
||||
...props
|
||||
}: { item: ButtonItem } & HTMLAttributes<HTMLAnchorElement>): ReactNode {
|
||||
return (
|
||||
<Link
|
||||
href={item.url}
|
||||
external={item.external}
|
||||
{...props}
|
||||
className={cn(
|
||||
buttonVariants({ color: 'secondary' }),
|
||||
'gap-1.5 [&_svg]:size-4',
|
||||
props.className,
|
||||
)}
|
||||
>
|
||||
{item.icon}
|
||||
{item.text}
|
||||
</Link>
|
||||
);
|
||||
|
||||
@@ -52,7 +52,7 @@ export function NavBox({
|
||||
<header
|
||||
{...props}
|
||||
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
|
||||
? 'border-transparent'
|
||||
: 'border-fd-foreground/10 bg-fd-background/80 backdrop-blur-md',
|
||||
|
||||
@@ -16,7 +16,7 @@ import { cn } from '@/utils/cn';
|
||||
import { useTreeContext } from '@/contexts/tree';
|
||||
import { ScrollArea, ScrollViewport } from '@/components/ui/scroll-area';
|
||||
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 { useSearchContext } from '@/contexts/search';
|
||||
import { itemVariants } from '@/components/layout/variants';
|
||||
@@ -113,13 +113,15 @@ export function Sidebar({
|
||||
blockScrollingWidth={768} // md
|
||||
{...props.aside}
|
||||
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)]',
|
||||
'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',
|
||||
'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-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,
|
||||
)}
|
||||
style={
|
||||
{
|
||||
...props.aside?.style,
|
||||
'--fd-sidebar-height':
|
||||
'calc(100dvh - var(--fd-banner-height) - var(--fd-nav-height))',
|
||||
'--fd-sidebar-offset':
|
||||
'calc(var(--fd-c-sidebar) - var(--fd-sidebar-width))',
|
||||
} as object
|
||||
@@ -140,7 +142,15 @@ export function Sidebar({
|
||||
) : null}
|
||||
</div>
|
||||
) : 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 ? (
|
||||
<div
|
||||
{...props.footerProps}
|
||||
@@ -158,9 +168,9 @@ export function Sidebar({
|
||||
}
|
||||
|
||||
function ViewportContent({
|
||||
items,
|
||||
children,
|
||||
}: {
|
||||
items: LinkItemType[];
|
||||
children: React.ReactNode;
|
||||
}): React.ReactElement {
|
||||
const { root } = useTreeContext();
|
||||
|
||||
@@ -171,13 +181,7 @@ function ViewportContent({
|
||||
maskImage: 'linear-gradient(to bottom, transparent 2px, white 24px)',
|
||||
}}
|
||||
>
|
||||
{items.length > 0 ? (
|
||||
<div className="flex flex-col px-4 pt-6 md:hidden">
|
||||
{items.map((item, i) => (
|
||||
<LinkItem key={i} item={item} on="menu" />
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
{children}
|
||||
<NodeList items={root.children} className="px-4 py-6 md:px-3" />
|
||||
</ScrollViewport>
|
||||
</ScrollArea>
|
||||
@@ -227,7 +231,8 @@ function PageNode({
|
||||
<Link
|
||||
href={url}
|
||||
external={external}
|
||||
className={cn(itemVariants({ active }))}
|
||||
data-active={active}
|
||||
className={cn(itemVariants())}
|
||||
prefetch={prefetch}
|
||||
>
|
||||
{icon ?? (external ? <ExternalLinkIcon /> : null)}
|
||||
@@ -289,7 +294,8 @@ function FolderNode({
|
||||
<Collapsible open={open} onOpenChange={setOpen}>
|
||||
{item.index ? (
|
||||
<Link
|
||||
className={cn(itemVariants({ active }))}
|
||||
data-active={active}
|
||||
className={cn(itemVariants())}
|
||||
href={item.index.url}
|
||||
onClick={onClick}
|
||||
prefetch={prefetch}
|
||||
@@ -297,7 +303,7 @@ function FolderNode({
|
||||
{content}
|
||||
</Link>
|
||||
) : (
|
||||
<CollapsibleTrigger className={cn(itemVariants({ active }))}>
|
||||
<CollapsibleTrigger data-active={active} className={cn(itemVariants())}>
|
||||
{content}
|
||||
</CollapsibleTrigger>
|
||||
)}
|
||||
|
||||
@@ -32,7 +32,7 @@ export function Toc({ header, footer, children }: TOCProps): ReactElement {
|
||||
return (
|
||||
<div
|
||||
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={
|
||||
{
|
||||
'--fd-toc-offset':
|
||||
|
||||
@@ -1,17 +1,7 @@
|
||||
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',
|
||||
{
|
||||
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',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
active: false,
|
||||
},
|
||||
},
|
||||
);
|
||||
'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',
|
||||
]);
|
||||
|
||||
@@ -25,10 +25,7 @@ export function SubNav({
|
||||
<NavBox
|
||||
id="nd-subnav"
|
||||
{...props}
|
||||
className={cn(
|
||||
'flex h-[var(--fd-nav-height)] flex-row items-center px-4',
|
||||
props.className,
|
||||
)}
|
||||
className={cn('flex flex-row items-center px-4', props.className)}
|
||||
>
|
||||
<Title url={url} title={title} />
|
||||
<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 { Sidebar } from '@/components/layout/sidebar';
|
||||
export { TreeContextProvider } from '@/contexts/tree';
|
||||
|
||||
@@ -21,9 +21,10 @@ declare const {
|
||||
LanguageToggleText,
|
||||
LinksMenu,
|
||||
RootToggle,
|
||||
LinkItem,
|
||||
DynamicSidebar,
|
||||
Sidebar,
|
||||
IconItem,
|
||||
MenuItem,
|
||||
}: typeof import('./docs.client');
|
||||
|
||||
export type { LinkItemType };
|
||||
@@ -85,7 +86,7 @@ export function DocsLayout({
|
||||
<LinksMenu
|
||||
key="links"
|
||||
items={links.map((item, i) => (
|
||||
<LinkItem key={i} item={item} on="menu" />
|
||||
<MenuItem key={i} item={item} />
|
||||
))}
|
||||
className={cn(
|
||||
buttonVariants({
|
||||
@@ -121,11 +122,10 @@ export function DocsLayout({
|
||||
<Fragment key="links">
|
||||
{links
|
||||
.filter((v) => v.type === 'icon')
|
||||
.map((v, i) => (
|
||||
<LinkItem
|
||||
.map((item, i) => (
|
||||
<IconItem
|
||||
key={i}
|
||||
item={v}
|
||||
on="nav"
|
||||
item={item}
|
||||
className="text-fd-muted-foreground md:hidden"
|
||||
/>
|
||||
))}
|
||||
@@ -147,11 +147,17 @@ export function DocsLayout({
|
||||
|
||||
return (
|
||||
<TreeContextProvider tree={props.tree}>
|
||||
{replaceOrDefault(nav, <SubNav className="md:hidden" {...nav} />)}
|
||||
{replaceOrDefault(nav, <SubNav className="h-14 md:hidden" {...nav} />)}
|
||||
<main
|
||||
id="nd-docs-layout"
|
||||
{...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(
|
||||
{ enabled: sidebarEnabled, component: sidebarReplace },
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
'use client';
|
||||
|
||||
import { ChevronDown, Languages, MoreVertical } from 'lucide-react';
|
||||
import { useMemo } from 'react';
|
||||
import { useSearchContext } from '@/contexts/search';
|
||||
import {
|
||||
LinkItem,
|
||||
type LinkItemType,
|
||||
LinksMenu,
|
||||
MenuItem,
|
||||
} from '@/components/layout/link-item';
|
||||
import {
|
||||
LargeSearchToggle,
|
||||
@@ -31,6 +33,13 @@ export function Nav({
|
||||
items: LinkItemType[];
|
||||
}): React.ReactElement {
|
||||
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 (
|
||||
<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">
|
||||
<Title title={props.title} url={props.url} />
|
||||
{props.children}
|
||||
{items
|
||||
{navItems
|
||||
.filter((item) => !isSecondary(item))
|
||||
.map((item, i) => (
|
||||
<LinkItem key={i} item={item} className="text-sm max-sm:hidden" />
|
||||
@@ -62,18 +71,17 @@ export function Nav({
|
||||
</LanguageToggle>
|
||||
) : null}
|
||||
|
||||
{items.filter(isSecondary).map((item, i) => (
|
||||
{navItems.filter(isSecondary).map((item, i) => (
|
||||
<LinkItem key={i} item={item} className="max-lg:hidden" />
|
||||
))}
|
||||
|
||||
<LinksMenu
|
||||
items={
|
||||
<>
|
||||
{items.map((item, i) => (
|
||||
<LinkItem
|
||||
{menuItems.map((item, i) => (
|
||||
<MenuItem
|
||||
key={i}
|
||||
item={item}
|
||||
on="menu"
|
||||
className={cn(!isSecondary(item) && 'sm:hidden')}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -22,6 +22,7 @@ export function HomeLayout({
|
||||
disableThemeSwitch={props.disableThemeSwitch}
|
||||
{...nav}
|
||||
>
|
||||
<style>{`:root { --fd-nav-height 3.5rem; }`}</style>
|
||||
{nav.children}
|
||||
</Nav>,
|
||||
)}
|
||||
|
||||
@@ -106,7 +106,7 @@ export function DocsPage({
|
||||
<div
|
||||
id="nd-tocnav"
|
||||
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',
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -126,9 +126,9 @@ export const docsUi = plugin.withOptions<DocsUIOptions>(
|
||||
'--fd-sidebar-width': '0px',
|
||||
'--fd-toc-width': '0px',
|
||||
'--fd-layout-width': layoutWidth,
|
||||
|
||||
'--fd-nav-height': '56px',
|
||||
'--fd-banner-height': '0px',
|
||||
'--fd-nav-height': '0px',
|
||||
|
||||
// computed
|
||||
'--fd-doc':
|
||||
'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': {
|
||||
':root': {
|
||||
'--fd-sidebar-width': '260px',
|
||||
'--fd-nav-height': '0px',
|
||||
},
|
||||
},
|
||||
'@screen lg': {
|
||||
@@ -202,7 +201,7 @@ export const docsUi = plugin.withOptions<DocsUIOptions>(
|
||||
'.shiki code .diff': {
|
||||
margin: '0 -16px',
|
||||
padding: '0 16px',
|
||||
display: 'inline-block',
|
||||
position: 'relative',
|
||||
},
|
||||
'.shiki .highlighted': {
|
||||
margin: '0 -16px',
|
||||
@@ -278,11 +277,10 @@ export const docsUi = plugin.withOptions<DocsUIOptions>(
|
||||
height: {
|
||||
'fd-toc-height':
|
||||
'calc(100dvh - var(--fd-banner-height) - var(--fd-nav-height))',
|
||||
'fd-sidebar-height': 'calc(100dvh - var(--fd-banner-height))',
|
||||
},
|
||||
spacing: {
|
||||
'fd-toc-top': 'calc(var(--fd-banner-height) + var(--fd-nav-height))',
|
||||
'fd-sidebar-top': 'var(--fd-banner-height)',
|
||||
'fd-layout-top':
|
||||
'calc(var(--fd-banner-height) + var(--fd-nav-height))',
|
||||
},
|
||||
margin: {
|
||||
// the offset given to docs content when the sidebar is collapsed
|
||||
|
||||
Reference in New Issue
Block a user