boltdocs 1.10.2 → 1.11.0
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/package.json +29 -7
- package/src/client/app/config-context.tsx +18 -0
- package/src/client/app/docs-layout.tsx +14 -0
- package/src/client/app/index.tsx +132 -260
- package/src/client/app/mdx-component.tsx +52 -0
- package/src/client/app/mdx-components-context.tsx +23 -0
- package/src/client/app/mdx-page.tsx +20 -0
- package/src/client/app/preload.tsx +38 -30
- package/src/client/app/router.tsx +30 -0
- package/src/client/app/scroll-handler.tsx +40 -0
- package/src/client/app/theme-context.tsx +75 -0
- package/src/client/components/default-layout.tsx +80 -0
- package/src/client/components/docs-layout.tsx +105 -0
- package/src/client/components/icons-dev.tsx +74 -0
- package/src/client/components/mdx/admonition.tsx +107 -0
- package/src/client/components/mdx/badge.tsx +41 -0
- package/src/client/components/mdx/button.tsx +35 -0
- package/src/client/components/mdx/card.tsx +124 -0
- package/src/client/components/mdx/code-block.tsx +119 -0
- package/src/client/components/mdx/component-preview.tsx +47 -0
- package/src/client/components/mdx/component-props.tsx +83 -0
- package/src/client/components/mdx/field.tsx +66 -0
- package/src/client/components/mdx/file-tree.tsx +287 -0
- package/src/client/components/mdx/hooks/use-code-block.ts +56 -0
- package/src/client/components/mdx/hooks/use-component-preview.ts +16 -0
- package/src/client/components/mdx/hooks/useTable.ts +74 -0
- package/src/client/components/mdx/hooks/useTabs.ts +68 -0
- package/src/client/components/mdx/image.tsx +23 -0
- package/src/client/components/mdx/index.ts +53 -0
- package/src/client/components/mdx/link.tsx +38 -0
- package/src/client/components/mdx/list.tsx +192 -0
- package/src/client/components/mdx/table.tsx +156 -0
- package/src/client/components/mdx/tabs.tsx +135 -0
- package/src/client/components/mdx/video.tsx +68 -0
- package/src/client/components/primitives/breadcrumbs.tsx +79 -0
- package/src/client/components/primitives/button-group.tsx +54 -0
- package/src/client/components/primitives/button.tsx +145 -0
- package/src/client/components/primitives/helpers/observer.ts +120 -0
- package/src/client/components/primitives/index.ts +17 -0
- package/src/client/components/primitives/link.tsx +122 -0
- package/src/client/components/primitives/menu.tsx +159 -0
- package/src/client/components/primitives/navbar.tsx +359 -0
- package/src/client/components/primitives/navigation-menu.tsx +116 -0
- package/src/client/components/primitives/on-this-page.tsx +461 -0
- package/src/client/components/primitives/page-nav.tsx +87 -0
- package/src/client/components/primitives/popover.tsx +47 -0
- package/src/client/components/primitives/search-dialog.tsx +183 -0
- package/src/client/components/primitives/sidebar.tsx +154 -0
- package/src/client/components/primitives/tabs.tsx +90 -0
- package/src/client/components/primitives/tooltip.tsx +83 -0
- package/src/client/components/primitives/types.ts +11 -0
- package/src/client/components/ui-base/breadcrumbs.tsx +42 -0
- package/src/client/components/ui-base/copy-markdown.tsx +112 -0
- package/src/client/components/ui-base/error-boundary.tsx +52 -0
- package/src/client/components/ui-base/github-stars.tsx +27 -0
- package/src/client/components/ui-base/head.tsx +69 -0
- package/src/client/components/ui-base/loading.tsx +87 -0
- package/src/client/components/ui-base/navbar.tsx +138 -0
- package/src/client/components/ui-base/not-found.tsx +24 -0
- package/src/client/components/ui-base/on-this-page.tsx +152 -0
- package/src/client/components/ui-base/page-nav.tsx +39 -0
- package/src/client/components/ui-base/powered-by.tsx +19 -0
- package/src/client/components/ui-base/progress-bar.tsx +67 -0
- package/src/client/components/ui-base/search-dialog.tsx +82 -0
- package/src/client/components/ui-base/sidebar.tsx +104 -0
- package/src/client/components/ui-base/tabs.tsx +65 -0
- package/src/client/components/ui-base/theme-toggle.tsx +32 -0
- package/src/client/hooks/index.ts +12 -0
- package/src/client/hooks/use-breadcrumbs.ts +22 -0
- package/src/client/hooks/use-i18n.ts +84 -0
- package/src/client/hooks/use-localized-to.ts +95 -0
- package/src/client/hooks/use-location.ts +5 -0
- package/src/client/hooks/use-navbar.ts +60 -0
- package/src/client/hooks/use-onthispage.ts +23 -0
- package/src/client/hooks/use-page-nav.ts +22 -0
- package/src/client/hooks/use-routes.ts +72 -0
- package/src/client/hooks/use-search.ts +71 -0
- package/src/client/hooks/use-sidebar.ts +49 -0
- package/src/client/hooks/use-tabs.ts +43 -0
- package/src/client/hooks/use-version.ts +78 -0
- package/src/client/index.ts +55 -17
- package/src/client/integrations/codesandbox.ts +179 -0
- package/src/client/ssr.tsx +27 -16
- package/src/client/theme/neutral.css +360 -0
- package/src/client/types.ts +131 -27
- package/src/client/utils/cn.ts +6 -0
- package/src/client/utils/copy-clipboard.ts +22 -0
- package/src/client/utils/get-base-file-path.ts +21 -0
- package/src/client/utils/github.ts +121 -0
- package/src/client/utils/use-on-change.ts +15 -0
- package/src/client/virtual.d.ts +24 -0
- package/src/node/cache.ts +156 -156
- package/src/node/config.ts +159 -103
- package/src/node/index.ts +13 -13
- package/src/node/mdx.ts +213 -61
- package/src/node/plugin/entry.ts +29 -18
- package/src/node/plugin/html.ts +11 -11
- package/src/node/plugin/index.ts +161 -83
- package/src/node/plugin/types.ts +2 -4
- package/src/node/routes/cache.ts +6 -6
- package/src/node/routes/index.ts +206 -113
- package/src/node/routes/parser.ts +106 -81
- package/src/node/routes/sorter.ts +15 -15
- package/src/node/routes/types.ts +24 -24
- package/src/node/ssg/index.ts +46 -46
- package/src/node/ssg/meta.ts +4 -4
- package/src/node/ssg/options.ts +5 -5
- package/src/node/ssg/sitemap.ts +14 -14
- package/src/node/utils.ts +31 -31
- package/tsconfig.json +25 -20
- package/tsup.config.ts +23 -14
- package/dist/PackageManagerTabs-NVT7G625.mjs +0 -99
- package/dist/SearchDialog-AGVF6JBO.mjs +0 -194
- package/dist/SearchDialog-YPDOM7Q6.css +0 -2847
- package/dist/Video-KNTY5BNO.mjs +0 -6
- package/dist/cache-KNL5B4EE.mjs +0 -12
- package/dist/chunk-7SFUJWTB.mjs +0 -211
- package/dist/chunk-FFBNU6IJ.mjs +0 -386
- package/dist/chunk-FMTOYQLO.mjs +0 -37
- package/dist/chunk-TKLQWU7H.mjs +0 -1920
- package/dist/chunk-Z7JHYNAS.mjs +0 -57
- package/dist/client/index.css +0 -2847
- package/dist/client/index.d.mts +0 -372
- package/dist/client/index.d.ts +0 -372
- package/dist/client/index.js +0 -3630
- package/dist/client/index.mjs +0 -697
- package/dist/client/ssr.css +0 -2847
- package/dist/client/ssr.d.mts +0 -27
- package/dist/client/ssr.d.ts +0 -27
- package/dist/client/ssr.js +0 -2928
- package/dist/client/ssr.mjs +0 -33
- package/dist/config-BsFQ-ErD.d.mts +0 -159
- package/dist/config-BsFQ-ErD.d.ts +0 -159
- package/dist/node/index.d.mts +0 -91
- package/dist/node/index.d.ts +0 -91
- package/dist/node/index.js +0 -1187
- package/dist/node/index.mjs +0 -762
- package/dist/types-Dj-bfnC3.d.mts +0 -74
- package/dist/types-Dj-bfnC3.d.ts +0 -74
- package/src/client/theme/components/CodeBlock/CodeBlock.tsx +0 -61
- package/src/client/theme/components/CodeBlock/index.ts +0 -1
- package/src/client/theme/components/PackageManagerTabs/PackageManagerTabs.tsx +0 -131
- package/src/client/theme/components/PackageManagerTabs/index.ts +0 -1
- package/src/client/theme/components/PackageManagerTabs/pkg-tabs.css +0 -64
- package/src/client/theme/components/Playground/Playground.tsx +0 -180
- package/src/client/theme/components/Playground/index.ts +0 -1
- package/src/client/theme/components/Playground/playground.css +0 -238
- package/src/client/theme/components/Video/Video.tsx +0 -84
- package/src/client/theme/components/Video/index.ts +0 -1
- package/src/client/theme/components/Video/video.css +0 -41
- package/src/client/theme/components/mdx/Admonition.tsx +0 -80
- package/src/client/theme/components/mdx/Badge.tsx +0 -31
- package/src/client/theme/components/mdx/Button.tsx +0 -50
- package/src/client/theme/components/mdx/Card.tsx +0 -80
- package/src/client/theme/components/mdx/Field.tsx +0 -60
- package/src/client/theme/components/mdx/FileTree.tsx +0 -229
- package/src/client/theme/components/mdx/List.tsx +0 -57
- package/src/client/theme/components/mdx/Table.tsx +0 -151
- package/src/client/theme/components/mdx/Tabs.tsx +0 -123
- package/src/client/theme/components/mdx/index.ts +0 -27
- package/src/client/theme/components/mdx/mdx-components.css +0 -764
- package/src/client/theme/icons/bun.tsx +0 -62
- package/src/client/theme/icons/deno.tsx +0 -20
- package/src/client/theme/icons/discord.tsx +0 -12
- package/src/client/theme/icons/github.tsx +0 -15
- package/src/client/theme/icons/npm.tsx +0 -13
- package/src/client/theme/icons/pnpm.tsx +0 -72
- package/src/client/theme/icons/twitter.tsx +0 -12
- package/src/client/theme/styles/markdown.css +0 -394
- package/src/client/theme/styles/variables.css +0 -175
- package/src/client/theme/styles.css +0 -39
- package/src/client/theme/ui/Breadcrumbs/Breadcrumbs.tsx +0 -68
- package/src/client/theme/ui/Breadcrumbs/index.ts +0 -1
- package/src/client/theme/ui/CopyMarkdown/CopyMarkdown.tsx +0 -82
- package/src/client/theme/ui/CopyMarkdown/copy-markdown.css +0 -112
- package/src/client/theme/ui/CopyMarkdown/index.ts +0 -1
- package/src/client/theme/ui/ErrorBoundary/ErrorBoundary.tsx +0 -50
- package/src/client/theme/ui/ErrorBoundary/error-boundary.css +0 -55
- package/src/client/theme/ui/ErrorBoundary/index.ts +0 -1
- package/src/client/theme/ui/Footer/footer.css +0 -32
- package/src/client/theme/ui/Head/Head.tsx +0 -69
- package/src/client/theme/ui/Head/index.ts +0 -1
- package/src/client/theme/ui/LanguageSwitcher/LanguageSwitcher.tsx +0 -125
- package/src/client/theme/ui/LanguageSwitcher/index.ts +0 -1
- package/src/client/theme/ui/LanguageSwitcher/language-switcher.css +0 -98
- package/src/client/theme/ui/Layout/Layout.tsx +0 -203
- package/src/client/theme/ui/Layout/base.css +0 -106
- package/src/client/theme/ui/Layout/index.ts +0 -2
- package/src/client/theme/ui/Layout/pagination.css +0 -72
- package/src/client/theme/ui/Layout/responsive.css +0 -47
- package/src/client/theme/ui/Link/Link.tsx +0 -392
- package/src/client/theme/ui/Link/LinkPreview.tsx +0 -59
- package/src/client/theme/ui/Link/index.ts +0 -2
- package/src/client/theme/ui/Link/link-preview.css +0 -48
- package/src/client/theme/ui/Loading/Loading.tsx +0 -10
- package/src/client/theme/ui/Loading/index.ts +0 -1
- package/src/client/theme/ui/Loading/loading.css +0 -30
- package/src/client/theme/ui/Navbar/GithubStars.tsx +0 -27
- package/src/client/theme/ui/Navbar/Navbar.tsx +0 -193
- package/src/client/theme/ui/Navbar/Tabs.tsx +0 -99
- package/src/client/theme/ui/Navbar/index.ts +0 -2
- package/src/client/theme/ui/Navbar/navbar.css +0 -347
- package/src/client/theme/ui/NotFound/NotFound.tsx +0 -19
- package/src/client/theme/ui/NotFound/index.ts +0 -1
- package/src/client/theme/ui/NotFound/not-found.css +0 -64
- package/src/client/theme/ui/OnThisPage/OnThisPage.tsx +0 -244
- package/src/client/theme/ui/OnThisPage/index.ts +0 -1
- package/src/client/theme/ui/OnThisPage/toc.css +0 -152
- package/src/client/theme/ui/PoweredBy/PoweredBy.tsx +0 -18
- package/src/client/theme/ui/PoweredBy/index.ts +0 -1
- package/src/client/theme/ui/PoweredBy/powered-by.css +0 -76
- package/src/client/theme/ui/ProgressBar/ProgressBar.css +0 -17
- package/src/client/theme/ui/ProgressBar/ProgressBar.tsx +0 -51
- package/src/client/theme/ui/ProgressBar/index.ts +0 -1
- package/src/client/theme/ui/SearchDialog/SearchDialog.tsx +0 -209
- package/src/client/theme/ui/SearchDialog/index.ts +0 -1
- package/src/client/theme/ui/SearchDialog/search.css +0 -152
- package/src/client/theme/ui/Sidebar/Sidebar.tsx +0 -244
- package/src/client/theme/ui/Sidebar/index.ts +0 -1
- package/src/client/theme/ui/Sidebar/sidebar.css +0 -230
- package/src/client/theme/ui/ThemeToggle/ThemeToggle.tsx +0 -69
- package/src/client/theme/ui/ThemeToggle/index.ts +0 -1
- package/src/client/theme/ui/VersionSwitcher/VersionSwitcher.tsx +0 -136
- package/src/client/theme/ui/VersionSwitcher/index.ts +0 -1
- package/src/client/utils.ts +0 -49
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Children,
|
|
3
|
+
isValidElement,
|
|
4
|
+
type ReactNode,
|
|
5
|
+
type ReactElement,
|
|
6
|
+
type ComponentPropsWithoutRef,
|
|
7
|
+
} from 'react'
|
|
8
|
+
import { Check, ChevronRight, Circle } from 'lucide-react'
|
|
9
|
+
import { cn } from '@client/utils/cn'
|
|
10
|
+
import { cva, type VariantProps } from 'class-variance-authority'
|
|
11
|
+
|
|
12
|
+
const listVariants = cva('my-6 transition-all duration-200', {
|
|
13
|
+
variants: {
|
|
14
|
+
variant: {
|
|
15
|
+
default: 'list-disc pl-5 text-text-muted marker:text-primary-500/50',
|
|
16
|
+
number:
|
|
17
|
+
'list-decimal pl-5 text-text-muted marker:text-primary-500/50 marker:font-bold',
|
|
18
|
+
checked: 'list-none p-0',
|
|
19
|
+
arrow: 'list-none p-0',
|
|
20
|
+
bubble: 'list-none p-0',
|
|
21
|
+
},
|
|
22
|
+
cols: {
|
|
23
|
+
1: 'grid-cols-1',
|
|
24
|
+
2: 'grid-cols-1 sm:grid-cols-2',
|
|
25
|
+
3: 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-3',
|
|
26
|
+
4: 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-4',
|
|
27
|
+
},
|
|
28
|
+
isGrid: {
|
|
29
|
+
true: 'grid gap-x-8 gap-y-3',
|
|
30
|
+
false: 'space-y-2',
|
|
31
|
+
},
|
|
32
|
+
dense: {
|
|
33
|
+
true: 'space-y-1',
|
|
34
|
+
false: 'space-y-2',
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
compoundVariants: [
|
|
38
|
+
{
|
|
39
|
+
variant: 'default',
|
|
40
|
+
dense: true,
|
|
41
|
+
className: 'space-y-0.5',
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
defaultVariants: {
|
|
45
|
+
variant: 'default',
|
|
46
|
+
cols: 1,
|
|
47
|
+
isGrid: false,
|
|
48
|
+
dense: false,
|
|
49
|
+
},
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
const itemVariants = cva(
|
|
53
|
+
'group flex items-start gap-3 text-sm leading-relaxed transition-all duration-200',
|
|
54
|
+
{
|
|
55
|
+
variants: {
|
|
56
|
+
variant: {
|
|
57
|
+
default: '',
|
|
58
|
+
number: '',
|
|
59
|
+
checked: 'hover:translate-x-0.5',
|
|
60
|
+
arrow: 'hover:translate-x-0.5',
|
|
61
|
+
bubble: 'hover:translate-x-0.5',
|
|
62
|
+
},
|
|
63
|
+
dense: {
|
|
64
|
+
true: 'py-0',
|
|
65
|
+
false: 'py-0.5',
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
defaultVariants: {
|
|
69
|
+
variant: 'default',
|
|
70
|
+
dense: false,
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
const iconContainerVariants = cva(
|
|
76
|
+
'mt-1 shrink-0 flex items-center justify-center transition-transform group-hover:scale-110',
|
|
77
|
+
{
|
|
78
|
+
variants: {
|
|
79
|
+
variant: {
|
|
80
|
+
bubble:
|
|
81
|
+
'h-5 w-5 rounded-full bg-primary-500/10 text-primary-500 text-[10px] font-bold',
|
|
82
|
+
default: '',
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
defaultVariants: {
|
|
86
|
+
variant: 'default',
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
type ListVariantProps = VariantProps<typeof listVariants>
|
|
92
|
+
|
|
93
|
+
export interface ListProps
|
|
94
|
+
extends ComponentPropsWithoutRef<'ul'>,
|
|
95
|
+
Omit<ListVariantProps, 'variant'> {
|
|
96
|
+
variant?: 'checked' | 'arrow' | 'default' | 'bubble' | 'number'
|
|
97
|
+
children: ReactNode
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
interface ListItemProps extends VariantProps<typeof itemVariants> {
|
|
101
|
+
icon?: ReactNode
|
|
102
|
+
children: ReactNode
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function ListItem({ icon, children, variant, dense }: ListItemProps) {
|
|
106
|
+
return (
|
|
107
|
+
<li className={cn(itemVariants({ variant, dense }))}>
|
|
108
|
+
{icon && (
|
|
109
|
+
<span
|
|
110
|
+
className={cn(
|
|
111
|
+
iconContainerVariants({
|
|
112
|
+
variant: variant === 'bubble' ? 'bubble' : 'default',
|
|
113
|
+
}),
|
|
114
|
+
)}
|
|
115
|
+
>
|
|
116
|
+
{icon}
|
|
117
|
+
</span>
|
|
118
|
+
)}
|
|
119
|
+
<div className="flex-1 text-text-muted group-hover:text-text-main transition-colors">
|
|
120
|
+
{children}
|
|
121
|
+
</div>
|
|
122
|
+
</li>
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const ICON_MAP: Record<string, (cls?: string) => ReactNode> = {
|
|
127
|
+
checked: (cls) => (
|
|
128
|
+
<Check size={14} className={cn('text-emerald-500 shrink-0', cls)} />
|
|
129
|
+
),
|
|
130
|
+
arrow: (cls) => (
|
|
131
|
+
<ChevronRight size={14} className={cn('text-primary-400 shrink-0', cls)} />
|
|
132
|
+
),
|
|
133
|
+
bubble: (cls) => (
|
|
134
|
+
<Circle
|
|
135
|
+
size={6}
|
|
136
|
+
fill="currentColor"
|
|
137
|
+
className={cn('text-primary-500 shrink-0', cls)}
|
|
138
|
+
/>
|
|
139
|
+
),
|
|
140
|
+
default: () => null,
|
|
141
|
+
number: () => null,
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function List({
|
|
145
|
+
variant = 'default',
|
|
146
|
+
cols = 1,
|
|
147
|
+
dense = false,
|
|
148
|
+
children,
|
|
149
|
+
className,
|
|
150
|
+
...props
|
|
151
|
+
}: ListProps) {
|
|
152
|
+
const isGrid = cols !== undefined && Number(cols) > 1
|
|
153
|
+
const renderIcon = ICON_MAP[variant]
|
|
154
|
+
const containerClasses = listVariants({
|
|
155
|
+
variant,
|
|
156
|
+
cols,
|
|
157
|
+
dense,
|
|
158
|
+
isGrid,
|
|
159
|
+
className,
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
const Component = variant === 'number' ? 'ol' : 'ul'
|
|
163
|
+
|
|
164
|
+
// Handling raw MDX siblings (nested logic)
|
|
165
|
+
if (variant === 'default' || variant === 'number') {
|
|
166
|
+
return (
|
|
167
|
+
<Component className={containerClasses} {...props}>
|
|
168
|
+
{children}
|
|
169
|
+
</Component>
|
|
170
|
+
)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return (
|
|
174
|
+
<ul className={containerClasses} {...props}>
|
|
175
|
+
{Children.map(children, (child) => {
|
|
176
|
+
if (!isValidElement(child)) return child
|
|
177
|
+
|
|
178
|
+
const element = child as ReactElement<{ children?: ReactNode }>
|
|
179
|
+
const content =
|
|
180
|
+
element.type === 'li'
|
|
181
|
+
? element.props.children
|
|
182
|
+
: element.props.children || child
|
|
183
|
+
|
|
184
|
+
return (
|
|
185
|
+
<ListItem icon={renderIcon()} variant={variant} dense={dense}>
|
|
186
|
+
{content}
|
|
187
|
+
</ListItem>
|
|
188
|
+
)
|
|
189
|
+
})}
|
|
190
|
+
</ul>
|
|
191
|
+
)
|
|
192
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import * as RAC from 'react-aria-components'
|
|
2
|
+
import { useTable } from './hooks/useTable'
|
|
3
|
+
import {
|
|
4
|
+
ChevronUp,
|
|
5
|
+
ChevronDown,
|
|
6
|
+
ChevronLeft,
|
|
7
|
+
ChevronRight,
|
|
8
|
+
ChevronsLeft,
|
|
9
|
+
ChevronsRight,
|
|
10
|
+
} from 'lucide-react'
|
|
11
|
+
import { cn } from '@client/utils/cn'
|
|
12
|
+
|
|
13
|
+
export interface TableProps {
|
|
14
|
+
headers?: string[]
|
|
15
|
+
data?: (string | React.ReactNode)[][]
|
|
16
|
+
children?: React.ReactNode
|
|
17
|
+
className?: string
|
|
18
|
+
sortable?: boolean
|
|
19
|
+
paginated?: boolean
|
|
20
|
+
pageSize?: number
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function Table({
|
|
24
|
+
headers,
|
|
25
|
+
data,
|
|
26
|
+
children,
|
|
27
|
+
className = '',
|
|
28
|
+
sortable = false,
|
|
29
|
+
paginated = false,
|
|
30
|
+
pageSize = 10,
|
|
31
|
+
}: TableProps) {
|
|
32
|
+
const {
|
|
33
|
+
sortConfig,
|
|
34
|
+
currentPage,
|
|
35
|
+
setCurrentPage,
|
|
36
|
+
totalPages,
|
|
37
|
+
paginatedData,
|
|
38
|
+
requestSort,
|
|
39
|
+
} = useTable({
|
|
40
|
+
data,
|
|
41
|
+
sortable,
|
|
42
|
+
paginated,
|
|
43
|
+
pageSize,
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
const renderSortIcon = (index: number) => {
|
|
47
|
+
if (!sortable) return null
|
|
48
|
+
if (sortConfig?.key !== index)
|
|
49
|
+
return <ChevronDown size={14} className="ml-1 opacity-30" />
|
|
50
|
+
return sortConfig.direction === 'asc' ? (
|
|
51
|
+
<ChevronUp size={14} className="ml-1 text-primary-400" />
|
|
52
|
+
) : (
|
|
53
|
+
<ChevronDown size={14} className="ml-1 text-primary-400" />
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const tableContent = children ? (
|
|
58
|
+
children
|
|
59
|
+
) : (
|
|
60
|
+
<>
|
|
61
|
+
{headers && (
|
|
62
|
+
<thead>
|
|
63
|
+
<tr>
|
|
64
|
+
{headers.map((header, i) => (
|
|
65
|
+
<th
|
|
66
|
+
key={i}
|
|
67
|
+
onClick={() => requestSort(i)}
|
|
68
|
+
className={cn(
|
|
69
|
+
'text-left px-3 py-2.5 border-b-2 border-border-subtle text-text-main font-semibold text-sm',
|
|
70
|
+
sortable &&
|
|
71
|
+
'cursor-pointer select-none hover:text-primary-400 transition-colors',
|
|
72
|
+
)}
|
|
73
|
+
>
|
|
74
|
+
<div className="flex items-center">
|
|
75
|
+
{header}
|
|
76
|
+
{renderSortIcon(i)}
|
|
77
|
+
</div>
|
|
78
|
+
</th>
|
|
79
|
+
))}
|
|
80
|
+
</tr>
|
|
81
|
+
</thead>
|
|
82
|
+
)}
|
|
83
|
+
{paginatedData && (
|
|
84
|
+
<tbody>
|
|
85
|
+
{paginatedData.map((row, i) => (
|
|
86
|
+
<tr key={i} className="transition-colors hover:bg-bg-surface">
|
|
87
|
+
{row.map((cell, j) => (
|
|
88
|
+
<td
|
|
89
|
+
key={j}
|
|
90
|
+
className="px-3 py-2 border-b border-border-subtle text-sm text-text-muted"
|
|
91
|
+
>
|
|
92
|
+
{cell}
|
|
93
|
+
</td>
|
|
94
|
+
))}
|
|
95
|
+
</tr>
|
|
96
|
+
))}
|
|
97
|
+
</tbody>
|
|
98
|
+
)}
|
|
99
|
+
</>
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<div
|
|
104
|
+
className={cn(
|
|
105
|
+
'my-6 rounded-lg border border-border-subtle overflow-hidden',
|
|
106
|
+
className,
|
|
107
|
+
)}
|
|
108
|
+
>
|
|
109
|
+
<div className="overflow-x-auto">
|
|
110
|
+
<table className="w-full border-collapse text-sm">{tableContent}</table>
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
{paginated && totalPages > 1 && (
|
|
114
|
+
<div className="flex items-center justify-between border-t border-border-subtle px-4 py-3">
|
|
115
|
+
<span className="text-xs text-text-muted">
|
|
116
|
+
Page {currentPage} of {totalPages}
|
|
117
|
+
</span>
|
|
118
|
+
<div className="flex items-center gap-1">
|
|
119
|
+
<RAC.Button
|
|
120
|
+
onPress={() => setCurrentPage(1)}
|
|
121
|
+
isDisabled={currentPage === 1}
|
|
122
|
+
className="grid place-items-center h-7 w-7 rounded-md text-text-muted outline-none transition-colors hover:bg-bg-surface disabled:opacity-30 disabled:pointer-events-none cursor-pointer"
|
|
123
|
+
>
|
|
124
|
+
<ChevronsLeft size={16} />
|
|
125
|
+
</RAC.Button>
|
|
126
|
+
<RAC.Button
|
|
127
|
+
onPress={() =>
|
|
128
|
+
setCurrentPage((prev: number) => Math.max(prev - 1, 1))
|
|
129
|
+
}
|
|
130
|
+
isDisabled={currentPage === 1}
|
|
131
|
+
className="grid place-items-center h-7 w-7 rounded-md text-text-muted outline-none transition-colors hover:bg-bg-surface disabled:opacity-30 disabled:pointer-events-none cursor-pointer"
|
|
132
|
+
>
|
|
133
|
+
<ChevronLeft size={16} />
|
|
134
|
+
</RAC.Button>
|
|
135
|
+
<RAC.Button
|
|
136
|
+
onPress={() =>
|
|
137
|
+
setCurrentPage((prev: number) => Math.min(prev + 1, totalPages))
|
|
138
|
+
}
|
|
139
|
+
isDisabled={currentPage === totalPages}
|
|
140
|
+
className="grid place-items-center h-7 w-7 rounded-md text-text-muted outline-none transition-colors hover:bg-bg-surface disabled:opacity-30 disabled:pointer-events-none cursor-pointer"
|
|
141
|
+
>
|
|
142
|
+
<ChevronRight size={16} />
|
|
143
|
+
</RAC.Button>
|
|
144
|
+
<RAC.Button
|
|
145
|
+
onPress={() => setCurrentPage(totalPages)}
|
|
146
|
+
isDisabled={currentPage === totalPages}
|
|
147
|
+
className="grid place-items-center h-7 w-7 rounded-md text-text-muted outline-none transition-colors hover:bg-bg-surface disabled:opacity-30 disabled:pointer-events-none cursor-pointer"
|
|
148
|
+
>
|
|
149
|
+
<ChevronsRight size={16} />
|
|
150
|
+
</RAC.Button>
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
)}
|
|
154
|
+
</div>
|
|
155
|
+
)
|
|
156
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { Children, isValidElement, useMemo } from 'react'
|
|
2
|
+
import * as RAC from 'react-aria-components'
|
|
3
|
+
import { useTabs } from './hooks/useTabs'
|
|
4
|
+
import { cn } from '@client/utils/cn'
|
|
5
|
+
import { CodeBlock } from './code-block'
|
|
6
|
+
import { cva } from 'class-variance-authority'
|
|
7
|
+
|
|
8
|
+
const tabListVariants = cva(
|
|
9
|
+
'relative flex items-center border-b border-border-subtle gap-1 overflow-x-auto no-scrollbar',
|
|
10
|
+
{
|
|
11
|
+
variants: {
|
|
12
|
+
size: {
|
|
13
|
+
default: 'px-0',
|
|
14
|
+
compact: 'px-2',
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
defaultVariants: {
|
|
18
|
+
size: 'default',
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
const tabItemVariants = cva(
|
|
24
|
+
'flex items-center gap-2 px-4 py-2.5 text-sm font-medium outline-none transition-all duration-200 cursor-pointer bg-transparent border-none select-none whitespace-nowrap',
|
|
25
|
+
{
|
|
26
|
+
variants: {
|
|
27
|
+
isActive: {
|
|
28
|
+
true: 'text-primary-500',
|
|
29
|
+
false: 'text-text-muted hover:text-text-main',
|
|
30
|
+
},
|
|
31
|
+
isDisabled: {
|
|
32
|
+
true: 'opacity-40 pointer-events-none',
|
|
33
|
+
false: '',
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
defaultVariants: {
|
|
37
|
+
isActive: false,
|
|
38
|
+
isDisabled: false,
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
export interface TabProps {
|
|
44
|
+
label: string
|
|
45
|
+
icon?: React.ReactNode
|
|
46
|
+
disabled?: boolean
|
|
47
|
+
children: React.ReactNode
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function Tab({ children }: TabProps) {
|
|
51
|
+
const content =
|
|
52
|
+
typeof children === 'string' ? (
|
|
53
|
+
<CodeBlock className="language-bash">
|
|
54
|
+
<code>{children.trim()}</code>
|
|
55
|
+
</CodeBlock>
|
|
56
|
+
) : (
|
|
57
|
+
children
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
return <div className="py-4">{content}</div>
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface TabsProps {
|
|
64
|
+
defaultIndex?: number
|
|
65
|
+
children: React.ReactNode
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function Tabs({ defaultIndex = 0, children }: TabsProps) {
|
|
69
|
+
const tabs = useMemo(() => {
|
|
70
|
+
return Children.toArray(children).filter(
|
|
71
|
+
(child) =>
|
|
72
|
+
isValidElement(child) &&
|
|
73
|
+
(child as React.ReactElement<TabProps>).props?.label,
|
|
74
|
+
) as React.ReactElement<TabProps>[]
|
|
75
|
+
}, [children])
|
|
76
|
+
|
|
77
|
+
const { active, setActive, tabRefs, indicatorStyle } = useTabs({
|
|
78
|
+
initialIndex: defaultIndex,
|
|
79
|
+
tabs,
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<div className="my-8 w-full group/tabs">
|
|
84
|
+
<RAC.Tabs
|
|
85
|
+
selectedKey={active.toString()}
|
|
86
|
+
onSelectionChange={(key) => setActive(Number(key))}
|
|
87
|
+
className="w-full"
|
|
88
|
+
>
|
|
89
|
+
<RAC.TabList
|
|
90
|
+
aria-label="Content Tabs"
|
|
91
|
+
className={cn(tabListVariants())}
|
|
92
|
+
>
|
|
93
|
+
{tabs.map((child, i) => {
|
|
94
|
+
const { label, icon, disabled } = child.props
|
|
95
|
+
const key = i.toString()
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<RAC.Tab
|
|
99
|
+
key={key}
|
|
100
|
+
id={key}
|
|
101
|
+
isDisabled={disabled}
|
|
102
|
+
ref={(el: any) => {
|
|
103
|
+
tabRefs.current[i] = el
|
|
104
|
+
}}
|
|
105
|
+
className={({ isSelected, isDisabled }) =>
|
|
106
|
+
cn(tabItemVariants({ isActive: isSelected, isDisabled }))
|
|
107
|
+
}
|
|
108
|
+
>
|
|
109
|
+
{!!icon && (
|
|
110
|
+
<span className="shrink-0 [&>svg]:w-4 [&>svg]:h-4">
|
|
111
|
+
{icon}
|
|
112
|
+
</span>
|
|
113
|
+
)}
|
|
114
|
+
<span>{label}</span>
|
|
115
|
+
</RAC.Tab>
|
|
116
|
+
)
|
|
117
|
+
})}
|
|
118
|
+
|
|
119
|
+
<div
|
|
120
|
+
className="absolute bottom-0 h-0.5 bg-primary-500 transition-all duration-300 ease-in-out pointer-events-none"
|
|
121
|
+
style={indicatorStyle}
|
|
122
|
+
aria-hidden="true"
|
|
123
|
+
/>
|
|
124
|
+
</RAC.TabList>
|
|
125
|
+
|
|
126
|
+
{tabs.map((_tab, i) => (
|
|
127
|
+
<RAC.TabPanel key={i} id={i.toString()}>
|
|
128
|
+
{/* biome-ignore lint/suspicious/noExplicitAny: bypass version-specific ReactNode mismatch */}
|
|
129
|
+
{tabs[i] as any}
|
|
130
|
+
</RAC.TabPanel>
|
|
131
|
+
))}
|
|
132
|
+
</RAC.Tabs>
|
|
133
|
+
</div>
|
|
134
|
+
)
|
|
135
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { useRef, useState, useEffect } from 'react'
|
|
2
|
+
|
|
3
|
+
interface VideoProps {
|
|
4
|
+
src?: string
|
|
5
|
+
poster?: string
|
|
6
|
+
alt?: string
|
|
7
|
+
controls?: boolean
|
|
8
|
+
preload?: string
|
|
9
|
+
children?: React.ReactNode
|
|
10
|
+
[key: string]: any
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function Video({
|
|
14
|
+
src,
|
|
15
|
+
poster,
|
|
16
|
+
alt,
|
|
17
|
+
children,
|
|
18
|
+
controls,
|
|
19
|
+
preload = 'metadata',
|
|
20
|
+
...rest
|
|
21
|
+
}: VideoProps) {
|
|
22
|
+
const containerRef = useRef<HTMLDivElement>(null)
|
|
23
|
+
const [isVisible, setIsVisible] = useState(false)
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
const el = containerRef.current
|
|
27
|
+
if (!el) return
|
|
28
|
+
const observer = new IntersectionObserver(
|
|
29
|
+
([entry]) => {
|
|
30
|
+
if (entry.isIntersecting) {
|
|
31
|
+
setIsVisible(true)
|
|
32
|
+
observer.disconnect()
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
{ rootMargin: '200px' },
|
|
36
|
+
)
|
|
37
|
+
observer.observe(el)
|
|
38
|
+
return () => observer.disconnect()
|
|
39
|
+
}, [])
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<div
|
|
43
|
+
ref={containerRef}
|
|
44
|
+
className="my-6 overflow-hidden rounded-lg border border-border-subtle"
|
|
45
|
+
>
|
|
46
|
+
{isVisible ? (
|
|
47
|
+
<video
|
|
48
|
+
className="block w-full h-auto"
|
|
49
|
+
src={src}
|
|
50
|
+
poster={poster}
|
|
51
|
+
controls={true}
|
|
52
|
+
preload={preload}
|
|
53
|
+
playsInline
|
|
54
|
+
{...rest}
|
|
55
|
+
>
|
|
56
|
+
{children}
|
|
57
|
+
Your browser does not support the video tag.
|
|
58
|
+
</video>
|
|
59
|
+
) : (
|
|
60
|
+
<div
|
|
61
|
+
className="aspect-video bg-bg-surface animate-pulse"
|
|
62
|
+
role="img"
|
|
63
|
+
aria-label={alt || 'Video'}
|
|
64
|
+
/>
|
|
65
|
+
)}
|
|
66
|
+
</div>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Breadcrumb,
|
|
3
|
+
Breadcrumbs as BreadcrumbsRAC,
|
|
4
|
+
Link,
|
|
5
|
+
} from 'react-aria-components'
|
|
6
|
+
import type { LinkProps } from 'react-aria-components'
|
|
7
|
+
import { ChevronRight } from 'lucide-react'
|
|
8
|
+
import { cn } from '../../utils/cn'
|
|
9
|
+
import type { ComponentBase } from './types'
|
|
10
|
+
|
|
11
|
+
export const BreadcrumbsRoot = ({
|
|
12
|
+
children,
|
|
13
|
+
className,
|
|
14
|
+
...props
|
|
15
|
+
}: ComponentBase) => {
|
|
16
|
+
return (
|
|
17
|
+
<BreadcrumbsRAC
|
|
18
|
+
className={cn(
|
|
19
|
+
'flex items-center gap-1.5 mb-0 text-sm text-text-muted',
|
|
20
|
+
className,
|
|
21
|
+
)}
|
|
22
|
+
{...props}
|
|
23
|
+
>
|
|
24
|
+
{children as any}
|
|
25
|
+
</BreadcrumbsRAC>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const BreadcrumbsItem = ({
|
|
30
|
+
children,
|
|
31
|
+
className,
|
|
32
|
+
...props
|
|
33
|
+
}: ComponentBase) => {
|
|
34
|
+
return (
|
|
35
|
+
<Breadcrumb
|
|
36
|
+
className={cn('flex items-center mb-0 gap-1.5', className)}
|
|
37
|
+
{...props}
|
|
38
|
+
>
|
|
39
|
+
{children as any}
|
|
40
|
+
</Breadcrumb>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const BreadcrumbsLink = ({
|
|
45
|
+
children,
|
|
46
|
+
href,
|
|
47
|
+
className,
|
|
48
|
+
...props
|
|
49
|
+
}: LinkProps & { className?: string }) => {
|
|
50
|
+
return (
|
|
51
|
+
<Link
|
|
52
|
+
href={href}
|
|
53
|
+
className={cn(
|
|
54
|
+
'transition-colors outline-none hover:text-text-main focus-visible:ring-2 focus-visible:ring-primary-500/30 rounded-sm',
|
|
55
|
+
'current:font-medium current:text-text-main current:pointer-events-none cursor-pointer',
|
|
56
|
+
className,
|
|
57
|
+
)}
|
|
58
|
+
{...props}
|
|
59
|
+
>
|
|
60
|
+
{children as any}
|
|
61
|
+
</Link>
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const BreadcrumbsSeparator = ({ className }: ComponentBase) => {
|
|
66
|
+
return (
|
|
67
|
+
<ChevronRight
|
|
68
|
+
size={14}
|
|
69
|
+
className={cn('shrink-0 text-text-dim', className)}
|
|
70
|
+
/>
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export default {
|
|
75
|
+
BreadcrumbsRoot,
|
|
76
|
+
BreadcrumbsItem,
|
|
77
|
+
BreadcrumbsLink,
|
|
78
|
+
BreadcrumbsSeparator,
|
|
79
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { cn } from '@client/utils/cn'
|
|
2
|
+
import type { ComponentBase } from './types'
|
|
3
|
+
|
|
4
|
+
export interface ButtonGroupProps extends ComponentBase {
|
|
5
|
+
vertical?: boolean
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const ButtonGroup = ({
|
|
9
|
+
children,
|
|
10
|
+
className,
|
|
11
|
+
vertical = false,
|
|
12
|
+
}: ButtonGroupProps) => {
|
|
13
|
+
return (
|
|
14
|
+
<div
|
|
15
|
+
className={cn(
|
|
16
|
+
'inline-flex',
|
|
17
|
+
vertical ? 'flex-col' : 'flex-row',
|
|
18
|
+
// Handle nested button borders and radii
|
|
19
|
+
!vertical && [
|
|
20
|
+
'[&>*:not(:first-child)]:-ml-px',
|
|
21
|
+
'[&>*:first-child]:rounded-r-none',
|
|
22
|
+
'[&>*:last-child]:rounded-l-none',
|
|
23
|
+
'[&>*:not(:first-child):not(:last-child)]:rounded-none',
|
|
24
|
+
// Extra polish for outer corners
|
|
25
|
+
className?.includes('rounded-full') && [
|
|
26
|
+
'[&>*:first-child]:rounded-l-full',
|
|
27
|
+
'[&>*:last-child]:rounded-r-full',
|
|
28
|
+
],
|
|
29
|
+
className?.includes('rounded-xl') && [
|
|
30
|
+
'[&>*:first-child]:rounded-l-xl',
|
|
31
|
+
'[&>*:last-child]:rounded-r-xl',
|
|
32
|
+
],
|
|
33
|
+
className?.includes('rounded-lg') && [
|
|
34
|
+
'[&>*:first-child]:rounded-l-lg',
|
|
35
|
+
'[&>*:last-child]:rounded-r-lg',
|
|
36
|
+
],
|
|
37
|
+
],
|
|
38
|
+
vertical && [
|
|
39
|
+
'[&>*:not(:first-child)]:-mt-px',
|
|
40
|
+
'[&>*:first-child]:rounded-b-none',
|
|
41
|
+
'[&>*:last-child]:rounded-t-none',
|
|
42
|
+
'[&>*:not(:first-child):not(:last-child)]:rounded-none',
|
|
43
|
+
className?.includes('rounded-full') && [
|
|
44
|
+
'[&>*:first-child]:rounded-t-full',
|
|
45
|
+
'[&>*:last-child]:rounded-b-full',
|
|
46
|
+
],
|
|
47
|
+
],
|
|
48
|
+
className,
|
|
49
|
+
)}
|
|
50
|
+
>
|
|
51
|
+
{children}
|
|
52
|
+
</div>
|
|
53
|
+
)
|
|
54
|
+
}
|