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.
Files changed (225) hide show
  1. package/package.json +29 -7
  2. package/src/client/app/config-context.tsx +18 -0
  3. package/src/client/app/docs-layout.tsx +14 -0
  4. package/src/client/app/index.tsx +132 -260
  5. package/src/client/app/mdx-component.tsx +52 -0
  6. package/src/client/app/mdx-components-context.tsx +23 -0
  7. package/src/client/app/mdx-page.tsx +20 -0
  8. package/src/client/app/preload.tsx +38 -30
  9. package/src/client/app/router.tsx +30 -0
  10. package/src/client/app/scroll-handler.tsx +40 -0
  11. package/src/client/app/theme-context.tsx +75 -0
  12. package/src/client/components/default-layout.tsx +80 -0
  13. package/src/client/components/docs-layout.tsx +105 -0
  14. package/src/client/components/icons-dev.tsx +74 -0
  15. package/src/client/components/mdx/admonition.tsx +107 -0
  16. package/src/client/components/mdx/badge.tsx +41 -0
  17. package/src/client/components/mdx/button.tsx +35 -0
  18. package/src/client/components/mdx/card.tsx +124 -0
  19. package/src/client/components/mdx/code-block.tsx +119 -0
  20. package/src/client/components/mdx/component-preview.tsx +47 -0
  21. package/src/client/components/mdx/component-props.tsx +83 -0
  22. package/src/client/components/mdx/field.tsx +66 -0
  23. package/src/client/components/mdx/file-tree.tsx +287 -0
  24. package/src/client/components/mdx/hooks/use-code-block.ts +56 -0
  25. package/src/client/components/mdx/hooks/use-component-preview.ts +16 -0
  26. package/src/client/components/mdx/hooks/useTable.ts +74 -0
  27. package/src/client/components/mdx/hooks/useTabs.ts +68 -0
  28. package/src/client/components/mdx/image.tsx +23 -0
  29. package/src/client/components/mdx/index.ts +53 -0
  30. package/src/client/components/mdx/link.tsx +38 -0
  31. package/src/client/components/mdx/list.tsx +192 -0
  32. package/src/client/components/mdx/table.tsx +156 -0
  33. package/src/client/components/mdx/tabs.tsx +135 -0
  34. package/src/client/components/mdx/video.tsx +68 -0
  35. package/src/client/components/primitives/breadcrumbs.tsx +79 -0
  36. package/src/client/components/primitives/button-group.tsx +54 -0
  37. package/src/client/components/primitives/button.tsx +145 -0
  38. package/src/client/components/primitives/helpers/observer.ts +120 -0
  39. package/src/client/components/primitives/index.ts +17 -0
  40. package/src/client/components/primitives/link.tsx +122 -0
  41. package/src/client/components/primitives/menu.tsx +159 -0
  42. package/src/client/components/primitives/navbar.tsx +359 -0
  43. package/src/client/components/primitives/navigation-menu.tsx +116 -0
  44. package/src/client/components/primitives/on-this-page.tsx +461 -0
  45. package/src/client/components/primitives/page-nav.tsx +87 -0
  46. package/src/client/components/primitives/popover.tsx +47 -0
  47. package/src/client/components/primitives/search-dialog.tsx +183 -0
  48. package/src/client/components/primitives/sidebar.tsx +154 -0
  49. package/src/client/components/primitives/tabs.tsx +90 -0
  50. package/src/client/components/primitives/tooltip.tsx +83 -0
  51. package/src/client/components/primitives/types.ts +11 -0
  52. package/src/client/components/ui-base/breadcrumbs.tsx +42 -0
  53. package/src/client/components/ui-base/copy-markdown.tsx +112 -0
  54. package/src/client/components/ui-base/error-boundary.tsx +52 -0
  55. package/src/client/components/ui-base/github-stars.tsx +27 -0
  56. package/src/client/components/ui-base/head.tsx +69 -0
  57. package/src/client/components/ui-base/loading.tsx +87 -0
  58. package/src/client/components/ui-base/navbar.tsx +138 -0
  59. package/src/client/components/ui-base/not-found.tsx +24 -0
  60. package/src/client/components/ui-base/on-this-page.tsx +152 -0
  61. package/src/client/components/ui-base/page-nav.tsx +39 -0
  62. package/src/client/components/ui-base/powered-by.tsx +19 -0
  63. package/src/client/components/ui-base/progress-bar.tsx +67 -0
  64. package/src/client/components/ui-base/search-dialog.tsx +82 -0
  65. package/src/client/components/ui-base/sidebar.tsx +104 -0
  66. package/src/client/components/ui-base/tabs.tsx +65 -0
  67. package/src/client/components/ui-base/theme-toggle.tsx +32 -0
  68. package/src/client/hooks/index.ts +12 -0
  69. package/src/client/hooks/use-breadcrumbs.ts +22 -0
  70. package/src/client/hooks/use-i18n.ts +84 -0
  71. package/src/client/hooks/use-localized-to.ts +95 -0
  72. package/src/client/hooks/use-location.ts +5 -0
  73. package/src/client/hooks/use-navbar.ts +60 -0
  74. package/src/client/hooks/use-onthispage.ts +23 -0
  75. package/src/client/hooks/use-page-nav.ts +22 -0
  76. package/src/client/hooks/use-routes.ts +72 -0
  77. package/src/client/hooks/use-search.ts +71 -0
  78. package/src/client/hooks/use-sidebar.ts +49 -0
  79. package/src/client/hooks/use-tabs.ts +43 -0
  80. package/src/client/hooks/use-version.ts +78 -0
  81. package/src/client/index.ts +55 -17
  82. package/src/client/integrations/codesandbox.ts +179 -0
  83. package/src/client/ssr.tsx +27 -16
  84. package/src/client/theme/neutral.css +360 -0
  85. package/src/client/types.ts +131 -27
  86. package/src/client/utils/cn.ts +6 -0
  87. package/src/client/utils/copy-clipboard.ts +22 -0
  88. package/src/client/utils/get-base-file-path.ts +21 -0
  89. package/src/client/utils/github.ts +121 -0
  90. package/src/client/utils/use-on-change.ts +15 -0
  91. package/src/client/virtual.d.ts +24 -0
  92. package/src/node/cache.ts +156 -156
  93. package/src/node/config.ts +159 -103
  94. package/src/node/index.ts +13 -13
  95. package/src/node/mdx.ts +213 -61
  96. package/src/node/plugin/entry.ts +29 -18
  97. package/src/node/plugin/html.ts +11 -11
  98. package/src/node/plugin/index.ts +161 -83
  99. package/src/node/plugin/types.ts +2 -4
  100. package/src/node/routes/cache.ts +6 -6
  101. package/src/node/routes/index.ts +206 -113
  102. package/src/node/routes/parser.ts +106 -81
  103. package/src/node/routes/sorter.ts +15 -15
  104. package/src/node/routes/types.ts +24 -24
  105. package/src/node/ssg/index.ts +46 -46
  106. package/src/node/ssg/meta.ts +4 -4
  107. package/src/node/ssg/options.ts +5 -5
  108. package/src/node/ssg/sitemap.ts +14 -14
  109. package/src/node/utils.ts +31 -31
  110. package/tsconfig.json +25 -20
  111. package/tsup.config.ts +23 -14
  112. package/dist/PackageManagerTabs-NVT7G625.mjs +0 -99
  113. package/dist/SearchDialog-AGVF6JBO.mjs +0 -194
  114. package/dist/SearchDialog-YPDOM7Q6.css +0 -2847
  115. package/dist/Video-KNTY5BNO.mjs +0 -6
  116. package/dist/cache-KNL5B4EE.mjs +0 -12
  117. package/dist/chunk-7SFUJWTB.mjs +0 -211
  118. package/dist/chunk-FFBNU6IJ.mjs +0 -386
  119. package/dist/chunk-FMTOYQLO.mjs +0 -37
  120. package/dist/chunk-TKLQWU7H.mjs +0 -1920
  121. package/dist/chunk-Z7JHYNAS.mjs +0 -57
  122. package/dist/client/index.css +0 -2847
  123. package/dist/client/index.d.mts +0 -372
  124. package/dist/client/index.d.ts +0 -372
  125. package/dist/client/index.js +0 -3630
  126. package/dist/client/index.mjs +0 -697
  127. package/dist/client/ssr.css +0 -2847
  128. package/dist/client/ssr.d.mts +0 -27
  129. package/dist/client/ssr.d.ts +0 -27
  130. package/dist/client/ssr.js +0 -2928
  131. package/dist/client/ssr.mjs +0 -33
  132. package/dist/config-BsFQ-ErD.d.mts +0 -159
  133. package/dist/config-BsFQ-ErD.d.ts +0 -159
  134. package/dist/node/index.d.mts +0 -91
  135. package/dist/node/index.d.ts +0 -91
  136. package/dist/node/index.js +0 -1187
  137. package/dist/node/index.mjs +0 -762
  138. package/dist/types-Dj-bfnC3.d.mts +0 -74
  139. package/dist/types-Dj-bfnC3.d.ts +0 -74
  140. package/src/client/theme/components/CodeBlock/CodeBlock.tsx +0 -61
  141. package/src/client/theme/components/CodeBlock/index.ts +0 -1
  142. package/src/client/theme/components/PackageManagerTabs/PackageManagerTabs.tsx +0 -131
  143. package/src/client/theme/components/PackageManagerTabs/index.ts +0 -1
  144. package/src/client/theme/components/PackageManagerTabs/pkg-tabs.css +0 -64
  145. package/src/client/theme/components/Playground/Playground.tsx +0 -180
  146. package/src/client/theme/components/Playground/index.ts +0 -1
  147. package/src/client/theme/components/Playground/playground.css +0 -238
  148. package/src/client/theme/components/Video/Video.tsx +0 -84
  149. package/src/client/theme/components/Video/index.ts +0 -1
  150. package/src/client/theme/components/Video/video.css +0 -41
  151. package/src/client/theme/components/mdx/Admonition.tsx +0 -80
  152. package/src/client/theme/components/mdx/Badge.tsx +0 -31
  153. package/src/client/theme/components/mdx/Button.tsx +0 -50
  154. package/src/client/theme/components/mdx/Card.tsx +0 -80
  155. package/src/client/theme/components/mdx/Field.tsx +0 -60
  156. package/src/client/theme/components/mdx/FileTree.tsx +0 -229
  157. package/src/client/theme/components/mdx/List.tsx +0 -57
  158. package/src/client/theme/components/mdx/Table.tsx +0 -151
  159. package/src/client/theme/components/mdx/Tabs.tsx +0 -123
  160. package/src/client/theme/components/mdx/index.ts +0 -27
  161. package/src/client/theme/components/mdx/mdx-components.css +0 -764
  162. package/src/client/theme/icons/bun.tsx +0 -62
  163. package/src/client/theme/icons/deno.tsx +0 -20
  164. package/src/client/theme/icons/discord.tsx +0 -12
  165. package/src/client/theme/icons/github.tsx +0 -15
  166. package/src/client/theme/icons/npm.tsx +0 -13
  167. package/src/client/theme/icons/pnpm.tsx +0 -72
  168. package/src/client/theme/icons/twitter.tsx +0 -12
  169. package/src/client/theme/styles/markdown.css +0 -394
  170. package/src/client/theme/styles/variables.css +0 -175
  171. package/src/client/theme/styles.css +0 -39
  172. package/src/client/theme/ui/Breadcrumbs/Breadcrumbs.tsx +0 -68
  173. package/src/client/theme/ui/Breadcrumbs/index.ts +0 -1
  174. package/src/client/theme/ui/CopyMarkdown/CopyMarkdown.tsx +0 -82
  175. package/src/client/theme/ui/CopyMarkdown/copy-markdown.css +0 -112
  176. package/src/client/theme/ui/CopyMarkdown/index.ts +0 -1
  177. package/src/client/theme/ui/ErrorBoundary/ErrorBoundary.tsx +0 -50
  178. package/src/client/theme/ui/ErrorBoundary/error-boundary.css +0 -55
  179. package/src/client/theme/ui/ErrorBoundary/index.ts +0 -1
  180. package/src/client/theme/ui/Footer/footer.css +0 -32
  181. package/src/client/theme/ui/Head/Head.tsx +0 -69
  182. package/src/client/theme/ui/Head/index.ts +0 -1
  183. package/src/client/theme/ui/LanguageSwitcher/LanguageSwitcher.tsx +0 -125
  184. package/src/client/theme/ui/LanguageSwitcher/index.ts +0 -1
  185. package/src/client/theme/ui/LanguageSwitcher/language-switcher.css +0 -98
  186. package/src/client/theme/ui/Layout/Layout.tsx +0 -203
  187. package/src/client/theme/ui/Layout/base.css +0 -106
  188. package/src/client/theme/ui/Layout/index.ts +0 -2
  189. package/src/client/theme/ui/Layout/pagination.css +0 -72
  190. package/src/client/theme/ui/Layout/responsive.css +0 -47
  191. package/src/client/theme/ui/Link/Link.tsx +0 -392
  192. package/src/client/theme/ui/Link/LinkPreview.tsx +0 -59
  193. package/src/client/theme/ui/Link/index.ts +0 -2
  194. package/src/client/theme/ui/Link/link-preview.css +0 -48
  195. package/src/client/theme/ui/Loading/Loading.tsx +0 -10
  196. package/src/client/theme/ui/Loading/index.ts +0 -1
  197. package/src/client/theme/ui/Loading/loading.css +0 -30
  198. package/src/client/theme/ui/Navbar/GithubStars.tsx +0 -27
  199. package/src/client/theme/ui/Navbar/Navbar.tsx +0 -193
  200. package/src/client/theme/ui/Navbar/Tabs.tsx +0 -99
  201. package/src/client/theme/ui/Navbar/index.ts +0 -2
  202. package/src/client/theme/ui/Navbar/navbar.css +0 -347
  203. package/src/client/theme/ui/NotFound/NotFound.tsx +0 -19
  204. package/src/client/theme/ui/NotFound/index.ts +0 -1
  205. package/src/client/theme/ui/NotFound/not-found.css +0 -64
  206. package/src/client/theme/ui/OnThisPage/OnThisPage.tsx +0 -244
  207. package/src/client/theme/ui/OnThisPage/index.ts +0 -1
  208. package/src/client/theme/ui/OnThisPage/toc.css +0 -152
  209. package/src/client/theme/ui/PoweredBy/PoweredBy.tsx +0 -18
  210. package/src/client/theme/ui/PoweredBy/index.ts +0 -1
  211. package/src/client/theme/ui/PoweredBy/powered-by.css +0 -76
  212. package/src/client/theme/ui/ProgressBar/ProgressBar.css +0 -17
  213. package/src/client/theme/ui/ProgressBar/ProgressBar.tsx +0 -51
  214. package/src/client/theme/ui/ProgressBar/index.ts +0 -1
  215. package/src/client/theme/ui/SearchDialog/SearchDialog.tsx +0 -209
  216. package/src/client/theme/ui/SearchDialog/index.ts +0 -1
  217. package/src/client/theme/ui/SearchDialog/search.css +0 -152
  218. package/src/client/theme/ui/Sidebar/Sidebar.tsx +0 -244
  219. package/src/client/theme/ui/Sidebar/index.ts +0 -1
  220. package/src/client/theme/ui/Sidebar/sidebar.css +0 -230
  221. package/src/client/theme/ui/ThemeToggle/ThemeToggle.tsx +0 -69
  222. package/src/client/theme/ui/ThemeToggle/index.ts +0 -1
  223. package/src/client/theme/ui/VersionSwitcher/VersionSwitcher.tsx +0 -136
  224. package/src/client/theme/ui/VersionSwitcher/index.ts +0 -1
  225. 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
+ }