Skip to content

Commit 146036b

Browse files
committed
feat: add base fumadocs components
Signed-off-by: ItsNeil17 <neil@willofsteel.me>
1 parent 2de03df commit 146036b

27 files changed

Lines changed: 3025 additions & 12 deletions

bun.lock

Lines changed: 10 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cli.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"aliases": {
3+
"uiDir": "./components/ui",
4+
"componentsDir": "./components",
5+
"blockDir": "./components",
6+
"cssDir": "./styles",
7+
"libDir": "./lib"
8+
},
9+
"baseDir": "src",
10+
"commands": {}
11+
}

package.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,20 @@
1111
"lint": "eslint"
1212
},
1313
"dependencies": {
14+
"@radix-ui/react-collapsible": "^1.1.12",
15+
"@radix-ui/react-popover": "^1.1.15",
16+
"@radix-ui/react-presence": "^1.1.5",
17+
"@radix-ui/react-scroll-area": "^1.2.10",
18+
"class-variance-authority": "^0.7.1",
1419
"fumadocs-core": "16.0.15",
1520
"fumadocs-mdx": "14.0.3",
1621
"fumadocs-ui": "16.0.15",
1722
"lucide-react": "^0.552.0",
1823
"next": "16.0.1",
24+
"next-themes": "^0.4.6",
1925
"react": "^19.2.0",
20-
"react-dom": "^19.2.0"
26+
"react-dom": "^19.2.0",
27+
"tailwind-merge": "^3.4.0"
2128
},
2229
"devDependencies": {
2330
"@tailwindcss/postcss": "^4.1.16",

src/app/docs/[[...slug]]/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {
44
DocsDescription,
55
DocsPage,
66
DocsTitle,
7-
} from 'fumadocs-ui/page';
7+
} from '@/components/layout/page';
88
import { notFound } from 'next/navigation';
99
import { getMDXComponents } from '@/mdx-components';
1010
import type { Metadata } from 'next';

src/app/docs/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { DocsLayout } from 'fumadocs-ui/layouts/docs';
1+
import { DocsLayout } from '@/components/layout/docs/';
22
import { source } from '@/lib/source';
33
import { baseOptions } from '@/lib/layout.shared';
44
import { DocsBanner } from './docs-banner'; // Adjust path as needed

src/components/language-toggle.tsx

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
'use client';
2+
import { type ButtonHTMLAttributes, type HTMLAttributes } from 'react';
3+
import { useI18n } from 'fumadocs-ui/contexts/i18n';
4+
import {
5+
Popover,
6+
PopoverContent,
7+
PopoverTrigger,
8+
} from './ui/popover';
9+
import { cn } from '../lib/cn';
10+
import { buttonVariants } from './ui/button';
11+
12+
export type LanguageSelectProps = ButtonHTMLAttributes<HTMLButtonElement>;
13+
14+
export function LanguageToggle(props: LanguageSelectProps): React.ReactElement {
15+
const context = useI18n();
16+
if (!context.locales) throw new Error('Missing `<I18nProvider />`');
17+
18+
return (
19+
<Popover>
20+
<PopoverTrigger
21+
aria-label={context.text.chooseLanguage}
22+
{...props}
23+
className={cn(
24+
buttonVariants({
25+
color: 'ghost',
26+
className: 'gap-1.5 p-1.5',
27+
}),
28+
props.className,
29+
)}
30+
>
31+
{props.children}
32+
</PopoverTrigger>
33+
<PopoverContent className="flex flex-col overflow-x-hidden p-0">
34+
<p className="mb-1 p-2 text-xs font-medium text-fd-muted-foreground">
35+
{context.text.chooseLanguage}
36+
</p>
37+
{context.locales.map((item) => (
38+
<button
39+
key={item.locale}
40+
type="button"
41+
className={cn(
42+
'p-2 text-start text-sm',
43+
item.locale === context.locale
44+
? 'bg-fd-primary/10 font-medium text-fd-primary'
45+
: 'hover:bg-fd-accent hover:text-fd-accent-foreground',
46+
)}
47+
onClick={() => {
48+
context.onChange?.(item.locale);
49+
}}
50+
>
51+
{item.name}
52+
</button>
53+
))}
54+
</PopoverContent>
55+
</Popover>
56+
);
57+
}
58+
59+
export function LanguageToggleText(
60+
props: HTMLAttributes<HTMLSpanElement>,
61+
): React.ReactElement {
62+
const context = useI18n();
63+
const text = context.locales?.find(
64+
(item) => item.locale === context.locale,
65+
)?.name;
66+
67+
return <span {...props}>{text}</span>;
68+
}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
'use client';
2+
3+
import { Sidebar as SidebarIcon } from 'lucide-react';
4+
import { type ComponentProps, useMemo } from 'react';
5+
import { cn } from '../../../lib/cn';
6+
import { buttonVariants } from '../../ui/button';
7+
import { useSidebar } from 'fumadocs-ui/contexts/sidebar';
8+
import { useNav } from 'fumadocs-ui/contexts/layout';
9+
import { SidebarCollapseTrigger } from '../../sidebar';
10+
import { SearchToggle } from '../../search-toggle';
11+
import type { Option } from '../../root-toggle';
12+
import { usePathname } from 'fumadocs-core/framework';
13+
import { isTabActive } from '../../../lib/is-active';
14+
import Link from 'fumadocs-core/link';
15+
16+
export function Navbar(props: ComponentProps<'header'>) {
17+
const { isTransparent } = useNav();
18+
19+
return (
20+
<header
21+
id="nd-subnav"
22+
{...props}
23+
className={cn(
24+
'fixed top-(--fd-banner-height) left-0 right-(--removed-body-scroll-bar-size,0) z-30 flex items-center ps-4 pe-2.5 border-b transition-colors backdrop-blur-sm',
25+
!isTransparent && 'bg-fd-background/80',
26+
props.className,
27+
)}
28+
>
29+
{props.children}
30+
</header>
31+
);
32+
}
33+
34+
export function LayoutBody(props: ComponentProps<'main'>) {
35+
const { collapsed } = useSidebar();
36+
37+
return (
38+
<main
39+
id="nd-docs-layout"
40+
{...props}
41+
className={cn(
42+
'flex flex-1 flex-col pt-(--fd-nav-height) transition-[padding] fd-default-layout',
43+
!collapsed && 'mx-(--fd-layout-offset)',
44+
props.className,
45+
)}
46+
style={{
47+
...props.style,
48+
paddingInlineStart: collapsed
49+
? 'min(calc(100vw - var(--fd-page-width)), var(--fd-sidebar-width))'
50+
: 'var(--fd-sidebar-width)',
51+
}}
52+
>
53+
{props.children}
54+
</main>
55+
);
56+
}
57+
58+
export function CollapsibleControl() {
59+
const { collapsed } = useSidebar();
60+
61+
return (
62+
<div
63+
className={cn(
64+
'fixed flex shadow-lg transition-opacity rounded-xl p-0.5 border bg-fd-muted text-fd-muted-foreground z-10 max-md:hidden xl:start-4 max-xl:end-4',
65+
!collapsed && 'pointer-events-none opacity-0',
66+
)}
67+
style={{
68+
top: 'calc(var(--fd-banner-height) + var(--fd-tocnav-height) + var(--spacing) * 4)',
69+
}}
70+
>
71+
<SidebarCollapseTrigger
72+
className={cn(
73+
buttonVariants({
74+
color: 'ghost',
75+
size: 'icon-sm',
76+
className: 'rounded-lg',
77+
}),
78+
)}
79+
>
80+
<SidebarIcon />
81+
</SidebarCollapseTrigger>
82+
<SearchToggle className="rounded-lg" hideIfDisabled />
83+
</div>
84+
);
85+
}
86+
87+
export function LayoutTabs({
88+
options,
89+
...props
90+
}: ComponentProps<'div'> & {
91+
options: Option[];
92+
}) {
93+
const pathname = usePathname();
94+
const selected = useMemo(() => {
95+
return options.findLast((option) => isTabActive(option, pathname));
96+
}, [options, pathname]);
97+
98+
return (
99+
<div
100+
{...props}
101+
className={cn(
102+
'flex flex-row items-end gap-6 overflow-auto',
103+
props.className,
104+
)}
105+
>
106+
{options.map((option) => (
107+
<LayoutTab
108+
key={option.url}
109+
selected={selected === option}
110+
option={option}
111+
/>
112+
))}
113+
</div>
114+
);
115+
}
116+
117+
function LayoutTab({
118+
option: { title, url, unlisted, props },
119+
selected = false,
120+
}: {
121+
option: Option;
122+
selected?: boolean;
123+
}) {
124+
return (
125+
<Link
126+
href={url}
127+
{...props}
128+
className={cn(
129+
'inline-flex border-b-2 border-transparent transition-colors items-center pb-1.5 font-medium gap-2 text-fd-muted-foreground text-sm text-nowrap hover:text-fd-accent-foreground',
130+
unlisted && !selected && 'hidden',
131+
selected && 'border-fd-primary text-fd-primary',
132+
props?.className,
133+
)}
134+
>
135+
{title}
136+
</Link>
137+
);
138+
}

0 commit comments

Comments
 (0)