@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,81 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { cva } from 'class-variance-authority';
|
|
4
|
+
import { File as FileIcon, Folder as FolderIcon, FolderOpen } from '@icons';
|
|
5
|
+
import { type HTMLAttributes, type ReactNode, useState } from 'react';
|
|
6
|
+
import { cn } from '@/utils/cn';
|
|
7
|
+
import {
|
|
8
|
+
Collapsible,
|
|
9
|
+
CollapsibleContent,
|
|
10
|
+
CollapsibleTrigger,
|
|
11
|
+
} from './ui/collapsible';
|
|
12
|
+
|
|
13
|
+
const itemVariants = cva(
|
|
14
|
+
'flex flex-row items-center gap-2 rounded-md px-2 py-1.5 text-sm hover:bg-fd-accent hover:text-fd-accent-foreground [&_svg]:size-4',
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
export function Files({
|
|
18
|
+
className,
|
|
19
|
+
...props
|
|
20
|
+
}: HTMLAttributes<HTMLDivElement>): React.ReactElement {
|
|
21
|
+
return (
|
|
22
|
+
<div
|
|
23
|
+
className={cn('not-prose rounded-md border bg-fd-card p-2', className)}
|
|
24
|
+
{...props}
|
|
25
|
+
>
|
|
26
|
+
{props.children}
|
|
27
|
+
</div>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface FileProps extends HTMLAttributes<HTMLDivElement> {
|
|
32
|
+
name: string;
|
|
33
|
+
icon?: ReactNode;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface FolderProps extends HTMLAttributes<HTMLDivElement> {
|
|
37
|
+
name: string;
|
|
38
|
+
|
|
39
|
+
disabled?: boolean;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Open folder by default
|
|
43
|
+
*
|
|
44
|
+
* @defaultValue false
|
|
45
|
+
*/
|
|
46
|
+
defaultOpen?: boolean;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function File({
|
|
50
|
+
name,
|
|
51
|
+
icon = <FileIcon />,
|
|
52
|
+
className,
|
|
53
|
+
...rest
|
|
54
|
+
}: FileProps): React.ReactElement {
|
|
55
|
+
return (
|
|
56
|
+
<div className={cn(itemVariants({ className }))} {...rest}>
|
|
57
|
+
{icon}
|
|
58
|
+
{name}
|
|
59
|
+
</div>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function Folder({
|
|
64
|
+
name,
|
|
65
|
+
defaultOpen = false,
|
|
66
|
+
...props
|
|
67
|
+
}: FolderProps): React.ReactElement {
|
|
68
|
+
const [open, setOpen] = useState(defaultOpen);
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<Collapsible open={open} onOpenChange={setOpen} {...props}>
|
|
72
|
+
<CollapsibleTrigger className={cn(itemVariants({ className: 'w-full' }))}>
|
|
73
|
+
{open ? <FolderOpen /> : <FolderIcon />}
|
|
74
|
+
{name}
|
|
75
|
+
</CollapsibleTrigger>
|
|
76
|
+
<CollapsibleContent>
|
|
77
|
+
<div className="ms-2 flex flex-col border-l ps-2">{props.children}</div>
|
|
78
|
+
</CollapsibleContent>
|
|
79
|
+
</Collapsible>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { cn } from '@/utils/cn';
|
|
2
|
+
import { Star } from '@icons';
|
|
3
|
+
import { type AnchorHTMLAttributes } from 'react';
|
|
4
|
+
|
|
5
|
+
async function getRepoStarsAndForks(
|
|
6
|
+
owner: string,
|
|
7
|
+
repo: string,
|
|
8
|
+
token?: string,
|
|
9
|
+
baseUrl: string = 'https://api.github.com',
|
|
10
|
+
): Promise<{
|
|
11
|
+
stars: number;
|
|
12
|
+
forks: number;
|
|
13
|
+
}> {
|
|
14
|
+
const endpoint = `${baseUrl}/repos/${owner}/${repo}`;
|
|
15
|
+
const headers = new Headers({
|
|
16
|
+
'Content-Type': 'application/json',
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
if (token) headers.set('Authorization', `Bearer ${token}`);
|
|
20
|
+
|
|
21
|
+
const response = await fetch(endpoint, {
|
|
22
|
+
headers,
|
|
23
|
+
next: {
|
|
24
|
+
revalidate: 60,
|
|
25
|
+
},
|
|
26
|
+
} as RequestInit);
|
|
27
|
+
|
|
28
|
+
if (!response.ok) {
|
|
29
|
+
const message = await response.text();
|
|
30
|
+
|
|
31
|
+
throw new Error(`Failed to fetch repository data: ${message}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const data = await response.json();
|
|
35
|
+
return {
|
|
36
|
+
stars: data.stargazers_count,
|
|
37
|
+
forks: data.forks_count,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function GithubInfo({
|
|
42
|
+
repo,
|
|
43
|
+
owner,
|
|
44
|
+
token,
|
|
45
|
+
baseUrl,
|
|
46
|
+
...props
|
|
47
|
+
}: AnchorHTMLAttributes<HTMLAnchorElement> & {
|
|
48
|
+
owner: string;
|
|
49
|
+
repo: string;
|
|
50
|
+
token?: string;
|
|
51
|
+
baseUrl?: string;
|
|
52
|
+
}) {
|
|
53
|
+
const { stars } = await getRepoStarsAndForks(owner, repo, token, baseUrl);
|
|
54
|
+
const humanizedStars = humanizeNumber(stars);
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<a
|
|
58
|
+
href={`https://github.com/${owner}/${repo}`}
|
|
59
|
+
rel="noreferrer noopener"
|
|
60
|
+
target="_blank"
|
|
61
|
+
{...props}
|
|
62
|
+
className={cn(
|
|
63
|
+
'flex flex-col gap-1.5 p-2 rounded-lg text-sm text-fd-foreground/80 transition-colors lg:flex-row lg:items-center hover:text-fd-accent-foreground hover:bg-fd-accent',
|
|
64
|
+
props.className,
|
|
65
|
+
)}
|
|
66
|
+
>
|
|
67
|
+
<p className="flex items-center gap-2 truncate">
|
|
68
|
+
<svg fill="currentColor" viewBox="0 0 24 24" className="size-3.5">
|
|
69
|
+
<title>GitHub</title>
|
|
70
|
+
<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" />
|
|
71
|
+
</svg>
|
|
72
|
+
{owner}/{repo}
|
|
73
|
+
</p>
|
|
74
|
+
<p className="flex text-xs items-center gap-1 text-fd-muted-foreground">
|
|
75
|
+
<Star className="size-3" />
|
|
76
|
+
{humanizedStars}
|
|
77
|
+
</p>
|
|
78
|
+
</a>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Converts a number to a human-readable string with K suffix for thousands
|
|
84
|
+
* @example 1500 -> "1.5K", 1000000 -> "1000000"
|
|
85
|
+
*/
|
|
86
|
+
function humanizeNumber(num: number): string {
|
|
87
|
+
if (num < 1000) {
|
|
88
|
+
return num.toString();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (num < 100000) {
|
|
92
|
+
// For numbers between 1,000 and 99,999, show with one decimal (e.g., 1.5K)
|
|
93
|
+
const value = (num / 1000).toFixed(1);
|
|
94
|
+
// Remove trailing .0 if present
|
|
95
|
+
const formattedValue = value.endsWith('.0') ? value.slice(0, -2) : value;
|
|
96
|
+
|
|
97
|
+
return `${formattedValue}K`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (num < 1000000) {
|
|
101
|
+
// For numbers between 10,000 and 999,999, show as whole K (e.g., 10K, 999K)
|
|
102
|
+
return `${Math.floor(num / 1000)}K`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// For 1,000,000 and above, just return the number
|
|
106
|
+
return num.toString();
|
|
107
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Link } from '@icons';
|
|
2
|
+
import type { ComponentPropsWithoutRef } from 'react';
|
|
3
|
+
import { cn } from '@/utils/cn';
|
|
4
|
+
|
|
5
|
+
type Types = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
|
|
6
|
+
type HeadingProps<T extends Types> = Omit<ComponentPropsWithoutRef<T>, 'as'> & {
|
|
7
|
+
as?: T;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function Heading<T extends Types = 'h1'>({
|
|
11
|
+
as,
|
|
12
|
+
className,
|
|
13
|
+
...props
|
|
14
|
+
}: HeadingProps<T>): React.ReactElement {
|
|
15
|
+
const As = as ?? 'h1';
|
|
16
|
+
|
|
17
|
+
if (!props.id) return <As className={className} {...props} />;
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<As
|
|
21
|
+
className={cn('flex scroll-m-28 flex-row items-center gap-2', className)}
|
|
22
|
+
{...props}
|
|
23
|
+
>
|
|
24
|
+
<a data-card="" href={`#${props.id}`} className="peer">
|
|
25
|
+
{props.children}
|
|
26
|
+
</a>
|
|
27
|
+
<Link
|
|
28
|
+
aria-hidden
|
|
29
|
+
className="size-3.5 shrink-0 text-fd-muted-foreground opacity-0 transition-opacity peer-hover:opacity-100"
|
|
30
|
+
/>
|
|
31
|
+
</As>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
[data-rmiz] {
|
|
2
|
+
display: block;
|
|
3
|
+
position: relative;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
[data-rmiz-ghost] {
|
|
7
|
+
pointer-events: none;
|
|
8
|
+
position: absolute;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
[data-rmiz-btn-zoom],
|
|
12
|
+
[data-rmiz-btn-unzoom] {
|
|
13
|
+
display: none;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
[data-rmiz-content='found'] img {
|
|
17
|
+
cursor: zoom-in;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
[data-rmiz-modal][open] {
|
|
21
|
+
width: 100vw /* fallback */;
|
|
22
|
+
width: 100dvw;
|
|
23
|
+
|
|
24
|
+
height: 100vh /* fallback */;
|
|
25
|
+
height: 100dvh;
|
|
26
|
+
|
|
27
|
+
background-color: transparent;
|
|
28
|
+
max-width: none;
|
|
29
|
+
max-height: none;
|
|
30
|
+
margin: 0;
|
|
31
|
+
padding: 0;
|
|
32
|
+
position: fixed;
|
|
33
|
+
overflow: hidden;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
[data-rmiz-modal]:focus-visible {
|
|
37
|
+
outline: none;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
[data-rmiz-modal-overlay] {
|
|
41
|
+
transition: background-color 0.3s;
|
|
42
|
+
position: absolute;
|
|
43
|
+
inset: 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
[data-rmiz-modal-overlay='visible'] {
|
|
47
|
+
background-color: var(--color-fd-background);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
[data-rmiz-modal-overlay='hidden'] {
|
|
51
|
+
background-color: transparent;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
[data-rmiz-modal-content] {
|
|
55
|
+
width: 100%;
|
|
56
|
+
height: 100%;
|
|
57
|
+
position: relative;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
[data-rmiz-modal]::backdrop {
|
|
61
|
+
display: none;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
[data-rmiz-modal-img] {
|
|
65
|
+
cursor: zoom-out;
|
|
66
|
+
image-rendering: high-quality;
|
|
67
|
+
transform-origin: 0 0;
|
|
68
|
+
transition: transform 0.3s;
|
|
69
|
+
position: absolute;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@media (prefers-reduced-motion: reduce) {
|
|
73
|
+
[data-rmiz-modal-overlay],
|
|
74
|
+
[data-rmiz-modal-img] {
|
|
75
|
+
transition-duration: 0.01ms !important;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Image, type ImageProps } from '@hanzo/docs-core/framework';
|
|
4
|
+
import type { ComponentProps } from 'react';
|
|
5
|
+
import Zoom, { type UncontrolledProps } from 'react-medium-image-zoom';
|
|
6
|
+
import './image-zoom.css';
|
|
7
|
+
|
|
8
|
+
export type ImageZoomProps = ImageProps & {
|
|
9
|
+
/**
|
|
10
|
+
* Image props when zoom in
|
|
11
|
+
*/
|
|
12
|
+
zoomInProps?: ComponentProps<'img'>;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Props for `react-medium-image-zoom`
|
|
16
|
+
*/
|
|
17
|
+
rmiz?: UncontrolledProps;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
function getImageSrc(src: ImageProps['src']): string {
|
|
21
|
+
if (typeof src === 'string') return src;
|
|
22
|
+
|
|
23
|
+
if (typeof src === 'object') {
|
|
24
|
+
// Next.js
|
|
25
|
+
if ('default' in src)
|
|
26
|
+
return (src as { default: { src: string } }).default.src;
|
|
27
|
+
return src.src;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return '';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function ImageZoom({
|
|
34
|
+
zoomInProps,
|
|
35
|
+
children,
|
|
36
|
+
rmiz,
|
|
37
|
+
...props
|
|
38
|
+
}: ImageZoomProps) {
|
|
39
|
+
return (
|
|
40
|
+
<Zoom
|
|
41
|
+
zoomMargin={20}
|
|
42
|
+
wrapElement="span"
|
|
43
|
+
{...rmiz}
|
|
44
|
+
zoomImg={{
|
|
45
|
+
src: getImageSrc(props.src),
|
|
46
|
+
sizes: undefined,
|
|
47
|
+
...zoomInProps,
|
|
48
|
+
}}
|
|
49
|
+
>
|
|
50
|
+
{children ?? (
|
|
51
|
+
<Image
|
|
52
|
+
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 70vw, 900px"
|
|
53
|
+
{...props}
|
|
54
|
+
/>
|
|
55
|
+
)}
|
|
56
|
+
</Zoom>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Documentation-specific components
|
|
2
|
+
// For general content components (Card, Tabs, Steps, Callout, Accordion),
|
|
3
|
+
// use @hanzo/ui/content instead
|
|
4
|
+
|
|
5
|
+
// TypeTable is truly docs-specific (API reference tables)
|
|
6
|
+
export { TypeTable } from './type-table'
|
|
7
|
+
export type { TypeNode, ParameterNode } from './type-table'
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { ChevronDown } from '@icons';
|
|
4
|
+
import type { TOCItemType } from '@hanzo/docs-core/toc';
|
|
5
|
+
import {
|
|
6
|
+
Collapsible,
|
|
7
|
+
CollapsibleContent,
|
|
8
|
+
CollapsibleTrigger,
|
|
9
|
+
} from './ui/collapsible';
|
|
10
|
+
import type { ComponentProps } from 'react';
|
|
11
|
+
import { cn } from '@/utils/cn';
|
|
12
|
+
|
|
13
|
+
export interface InlineTocProps extends ComponentProps<typeof Collapsible> {
|
|
14
|
+
items: TOCItemType[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function InlineTOC({ items, ...props }: InlineTocProps) {
|
|
18
|
+
return (
|
|
19
|
+
<Collapsible
|
|
20
|
+
{...props}
|
|
21
|
+
className={cn(
|
|
22
|
+
'not-prose rounded-lg border bg-fd-card text-fd-card-foreground',
|
|
23
|
+
props.className,
|
|
24
|
+
)}
|
|
25
|
+
>
|
|
26
|
+
<CollapsibleTrigger className="group inline-flex w-full items-center justify-between px-4 py-2.5 font-medium">
|
|
27
|
+
{props.children ?? 'Table of Contents'}
|
|
28
|
+
<ChevronDown className="size-4 transition-transform duration-200 group-data-[state=open]:rotate-180" />
|
|
29
|
+
</CollapsibleTrigger>
|
|
30
|
+
<CollapsibleContent>
|
|
31
|
+
<div className="flex flex-col p-4 pt-0 text-sm text-fd-muted-foreground">
|
|
32
|
+
{items.map((item) => (
|
|
33
|
+
<a
|
|
34
|
+
key={item.url}
|
|
35
|
+
href={item.url}
|
|
36
|
+
className="border-s py-1.5 hover:text-fd-accent-foreground"
|
|
37
|
+
style={{
|
|
38
|
+
paddingInlineStart: 12 * Math.max(item.depth - 1, 0),
|
|
39
|
+
}}
|
|
40
|
+
>
|
|
41
|
+
{item.title}
|
|
42
|
+
</a>
|
|
43
|
+
))}
|
|
44
|
+
</div>
|
|
45
|
+
</CollapsibleContent>
|
|
46
|
+
</Collapsible>
|
|
47
|
+
);
|
|
48
|
+
}
|