@hanzo/ui 5.3.26 → 5.3.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/content/index.ts +26 -0
- package/dist/util/index.js +6 -0
- package/dist/util/index.mjs +6 -1
- package/docs/_registry/index.ts +426 -0
- package/docs/_registry/layout/docs-min.tsx +197 -0
- package/docs/_registry/layout/page-min.tsx +128 -0
- package/docs/components/accordion.tsx +118 -0
- package/docs/components/banner.tsx +144 -0
- package/docs/components/callout.tsx +112 -0
- package/docs/components/card.tsx +52 -0
- package/docs/components/codeblock.tsx +258 -0
- package/docs/components/dialog/search-algolia.tsx +132 -0
- package/docs/components/dialog/search-default.tsx +131 -0
- package/docs/components/dialog/search-orama.tsx +143 -0
- package/docs/components/dialog/search.tsx +529 -0
- package/docs/components/dynamic-codeblock.tsx +129 -0
- package/docs/components/files.tsx +81 -0
- package/docs/components/github-info.tsx +107 -0
- package/docs/components/heading.tsx +33 -0
- package/docs/components/image-zoom.css +77 -0
- package/docs/components/image-zoom.tsx +58 -0
- package/docs/components/index.ts +7 -0
- package/docs/components/inline-toc.tsx +48 -0
- package/docs/components/sidebar/base.tsx +451 -0
- package/docs/components/sidebar/link-item.tsx +65 -0
- package/docs/components/sidebar/page-tree.tsx +113 -0
- package/docs/components/sidebar/tabs/dropdown.tsx +109 -0
- package/docs/components/sidebar/tabs/index.tsx +89 -0
- package/docs/components/steps.tsx +9 -0
- package/docs/components/tabs.tsx +203 -0
- package/docs/components/toc/clerk.tsx +173 -0
- package/docs/components/toc/default.tsx +57 -0
- package/docs/components/toc/index.tsx +136 -0
- package/docs/components/type-table.tsx +174 -0
- package/docs/components/ui/accordion.tsx +88 -0
- package/docs/components/ui/button.tsx +28 -0
- package/docs/components/ui/collapsible.tsx +42 -0
- package/docs/components/ui/navigation-menu.tsx +83 -0
- package/docs/components/ui/popover.tsx +32 -0
- package/docs/components/ui/scroll-area.tsx +59 -0
- package/docs/components/ui/tabs.tsx +145 -0
- package/docs/contexts/i18n.tsx +56 -0
- package/docs/contexts/search.tsx +165 -0
- package/docs/contexts/tree.tsx +65 -0
- package/docs/css/black.css +39 -0
- package/docs/css/catppuccin.css +49 -0
- package/docs/css/colors/index.css +51 -0
- package/docs/css/dusk.css +47 -0
- package/docs/css/layouts/docs.css +1 -0
- package/docs/css/layouts/home.css +1 -0
- package/docs/css/layouts/notebook.css +1 -0
- package/docs/css/neutral.css +7 -0
- package/docs/css/ocean.css +48 -0
- package/docs/css/preset.css +305 -0
- package/docs/css/purple.css +39 -0
- package/docs/css/shadcn.css +36 -0
- package/docs/css/shiki.css +90 -0
- package/docs/css/solar.css +75 -0
- package/docs/css/style.css +9 -0
- package/docs/css/vitepress.css +77 -0
- package/docs/i18n.tsx +30 -0
- package/docs/icons.tsx +354 -0
- package/docs/layouts/docs/client.tsx +129 -0
- package/docs/layouts/docs/index.tsx +321 -0
- package/docs/layouts/docs/page/client.tsx +376 -0
- package/docs/layouts/docs/page/index.tsx +251 -0
- package/docs/layouts/docs/sidebar.tsx +265 -0
- package/docs/layouts/home/client.tsx +375 -0
- package/docs/layouts/home/index.tsx +51 -0
- package/docs/layouts/home/navbar.tsx +55 -0
- package/docs/layouts/notebook/client.tsx +281 -0
- package/docs/layouts/notebook/index.tsx +461 -0
- package/docs/layouts/notebook/page/client.tsx +375 -0
- package/docs/layouts/notebook/page/index.tsx +251 -0
- package/docs/layouts/notebook/sidebar.tsx +248 -0
- package/docs/layouts/shared/index.tsx +89 -0
- package/docs/layouts/shared/language-toggle.tsx +66 -0
- package/docs/layouts/shared/link-item.tsx +119 -0
- package/docs/layouts/shared/search-toggle.tsx +78 -0
- package/docs/layouts/shared/theme-toggle.tsx +86 -0
- package/docs/mdx.server.tsx +37 -0
- package/docs/mdx.tsx +97 -0
- package/docs/og.tsx +101 -0
- package/docs/page.tsx +85 -0
- package/docs/provider/base.tsx +173 -0
- package/docs/provider/next.tsx +23 -0
- package/docs/provider/react-router.tsx +23 -0
- package/docs/provider/tanstack.tsx +23 -0
- package/docs/provider/waku.tsx +23 -0
- package/docs/source.ts +3 -0
- package/docs/theme/typography/LICENSE +21 -0
- package/docs/theme/typography/index.ts +201 -0
- package/docs/theme/typography/styles.ts +449 -0
- package/docs/utils/cn.ts +1 -0
- package/docs/utils/is-active.ts +23 -0
- package/docs/utils/merge-refs.ts +15 -0
- package/docs/utils/use-copy-button.ts +39 -0
- package/docs/utils/use-footer-items.ts +27 -0
- package/docs/utils/use-is-scroll-top.ts +21 -0
- package/package.json +4 -2
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
type ComponentProps,
|
|
5
|
+
createContext,
|
|
6
|
+
Fragment,
|
|
7
|
+
use,
|
|
8
|
+
useEffect,
|
|
9
|
+
useEffectEvent,
|
|
10
|
+
useMemo,
|
|
11
|
+
useRef,
|
|
12
|
+
useState,
|
|
13
|
+
} from 'react';
|
|
14
|
+
import { ChevronDown, ChevronLeft, ChevronRight } from '@icons';
|
|
15
|
+
import Link from '@hanzo/docs-core/link';
|
|
16
|
+
import { cn } from '@/utils/cn';
|
|
17
|
+
import { useI18n } from '@/contexts/i18n';
|
|
18
|
+
import { useTreeContext, useTreePath } from '@/contexts/tree';
|
|
19
|
+
import type * as PageTree from '@hanzo/docs-core/page-tree';
|
|
20
|
+
import { usePathname } from '@hanzo/docs-core/framework';
|
|
21
|
+
import {
|
|
22
|
+
type BreadcrumbOptions,
|
|
23
|
+
getBreadcrumbItemsFromPath,
|
|
24
|
+
} from '@hanzo/docs-core/breadcrumb';
|
|
25
|
+
import { isActive } from '@/utils/is-active';
|
|
26
|
+
import {
|
|
27
|
+
Collapsible,
|
|
28
|
+
CollapsibleContent,
|
|
29
|
+
CollapsibleTrigger,
|
|
30
|
+
} from '@/components/ui/collapsible';
|
|
31
|
+
import { useTOCItems } from '@/components/toc';
|
|
32
|
+
import { useActiveAnchor } from '@hanzo/docs-core/toc';
|
|
33
|
+
import { LayoutContext } from '../client';
|
|
34
|
+
import { useFooterItems } from '@/utils/use-footer-items';
|
|
35
|
+
|
|
36
|
+
const TocPopoverContext = createContext<{
|
|
37
|
+
open: boolean;
|
|
38
|
+
setOpen: (open: boolean) => void;
|
|
39
|
+
} | null>(null);
|
|
40
|
+
|
|
41
|
+
export function PageTOCPopover({
|
|
42
|
+
className,
|
|
43
|
+
children,
|
|
44
|
+
...rest
|
|
45
|
+
}: ComponentProps<'div'>) {
|
|
46
|
+
const ref = useRef<HTMLElement>(null);
|
|
47
|
+
const [open, setOpen] = useState(false);
|
|
48
|
+
const { isNavTransparent } = use(LayoutContext)!;
|
|
49
|
+
|
|
50
|
+
const onClick = useEffectEvent((e: Event) => {
|
|
51
|
+
if (!open) return;
|
|
52
|
+
|
|
53
|
+
if (ref.current && !ref.current.contains(e.target as HTMLElement))
|
|
54
|
+
setOpen(false);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
window.addEventListener('click', onClick);
|
|
59
|
+
|
|
60
|
+
return () => {
|
|
61
|
+
window.removeEventListener('click', onClick);
|
|
62
|
+
};
|
|
63
|
+
}, []);
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<TocPopoverContext
|
|
67
|
+
value={useMemo(
|
|
68
|
+
() => ({
|
|
69
|
+
open,
|
|
70
|
+
setOpen,
|
|
71
|
+
}),
|
|
72
|
+
[setOpen, open],
|
|
73
|
+
)}
|
|
74
|
+
>
|
|
75
|
+
<Collapsible
|
|
76
|
+
open={open}
|
|
77
|
+
onOpenChange={setOpen}
|
|
78
|
+
data-toc-popover=""
|
|
79
|
+
className={cn(
|
|
80
|
+
'sticky top-(--fd-docs-row-2) z-10 [grid-area:toc-popover] h-(--fd-toc-popover-height) xl:hidden max-xl:layout:[--fd-toc-popover-height:--spacing(10)]',
|
|
81
|
+
className,
|
|
82
|
+
)}
|
|
83
|
+
{...rest}
|
|
84
|
+
>
|
|
85
|
+
<header
|
|
86
|
+
ref={ref}
|
|
87
|
+
className={cn(
|
|
88
|
+
'border-b backdrop-blur-sm transition-colors',
|
|
89
|
+
(!isNavTransparent || open) && 'bg-fd-background/80',
|
|
90
|
+
open && 'shadow-lg',
|
|
91
|
+
)}
|
|
92
|
+
>
|
|
93
|
+
{children}
|
|
94
|
+
</header>
|
|
95
|
+
</Collapsible>
|
|
96
|
+
</TocPopoverContext>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function PageTOCPopoverTrigger({
|
|
101
|
+
className,
|
|
102
|
+
...props
|
|
103
|
+
}: ComponentProps<'button'>) {
|
|
104
|
+
const { text } = useI18n();
|
|
105
|
+
const { open } = use(TocPopoverContext)!;
|
|
106
|
+
const items = useTOCItems();
|
|
107
|
+
const active = useActiveAnchor();
|
|
108
|
+
const selected = useMemo(
|
|
109
|
+
() => items.findIndex((item) => active === item.url.slice(1)),
|
|
110
|
+
[items, active],
|
|
111
|
+
);
|
|
112
|
+
const path = useTreePath().at(-1);
|
|
113
|
+
const showItem = selected !== -1 && !open;
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<CollapsibleTrigger
|
|
117
|
+
className={cn(
|
|
118
|
+
'flex w-full h-10 items-center text-sm text-fd-muted-foreground gap-2.5 px-4 py-2.5 text-start focus-visible:outline-none [&_svg]:size-4 md:px-6',
|
|
119
|
+
className,
|
|
120
|
+
)}
|
|
121
|
+
data-toc-popover-trigger=""
|
|
122
|
+
{...props}
|
|
123
|
+
>
|
|
124
|
+
<ProgressCircle
|
|
125
|
+
value={(selected + 1) / Math.max(1, items.length)}
|
|
126
|
+
max={1}
|
|
127
|
+
className={cn('shrink-0', open && 'text-fd-primary')}
|
|
128
|
+
/>
|
|
129
|
+
<span className="grid flex-1 *:my-auto *:row-start-1 *:col-start-1">
|
|
130
|
+
<span
|
|
131
|
+
className={cn(
|
|
132
|
+
'truncate transition-all',
|
|
133
|
+
open && 'text-fd-foreground',
|
|
134
|
+
showItem && 'opacity-0 -translate-y-full pointer-events-none',
|
|
135
|
+
)}
|
|
136
|
+
>
|
|
137
|
+
{path?.name ?? text.toc}
|
|
138
|
+
</span>
|
|
139
|
+
<span
|
|
140
|
+
className={cn(
|
|
141
|
+
'truncate transition-all',
|
|
142
|
+
!showItem && 'opacity-0 translate-y-full pointer-events-none',
|
|
143
|
+
)}
|
|
144
|
+
>
|
|
145
|
+
{items[selected]?.title}
|
|
146
|
+
</span>
|
|
147
|
+
</span>
|
|
148
|
+
<ChevronDown
|
|
149
|
+
className={cn(
|
|
150
|
+
'shrink-0 transition-transform mx-0.5',
|
|
151
|
+
open && 'rotate-180',
|
|
152
|
+
)}
|
|
153
|
+
/>
|
|
154
|
+
</CollapsibleTrigger>
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
interface ProgressCircleProps extends Omit<
|
|
159
|
+
React.ComponentProps<'svg'>,
|
|
160
|
+
'strokeWidth'
|
|
161
|
+
> {
|
|
162
|
+
value: number;
|
|
163
|
+
strokeWidth?: number;
|
|
164
|
+
size?: number;
|
|
165
|
+
min?: number;
|
|
166
|
+
max?: number;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function clamp(input: number, min: number, max: number): number {
|
|
170
|
+
if (input < min) return min;
|
|
171
|
+
if (input > max) return max;
|
|
172
|
+
return input;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function ProgressCircle({
|
|
176
|
+
value,
|
|
177
|
+
strokeWidth = 2,
|
|
178
|
+
size = 24,
|
|
179
|
+
min = 0,
|
|
180
|
+
max = 100,
|
|
181
|
+
...restSvgProps
|
|
182
|
+
}: ProgressCircleProps) {
|
|
183
|
+
const normalizedValue = clamp(value, min, max);
|
|
184
|
+
const radius = (size - strokeWidth) / 2;
|
|
185
|
+
const circumference = 2 * Math.PI * radius;
|
|
186
|
+
const progress = (normalizedValue / max) * circumference;
|
|
187
|
+
const circleProps = {
|
|
188
|
+
cx: size / 2,
|
|
189
|
+
cy: size / 2,
|
|
190
|
+
r: radius,
|
|
191
|
+
fill: 'none',
|
|
192
|
+
strokeWidth,
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
return (
|
|
196
|
+
<svg
|
|
197
|
+
role="progressbar"
|
|
198
|
+
viewBox={`0 0 ${size} ${size}`}
|
|
199
|
+
aria-valuenow={normalizedValue}
|
|
200
|
+
aria-valuemin={min}
|
|
201
|
+
aria-valuemax={max}
|
|
202
|
+
{...restSvgProps}
|
|
203
|
+
>
|
|
204
|
+
<circle {...circleProps} className="stroke-current/25" />
|
|
205
|
+
<circle
|
|
206
|
+
{...circleProps}
|
|
207
|
+
stroke="currentColor"
|
|
208
|
+
strokeDasharray={circumference}
|
|
209
|
+
strokeDashoffset={circumference - progress}
|
|
210
|
+
strokeLinecap="round"
|
|
211
|
+
transform={`rotate(-90 ${size / 2} ${size / 2})`}
|
|
212
|
+
className="transition-all"
|
|
213
|
+
/>
|
|
214
|
+
</svg>
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function PageTOCPopoverContent(props: ComponentProps<'div'>) {
|
|
219
|
+
return (
|
|
220
|
+
<CollapsibleContent
|
|
221
|
+
data-toc-popover-content=""
|
|
222
|
+
{...props}
|
|
223
|
+
className={cn('flex flex-col px-4 max-h-[50vh] md:px-6', props.className)}
|
|
224
|
+
>
|
|
225
|
+
{props.children}
|
|
226
|
+
</CollapsibleContent>
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export function PageLastUpdate({
|
|
231
|
+
date: value,
|
|
232
|
+
...props
|
|
233
|
+
}: Omit<ComponentProps<'p'>, 'children'> & { date: Date }) {
|
|
234
|
+
const { text } = useI18n();
|
|
235
|
+
const [date, setDate] = useState('');
|
|
236
|
+
|
|
237
|
+
useEffect(() => {
|
|
238
|
+
// to the timezone of client
|
|
239
|
+
setDate(value.toLocaleDateString());
|
|
240
|
+
}, [value]);
|
|
241
|
+
|
|
242
|
+
return (
|
|
243
|
+
<p
|
|
244
|
+
{...props}
|
|
245
|
+
className={cn('text-sm text-fd-muted-foreground', props.className)}
|
|
246
|
+
>
|
|
247
|
+
{text.lastUpdate} {date}
|
|
248
|
+
</p>
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
type Item = Pick<PageTree.Item, 'name' | 'description' | 'url'>;
|
|
253
|
+
export interface FooterProps extends ComponentProps<'div'> {
|
|
254
|
+
/**
|
|
255
|
+
* Items including information for the next and previous page
|
|
256
|
+
*/
|
|
257
|
+
items?: {
|
|
258
|
+
previous?: Item;
|
|
259
|
+
next?: Item;
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export function PageFooter({ items, ...props }: FooterProps) {
|
|
264
|
+
const footerList = useFooterItems();
|
|
265
|
+
const pathname = usePathname();
|
|
266
|
+
const { previous, next } = useMemo(() => {
|
|
267
|
+
if (items) return items;
|
|
268
|
+
|
|
269
|
+
const idx = footerList.findIndex((item) =>
|
|
270
|
+
isActive(item.url, pathname, false),
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
if (idx === -1) return {};
|
|
274
|
+
return {
|
|
275
|
+
previous: footerList[idx - 1],
|
|
276
|
+
next: footerList[idx + 1],
|
|
277
|
+
};
|
|
278
|
+
}, [footerList, items, pathname]);
|
|
279
|
+
|
|
280
|
+
return (
|
|
281
|
+
<div
|
|
282
|
+
{...props}
|
|
283
|
+
className={cn(
|
|
284
|
+
'@container grid gap-4',
|
|
285
|
+
previous && next ? 'grid-cols-2' : 'grid-cols-1',
|
|
286
|
+
props.className,
|
|
287
|
+
)}
|
|
288
|
+
>
|
|
289
|
+
{previous ? <FooterItem item={previous} index={0} /> : null}
|
|
290
|
+
{next ? <FooterItem item={next} index={1} /> : null}
|
|
291
|
+
</div>
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function FooterItem({ item, index }: { item: Item; index: 0 | 1 }) {
|
|
296
|
+
const { text } = useI18n();
|
|
297
|
+
const Icon = index === 0 ? ChevronLeft : ChevronRight;
|
|
298
|
+
|
|
299
|
+
return (
|
|
300
|
+
<Link
|
|
301
|
+
href={item.url}
|
|
302
|
+
className={cn(
|
|
303
|
+
'flex flex-col gap-2 rounded-lg border p-4 text-sm transition-colors hover:bg-fd-accent/80 hover:text-fd-accent-foreground @max-lg:col-span-full',
|
|
304
|
+
index === 1 && 'text-end',
|
|
305
|
+
)}
|
|
306
|
+
>
|
|
307
|
+
<div
|
|
308
|
+
className={cn(
|
|
309
|
+
'inline-flex items-center gap-1.5 font-medium',
|
|
310
|
+
index === 1 && 'flex-row-reverse',
|
|
311
|
+
)}
|
|
312
|
+
>
|
|
313
|
+
<Icon className="-mx-1 size-4 shrink-0 rtl:rotate-180" />
|
|
314
|
+
<p>{item.name}</p>
|
|
315
|
+
</div>
|
|
316
|
+
<p className="text-fd-muted-foreground truncate">
|
|
317
|
+
{item.description ?? (index === 0 ? text.previousPage : text.nextPage)}
|
|
318
|
+
</p>
|
|
319
|
+
</Link>
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export type BreadcrumbProps = BreadcrumbOptions & ComponentProps<'div'>;
|
|
324
|
+
|
|
325
|
+
export function PageBreadcrumb({
|
|
326
|
+
includeRoot,
|
|
327
|
+
includeSeparator,
|
|
328
|
+
includePage,
|
|
329
|
+
...props
|
|
330
|
+
}: BreadcrumbProps) {
|
|
331
|
+
const path = useTreePath();
|
|
332
|
+
const { root } = useTreeContext();
|
|
333
|
+
const items = useMemo(() => {
|
|
334
|
+
return getBreadcrumbItemsFromPath(root, path, {
|
|
335
|
+
includePage,
|
|
336
|
+
includeSeparator,
|
|
337
|
+
includeRoot,
|
|
338
|
+
});
|
|
339
|
+
}, [includePage, includeRoot, includeSeparator, path, root]);
|
|
340
|
+
|
|
341
|
+
if (items.length === 0) return null;
|
|
342
|
+
|
|
343
|
+
return (
|
|
344
|
+
<div
|
|
345
|
+
{...props}
|
|
346
|
+
className={cn(
|
|
347
|
+
'flex items-center gap-1.5 text-sm text-fd-muted-foreground',
|
|
348
|
+
props.className,
|
|
349
|
+
)}
|
|
350
|
+
>
|
|
351
|
+
{items.map((item, i) => {
|
|
352
|
+
const className = cn(
|
|
353
|
+
'truncate',
|
|
354
|
+
i === items.length - 1 && 'text-fd-primary font-medium',
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
return (
|
|
358
|
+
<Fragment key={i}>
|
|
359
|
+
{i !== 0 && <ChevronRight className="size-3.5 shrink-0" />}
|
|
360
|
+
{item.url ? (
|
|
361
|
+
<Link
|
|
362
|
+
href={item.url}
|
|
363
|
+
className={cn(className, 'transition-opacity hover:opacity-80')}
|
|
364
|
+
>
|
|
365
|
+
{item.name}
|
|
366
|
+
</Link>
|
|
367
|
+
) : (
|
|
368
|
+
<span className={className}>{item.name}</span>
|
|
369
|
+
)}
|
|
370
|
+
</Fragment>
|
|
371
|
+
);
|
|
372
|
+
})}
|
|
373
|
+
</div>
|
|
374
|
+
);
|
|
375
|
+
}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import type { ComponentProps, ReactNode } from 'react';
|
|
2
|
+
import { cn } from '@/utils/cn';
|
|
3
|
+
import { buttonVariants } from '@/components/ui/button';
|
|
4
|
+
import { Edit, Text } from '@icons';
|
|
5
|
+
import { I18nLabel } from '@/contexts/i18n';
|
|
6
|
+
import {
|
|
7
|
+
type BreadcrumbProps,
|
|
8
|
+
type FooterProps,
|
|
9
|
+
PageBreadcrumb,
|
|
10
|
+
PageFooter,
|
|
11
|
+
PageTOCPopover,
|
|
12
|
+
PageTOCPopoverContent,
|
|
13
|
+
PageTOCPopoverTrigger,
|
|
14
|
+
} from './client';
|
|
15
|
+
import type { AnchorProviderProps, TOCItemType } from '@hanzo/docs-core/toc';
|
|
16
|
+
import * as TocDefault from '@/components/toc/default';
|
|
17
|
+
import * as TocClerk from '@/components/toc/clerk';
|
|
18
|
+
import { TOCProvider, TOCScrollArea } from '@/components/toc';
|
|
19
|
+
|
|
20
|
+
interface BreadcrumbOptions extends BreadcrumbProps {
|
|
21
|
+
enabled: boolean;
|
|
22
|
+
component: ReactNode;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface FooterOptions extends FooterProps {
|
|
26
|
+
enabled: boolean;
|
|
27
|
+
component: ReactNode;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface DocsPageProps {
|
|
31
|
+
toc?: TOCItemType[];
|
|
32
|
+
tableOfContent?: Partial<TableOfContentOptions>;
|
|
33
|
+
tableOfContentPopover?: Partial<TableOfContentPopoverOptions>;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Extend the page to fill all available space
|
|
37
|
+
*
|
|
38
|
+
* @defaultValue false
|
|
39
|
+
*/
|
|
40
|
+
full?: boolean;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Replace or disable breadcrumb
|
|
44
|
+
*/
|
|
45
|
+
breadcrumb?: Partial<BreadcrumbOptions>;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Footer navigation, you can disable it by passing `false`
|
|
49
|
+
*/
|
|
50
|
+
footer?: Partial<FooterOptions>;
|
|
51
|
+
|
|
52
|
+
children?: ReactNode;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
type TableOfContentOptions = Pick<AnchorProviderProps, 'single'> & {
|
|
56
|
+
/**
|
|
57
|
+
* Custom content in TOC container, before the main TOC
|
|
58
|
+
*/
|
|
59
|
+
header?: ReactNode;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Custom content in TOC container, after the main TOC
|
|
63
|
+
*/
|
|
64
|
+
footer?: ReactNode;
|
|
65
|
+
|
|
66
|
+
enabled: boolean;
|
|
67
|
+
component: ReactNode;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @defaultValue 'normal'
|
|
71
|
+
*/
|
|
72
|
+
style?: 'normal' | 'clerk';
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
type TableOfContentPopoverOptions = Omit<TableOfContentOptions, 'single'>;
|
|
76
|
+
|
|
77
|
+
export function DocsPage({
|
|
78
|
+
breadcrumb: {
|
|
79
|
+
enabled: breadcrumbEnabled = true,
|
|
80
|
+
component: breadcrumb,
|
|
81
|
+
...breadcrumbProps
|
|
82
|
+
} = {},
|
|
83
|
+
footer = {},
|
|
84
|
+
full = false,
|
|
85
|
+
tableOfContentPopover: {
|
|
86
|
+
enabled: tocPopoverEnabled,
|
|
87
|
+
component: tocPopover,
|
|
88
|
+
...tocPopoverOptions
|
|
89
|
+
} = {},
|
|
90
|
+
tableOfContent: {
|
|
91
|
+
enabled: tocEnabled,
|
|
92
|
+
component: tocReplace,
|
|
93
|
+
...tocOptions
|
|
94
|
+
} = {},
|
|
95
|
+
toc = [],
|
|
96
|
+
children,
|
|
97
|
+
}: DocsPageProps) {
|
|
98
|
+
// disable TOC on full mode, you can still enable it with `enabled` option.
|
|
99
|
+
tocEnabled ??=
|
|
100
|
+
!full &&
|
|
101
|
+
(toc.length > 0 ||
|
|
102
|
+
tocOptions.footer !== undefined ||
|
|
103
|
+
tocOptions.header !== undefined);
|
|
104
|
+
|
|
105
|
+
tocPopoverEnabled ??=
|
|
106
|
+
toc.length > 0 ||
|
|
107
|
+
tocPopoverOptions.header !== undefined ||
|
|
108
|
+
tocPopoverOptions.footer !== undefined;
|
|
109
|
+
|
|
110
|
+
let wrapper = (children: ReactNode) => children;
|
|
111
|
+
|
|
112
|
+
if (tocEnabled || tocPopoverEnabled) {
|
|
113
|
+
wrapper = (children) => (
|
|
114
|
+
<TOCProvider single={tocOptions.single} toc={toc}>
|
|
115
|
+
{children}
|
|
116
|
+
</TOCProvider>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return wrapper(
|
|
121
|
+
<>
|
|
122
|
+
{tocPopoverEnabled &&
|
|
123
|
+
(tocPopover ?? (
|
|
124
|
+
<PageTOCPopover>
|
|
125
|
+
<PageTOCPopoverTrigger />
|
|
126
|
+
<PageTOCPopoverContent>
|
|
127
|
+
{tocPopoverOptions.header}
|
|
128
|
+
<TOCScrollArea>
|
|
129
|
+
{tocPopoverOptions.style === 'clerk' ? (
|
|
130
|
+
<TocClerk.TOCItems />
|
|
131
|
+
) : (
|
|
132
|
+
<TocDefault.TOCItems />
|
|
133
|
+
)}
|
|
134
|
+
</TOCScrollArea>
|
|
135
|
+
{tocPopoverOptions.footer}
|
|
136
|
+
</PageTOCPopoverContent>
|
|
137
|
+
</PageTOCPopover>
|
|
138
|
+
))}
|
|
139
|
+
<article
|
|
140
|
+
id="nd-page"
|
|
141
|
+
data-full={full}
|
|
142
|
+
className={cn(
|
|
143
|
+
'flex flex-col [grid-area:main] px-4 py-6 gap-4 md:px-6 md:pt-8 xl:px-8 xl:pt-14 *:max-w-[900px]',
|
|
144
|
+
full && '*:max-w-[1285px]',
|
|
145
|
+
)}
|
|
146
|
+
>
|
|
147
|
+
{breadcrumbEnabled &&
|
|
148
|
+
(breadcrumb ?? <PageBreadcrumb {...breadcrumbProps} />)}
|
|
149
|
+
{children}
|
|
150
|
+
{footer.enabled !== false &&
|
|
151
|
+
(footer.component ?? <PageFooter items={footer.items} />)}
|
|
152
|
+
</article>
|
|
153
|
+
{tocEnabled &&
|
|
154
|
+
(tocReplace ?? (
|
|
155
|
+
<div
|
|
156
|
+
id="nd-toc"
|
|
157
|
+
className="sticky top-(--fd-docs-row-3) [grid-area:toc] h-[calc(var(--fd-docs-height)-var(--fd-docs-row-3))] flex flex-col w-(--fd-toc-width) pt-12 pe-4 pb-2 xl:layout:[--fd-toc-width:268px] max-xl:hidden"
|
|
158
|
+
>
|
|
159
|
+
{tocOptions.header}
|
|
160
|
+
<h3
|
|
161
|
+
id="toc-title"
|
|
162
|
+
className="inline-flex items-center gap-1.5 text-sm text-fd-muted-foreground"
|
|
163
|
+
>
|
|
164
|
+
<Text className="size-4" />
|
|
165
|
+
<I18nLabel label="toc" />
|
|
166
|
+
</h3>
|
|
167
|
+
<TOCScrollArea>
|
|
168
|
+
{tocOptions.style === 'clerk' ? (
|
|
169
|
+
<TocClerk.TOCItems />
|
|
170
|
+
) : (
|
|
171
|
+
<TocDefault.TOCItems />
|
|
172
|
+
)}
|
|
173
|
+
</TOCScrollArea>
|
|
174
|
+
{tocOptions.footer}
|
|
175
|
+
</div>
|
|
176
|
+
))}
|
|
177
|
+
</>,
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function EditOnGitHub(props: ComponentProps<'a'>) {
|
|
182
|
+
return (
|
|
183
|
+
<a
|
|
184
|
+
target="_blank"
|
|
185
|
+
rel="noreferrer noopener"
|
|
186
|
+
{...props}
|
|
187
|
+
className={cn(
|
|
188
|
+
buttonVariants({
|
|
189
|
+
color: 'secondary',
|
|
190
|
+
size: 'sm',
|
|
191
|
+
className: 'gap-1.5 not-prose',
|
|
192
|
+
}),
|
|
193
|
+
props.className,
|
|
194
|
+
)}
|
|
195
|
+
>
|
|
196
|
+
{props.children ?? (
|
|
197
|
+
<>
|
|
198
|
+
<Edit className="size-3.5" />
|
|
199
|
+
<I18nLabel label="editOnGithub" />
|
|
200
|
+
</>
|
|
201
|
+
)}
|
|
202
|
+
</a>
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Add typography styles
|
|
208
|
+
*/
|
|
209
|
+
export function DocsBody({
|
|
210
|
+
children,
|
|
211
|
+
className,
|
|
212
|
+
...props
|
|
213
|
+
}: ComponentProps<'div'>) {
|
|
214
|
+
return (
|
|
215
|
+
<div {...props} className={cn('prose flex-1', className)}>
|
|
216
|
+
{children}
|
|
217
|
+
</div>
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function DocsDescription({
|
|
222
|
+
children,
|
|
223
|
+
className,
|
|
224
|
+
...props
|
|
225
|
+
}: ComponentProps<'p'>) {
|
|
226
|
+
// Don't render if no description provided
|
|
227
|
+
if (children === undefined) return null;
|
|
228
|
+
|
|
229
|
+
return (
|
|
230
|
+
<p
|
|
231
|
+
{...props}
|
|
232
|
+
className={cn('mb-8 text-lg text-fd-muted-foreground', className)}
|
|
233
|
+
>
|
|
234
|
+
{children}
|
|
235
|
+
</p>
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export function DocsTitle({
|
|
240
|
+
children,
|
|
241
|
+
className,
|
|
242
|
+
...props
|
|
243
|
+
}: ComponentProps<'h1'>) {
|
|
244
|
+
return (
|
|
245
|
+
<h1 {...props} className={cn('text-[1.75em] font-semibold', className)}>
|
|
246
|
+
{children}
|
|
247
|
+
</h1>
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export { PageLastUpdate, PageBreadcrumb } from './client';
|