mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-25 04:20:02 +00:00
UI: Reduce sidebar intensity
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'fumadocs-ui': patch
|
||||
---
|
||||
|
||||
Reduce sidebar intensity
|
||||
@@ -1,6 +1,12 @@
|
||||
'use client';
|
||||
import { useState } from 'react';
|
||||
import { Check, ChevronDown, Copy } from 'lucide-react';
|
||||
import {
|
||||
Check,
|
||||
ChevronDown,
|
||||
Copy,
|
||||
ExternalLinkIcon,
|
||||
MessageCircleIcon,
|
||||
} from 'lucide-react';
|
||||
import { cn } from '@/lib/cn';
|
||||
import { useCopyButton } from 'fumadocs-ui/utils/use-copy-button';
|
||||
import { buttonVariants } from '@/components/ui/button';
|
||||
@@ -60,11 +66,12 @@ export function LLMCopyButton({ slug }: { slug: string[] }) {
|
||||
}
|
||||
|
||||
const optionVariants = cva(
|
||||
'text-sm px-3 py-2.5 inline-flex items-center font-medium gap-2 hover:text-fd-accent-foreground hover:bg-fd-accent [&_svg]:size-4',
|
||||
'text-sm p-2 rounded-lg inline-flex items-center gap-2 hover:text-fd-accent-foreground hover:bg-fd-accent [&_svg]:size-4',
|
||||
);
|
||||
|
||||
export function ViewOptions(props: { markdownUrl: string; githubUrl: string }) {
|
||||
const q = `Read ${props.markdownUrl}, I want to ask questions about it.`;
|
||||
const markdownUrl = new URL(props.markdownUrl, 'https://fumadocs.dev');
|
||||
const q = `Read ${markdownUrl}, I want to ask questions about it.`;
|
||||
|
||||
const claude = `https://claude.ai/new?${new URLSearchParams({
|
||||
q,
|
||||
@@ -73,6 +80,9 @@ export function ViewOptions(props: { markdownUrl: string; githubUrl: string }) {
|
||||
hints: 'search',
|
||||
q,
|
||||
})}`;
|
||||
const t3 = `https://t3.chat/new?${new URLSearchParams({
|
||||
q,
|
||||
})}`;
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
@@ -88,53 +98,66 @@ export function ViewOptions(props: { markdownUrl: string; githubUrl: string }) {
|
||||
Open
|
||||
<ChevronDown className="size-3.5 text-fd-muted-foreground" />
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="flex flex-col p-0 overflow-auto">
|
||||
<a
|
||||
href={props.githubUrl}
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
className={cn(optionVariants())}
|
||||
>
|
||||
<svg fill="currentColor" role="img" viewBox="0 0 24 24">
|
||||
<title>GitHub</title>
|
||||
<path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" />
|
||||
</svg>
|
||||
Open in GitHub
|
||||
</a>
|
||||
<a
|
||||
href={gpt}
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
className={cn(optionVariants())}
|
||||
>
|
||||
<svg
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
<PopoverContent className="flex flex-col overflow-auto">
|
||||
{[
|
||||
{
|
||||
title: 'Open in GitHub',
|
||||
href: props.githubUrl,
|
||||
icon: (
|
||||
<svg fill="currentColor" role="img" viewBox="0 0 24 24">
|
||||
<title>GitHub</title>
|
||||
<path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Open in ChatGPT',
|
||||
href: gpt,
|
||||
icon: (
|
||||
<svg
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<title>OpenAI</title>
|
||||
<path d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Open in Claude',
|
||||
href: claude,
|
||||
icon: (
|
||||
<svg
|
||||
fill="currentColor"
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<title>Anthropic</title>
|
||||
<path d="M17.3041 3.541h-3.6718l6.696 16.918H24Zm-10.6082 0L0 20.459h3.7442l1.3693-3.5527h7.0052l1.3693 3.5528h3.7442L10.5363 3.5409Zm-.3712 10.2232 2.2914-5.9456 2.2914 5.9456Z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Open in T3 Chat',
|
||||
href: t3,
|
||||
icon: <MessageCircleIcon />,
|
||||
},
|
||||
].map((item) => (
|
||||
<a
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
className={cn(optionVariants())}
|
||||
>
|
||||
<title>OpenAI</title>
|
||||
<path d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z" />
|
||||
</svg>
|
||||
Open in ChatGPT
|
||||
</a>
|
||||
<a
|
||||
href={claude}
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
className={cn(optionVariants())}
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<title>Anthropic</title>
|
||||
<path d="M17.3041 3.541h-3.6718l6.696 16.918H24Zm-10.6082 0L0 20.459h3.7442l1.3693-3.5527h7.0052l1.3693 3.5528h3.7442L10.5363 3.5409Zm-.3712 10.2232 2.2914-5.9456 2.2914 5.9456Z" />
|
||||
</svg>
|
||||
Open in Claude
|
||||
</a>
|
||||
{item.icon}
|
||||
{item.title}
|
||||
<ExternalLinkIcon className="text-fd-muted-foreground size-3.5 ms-auto" />
|
||||
</a>
|
||||
))}
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
|
||||
@@ -91,7 +91,7 @@ export default async function Page(props: {
|
||||
<p className="text-lg text-fd-muted-foreground">
|
||||
{page.data.description}
|
||||
</p>
|
||||
<div className="flex flex-row gap-2 items-center mb-8 border-b pb-6">
|
||||
<div className="flex flex-row gap-2 items-center border-b pb-6">
|
||||
<LLMCopyButton slug={params.slug} />
|
||||
<ViewOptions
|
||||
markdownUrl={`${page.url}.mdx`}
|
||||
|
||||
@@ -25,12 +25,13 @@ export default function Layout({ children }: { children: ReactNode }) {
|
||||
aria-label="Ask AI"
|
||||
className={cn(
|
||||
buttonVariants({
|
||||
variant: 'secondary',
|
||||
variant: 'ghost',
|
||||
size: 'xs',
|
||||
className: 'text-fd-muted-foreground',
|
||||
}),
|
||||
)}
|
||||
>
|
||||
<Sparkles className="size-4 text-fd-primary fill-current" />
|
||||
<Sparkles className="size-4" />
|
||||
</AISearchTrigger>
|
||||
</div>
|
||||
),
|
||||
@@ -68,18 +69,17 @@ export default function Layout({ children }: { children: ReactNode }) {
|
||||
const meta = source.getNodeMeta(node);
|
||||
if (!meta || !node.icon) return option;
|
||||
|
||||
const color = `var(--${meta.file.dirname}-color, var(--color-fd-foreground))`;
|
||||
const color = `var(--${meta.path.split('/')[0]}-color, var(--color-fd-foreground))`;
|
||||
|
||||
return {
|
||||
...option,
|
||||
icon: (
|
||||
<div
|
||||
className="rounded-lg p-1.5 shadow-lg ring-2 m-px border [&_svg]:size-6.5 md:[&_svg]:size-5"
|
||||
className="rounded-lg p-1.5 shadow-lg border [&_svg]:size-6 md:p-1 md:[&_svg]:size-5 md:rounded-md md:mb-auto"
|
||||
style={
|
||||
{
|
||||
color,
|
||||
borderColor: `color-mix(in oklab, ${color} 50%, transparent)`,
|
||||
'--tw-ring-color': `color-mix(in oklab, ${color} 20%, transparent)`,
|
||||
backgroundColor: `color-mix(in oklab, ${color} 10%, transparent)`,
|
||||
} as object
|
||||
}
|
||||
>
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
SearchDialogIcon,
|
||||
SearchDialogInput,
|
||||
SearchDialogList,
|
||||
SearchDialogListItem,
|
||||
SearchDialogOverlay,
|
||||
type SharedProps,
|
||||
} from 'fumadocs-ui/components/dialog/search';
|
||||
@@ -21,7 +20,7 @@ import {
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from 'fumadocs-ui/components/ui/popover';
|
||||
import { ChevronDown, FileText, Hash, TextIcon } from 'lucide-react';
|
||||
import { ChevronDown } from 'lucide-react';
|
||||
import { buttonVariants } from 'fumadocs-ui/components/ui/button';
|
||||
import { cn } from '@/lib/cn';
|
||||
|
||||
@@ -30,18 +29,6 @@ const client = new OramaClient({
|
||||
api_key: 'oPZjdlFbq5BpR54bV5Vj57RYt83Xosk7',
|
||||
});
|
||||
|
||||
const icons = {
|
||||
text: <TextIcon className="size-4 shrink-0 text-fd-muted-foreground" />,
|
||||
heading: <Hash className="size-4 shrink-0 text-fd-muted-foreground" />,
|
||||
page: (
|
||||
<FileText
|
||||
fill="currentColor"
|
||||
stroke="var(--color-fd-muted)"
|
||||
className="size-6 bg-fd-muted p-0.5 rounded-sm border border-fd-foreground/30 shadow-sm shrink-0"
|
||||
/>
|
||||
),
|
||||
};
|
||||
|
||||
const items = [
|
||||
{
|
||||
name: 'All',
|
||||
@@ -92,26 +79,7 @@ export default function CustomSearchDialog(props: SharedProps) {
|
||||
<SearchDialogInput />
|
||||
<SearchDialogClose />
|
||||
</SearchDialogHeader>
|
||||
<SearchDialogList
|
||||
items={query.data !== 'empty' ? query.data : null}
|
||||
Item={({ item, onClick }) => (
|
||||
<SearchDialogListItem item={item} onClick={onClick}>
|
||||
{item.type !== 'page' && (
|
||||
<div
|
||||
role="none"
|
||||
className="absolute start-5.5 inset-y-0 w-px bg-fd-border"
|
||||
/>
|
||||
)}
|
||||
{icons[item.type]}
|
||||
|
||||
{item.type === 'page' ? (
|
||||
<p className="min-w-0 truncate font-medium">{item.content}</p>
|
||||
) : (
|
||||
<p className="min-w-0 truncate">{item.content}</p>
|
||||
)}
|
||||
</SearchDialogListItem>
|
||||
)}
|
||||
/>
|
||||
<SearchDialogList items={query.data !== 'empty' ? query.data : null} />
|
||||
<SearchDialogFooter className="flex flex-row flex-wrap gap-2 items-center">
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
"title": "OpenAPI Example",
|
||||
"description": "A demo for Fumadocs OpenAPI",
|
||||
"root": true,
|
||||
"icon": "Play",
|
||||
"icon": "Shapes",
|
||||
"pages": ["index", "...(generated)"]
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
--color-fd-primary-foreground: hsl(0, 0%, 98%);
|
||||
--color-fd-secondary: hsl(0, 0%, 93.1%);
|
||||
--color-fd-secondary-foreground: hsl(0, 0%, 9%);
|
||||
--color-fd-accent: hsl(0, 0%, 90.1%);
|
||||
--color-fd-accent: hsla(0, 0%, 82%, 50%);
|
||||
--color-fd-accent-foreground: hsl(0, 0%, 9%);
|
||||
--color-fd-ring: hsl(0, 0%, 63.9%);
|
||||
}
|
||||
@@ -22,8 +22,8 @@
|
||||
--color-fd-foreground: hsl(0, 0%, 92%);
|
||||
--color-fd-muted: hsl(0, 0%, 12.9%);
|
||||
--color-fd-muted-foreground: hsl(0, 0%, 60.9%);
|
||||
--color-fd-popover: hsl(0, 0%, 9.8%);
|
||||
--color-fd-popover-foreground: hsl(0, 0%, 88%);
|
||||
--color-fd-popover: hsl(0, 0%, 10.6%);
|
||||
--color-fd-popover-foreground: hsl(0, 0%, 86.9%);
|
||||
--color-fd-card: hsl(0, 0%, 9.8%);
|
||||
--color-fd-card-foreground: hsl(0, 0%, 98%);
|
||||
--color-fd-border: hsla(0, 0%, 40%, 20%);
|
||||
|
||||
@@ -98,7 +98,7 @@
|
||||
"next-themes": "^0.4.6",
|
||||
"postcss-selector-parser": "^7.1.0",
|
||||
"react-medium-image-zoom": "^5.2.14",
|
||||
"react-remove-scroll": "^2.7.1",
|
||||
"scroll-into-view-if-needed": "^3.1.0",
|
||||
"tailwind-merge": "^3.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -116,11 +116,11 @@
|
||||
"tsconfig": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"next": "14.x.x || 15.x.x",
|
||||
"react": "18.x.x || 19.x.x",
|
||||
"react-dom": "18.x.x || 19.x.x",
|
||||
"tailwindcss": "^3.4.14 || ^4.0.0",
|
||||
"@types/react": "*"
|
||||
"tailwindcss": "^3.4.14 || ^4.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"next": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { FileText, Hash, Search as SearchIcon, Text, X } from 'lucide-react';
|
||||
import { FileText, Hash, Search as SearchIcon } from 'lucide-react';
|
||||
import {
|
||||
type ComponentProps,
|
||||
createContext,
|
||||
@@ -28,6 +28,7 @@ import { useEffectEvent } from 'fumadocs-core/utils/use-effect-event';
|
||||
import { useRouter } from 'fumadocs-core/framework';
|
||||
import type { SharedProps } from '@/contexts/search';
|
||||
import { useOnChange } from 'fumadocs-core/utils/use-on-change';
|
||||
import scrollIntoView from 'scroll-into-view-if-needed';
|
||||
|
||||
type ReactSortedResult = Omit<SortedResult, 'content'> & {
|
||||
external?: boolean;
|
||||
@@ -101,7 +102,10 @@ export function SearchDialogHeader(props: ComponentProps<'div'>) {
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
className={cn('flex flex-row items-center gap-2 px-3', props.className)}
|
||||
className={cn(
|
||||
'flex flex-row items-center gap-2 p-3 pb-2',
|
||||
props.className,
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -116,13 +120,13 @@ export function SearchDialogInput(props: ComponentProps<'input'>) {
|
||||
value={search}
|
||||
onChange={(e) => onSearchChange(e.target.value)}
|
||||
placeholder={text.search}
|
||||
className="w-0 flex-1 bg-transparent py-3 text-base placeholder:text-fd-muted-foreground focus-visible:outline-none"
|
||||
className="w-0 flex-1 bg-transparent text-lg placeholder:text-fd-muted-foreground/80 focus-visible:outline-none"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function SearchDialogClose({
|
||||
children = <X />,
|
||||
children = 'ESC',
|
||||
className,
|
||||
...props
|
||||
}: ComponentProps<'button'>) {
|
||||
@@ -131,13 +135,12 @@ export function SearchDialogClose({
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Close"
|
||||
onClick={() => onOpenChange(false)}
|
||||
className={cn(
|
||||
buttonVariants({
|
||||
color: 'ghost',
|
||||
size: 'icon-sm',
|
||||
className: 'text-fd-muted-foreground -me-1.5',
|
||||
color: 'outline',
|
||||
size: 'sm',
|
||||
className: 'font-mono text-fd-muted-foreground',
|
||||
}),
|
||||
className,
|
||||
)}
|
||||
@@ -152,10 +155,7 @@ export function SearchDialogFooter(props: ComponentProps<'div'>) {
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
className={cn(
|
||||
'border-t bg-fd-secondary/50 p-3 empty:hidden',
|
||||
props.className,
|
||||
)}
|
||||
className={cn('bg-fd-secondary/50 p-3 empty:hidden', props.className)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -185,7 +185,7 @@ export function SearchDialogContent({
|
||||
aria-describedby={undefined}
|
||||
{...props}
|
||||
className={cn(
|
||||
'fixed left-1/2 top-[10vh] z-50 w-[calc(100vw-2*var(--spacing))] max-w-screen-sm -translate-x-1/2 rounded-2xl border bg-fd-popover/50 backdrop-blur-lg text-fd-popover-foreground shadow-2xl overflow-hidden shadow-black/30 data-[state=closed]:animate-fd-dialog-out data-[state=open]:animate-fd-dialog-in',
|
||||
'fixed divide-y divide-fd-border left-1/2 top-4 md:top-[calc(50%-250px)] z-50 w-[calc(100%-2*var(--spacing))] max-w-screen-sm -translate-x-1/2 rounded-2xl border bg-fd-popover/60 backdrop-blur-xl text-fd-popover-foreground shadow-2xl shadow-black/40 overflow-hidden data-[state=closed]:animate-fd-dialog-out data-[state=open]:animate-fd-dialog-in',
|
||||
props.className,
|
||||
)}
|
||||
>
|
||||
@@ -284,13 +284,13 @@ export function SearchDialogList({
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'overflow-hidden h-(--fd-animated-height) transition-[height]',
|
||||
items && 'border-t',
|
||||
!items && 'border-b-0',
|
||||
props.className,
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
'w-full flex flex-col overflow-y-auto max-h-[460px]',
|
||||
'w-full flex flex-col overflow-y-auto max-h-[460px] p-1',
|
||||
!items && 'hidden',
|
||||
)}
|
||||
>
|
||||
@@ -317,9 +317,11 @@ export function SearchDialogList({
|
||||
}
|
||||
|
||||
const icons = {
|
||||
text: <Text className="size-4 shrink-0 text-fd-muted-foreground" />,
|
||||
text: null,
|
||||
heading: <Hash className="size-4 shrink-0 text-fd-muted-foreground" />,
|
||||
page: <FileText className="size-4 shrink-0" />,
|
||||
page: (
|
||||
<FileText className="size-6 text-fd-muted-foreground bg-fd-muted border p-0.5 rounded-sm shadow-sm shrink-0" />
|
||||
),
|
||||
};
|
||||
|
||||
export function SearchDialogListItem({
|
||||
@@ -339,8 +341,10 @@ export function SearchDialogListItem({
|
||||
ref={useCallback(
|
||||
(element: HTMLButtonElement | null) => {
|
||||
if (active && element) {
|
||||
element.scrollIntoView({
|
||||
scrollIntoView(element, {
|
||||
scrollMode: 'if-needed',
|
||||
block: 'nearest',
|
||||
boundary: element.parentElement,
|
||||
});
|
||||
}
|
||||
},
|
||||
@@ -348,9 +352,12 @@ export function SearchDialogListItem({
|
||||
)}
|
||||
aria-selected={active}
|
||||
className={cn(
|
||||
'relative flex select-none flex-row items-center gap-2.5 px-3 py-2 text-start text-sm',
|
||||
active && 'bg-fd-accent text-fd-accent-foreground',
|
||||
'relative flex select-none flex-row items-center gap-2 p-2 text-start text-sm rounded-lg',
|
||||
item.type !== 'page' && 'ps-8',
|
||||
item.type === 'page' || item.type === 'heading'
|
||||
? 'font-medium'
|
||||
: 'text-fd-muted-foreground',
|
||||
active && 'bg-fd-accent text-fd-accent-foreground',
|
||||
className,
|
||||
)}
|
||||
onPointerMove={() => setActive(item.id)}
|
||||
@@ -361,7 +368,7 @@ export function SearchDialogListItem({
|
||||
{item.type !== 'page' && (
|
||||
<div
|
||||
role="none"
|
||||
className="absolute start-5 inset-y-0 w-px bg-fd-border"
|
||||
className="absolute start-4.5 inset-y-0 w-px bg-fd-border"
|
||||
/>
|
||||
)}
|
||||
{icons[item.type]}
|
||||
@@ -379,7 +386,7 @@ export function SearchDialogIcon(props: ComponentProps<'svg'>) {
|
||||
<SearchIcon
|
||||
{...props}
|
||||
className={cn(
|
||||
'size-4.5 text-fd-muted-foreground',
|
||||
'size-5 text-fd-muted-foreground',
|
||||
isLoading && 'animate-pulse duration-400',
|
||||
props.className,
|
||||
)}
|
||||
|
||||
@@ -57,36 +57,35 @@ export function RootToggle({
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
{item ? (
|
||||
{item && (
|
||||
<PopoverTrigger
|
||||
{...props}
|
||||
className={cn(
|
||||
'flex items-center gap-2 rounded-lg pe-2 hover:text-fd-accent-foreground',
|
||||
'flex items-center gap-2 rounded-lg p-2 border bg-fd-secondary/50 text-fd-secondary-foreground transition-colors hover:bg-fd-accent hover:text-fd-accent-foreground data-[state=open]:bg-fd-accent',
|
||||
props.className,
|
||||
)}
|
||||
>
|
||||
{item}
|
||||
<ChevronsUpDown className="size-4 text-fd-muted-foreground" />
|
||||
</PopoverTrigger>
|
||||
) : null}
|
||||
<PopoverContent className="w-(--radix-popover-trigger-width) overflow-hidden p-0">
|
||||
{options.map((item) => (
|
||||
<Link
|
||||
key={item.url}
|
||||
href={item.url}
|
||||
onClick={onClick}
|
||||
{...item.props}
|
||||
className={cn(
|
||||
'flex w-full flex-row items-center gap-2 px-2 py-1.5',
|
||||
selected === item
|
||||
? 'bg-fd-accent text-fd-accent-foreground'
|
||||
: 'hover:bg-fd-accent/50',
|
||||
item.props?.className,
|
||||
)}
|
||||
>
|
||||
<Item {...item} />
|
||||
</Link>
|
||||
))}
|
||||
)}
|
||||
<PopoverContent className="flex flex-col gap-2 min-w-(--radix-popover-trigger-width) overflow-hidden p-1">
|
||||
{options
|
||||
.filter((item) => item !== selected)
|
||||
.map((item) => (
|
||||
<Link
|
||||
key={item.url}
|
||||
href={item.url}
|
||||
onClick={onClick}
|
||||
{...item.props}
|
||||
className={cn(
|
||||
'flex w-full items-center gap-2 rounded-lg p-1 hover:bg-fd-accent hover:text-fd-accent-foreground',
|
||||
item.props?.className,
|
||||
)}
|
||||
>
|
||||
<Item {...item} />
|
||||
</Link>
|
||||
))}
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
@@ -97,12 +96,12 @@ function Item(props: Option) {
|
||||
<>
|
||||
<>{props.icon}</>
|
||||
<div className="flex-1 text-start">
|
||||
<p className="text-[15px] font-medium md:text-sm">{props.title}</p>
|
||||
{props.description ? (
|
||||
<p className="text-sm text-fd-muted-foreground md:text-xs">
|
||||
{props.description}
|
||||
</p>
|
||||
) : null}
|
||||
<p className="text-[15px] mb-0.5 font-medium md:text-sm">
|
||||
{props.title}
|
||||
</p>
|
||||
<p className="text-sm text-fd-muted-foreground empty:hidden md:text-[13px]">
|
||||
{props.description}
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -32,7 +32,6 @@ import type {
|
||||
import type { PageTree } from 'fumadocs-core/server';
|
||||
import { useTreeContext, useTreePath } from '@/contexts/tree';
|
||||
import { useMediaQuery } from 'fumadocs-core/utils/use-media-query';
|
||||
import { RemoveScroll } from 'react-remove-scroll';
|
||||
import { Presence } from '@radix-ui/react-presence';
|
||||
|
||||
export interface SidebarProps extends ComponentProps<'aside'> {
|
||||
@@ -66,7 +65,7 @@ interface InternalContext {
|
||||
}
|
||||
|
||||
const itemVariants = cva(
|
||||
'relative flex flex-row items-center gap-2 rounded-lg p-2 text-start text-fd-muted-foreground [overflow-wrap:anywhere] md:py-1.5 [&_svg]:size-4 [&_svg]:shrink-0',
|
||||
'relative flex flex-row items-center gap-2 rounded-xl p-2 text-start text-fd-muted-foreground [overflow-wrap:anywhere] [&_svg]:size-4 [&_svg]:shrink-0',
|
||||
{
|
||||
variants: {
|
||||
active: {
|
||||
@@ -124,20 +123,18 @@ export function Sidebar({
|
||||
</Presence>
|
||||
<Presence present={open}>
|
||||
{({ present }) => (
|
||||
<RemoveScroll
|
||||
as="aside"
|
||||
enabled={present}
|
||||
<aside
|
||||
id="nd-sidebar-mobile"
|
||||
{...props}
|
||||
data-state={state}
|
||||
className={cn(
|
||||
'fixed text-[15px] flex flex-col py-2 rounded-e-2xl border-e start-0 inset-y-0 w-[85%] max-w-[380px] z-40 bg-fd-background data-[state=open]:animate-fd-sidebar-in data-[state=closed]:animate-fd-sidebar-out',
|
||||
'fixed text-[15px] flex flex-col rounded-2xl border start-2 inset-y-2 w-[85%] max-w-[380px] z-40 bg-fd-background data-[state=open]:animate-fd-sidebar-in data-[state=closed]:animate-fd-sidebar-out',
|
||||
!present && 'invisible',
|
||||
props.className,
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
</RemoveScroll>
|
||||
</aside>
|
||||
)}
|
||||
</Presence>
|
||||
</Context.Provider>
|
||||
@@ -249,7 +246,7 @@ export function SidebarSeparator(props: ComponentProps<'p'>) {
|
||||
<p
|
||||
{...props}
|
||||
className={cn(
|
||||
'inline-flex items-center gap-2 mb-1.5 px-2 font-medium empty:mb-0 [&_svg]:size-4 [&_svg]:shrink-0',
|
||||
'inline-flex items-center gap-2 mb-1.5 px-2 empty:mb-0 [&_svg]:size-4 [&_svg]:shrink-0',
|
||||
props.className,
|
||||
)}
|
||||
style={{
|
||||
|
||||
@@ -18,7 +18,7 @@ const PopoverContent = React.forwardRef<
|
||||
sideOffset={sideOffset}
|
||||
side="bottom"
|
||||
className={cn(
|
||||
'z-50 origin-(--radix-popover-content-transform-origin) min-w-[240px] max-w-[98vw] rounded-xl border bg-fd-popover/50 backdrop-blur-lg p-2 text-sm text-fd-popover-foreground shadow-lg focus-visible:outline-none data-[state=closed]:animate-fd-popover-out data-[state=open]:animate-fd-popover-in',
|
||||
'z-50 origin-(--radix-popover-content-transform-origin) min-w-[240px] max-w-[98vw] rounded-xl border bg-fd-popover/60 backdrop-blur-lg p-2 text-sm text-fd-popover-foreground shadow-lg focus-visible:outline-none data-[state=closed]:animate-fd-popover-out data-[state=open]:animate-fd-popover-in',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -94,7 +94,7 @@ export function DocsLayout({
|
||||
<div className="flex max-md:hidden">
|
||||
<Link
|
||||
href={nav.url ?? '/'}
|
||||
className="inline-flex text-[15px] items-center gap-2.5 font-medium"
|
||||
className="inline-flex text-[15px] items-center gap-2.5 font-medium me-auto"
|
||||
>
|
||||
{nav.title}
|
||||
</Link>
|
||||
@@ -106,7 +106,7 @@ export function DocsLayout({
|
||||
color: 'ghost',
|
||||
size: 'icon-sm',
|
||||
className:
|
||||
'ms-auto mb-auto text-fd-muted-foreground max-md:hidden',
|
||||
'mb-auto text-fd-muted-foreground max-md:hidden',
|
||||
}),
|
||||
)}
|
||||
>
|
||||
@@ -114,11 +114,12 @@ export function DocsLayout({
|
||||
</SidebarCollapseTrigger>
|
||||
)}
|
||||
</div>
|
||||
{tabs.length > 0 && <RootToggle options={tabs} />}
|
||||
{searchToggle.enabled !== false &&
|
||||
(searchToggle.components?.lg ?? (
|
||||
<LargeSearchToggle hideIfDisabled className="max-md:hidden" />
|
||||
))}
|
||||
{tabs.length > 0 && <RootToggle options={tabs} />}
|
||||
|
||||
{sidebarBanner}
|
||||
</SidebarHeader>
|
||||
</HideIfEmpty>
|
||||
@@ -139,14 +140,14 @@ export function DocsLayout({
|
||||
<div className="flex items-center justify-end">
|
||||
{links
|
||||
.filter((item) => item.type === 'icon')
|
||||
.map((item, i) => (
|
||||
.map((item, i, arr) => (
|
||||
<BaseLinkItem
|
||||
key={i}
|
||||
item={item}
|
||||
className={cn(
|
||||
buttonVariants({ size: 'icon', color: 'ghost' }),
|
||||
'text-fd-muted-foreground md:[&_svg]:size-4.5',
|
||||
i === links.length - 1 && 'me-auto',
|
||||
i === arr.length - 1 && 'me-auto',
|
||||
)}
|
||||
aria-label={item.label}
|
||||
>
|
||||
|
||||
Generated
+3
-3
@@ -1517,9 +1517,9 @@ importers:
|
||||
react-medium-image-zoom:
|
||||
specifier: ^5.2.14
|
||||
version: 5.2.14([email protected]([email protected]))([email protected])
|
||||
react-remove-scroll:
|
||||
specifier: ^2.7.1
|
||||
version: 2.7.1(@types/[email protected])([email protected])
|
||||
scroll-into-view-if-needed:
|
||||
specifier: ^3.1.0
|
||||
version: 3.1.0
|
||||
tailwind-merge:
|
||||
specifier: ^3.3.1
|
||||
version: 3.3.1
|
||||
|
||||
Reference in New Issue
Block a user