@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,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
|
+
}
|