@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.
Files changed (100) hide show
  1. package/content/index.ts +26 -0
  2. package/dist/util/index.js +6 -0
  3. package/dist/util/index.mjs +6 -1
  4. package/docs/_registry/index.ts +426 -0
  5. package/docs/_registry/layout/docs-min.tsx +197 -0
  6. package/docs/_registry/layout/page-min.tsx +128 -0
  7. package/docs/components/accordion.tsx +118 -0
  8. package/docs/components/banner.tsx +144 -0
  9. package/docs/components/callout.tsx +112 -0
  10. package/docs/components/card.tsx +52 -0
  11. package/docs/components/codeblock.tsx +258 -0
  12. package/docs/components/dialog/search-algolia.tsx +132 -0
  13. package/docs/components/dialog/search-default.tsx +131 -0
  14. package/docs/components/dialog/search-orama.tsx +143 -0
  15. package/docs/components/dialog/search.tsx +529 -0
  16. package/docs/components/dynamic-codeblock.tsx +129 -0
  17. package/docs/components/files.tsx +81 -0
  18. package/docs/components/github-info.tsx +107 -0
  19. package/docs/components/heading.tsx +33 -0
  20. package/docs/components/image-zoom.css +77 -0
  21. package/docs/components/image-zoom.tsx +58 -0
  22. package/docs/components/index.ts +7 -0
  23. package/docs/components/inline-toc.tsx +48 -0
  24. package/docs/components/sidebar/base.tsx +451 -0
  25. package/docs/components/sidebar/link-item.tsx +65 -0
  26. package/docs/components/sidebar/page-tree.tsx +113 -0
  27. package/docs/components/sidebar/tabs/dropdown.tsx +109 -0
  28. package/docs/components/sidebar/tabs/index.tsx +89 -0
  29. package/docs/components/steps.tsx +9 -0
  30. package/docs/components/tabs.tsx +203 -0
  31. package/docs/components/toc/clerk.tsx +173 -0
  32. package/docs/components/toc/default.tsx +57 -0
  33. package/docs/components/toc/index.tsx +136 -0
  34. package/docs/components/type-table.tsx +174 -0
  35. package/docs/components/ui/accordion.tsx +88 -0
  36. package/docs/components/ui/button.tsx +28 -0
  37. package/docs/components/ui/collapsible.tsx +42 -0
  38. package/docs/components/ui/navigation-menu.tsx +83 -0
  39. package/docs/components/ui/popover.tsx +32 -0
  40. package/docs/components/ui/scroll-area.tsx +59 -0
  41. package/docs/components/ui/tabs.tsx +145 -0
  42. package/docs/contexts/i18n.tsx +56 -0
  43. package/docs/contexts/search.tsx +165 -0
  44. package/docs/contexts/tree.tsx +65 -0
  45. package/docs/css/black.css +39 -0
  46. package/docs/css/catppuccin.css +49 -0
  47. package/docs/css/colors/index.css +51 -0
  48. package/docs/css/dusk.css +47 -0
  49. package/docs/css/layouts/docs.css +1 -0
  50. package/docs/css/layouts/home.css +1 -0
  51. package/docs/css/layouts/notebook.css +1 -0
  52. package/docs/css/neutral.css +7 -0
  53. package/docs/css/ocean.css +48 -0
  54. package/docs/css/preset.css +305 -0
  55. package/docs/css/purple.css +39 -0
  56. package/docs/css/shadcn.css +36 -0
  57. package/docs/css/shiki.css +90 -0
  58. package/docs/css/solar.css +75 -0
  59. package/docs/css/style.css +9 -0
  60. package/docs/css/vitepress.css +77 -0
  61. package/docs/i18n.tsx +30 -0
  62. package/docs/icons.tsx +354 -0
  63. package/docs/layouts/docs/client.tsx +129 -0
  64. package/docs/layouts/docs/index.tsx +321 -0
  65. package/docs/layouts/docs/page/client.tsx +376 -0
  66. package/docs/layouts/docs/page/index.tsx +251 -0
  67. package/docs/layouts/docs/sidebar.tsx +265 -0
  68. package/docs/layouts/home/client.tsx +375 -0
  69. package/docs/layouts/home/index.tsx +51 -0
  70. package/docs/layouts/home/navbar.tsx +55 -0
  71. package/docs/layouts/notebook/client.tsx +281 -0
  72. package/docs/layouts/notebook/index.tsx +461 -0
  73. package/docs/layouts/notebook/page/client.tsx +375 -0
  74. package/docs/layouts/notebook/page/index.tsx +251 -0
  75. package/docs/layouts/notebook/sidebar.tsx +248 -0
  76. package/docs/layouts/shared/index.tsx +89 -0
  77. package/docs/layouts/shared/language-toggle.tsx +66 -0
  78. package/docs/layouts/shared/link-item.tsx +119 -0
  79. package/docs/layouts/shared/search-toggle.tsx +78 -0
  80. package/docs/layouts/shared/theme-toggle.tsx +86 -0
  81. package/docs/mdx.server.tsx +37 -0
  82. package/docs/mdx.tsx +97 -0
  83. package/docs/og.tsx +101 -0
  84. package/docs/page.tsx +85 -0
  85. package/docs/provider/base.tsx +173 -0
  86. package/docs/provider/next.tsx +23 -0
  87. package/docs/provider/react-router.tsx +23 -0
  88. package/docs/provider/tanstack.tsx +23 -0
  89. package/docs/provider/waku.tsx +23 -0
  90. package/docs/source.ts +3 -0
  91. package/docs/theme/typography/LICENSE +21 -0
  92. package/docs/theme/typography/index.ts +201 -0
  93. package/docs/theme/typography/styles.ts +449 -0
  94. package/docs/utils/cn.ts +1 -0
  95. package/docs/utils/is-active.ts +23 -0
  96. package/docs/utils/merge-refs.ts +15 -0
  97. package/docs/utils/use-copy-button.ts +39 -0
  98. package/docs/utils/use-footer-items.ts +27 -0
  99. package/docs/utils/use-is-scroll-top.ts +21 -0
  100. package/package.json +4 -2
@@ -0,0 +1,451 @@
1
+ 'use client';
2
+ import { ChevronDown, ExternalLink } from '@icons';
3
+ import {
4
+ type ComponentProps,
5
+ createContext,
6
+ type PointerEvent,
7
+ type ReactNode,
8
+ type RefObject,
9
+ use,
10
+ useEffect,
11
+ useMemo,
12
+ useRef,
13
+ useState,
14
+ } from 'react';
15
+ import Link, { type LinkProps } from '@hanzo/docs-core/link';
16
+ import { useOnChange } from '@hanzo/docs-core/utils/use-on-change';
17
+ import { cn } from '@/utils/cn';
18
+ import {
19
+ ScrollArea,
20
+ type ScrollAreaProps,
21
+ ScrollViewport,
22
+ } from '@/components/ui/scroll-area';
23
+ import { isActive } from '@/utils/is-active';
24
+ import {
25
+ Collapsible,
26
+ CollapsibleContent,
27
+ type CollapsibleContentProps,
28
+ CollapsibleTrigger,
29
+ type CollapsibleTriggerProps,
30
+ } from '@/components/ui/collapsible';
31
+ import { useMediaQuery } from '@hanzo/docs-core/utils/use-media-query';
32
+ import { Presence } from '@radix-ui/react-presence';
33
+ import scrollIntoView from 'scroll-into-view-if-needed';
34
+ import { usePathname } from '@hanzo/docs-core/framework';
35
+
36
+ interface SidebarContext {
37
+ open: boolean;
38
+ setOpen: React.Dispatch<React.SetStateAction<boolean>>;
39
+ collapsed: boolean;
40
+ setCollapsed: React.Dispatch<React.SetStateAction<boolean>>;
41
+
42
+ /**
43
+ * When set to false, don't close the sidebar when navigate to another page
44
+ */
45
+ closeOnRedirect: RefObject<boolean>;
46
+ defaultOpenLevel: number;
47
+ prefetch: boolean;
48
+ mode: Mode;
49
+ }
50
+
51
+ export interface SidebarProviderProps {
52
+ /**
53
+ * Open folders by default if their level is lower or equal to a specific level
54
+ * (Starting from 1)
55
+ *
56
+ * @defaultValue 0
57
+ */
58
+ defaultOpenLevel?: number;
59
+
60
+ /**
61
+ * Prefetch links
62
+ *
63
+ * @defaultValue true
64
+ */
65
+ prefetch?: boolean;
66
+
67
+ children?: ReactNode;
68
+ }
69
+
70
+ type Mode = 'drawer' | 'full';
71
+
72
+ const SidebarContext = createContext<SidebarContext | null>(null);
73
+
74
+ const FolderContext = createContext<{
75
+ open: boolean;
76
+ setOpen: React.Dispatch<React.SetStateAction<boolean>>;
77
+ depth: number;
78
+ collapsible: boolean;
79
+ } | null>(null);
80
+
81
+ export function SidebarProvider({
82
+ defaultOpenLevel = 0,
83
+ prefetch = true,
84
+ children,
85
+ }: SidebarProviderProps) {
86
+ const closeOnRedirect = useRef(true);
87
+ const [open, setOpen] = useState(false);
88
+ const [collapsed, setCollapsed] = useState(false);
89
+ const pathname = usePathname();
90
+ const mode: Mode = useMediaQuery('(width < 768px)') ? 'drawer' : 'full';
91
+
92
+ useOnChange(pathname, () => {
93
+ if (closeOnRedirect.current) {
94
+ setOpen(false);
95
+ }
96
+ closeOnRedirect.current = true;
97
+ });
98
+
99
+ return (
100
+ <SidebarContext
101
+ value={useMemo(
102
+ () => ({
103
+ open,
104
+ setOpen,
105
+ collapsed,
106
+ setCollapsed,
107
+ closeOnRedirect,
108
+ defaultOpenLevel,
109
+ prefetch,
110
+ mode,
111
+ }),
112
+ [open, collapsed, defaultOpenLevel, prefetch, mode],
113
+ )}
114
+ >
115
+ {children}
116
+ </SidebarContext>
117
+ );
118
+ }
119
+
120
+ export function useSidebar(): SidebarContext {
121
+ const ctx = use(SidebarContext);
122
+ if (!ctx)
123
+ throw new Error(
124
+ 'Missing SidebarContext, make sure you have wrapped the component in <DocsLayout /> and the context is available.',
125
+ );
126
+
127
+ return ctx;
128
+ }
129
+
130
+ export function useFolder() {
131
+ return use(FolderContext);
132
+ }
133
+
134
+ export function useFolderDepth() {
135
+ return use(FolderContext)?.depth ?? 0;
136
+ }
137
+
138
+ export function SidebarContent({
139
+ children,
140
+ }: {
141
+ children: (state: {
142
+ ref: RefObject<HTMLElement | null>;
143
+ collapsed: boolean;
144
+ hovered: boolean;
145
+ onPointerEnter: (event: PointerEvent) => void;
146
+ onPointerLeave: (event: PointerEvent) => void;
147
+ }) => ReactNode;
148
+ }) {
149
+ const { collapsed, mode } = useSidebar();
150
+ const [hover, setHover] = useState(false);
151
+ const ref = useRef<HTMLElement>(null);
152
+ const timerRef = useRef(0);
153
+
154
+ useOnChange(collapsed, () => {
155
+ if (collapsed) setHover(false);
156
+ });
157
+
158
+ if (mode !== 'full') return;
159
+
160
+ function shouldIgnoreHover(e: PointerEvent): boolean {
161
+ const element = ref.current;
162
+ if (!element) return true;
163
+
164
+ return (
165
+ !collapsed ||
166
+ e.pointerType === 'touch' ||
167
+ element.getAnimations().length > 0
168
+ );
169
+ }
170
+
171
+ return children({
172
+ ref,
173
+ collapsed,
174
+ hovered: hover,
175
+ onPointerEnter(e) {
176
+ if (shouldIgnoreHover(e)) return;
177
+ window.clearTimeout(timerRef.current);
178
+ setHover(true);
179
+ },
180
+ onPointerLeave(e) {
181
+ if (shouldIgnoreHover(e)) return;
182
+ window.clearTimeout(timerRef.current);
183
+
184
+ timerRef.current = window.setTimeout(
185
+ () => setHover(false),
186
+ // if mouse is leaving the viewport, add a close delay
187
+ Math.min(e.clientX, document.body.clientWidth - e.clientX) > 100
188
+ ? 0
189
+ : 500,
190
+ );
191
+ },
192
+ });
193
+ }
194
+
195
+ export function SidebarDrawerOverlay(props: ComponentProps<'div'>) {
196
+ const { open, setOpen, mode } = useSidebar();
197
+
198
+ if (mode !== 'drawer') return;
199
+ return (
200
+ <Presence present={open}>
201
+ <div
202
+ data-state={open ? 'open' : 'closed'}
203
+ onClick={() => setOpen(false)}
204
+ {...props}
205
+ />
206
+ </Presence>
207
+ );
208
+ }
209
+
210
+ export function SidebarDrawerContent({
211
+ className,
212
+ children,
213
+ ...props
214
+ }: ComponentProps<'aside'>) {
215
+ const { open, mode } = useSidebar();
216
+ const state = open ? 'open' : 'closed';
217
+
218
+ if (mode !== 'drawer') return;
219
+ return (
220
+ <Presence present={open}>
221
+ {({ present }) => (
222
+ <aside
223
+ id="nd-sidebar-mobile"
224
+ data-state={state}
225
+ className={cn(!present && 'invisible', className)}
226
+ {...props}
227
+ >
228
+ {children}
229
+ </aside>
230
+ )}
231
+ </Presence>
232
+ );
233
+ }
234
+
235
+ export function SidebarViewport(props: ScrollAreaProps) {
236
+ return (
237
+ <ScrollArea {...props} className={cn('min-h-0 flex-1', props.className)}>
238
+ <ScrollViewport
239
+ className="p-4 overscroll-contain"
240
+ style={
241
+ {
242
+ maskImage:
243
+ 'linear-gradient(to bottom, transparent, white 12px, white calc(100% - 12px), transparent)',
244
+ } as object
245
+ }
246
+ >
247
+ {props.children}
248
+ </ScrollViewport>
249
+ </ScrollArea>
250
+ );
251
+ }
252
+
253
+ export function SidebarSeparator(props: ComponentProps<'p'>) {
254
+ const depth = useFolderDepth();
255
+ return (
256
+ <p
257
+ {...props}
258
+ className={cn(
259
+ 'inline-flex items-center gap-2 mb-1.5 px-2 mt-6 empty:mb-0',
260
+ depth === 0 && 'first:mt-0',
261
+ props.className,
262
+ )}
263
+ >
264
+ {props.children}
265
+ </p>
266
+ );
267
+ }
268
+
269
+ export function SidebarItem({
270
+ icon,
271
+ children,
272
+ ...props
273
+ }: LinkProps & {
274
+ icon?: ReactNode;
275
+ }) {
276
+ const pathname = usePathname();
277
+ const ref = useRef<HTMLAnchorElement>(null);
278
+ const { prefetch } = useSidebar();
279
+ const active =
280
+ props.href !== undefined && isActive(props.href, pathname, false);
281
+
282
+ useAutoScroll(active, ref);
283
+
284
+ return (
285
+ <Link ref={ref} data-active={active} prefetch={prefetch} {...props}>
286
+ {icon ?? (props.external ? <ExternalLink /> : null)}
287
+ {children}
288
+ </Link>
289
+ );
290
+ }
291
+
292
+ export function SidebarFolder({
293
+ defaultOpen: defaultOpenProp,
294
+ collapsible = true,
295
+ active = false,
296
+ children,
297
+ ...props
298
+ }: ComponentProps<'div'> & {
299
+ active?: boolean;
300
+ defaultOpen?: boolean;
301
+ collapsible?: boolean;
302
+ }) {
303
+ const { defaultOpenLevel } = useSidebar();
304
+ const depth = useFolderDepth() + 1;
305
+ const defaultOpen =
306
+ collapsible === false ||
307
+ active ||
308
+ (defaultOpenProp ?? defaultOpenLevel >= depth);
309
+ const [open, setOpen] = useState(defaultOpen);
310
+
311
+ useOnChange(defaultOpen, (v) => {
312
+ if (v) setOpen(v);
313
+ });
314
+
315
+ return (
316
+ <Collapsible
317
+ open={open}
318
+ onOpenChange={setOpen}
319
+ disabled={!collapsible}
320
+ {...props}
321
+ >
322
+ <FolderContext
323
+ value={useMemo(
324
+ () => ({ open, setOpen, depth, collapsible }),
325
+ [collapsible, depth, open],
326
+ )}
327
+ >
328
+ {children}
329
+ </FolderContext>
330
+ </Collapsible>
331
+ );
332
+ }
333
+
334
+ export function SidebarFolderTrigger({
335
+ children,
336
+ ...props
337
+ }: CollapsibleTriggerProps) {
338
+ const { open, collapsible } = use(FolderContext)!;
339
+
340
+ if (collapsible) {
341
+ return (
342
+ <CollapsibleTrigger {...props}>
343
+ {children}
344
+ <ChevronDown
345
+ data-icon
346
+ className={cn('ms-auto transition-transform', !open && '-rotate-90')}
347
+ />
348
+ </CollapsibleTrigger>
349
+ );
350
+ }
351
+
352
+ return <div {...(props as ComponentProps<'div'>)}>{children}</div>;
353
+ }
354
+
355
+ export function SidebarFolderLink({ children, ...props }: LinkProps) {
356
+ const ref = useRef<HTMLAnchorElement>(null);
357
+ const { open, setOpen, collapsible } = use(FolderContext)!;
358
+ const { prefetch } = useSidebar();
359
+ const pathname = usePathname();
360
+ const active =
361
+ props.href !== undefined && isActive(props.href, pathname, false);
362
+
363
+ useAutoScroll(active, ref);
364
+
365
+ return (
366
+ <Link
367
+ ref={ref}
368
+ data-active={active}
369
+ onClick={(e) => {
370
+ if (!collapsible) return;
371
+
372
+ if (
373
+ e.target instanceof Element &&
374
+ e.target.matches('[data-icon], [data-icon] *')
375
+ ) {
376
+ setOpen(!open);
377
+ e.preventDefault();
378
+ } else {
379
+ setOpen(active ? !open : true);
380
+ }
381
+ }}
382
+ prefetch={prefetch}
383
+ {...props}
384
+ >
385
+ {children}
386
+ {collapsible && (
387
+ <ChevronDown
388
+ data-icon
389
+ className={cn('ms-auto transition-transform', !open && '-rotate-90')}
390
+ />
391
+ )}
392
+ </Link>
393
+ );
394
+ }
395
+
396
+ export function SidebarFolderContent(props: CollapsibleContentProps) {
397
+ return <CollapsibleContent {...props}>{props.children}</CollapsibleContent>;
398
+ }
399
+
400
+ export function SidebarTrigger({
401
+ children,
402
+ ...props
403
+ }: ComponentProps<'button'>) {
404
+ const { setOpen } = useSidebar();
405
+
406
+ return (
407
+ <button
408
+ aria-label="Open Sidebar"
409
+ onClick={() => setOpen((prev) => !prev)}
410
+ {...props}
411
+ >
412
+ {children}
413
+ </button>
414
+ );
415
+ }
416
+
417
+ export function SidebarCollapseTrigger(props: ComponentProps<'button'>) {
418
+ const { collapsed, setCollapsed } = useSidebar();
419
+
420
+ return (
421
+ <button
422
+ type="button"
423
+ aria-label="Collapse Sidebar"
424
+ data-collapsed={collapsed}
425
+ onClick={() => {
426
+ setCollapsed((prev) => !prev);
427
+ }}
428
+ {...props}
429
+ >
430
+ {props.children}
431
+ </button>
432
+ );
433
+ }
434
+
435
+ function useAutoScroll(
436
+ active: boolean,
437
+ ref: RefObject<HTMLAnchorElement | null>,
438
+ ) {
439
+ const { mode } = useSidebar();
440
+
441
+ useEffect(() => {
442
+ if (active && ref.current) {
443
+ scrollIntoView(ref.current, {
444
+ boundary: document.getElementById(
445
+ mode === 'drawer' ? 'nd-sidebar-mobile' : 'nd-sidebar',
446
+ ),
447
+ scrollMode: 'if-needed',
448
+ });
449
+ }
450
+ }, [active, mode, ref]);
451
+ }
@@ -0,0 +1,65 @@
1
+ import type { HTMLAttributes } from 'react';
2
+ import type * as Base from './base';
3
+ import type { LinkItemType } from '@/layouts/shared/link-item';
4
+
5
+ type InternalComponents = Pick<
6
+ typeof Base,
7
+ | 'SidebarFolder'
8
+ | 'SidebarFolderLink'
9
+ | 'SidebarFolderContent'
10
+ | 'SidebarFolderTrigger'
11
+ | 'SidebarItem'
12
+ >;
13
+
14
+ export function createLinkItemRenderer({
15
+ SidebarFolder,
16
+ SidebarFolderContent,
17
+ SidebarFolderLink,
18
+ SidebarFolderTrigger,
19
+ SidebarItem,
20
+ }: InternalComponents) {
21
+ /**
22
+ * Render sidebar items from page tree
23
+ */
24
+ return function SidebarLinkItem({
25
+ item,
26
+ ...props
27
+ }: HTMLAttributes<HTMLElement> & {
28
+ item: Exclude<LinkItemType, { type: 'icon' }>;
29
+ }) {
30
+ if (item.type === 'custom') return <div {...props}>{item.children}</div>;
31
+
32
+ if (item.type === 'menu')
33
+ return (
34
+ <SidebarFolder {...props}>
35
+ {item.url ? (
36
+ <SidebarFolderLink href={item.url} external={item.external}>
37
+ {item.icon}
38
+ {item.text}
39
+ </SidebarFolderLink>
40
+ ) : (
41
+ <SidebarFolderTrigger>
42
+ {item.icon}
43
+ {item.text}
44
+ </SidebarFolderTrigger>
45
+ )}
46
+ <SidebarFolderContent>
47
+ {item.items.map((child, i) => (
48
+ <SidebarLinkItem key={i} item={child} />
49
+ ))}
50
+ </SidebarFolderContent>
51
+ </SidebarFolder>
52
+ );
53
+
54
+ return (
55
+ <SidebarItem
56
+ href={item.url}
57
+ icon={item.icon}
58
+ external={item.external}
59
+ {...props}
60
+ >
61
+ {item.text}
62
+ </SidebarItem>
63
+ );
64
+ };
65
+ }
@@ -0,0 +1,113 @@
1
+ import { useTreeContext, useTreePath } from '@/contexts/tree';
2
+ import { type FC, type ReactNode, useMemo, Fragment } from 'react';
3
+ import type * as PageTree from '@hanzo/docs-core/page-tree';
4
+ import type * as Base from './base';
5
+
6
+ export interface SidebarPageTreeComponents {
7
+ Item: FC<{ item: PageTree.Item }>;
8
+ Folder: FC<{ item: PageTree.Folder; children: ReactNode }>;
9
+ Separator: FC<{ item: PageTree.Separator }>;
10
+ }
11
+
12
+ type InternalComponents = Pick<
13
+ typeof Base,
14
+ | 'SidebarSeparator'
15
+ | 'SidebarFolder'
16
+ | 'SidebarFolderLink'
17
+ | 'SidebarFolderContent'
18
+ | 'SidebarFolderTrigger'
19
+ | 'SidebarItem'
20
+ >;
21
+
22
+ export function createPageTreeRenderer({
23
+ SidebarFolder,
24
+ SidebarFolderContent,
25
+ SidebarFolderLink,
26
+ SidebarFolderTrigger,
27
+ SidebarSeparator,
28
+ SidebarItem,
29
+ }: InternalComponents) {
30
+ function PageTreeFolder({
31
+ item,
32
+ children,
33
+ }: {
34
+ item: PageTree.Folder;
35
+ children: ReactNode;
36
+ }) {
37
+ const path = useTreePath();
38
+
39
+ return (
40
+ <SidebarFolder
41
+ collapsible={item.collapsible}
42
+ active={path.includes(item)}
43
+ defaultOpen={item.defaultOpen}
44
+ >
45
+ {item.index ? (
46
+ <SidebarFolderLink
47
+ href={item.index.url}
48
+ external={item.index.external}
49
+ >
50
+ {item.icon}
51
+ {item.name}
52
+ </SidebarFolderLink>
53
+ ) : (
54
+ <SidebarFolderTrigger>
55
+ {item.icon}
56
+ {item.name}
57
+ </SidebarFolderTrigger>
58
+ )}
59
+ <SidebarFolderContent>{children}</SidebarFolderContent>
60
+ </SidebarFolder>
61
+ );
62
+ }
63
+
64
+ /**
65
+ * Render sidebar items from page tree
66
+ */
67
+ return function SidebarPageTree(
68
+ components: Partial<SidebarPageTreeComponents>,
69
+ ) {
70
+ const { root } = useTreeContext();
71
+ const { Separator, Item, Folder = PageTreeFolder } = components;
72
+
73
+ return useMemo(() => {
74
+ function renderSidebarList(items: PageTree.Node[]) {
75
+ return items.map((item, i) => {
76
+ if (item.type === 'separator') {
77
+ if (Separator) return <Separator key={i} item={item} />;
78
+ return (
79
+ <SidebarSeparator key={i}>
80
+ {item.icon}
81
+ {item.name}
82
+ </SidebarSeparator>
83
+ );
84
+ }
85
+
86
+ if (item.type === 'folder') {
87
+ return (
88
+ <Folder key={i} item={item}>
89
+ {renderSidebarList(item.children)}
90
+ </Folder>
91
+ );
92
+ }
93
+
94
+ if (Item) return <Item key={item.url} item={item} />;
95
+ return (
96
+ <SidebarItem
97
+ key={item.url}
98
+ href={item.url}
99
+ external={item.external}
100
+ icon={item.icon}
101
+ >
102
+ {item.name}
103
+ </SidebarItem>
104
+ );
105
+ });
106
+ }
107
+
108
+ return (
109
+ <Fragment key={root.$id}>{renderSidebarList(root.children)}</Fragment>
110
+ );
111
+ }, [Folder, Item, Separator, root]);
112
+ };
113
+ }