UI: Reduce sidebar intensity

This commit is contained in:
Fuma Nama
2025-06-21 21:56:13 +08:00
parent 58b7596ea4
commit 4a1d3cf0b8
14 changed files with 163 additions and 163 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'fumadocs-ui': patch
---
Reduce sidebar intensity
+72 -49
View File
@@ -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>
);
+1 -1
View File
@@ -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`}
+6 -6
View File
@@ -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
}
>
+2 -34
View File
@@ -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
+1 -1
View File
@@ -2,6 +2,6 @@
"title": "OpenAPI Example",
"description": "A demo for Fumadocs OpenAPI",
"root": true,
"icon": "Play",
"icon": "Shapes",
"pages": ["index", "...(generated)"]
}
+3 -3
View File
@@ -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%);
+3 -3
View File
@@ -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": {
+29 -22
View File
@@ -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={{
+1 -1
View File
@@ -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}
+6 -5
View File
@@ -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}
>
+3 -3
View File
@@ -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