boltdocs 1.10.2 → 2.0.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 (250) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/LICENSE +21 -0
  3. package/dist/cache-7G6D532T.mjs +1 -0
  4. package/dist/chunk-A4HQPEPU.mjs +1 -0
  5. package/dist/chunk-BA5NH5HU.mjs +1 -0
  6. package/dist/chunk-BQCD3DWG.mjs +1 -0
  7. package/dist/chunk-H63UMKYF.mjs +1 -0
  8. package/dist/chunk-IWHRQHS7.mjs +1 -0
  9. package/dist/chunk-JZXLCA2E.mjs +1 -0
  10. package/dist/chunk-MFU7Q6WF.mjs +1 -0
  11. package/dist/chunk-QYPNX5UN.mjs +1 -0
  12. package/dist/chunk-XEAPSFMB.mjs +1 -0
  13. package/dist/client/components/mdx/index.d.mts +209 -0
  14. package/dist/client/components/mdx/index.d.ts +209 -0
  15. package/dist/client/components/mdx/index.js +1 -0
  16. package/dist/client/components/mdx/index.mjs +1 -0
  17. package/dist/client/hooks/index.d.mts +133 -0
  18. package/dist/client/hooks/index.d.ts +133 -0
  19. package/dist/client/hooks/index.js +1 -0
  20. package/dist/client/hooks/index.mjs +1 -0
  21. package/dist/client/index.d.mts +138 -298
  22. package/dist/client/index.d.ts +138 -298
  23. package/dist/client/index.js +1 -3630
  24. package/dist/client/index.mjs +1 -697
  25. package/dist/client/ssr.d.mts +7 -3
  26. package/dist/client/ssr.d.ts +7 -3
  27. package/dist/client/ssr.js +1 -2928
  28. package/dist/client/ssr.mjs +1 -33
  29. package/dist/{config-BsFQ-ErD.d.ts → config-CX4l-ZNp.d.mts} +42 -35
  30. package/dist/{config-BsFQ-ErD.d.mts → config-CX4l-ZNp.d.ts} +42 -35
  31. package/dist/node/index.d.mts +2 -4
  32. package/dist/node/index.d.ts +2 -4
  33. package/dist/node/index.js +31 -1161
  34. package/dist/node/index.mjs +31 -736
  35. package/dist/search-dialog-EB3N4TYM.mjs +1 -0
  36. package/dist/types-BuZWFT7r.d.ts +159 -0
  37. package/dist/types-CvT-SGbK.d.mts +159 -0
  38. package/dist/use-routes-5bAtAAYX.d.mts +30 -0
  39. package/dist/use-routes-BefRXY3v.d.ts +30 -0
  40. package/package.json +34 -12
  41. package/src/client/app/config-context.tsx +18 -0
  42. package/src/client/app/docs-layout.tsx +14 -0
  43. package/src/client/app/index.tsx +137 -262
  44. package/src/client/app/mdx-component.tsx +52 -0
  45. package/src/client/app/mdx-components-context.tsx +23 -0
  46. package/src/client/app/mdx-page.tsx +20 -0
  47. package/src/client/app/preload.tsx +38 -30
  48. package/src/client/app/router.tsx +30 -0
  49. package/src/client/app/scroll-handler.tsx +40 -0
  50. package/src/client/app/theme-context.tsx +75 -0
  51. package/src/client/components/default-layout.tsx +80 -0
  52. package/src/client/components/docs-layout.tsx +105 -0
  53. package/src/client/components/icons-dev.tsx +74 -0
  54. package/src/client/components/mdx/admonition.tsx +107 -0
  55. package/src/client/components/mdx/badge.tsx +41 -0
  56. package/src/client/components/mdx/button.tsx +35 -0
  57. package/src/client/components/mdx/card.tsx +124 -0
  58. package/src/client/components/mdx/code-block.tsx +119 -0
  59. package/src/client/components/mdx/component-preview.tsx +47 -0
  60. package/src/client/components/mdx/component-props.tsx +83 -0
  61. package/src/client/components/mdx/field.tsx +66 -0
  62. package/src/client/components/mdx/file-tree.tsx +287 -0
  63. package/src/client/components/mdx/hooks/use-code-block.ts +56 -0
  64. package/src/client/components/mdx/hooks/use-component-preview.ts +16 -0
  65. package/src/client/components/mdx/hooks/useTable.ts +74 -0
  66. package/src/client/components/mdx/hooks/useTabs.ts +68 -0
  67. package/src/client/components/mdx/image.tsx +23 -0
  68. package/src/client/components/mdx/index.ts +53 -0
  69. package/src/client/components/mdx/link.tsx +38 -0
  70. package/src/client/components/mdx/list.tsx +192 -0
  71. package/src/client/components/mdx/table.tsx +156 -0
  72. package/src/client/components/mdx/tabs.tsx +135 -0
  73. package/src/client/components/mdx/video.tsx +68 -0
  74. package/src/client/components/primitives/breadcrumbs.tsx +79 -0
  75. package/src/client/components/primitives/button-group.tsx +54 -0
  76. package/src/client/components/primitives/button.tsx +145 -0
  77. package/src/client/components/primitives/helpers/observer.ts +120 -0
  78. package/src/client/components/primitives/index.ts +17 -0
  79. package/src/client/components/primitives/link.tsx +122 -0
  80. package/src/client/components/primitives/menu.tsx +159 -0
  81. package/src/client/components/primitives/navbar.tsx +359 -0
  82. package/src/client/components/primitives/navigation-menu.tsx +116 -0
  83. package/src/client/components/primitives/on-this-page.tsx +461 -0
  84. package/src/client/components/primitives/page-nav.tsx +87 -0
  85. package/src/client/components/primitives/popover.tsx +47 -0
  86. package/src/client/components/primitives/search-dialog.tsx +183 -0
  87. package/src/client/components/primitives/sidebar.tsx +154 -0
  88. package/src/client/components/primitives/tabs.tsx +90 -0
  89. package/src/client/components/primitives/tooltip.tsx +83 -0
  90. package/src/client/components/primitives/types.ts +11 -0
  91. package/src/client/components/ui-base/breadcrumbs.tsx +42 -0
  92. package/src/client/components/ui-base/copy-markdown.tsx +112 -0
  93. package/src/client/components/ui-base/error-boundary.tsx +52 -0
  94. package/src/client/components/ui-base/github-stars.tsx +27 -0
  95. package/src/client/components/ui-base/head.tsx +69 -0
  96. package/src/client/components/ui-base/loading.tsx +87 -0
  97. package/src/client/components/ui-base/navbar.tsx +138 -0
  98. package/src/client/components/ui-base/not-found.tsx +24 -0
  99. package/src/client/components/ui-base/on-this-page.tsx +152 -0
  100. package/src/client/components/ui-base/page-nav.tsx +39 -0
  101. package/src/client/components/ui-base/powered-by.tsx +19 -0
  102. package/src/client/components/ui-base/progress-bar.tsx +67 -0
  103. package/src/client/components/ui-base/search-dialog.tsx +82 -0
  104. package/src/client/components/ui-base/sidebar.tsx +104 -0
  105. package/src/client/components/ui-base/tabs.tsx +65 -0
  106. package/src/client/components/ui-base/theme-toggle.tsx +32 -0
  107. package/src/client/hooks/index.ts +12 -0
  108. package/src/client/hooks/use-breadcrumbs.ts +22 -0
  109. package/src/client/hooks/use-i18n.ts +84 -0
  110. package/src/client/hooks/use-localized-to.ts +95 -0
  111. package/src/client/hooks/use-location.ts +5 -0
  112. package/src/client/hooks/use-navbar.ts +60 -0
  113. package/src/client/hooks/use-onthispage.ts +23 -0
  114. package/src/client/hooks/use-page-nav.ts +22 -0
  115. package/src/client/hooks/use-routes.ts +72 -0
  116. package/src/client/hooks/use-search.ts +71 -0
  117. package/src/client/hooks/use-sidebar.ts +49 -0
  118. package/src/client/hooks/use-tabs.ts +43 -0
  119. package/src/client/hooks/use-version.ts +78 -0
  120. package/src/client/index.ts +55 -17
  121. package/src/client/integrations/codesandbox.ts +179 -0
  122. package/src/client/ssr.tsx +27 -16
  123. package/src/client/theme/neutral.css +360 -0
  124. package/src/client/types.ts +131 -27
  125. package/src/client/utils/cn.ts +6 -0
  126. package/src/client/utils/copy-clipboard.ts +22 -0
  127. package/src/client/utils/get-base-file-path.ts +21 -0
  128. package/src/client/utils/github.ts +121 -0
  129. package/src/client/utils/use-on-change.ts +15 -0
  130. package/src/client/virtual.d.ts +24 -0
  131. package/src/node/cache.ts +156 -156
  132. package/src/node/config.ts +159 -103
  133. package/src/node/index.ts +13 -13
  134. package/src/node/mdx.ts +213 -61
  135. package/src/node/plugin/entry.ts +29 -18
  136. package/src/node/plugin/html.ts +11 -11
  137. package/src/node/plugin/index.ts +161 -84
  138. package/src/node/plugin/types.ts +2 -4
  139. package/src/node/routes/cache.ts +6 -6
  140. package/src/node/routes/index.ts +206 -113
  141. package/src/node/routes/parser.ts +102 -82
  142. package/src/node/routes/sorter.ts +15 -15
  143. package/src/node/routes/types.ts +24 -24
  144. package/src/node/ssg/index.ts +73 -47
  145. package/src/node/ssg/meta.ts +4 -4
  146. package/src/node/ssg/options.ts +5 -5
  147. package/src/node/ssg/sitemap.ts +14 -14
  148. package/src/node/utils.ts +54 -31
  149. package/tsconfig.json +25 -20
  150. package/tsup.config.ts +23 -14
  151. package/dist/PackageManagerTabs-NVT7G625.mjs +0 -99
  152. package/dist/SearchDialog-AGVF6JBO.mjs +0 -194
  153. package/dist/SearchDialog-YPDOM7Q6.css +0 -2847
  154. package/dist/Video-KNTY5BNO.mjs +0 -6
  155. package/dist/cache-KNL5B4EE.mjs +0 -12
  156. package/dist/chunk-7SFUJWTB.mjs +0 -211
  157. package/dist/chunk-FFBNU6IJ.mjs +0 -386
  158. package/dist/chunk-FMTOYQLO.mjs +0 -37
  159. package/dist/chunk-TKLQWU7H.mjs +0 -1920
  160. package/dist/chunk-Z7JHYNAS.mjs +0 -57
  161. package/dist/client/index.css +0 -2847
  162. package/dist/client/ssr.css +0 -2847
  163. package/dist/types-Dj-bfnC3.d.mts +0 -74
  164. package/dist/types-Dj-bfnC3.d.ts +0 -74
  165. package/src/client/theme/components/CodeBlock/CodeBlock.tsx +0 -61
  166. package/src/client/theme/components/CodeBlock/index.ts +0 -1
  167. package/src/client/theme/components/PackageManagerTabs/PackageManagerTabs.tsx +0 -131
  168. package/src/client/theme/components/PackageManagerTabs/index.ts +0 -1
  169. package/src/client/theme/components/PackageManagerTabs/pkg-tabs.css +0 -64
  170. package/src/client/theme/components/Playground/Playground.tsx +0 -180
  171. package/src/client/theme/components/Playground/index.ts +0 -1
  172. package/src/client/theme/components/Playground/playground.css +0 -238
  173. package/src/client/theme/components/Video/Video.tsx +0 -84
  174. package/src/client/theme/components/Video/index.ts +0 -1
  175. package/src/client/theme/components/Video/video.css +0 -41
  176. package/src/client/theme/components/mdx/Admonition.tsx +0 -80
  177. package/src/client/theme/components/mdx/Badge.tsx +0 -31
  178. package/src/client/theme/components/mdx/Button.tsx +0 -50
  179. package/src/client/theme/components/mdx/Card.tsx +0 -80
  180. package/src/client/theme/components/mdx/Field.tsx +0 -60
  181. package/src/client/theme/components/mdx/FileTree.tsx +0 -229
  182. package/src/client/theme/components/mdx/List.tsx +0 -57
  183. package/src/client/theme/components/mdx/Table.tsx +0 -151
  184. package/src/client/theme/components/mdx/Tabs.tsx +0 -123
  185. package/src/client/theme/components/mdx/index.ts +0 -27
  186. package/src/client/theme/components/mdx/mdx-components.css +0 -764
  187. package/src/client/theme/icons/bun.tsx +0 -62
  188. package/src/client/theme/icons/deno.tsx +0 -20
  189. package/src/client/theme/icons/discord.tsx +0 -12
  190. package/src/client/theme/icons/github.tsx +0 -15
  191. package/src/client/theme/icons/npm.tsx +0 -13
  192. package/src/client/theme/icons/pnpm.tsx +0 -72
  193. package/src/client/theme/icons/twitter.tsx +0 -12
  194. package/src/client/theme/styles/markdown.css +0 -394
  195. package/src/client/theme/styles/variables.css +0 -175
  196. package/src/client/theme/styles.css +0 -39
  197. package/src/client/theme/ui/Breadcrumbs/Breadcrumbs.tsx +0 -68
  198. package/src/client/theme/ui/Breadcrumbs/index.ts +0 -1
  199. package/src/client/theme/ui/CopyMarkdown/CopyMarkdown.tsx +0 -82
  200. package/src/client/theme/ui/CopyMarkdown/copy-markdown.css +0 -112
  201. package/src/client/theme/ui/CopyMarkdown/index.ts +0 -1
  202. package/src/client/theme/ui/ErrorBoundary/ErrorBoundary.tsx +0 -50
  203. package/src/client/theme/ui/ErrorBoundary/error-boundary.css +0 -55
  204. package/src/client/theme/ui/ErrorBoundary/index.ts +0 -1
  205. package/src/client/theme/ui/Footer/footer.css +0 -32
  206. package/src/client/theme/ui/Head/Head.tsx +0 -69
  207. package/src/client/theme/ui/Head/index.ts +0 -1
  208. package/src/client/theme/ui/LanguageSwitcher/LanguageSwitcher.tsx +0 -125
  209. package/src/client/theme/ui/LanguageSwitcher/index.ts +0 -1
  210. package/src/client/theme/ui/LanguageSwitcher/language-switcher.css +0 -98
  211. package/src/client/theme/ui/Layout/Layout.tsx +0 -203
  212. package/src/client/theme/ui/Layout/base.css +0 -106
  213. package/src/client/theme/ui/Layout/index.ts +0 -2
  214. package/src/client/theme/ui/Layout/pagination.css +0 -72
  215. package/src/client/theme/ui/Layout/responsive.css +0 -47
  216. package/src/client/theme/ui/Link/Link.tsx +0 -392
  217. package/src/client/theme/ui/Link/LinkPreview.tsx +0 -59
  218. package/src/client/theme/ui/Link/index.ts +0 -2
  219. package/src/client/theme/ui/Link/link-preview.css +0 -48
  220. package/src/client/theme/ui/Loading/Loading.tsx +0 -10
  221. package/src/client/theme/ui/Loading/index.ts +0 -1
  222. package/src/client/theme/ui/Loading/loading.css +0 -30
  223. package/src/client/theme/ui/Navbar/GithubStars.tsx +0 -27
  224. package/src/client/theme/ui/Navbar/Navbar.tsx +0 -193
  225. package/src/client/theme/ui/Navbar/Tabs.tsx +0 -99
  226. package/src/client/theme/ui/Navbar/index.ts +0 -2
  227. package/src/client/theme/ui/Navbar/navbar.css +0 -347
  228. package/src/client/theme/ui/NotFound/NotFound.tsx +0 -19
  229. package/src/client/theme/ui/NotFound/index.ts +0 -1
  230. package/src/client/theme/ui/NotFound/not-found.css +0 -64
  231. package/src/client/theme/ui/OnThisPage/OnThisPage.tsx +0 -244
  232. package/src/client/theme/ui/OnThisPage/index.ts +0 -1
  233. package/src/client/theme/ui/OnThisPage/toc.css +0 -152
  234. package/src/client/theme/ui/PoweredBy/PoweredBy.tsx +0 -18
  235. package/src/client/theme/ui/PoweredBy/index.ts +0 -1
  236. package/src/client/theme/ui/PoweredBy/powered-by.css +0 -76
  237. package/src/client/theme/ui/ProgressBar/ProgressBar.css +0 -17
  238. package/src/client/theme/ui/ProgressBar/ProgressBar.tsx +0 -51
  239. package/src/client/theme/ui/ProgressBar/index.ts +0 -1
  240. package/src/client/theme/ui/SearchDialog/SearchDialog.tsx +0 -209
  241. package/src/client/theme/ui/SearchDialog/index.ts +0 -1
  242. package/src/client/theme/ui/SearchDialog/search.css +0 -152
  243. package/src/client/theme/ui/Sidebar/Sidebar.tsx +0 -244
  244. package/src/client/theme/ui/Sidebar/index.ts +0 -1
  245. package/src/client/theme/ui/Sidebar/sidebar.css +0 -230
  246. package/src/client/theme/ui/ThemeToggle/ThemeToggle.tsx +0 -69
  247. package/src/client/theme/ui/ThemeToggle/index.ts +0 -1
  248. package/src/client/theme/ui/VersionSwitcher/VersionSwitcher.tsx +0 -136
  249. package/src/client/theme/ui/VersionSwitcher/index.ts +0 -1
  250. package/src/client/utils.ts +0 -49
@@ -0,0 +1,119 @@
1
+ import * as RAC from 'react-aria-components'
2
+ import { Copy, Check } from 'lucide-react'
3
+ import { cn } from '@client/utils/cn'
4
+ import { useCodeBlock } from './hooks/use-code-block'
5
+ import { useConfig } from '@client/app/config-context'
6
+ import { CodeSandbox } from '@components/icons-dev'
7
+ import { Tooltip } from '@components/primitives/tooltip'
8
+
9
+ export interface CodeBlockProps {
10
+ children?: React.ReactNode
11
+ className?: string
12
+ sandbox?: boolean | any
13
+ hideCopy?: boolean
14
+ hideSandbox?: boolean
15
+ title?: string
16
+ lang?: string
17
+ highlightedHtml?: string
18
+ [key: string]: any
19
+ }
20
+
21
+ export function CodeBlock(props: CodeBlockProps) {
22
+ const {
23
+ children,
24
+ sandbox: localSandbox,
25
+ hideSandbox = true,
26
+ hideCopy = false,
27
+ highlightedHtml,
28
+ ...rest
29
+ } = props
30
+ const config = useConfig()
31
+ const globalSandbox = config?.integrations?.sandbox
32
+ const isSandboxEnabled = !!globalSandbox?.enable && !hideSandbox
33
+ const {
34
+ copied,
35
+ isExpanded,
36
+ setIsExpanded,
37
+ isExpandable,
38
+ preRef,
39
+ handleCopy,
40
+ handleSandbox,
41
+ shouldTruncate,
42
+ } = useCodeBlock(props)
43
+
44
+ return (
45
+ <div
46
+ className={cn(
47
+ 'group relative my-6 overflow-hidden rounded-lg border border-border-subtle bg-(--color-code-bg)',
48
+ shouldTruncate && '[&>pre]:max-h-[250px] [&>pre]:overflow-hidden',
49
+ )}
50
+ >
51
+ {/* Toolbar */}
52
+ <div className="absolute top-3 right-4 z-50 flex items-center gap-2 transition-all duration-300 opacity-0 group-hover:opacity-100">
53
+ {isSandboxEnabled && (
54
+ <Tooltip content="Open in CodeSandbox">
55
+ <RAC.Button
56
+ onPress={handleSandbox}
57
+ className="grid place-items-center w-8 h-8 bg-transparent text-text-muted outline-none cursor-pointer transition-all duration-200 hover:scale-115 hover:text-sky-400 active:scale-95 [&>svg]:w-5 [&>svg]:h-5 [&>svg]:stroke-2"
58
+ aria-label="Open in CodeSandbox"
59
+ >
60
+ <CodeSandbox size={20} />
61
+ </RAC.Button>
62
+ </Tooltip>
63
+ )}
64
+ {!hideCopy && (
65
+ <Tooltip content={copied ? 'Copied!' : 'Copy code'}>
66
+ <RAC.Button
67
+ onPress={handleCopy}
68
+ className={cn(
69
+ 'grid place-items-center w-8 h-8 bg-transparent outline-none cursor-pointer transition-all duration-200 hover:scale-115 active:scale-95 [&>svg]:w-5 [&>svg]:h-5 [&>svg]:stroke-2',
70
+ copied
71
+ ? 'text-emerald-400'
72
+ : 'text-text-muted hover:text-text-main',
73
+ )}
74
+ aria-label="Copy code"
75
+ >
76
+ {copied ? <Check size={20} /> : <Copy size={20} />}
77
+ </RAC.Button>
78
+ </Tooltip>
79
+ )}
80
+ </div>
81
+
82
+ {/* Code Content */}
83
+ {highlightedHtml ? (
84
+ <div
85
+ // @ts-ignore
86
+ ref={preRef}
87
+ className="shiki-wrapper [&>pre]:m-0! [&>pre]:rounded-none! [&>pre]:border-none! [&>pre]:bg-inherit! [&>pre>code]:grid! [&>pre>code]:p-5! [&>.shiki.shiki-themes]:bg-transparent!"
88
+ dangerouslySetInnerHTML={{ __html: highlightedHtml }}
89
+ />
90
+ ) : (
91
+ <pre
92
+ ref={preRef}
93
+ className="m-0! rounded-none! border-none! bg-inherit! font-mono text-[0.8125rem] leading-[1.7]"
94
+ {...rest}
95
+ >
96
+ {children}
97
+ </pre>
98
+ )}
99
+
100
+ {/* Expand/Collapse */}
101
+ {isExpandable && (
102
+ <div
103
+ className={cn(
104
+ shouldTruncate
105
+ ? 'absolute bottom-0 inset-x-0 h-24 bg-linear-to-t from-(--color-code-bg) to-transparent flex items-end justify-center pb-4 z-10'
106
+ : 'relative flex justify-center py-4',
107
+ )}
108
+ >
109
+ <RAC.Button
110
+ onPress={() => setIsExpanded(!isExpanded)}
111
+ className="rounded-full bg-bg-surface border border-border-subtle px-5 py-2 text-[0.8125rem] font-medium text-text-main outline-none cursor-pointer transition-all hover:bg-border-subtle hover:-translate-y-px backdrop-blur-md"
112
+ >
113
+ {isExpanded ? 'Show less' : 'Expand code'}
114
+ </RAC.Button>
115
+ </div>
116
+ )}
117
+ </div>
118
+ )
119
+ }
@@ -0,0 +1,47 @@
1
+ import type { SandboxOptions } from '../../types'
2
+ import { CodeBlock } from './code-block'
3
+ import { useComponentPreview } from './hooks/use-component-preview'
4
+
5
+ export interface ComponentPreviewProps {
6
+ code?: string
7
+ highlightedHtml?: string
8
+ children?: string
9
+ preview?: React.ReactNode
10
+ hideCode?: boolean
11
+ hideSandbox?: boolean
12
+ hideCopy?: boolean
13
+ sandboxOptions?: SandboxOptions
14
+ }
15
+
16
+ export function ComponentPreview(props: ComponentPreviewProps) {
17
+ const {
18
+ highlightedHtml,
19
+ hideCode = false,
20
+ hideSandbox = false,
21
+ hideCopy = false,
22
+ sandboxOptions = {},
23
+ } = props
24
+ const { initialCode, previewElement } = useComponentPreview(props)
25
+
26
+ return (
27
+ <div className="my-6 overflow-hidden rounded-xl border border-border-subtle">
28
+ <div className="flex items-center justify-center p-8 bg-bg-surface">
29
+ {previewElement}
30
+ </div>
31
+
32
+ {!hideCode && (
33
+ <div className="border-t border-border-subtle">
34
+ <CodeBlock
35
+ hideSandbox={hideSandbox}
36
+ hideCopy={hideCopy}
37
+ title={sandboxOptions.title}
38
+ lang="tsx"
39
+ highlightedHtml={highlightedHtml}
40
+ >
41
+ {initialCode}
42
+ </CodeBlock>
43
+ </div>
44
+ )}
45
+ </div>
46
+ )
47
+ }
@@ -0,0 +1,83 @@
1
+ import { cn } from '@client/utils/cn'
2
+
3
+ export interface PropItem {
4
+ name: string
5
+ type: string
6
+ defaultValue?: string
7
+ required?: boolean
8
+ description: React.ReactNode
9
+ }
10
+
11
+ export interface ComponentPropsProps {
12
+ title?: string
13
+ props: PropItem[]
14
+ className?: string
15
+ }
16
+
17
+ export function ComponentProps({
18
+ title,
19
+ props,
20
+ className = '',
21
+ }: ComponentPropsProps) {
22
+ return (
23
+ <div className={cn('my-6', className)}>
24
+ {title && (
25
+ <h3 className="text-base font-bold text-text-main mb-3">{title}</h3>
26
+ )}
27
+ <div className="overflow-x-auto rounded-lg border border-border-subtle">
28
+ <table className="w-full border-collapse text-sm">
29
+ <thead>
30
+ <tr>
31
+ <th className="text-left px-4 py-3 border-b-2 border-border-subtle text-xs font-bold uppercase tracking-wider text-text-muted">
32
+ Property
33
+ </th>
34
+ <th className="text-left px-4 py-3 border-b-2 border-border-subtle text-xs font-bold uppercase tracking-wider text-text-muted">
35
+ Type
36
+ </th>
37
+ <th className="text-left px-4 py-3 border-b-2 border-border-subtle text-xs font-bold uppercase tracking-wider text-text-muted">
38
+ Default
39
+ </th>
40
+ <th className="text-left px-4 py-3 border-b-2 border-border-subtle text-xs font-bold uppercase tracking-wider text-text-muted">
41
+ Description
42
+ </th>
43
+ </tr>
44
+ </thead>
45
+ <tbody>
46
+ {props.map((prop, index) => (
47
+ <tr
48
+ key={`${prop.name}-${index}`}
49
+ className="transition-colors hover:bg-bg-surface"
50
+ >
51
+ <td className="px-4 py-2.5 border-b border-border-subtle">
52
+ <code className="rounded bg-bg-surface px-1.5 py-0.5 font-mono text-xs font-bold text-primary-400">
53
+ {prop.name}
54
+ </code>
55
+ {prop.required && (
56
+ <span className="ml-1 text-red-400 font-bold">*</span>
57
+ )}
58
+ </td>
59
+ <td className="px-4 py-2.5 border-b border-border-subtle">
60
+ <code className="rounded bg-bg-muted px-1.5 py-0.5 font-mono text-xs text-text-muted">
61
+ {prop.type}
62
+ </code>
63
+ </td>
64
+ <td className="px-4 py-2.5 border-b border-border-subtle">
65
+ {prop.defaultValue ? (
66
+ <code className="rounded bg-bg-muted px-1.5 py-0.5 font-mono text-xs text-primary-400">
67
+ {prop.defaultValue}
68
+ </code>
69
+ ) : (
70
+ <span className="text-text-dim">—</span>
71
+ )}
72
+ </td>
73
+ <td className="px-4 py-2.5 border-b border-border-subtle text-text-muted">
74
+ {prop.description}
75
+ </td>
76
+ </tr>
77
+ ))}
78
+ </tbody>
79
+ </table>
80
+ </div>
81
+ </div>
82
+ )
83
+ }
@@ -0,0 +1,66 @@
1
+ import { cn } from '@client/utils/cn'
2
+
3
+ export interface FieldProps {
4
+ name: string
5
+ type?: string
6
+ defaultValue?: string
7
+ required?: boolean
8
+ children: React.ReactNode
9
+ id?: string
10
+ className?: string
11
+ }
12
+
13
+ export function Field({
14
+ name,
15
+ type,
16
+ defaultValue,
17
+ required = false,
18
+ children,
19
+ id,
20
+ className = '',
21
+ }: FieldProps) {
22
+ return (
23
+ <article
24
+ className={cn(
25
+ 'group relative my-6 rounded-xl border border-border-subtle bg-bg-surface p-5 transition-all duration-300',
26
+ 'hover:border-primary-500/30 hover:shadow-lg hover:shadow-primary-500/5',
27
+ className,
28
+ )}
29
+ id={id}
30
+ >
31
+ <div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between mb-4">
32
+ <div className="flex flex-wrap items-center gap-2.5">
33
+ <code className="inline-flex items-center rounded-md bg-primary-500/10 px-2.5 py-1 font-mono text-sm font-bold text-primary-400 border border-primary-500/20 shadow-sm transition-colors group-hover:bg-primary-500/15">
34
+ {name}
35
+ </code>
36
+ {type && (
37
+ <span className="rounded-md bg-bg-muted/80 border border-border-subtle px-2 py-0.5 text-[11px] font-semibold text-text-muted uppercase tracking-wider shadow-sm">
38
+ {type}
39
+ </span>
40
+ )}
41
+ {required && (
42
+ <div className="flex items-center gap-1.5 rounded-full bg-red-500/10 px-2.5 py-0.5 text-[10px] font-bold uppercase tracking-wider text-red-400 border border-red-500/20 shadow-sm">
43
+ <span className="h-1 w-1 rounded-full bg-red-400 animate-pulse" />
44
+ Required
45
+ </div>
46
+ )}
47
+ </div>
48
+
49
+ {defaultValue && (
50
+ <div className="flex items-center gap-2 text-[11px] text-text-muted bg-bg-muted/30 px-2.5 py-1 rounded-md border border-border-subtle/50">
51
+ <span className="font-semibold opacity-60 uppercase tracking-tighter">
52
+ Default
53
+ </span>
54
+ <code className="font-mono text-text-main font-medium">
55
+ {defaultValue}
56
+ </code>
57
+ </div>
58
+ )}
59
+ </div>
60
+
61
+ <div className="text-sm text-text-muted leading-relaxed [&>p]:m-0 selection:bg-primary-500/30">
62
+ {children}
63
+ </div>
64
+ </article>
65
+ )
66
+ }
@@ -0,0 +1,287 @@
1
+ import { Children, isValidElement, useMemo } from 'react'
2
+ import * as RAC from 'react-aria-components'
3
+ import {
4
+ Folder,
5
+ FileText,
6
+ File,
7
+ FileCode,
8
+ FileImage,
9
+ ChevronRight,
10
+ } from 'lucide-react'
11
+ import { cn } from '@client/utils/cn'
12
+
13
+ // --- Constants & Types ---
14
+
15
+ const ICON_SIZE = 16
16
+ const STROKE_WIDTH = 2
17
+
18
+ const FILE_REGEXES = {
19
+ CODE: /\.(ts|tsx|js|jsx|json|mjs|cjs|astro|vue|svelte)$/i,
20
+ TEXT: /\.(md|mdx|txt)$/i,
21
+ IMAGE: /\.(png|jpg|jpeg|svg|gif)$/i,
22
+ } as const
23
+
24
+ export interface FileTreeProps {
25
+ children: React.ReactNode
26
+ }
27
+
28
+ interface TreeItemData {
29
+ id: string
30
+ name: string
31
+ comment?: string
32
+ isFolder: boolean
33
+ children?: TreeItemData[]
34
+ }
35
+
36
+ // --- Helpers ---
37
+
38
+ function getTextContent(node: React.ReactNode): string {
39
+ if (typeof node === 'string') return node
40
+ if (typeof node === 'number') return node.toString()
41
+ if (Array.isArray(node)) return node.map(getTextContent).join('')
42
+ if (
43
+ isValidElement(node) &&
44
+ node.props &&
45
+ typeof node.props === 'object' &&
46
+ 'children' in node.props
47
+ ) {
48
+ return getTextContent(
49
+ (node.props as { children?: React.ReactNode }).children,
50
+ )
51
+ }
52
+ return ''
53
+ }
54
+
55
+ function getFileIcon(filename: string, isFolder: boolean) {
56
+ const name = filename.toLowerCase()
57
+ const iconClass = 'shrink-0 transition-colors duration-200'
58
+
59
+ if (isFolder) {
60
+ return (
61
+ <Folder
62
+ size={ICON_SIZE}
63
+ strokeWidth={STROKE_WIDTH}
64
+ className={cn(iconClass, 'text-primary-400')}
65
+ fill="currentColor"
66
+ fillOpacity={0.15}
67
+ />
68
+ )
69
+ }
70
+
71
+ const fileIconClass = cn(
72
+ iconClass,
73
+ 'text-text-dim group-hover:text-text-main',
74
+ )
75
+
76
+ if (FILE_REGEXES.CODE.test(name))
77
+ return (
78
+ <FileCode
79
+ size={ICON_SIZE}
80
+ strokeWidth={STROKE_WIDTH}
81
+ className={fileIconClass}
82
+ />
83
+ )
84
+ if (FILE_REGEXES.TEXT.test(name))
85
+ return (
86
+ <FileText
87
+ size={ICON_SIZE}
88
+ strokeWidth={STROKE_WIDTH}
89
+ className={fileIconClass}
90
+ />
91
+ )
92
+ if (FILE_REGEXES.IMAGE.test(name))
93
+ return (
94
+ <FileImage
95
+ size={ICON_SIZE}
96
+ strokeWidth={STROKE_WIDTH}
97
+ className={fileIconClass}
98
+ />
99
+ )
100
+
101
+ return (
102
+ <File
103
+ size={ICON_SIZE}
104
+ strokeWidth={STROKE_WIDTH}
105
+ className={fileIconClass}
106
+ />
107
+ )
108
+ }
109
+
110
+ function isListElement(
111
+ node: unknown,
112
+ tag: 'ul' | 'li',
113
+ ): node is React.ReactElement<{ children?: React.ReactNode }> {
114
+ if (!isValidElement(node)) return false
115
+
116
+ const type = node.type
117
+ if (typeof type === 'string') return type === tag
118
+ if (typeof type === 'function') {
119
+ return type.name === tag || type.name?.toLowerCase() === tag
120
+ }
121
+
122
+ const props = node.props as any
123
+ return props?.originalType === tag || props?.mdxType === tag
124
+ }
125
+
126
+ function parseLabel(rawLabel: string): { name: string; comment?: string } {
127
+ const commentMatch = rawLabel.match(/\s+(\/\/|#)\s+(.*)$/)
128
+ if (commentMatch) {
129
+ return {
130
+ name: rawLabel.slice(0, commentMatch.index).trim(),
131
+ comment: commentMatch[2],
132
+ }
133
+ }
134
+ return { name: rawLabel.trim() }
135
+ }
136
+
137
+ function parseMdxToData(
138
+ node: React.ReactNode,
139
+ path: string = 'root',
140
+ ): TreeItemData[] {
141
+ if (!isValidElement(node)) return []
142
+
143
+ const items: TreeItemData[] = []
144
+
145
+ if (isListElement(node, 'ul')) {
146
+ Children.forEach(
147
+ (node.props as { children?: React.ReactNode }).children,
148
+ (child, index) => {
149
+ items.push(...parseMdxToData(child, `${path}-${index}`))
150
+ },
151
+ )
152
+ return items
153
+ }
154
+
155
+ if (isListElement(node, 'li')) {
156
+ const children = Children.toArray(
157
+ (node.props as { children?: React.ReactNode }).children,
158
+ )
159
+ const nestedListIndex = children.findIndex((child) =>
160
+ isListElement(child, 'ul'),
161
+ )
162
+
163
+ const hasNested = nestedListIndex !== -1
164
+ const labelNodes = hasNested ? children.slice(0, nestedListIndex) : children
165
+ const nestedNodes = hasNested ? children.slice(nestedListIndex) : []
166
+
167
+ const rawLabelContent = getTextContent(labelNodes)
168
+ const { name, comment } = parseLabel(rawLabelContent)
169
+
170
+ const isExplicitDir = name.endsWith('/')
171
+ const labelText = isExplicitDir ? name.slice(0, -1) : name
172
+ const isFolder = hasNested || isExplicitDir
173
+
174
+ items.push({
175
+ id: `${path}-${labelText}`,
176
+ name: labelText,
177
+ comment,
178
+ isFolder,
179
+ children: hasNested
180
+ ? parseMdxToData(nestedNodes[0], `${path}-${labelText}`)
181
+ : undefined,
182
+ })
183
+ return items
184
+ }
185
+
186
+ if (
187
+ node.props &&
188
+ typeof node.props === 'object' &&
189
+ 'children' in node.props
190
+ ) {
191
+ Children.forEach(
192
+ (node.props as { children?: React.ReactNode }).children,
193
+ (child, index) => {
194
+ items.push(...parseMdxToData(child, `${path}-${index}`))
195
+ },
196
+ )
197
+ }
198
+
199
+ return items
200
+ }
201
+
202
+ // --- Sub-Components ---
203
+
204
+ function FileTreeNode({ item }: { item: TreeItemData }) {
205
+ return (
206
+ <RAC.TreeItem
207
+ id={item.id}
208
+ textValue={item.name}
209
+ className="outline-none group focus-visible:ring-2 focus-visible:ring-primary-500/30 rounded-md"
210
+ >
211
+ <RAC.TreeItemContent>
212
+ {({ isExpanded, hasChildItems }) => (
213
+ <div className="flex items-center gap-2 py-1 px-1.5 rounded-md transition-colors hover:bg-primary-500/5 cursor-pointer">
214
+ <div
215
+ style={{ width: `calc((var(--tree-item-level) - 1) * 1rem)` }}
216
+ className="shrink-0"
217
+ />
218
+ {hasChildItems ? (
219
+ <RAC.Button
220
+ slot="chevron"
221
+ className="outline-none text-text-dim hover:text-primary-400 p-0.5 rounded transition-colors"
222
+ >
223
+ <ChevronRight
224
+ size={14}
225
+ strokeWidth={3}
226
+ className={cn(
227
+ 'transition-transform duration-200',
228
+ isExpanded && 'rotate-90',
229
+ )}
230
+ />
231
+ </RAC.Button>
232
+ ) : (
233
+ <div className="w-[18px]" />
234
+ )}
235
+
236
+ {getFileIcon(item.name, item.isFolder)}
237
+
238
+ <span
239
+ className={cn(
240
+ 'text-sm transition-colors truncate select-none',
241
+ item.isFolder
242
+ ? 'font-semibold text-text-main'
243
+ : 'text-text-muted group-hover:text-text-main',
244
+ )}
245
+ >
246
+ {item.name}
247
+ </span>
248
+
249
+ {item.comment && (
250
+ <span className="ml-2 text-xs italic text-text-dim opacity-70 group-hover:opacity-100 transition-opacity whitespace-nowrap overflow-hidden text-ellipsis font-sans">
251
+ {'//'} {item.comment}
252
+ </span>
253
+ )}
254
+ </div>
255
+ )}
256
+ </RAC.TreeItemContent>
257
+
258
+ {item.children && (
259
+ <RAC.Collection items={item.children}>
260
+ {(child) => <FileTreeNode item={child} />}
261
+ </RAC.Collection>
262
+ )}
263
+ </RAC.TreeItem>
264
+ )
265
+ }
266
+
267
+ // --- Main Component ---
268
+
269
+ export function FileTree({ children }: FileTreeProps) {
270
+ const items = useMemo(() => parseMdxToData(children), [children])
271
+
272
+ return (
273
+ <div className="my-8">
274
+ <RAC.Tree
275
+ items={items}
276
+ aria-label="File Tree"
277
+ className={cn(
278
+ 'rounded-xl border border-border-subtle bg-bg-surface/50 p-4 font-mono text-sm shadow-sm backdrop-blur-sm outline-none',
279
+ 'max-h-[500px] overflow-y-auto scrollbar-thin scrollbar-thumb-border-subtle',
280
+ 'focus-visible:ring-2 focus-visible:ring-primary-500/20',
281
+ )}
282
+ >
283
+ {(item) => <FileTreeNode item={item} />}
284
+ </RAC.Tree>
285
+ </div>
286
+ )
287
+ }
@@ -0,0 +1,56 @@
1
+ import { useConfig } from '@client/app/config-context'
2
+ import { openSandbox } from '@client/integrations/codesandbox'
3
+ import { copyToClipboard } from '@client/utils/copy-clipboard'
4
+ import { useCallback, useEffect, useRef, useState } from 'react'
5
+ import type { CodeBlockProps } from '../code-block'
6
+
7
+ export function useCodeBlock(props: CodeBlockProps) {
8
+ const { title, sandbox: localSandbox } = props
9
+ const [copied, setCopied] = useState(false)
10
+ const [isExpanded, setIsExpanded] = useState(false)
11
+ const [isExpandable, setIsExpandable] = useState(false)
12
+ const preRef = useRef<HTMLPreElement>(null)
13
+ const config = useConfig()
14
+
15
+ const handleCopy = useCallback(async () => {
16
+ const code = preRef.current?.textContent ?? ''
17
+ copyToClipboard(code)
18
+ setCopied(true)
19
+ setTimeout(() => setCopied(false), 2000)
20
+ }, [])
21
+
22
+ const handleSandbox = useCallback(() => {
23
+ const code = preRef.current?.textContent ?? ''
24
+ const globalSandbox = config?.integrations?.sandbox?.config || {}
25
+ const baseOptions =
26
+ typeof localSandbox === 'object' ? localSandbox : globalSandbox
27
+
28
+ const entry = baseOptions.entry || 'src/App.tsx'
29
+
30
+ openSandbox({
31
+ title: title ?? 'Code Snippet',
32
+ ...baseOptions,
33
+ files: {
34
+ ...baseOptions.files,
35
+ [entry]: { content: code },
36
+ },
37
+ })
38
+ }, [title, config, localSandbox])
39
+
40
+ // biome-ignore lint/correctness/useExhaustiveDependencies: updates when content changes
41
+ useEffect(() => {
42
+ const codeLength = preRef.current?.textContent?.length ?? 0
43
+ setIsExpandable(codeLength > 120)
44
+ }, [props.children, props.highlightedHtml])
45
+
46
+ return {
47
+ copied,
48
+ isExpanded,
49
+ setIsExpanded,
50
+ isExpandable,
51
+ preRef,
52
+ handleCopy,
53
+ handleSandbox,
54
+ shouldTruncate: isExpandable && !isExpanded,
55
+ }
56
+ }
@@ -0,0 +1,16 @@
1
+ import { useMemo } from 'react'
2
+ import type { ComponentPreviewProps } from '../component-preview'
3
+
4
+ export function useComponentPreview(props: ComponentPreviewProps) {
5
+ const { code: propsCode, children, preview } = props
6
+ const initialCode = useMemo(() => {
7
+ const base = propsCode ?? (typeof children === 'string' ? children : '')
8
+ return base.trim()
9
+ }, [propsCode, children])
10
+
11
+ const previewElement = useMemo(() => {
12
+ return preview ?? (typeof children !== 'string' ? children : null)
13
+ }, [preview, children])
14
+
15
+ return { initialCode, previewElement }
16
+ }