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.
- package/CHANGELOG.md +7 -0
- package/LICENSE +21 -0
- package/dist/cache-7G6D532T.mjs +1 -0
- package/dist/chunk-A4HQPEPU.mjs +1 -0
- package/dist/chunk-BA5NH5HU.mjs +1 -0
- package/dist/chunk-BQCD3DWG.mjs +1 -0
- package/dist/chunk-H63UMKYF.mjs +1 -0
- package/dist/chunk-IWHRQHS7.mjs +1 -0
- package/dist/chunk-JZXLCA2E.mjs +1 -0
- package/dist/chunk-MFU7Q6WF.mjs +1 -0
- package/dist/chunk-QYPNX5UN.mjs +1 -0
- package/dist/chunk-XEAPSFMB.mjs +1 -0
- package/dist/client/components/mdx/index.d.mts +209 -0
- package/dist/client/components/mdx/index.d.ts +209 -0
- package/dist/client/components/mdx/index.js +1 -0
- package/dist/client/components/mdx/index.mjs +1 -0
- package/dist/client/hooks/index.d.mts +133 -0
- package/dist/client/hooks/index.d.ts +133 -0
- package/dist/client/hooks/index.js +1 -0
- package/dist/client/hooks/index.mjs +1 -0
- package/dist/client/index.d.mts +138 -298
- package/dist/client/index.d.ts +138 -298
- package/dist/client/index.js +1 -3630
- package/dist/client/index.mjs +1 -697
- package/dist/client/ssr.d.mts +7 -3
- package/dist/client/ssr.d.ts +7 -3
- package/dist/client/ssr.js +1 -2928
- package/dist/client/ssr.mjs +1 -33
- package/dist/{config-BsFQ-ErD.d.ts → config-CX4l-ZNp.d.mts} +42 -35
- package/dist/{config-BsFQ-ErD.d.mts → config-CX4l-ZNp.d.ts} +42 -35
- package/dist/node/index.d.mts +2 -4
- package/dist/node/index.d.ts +2 -4
- package/dist/node/index.js +31 -1161
- package/dist/node/index.mjs +31 -736
- package/dist/search-dialog-EB3N4TYM.mjs +1 -0
- package/dist/types-BuZWFT7r.d.ts +159 -0
- package/dist/types-CvT-SGbK.d.mts +159 -0
- package/dist/use-routes-5bAtAAYX.d.mts +30 -0
- package/dist/use-routes-BefRXY3v.d.ts +30 -0
- package/package.json +34 -12
- package/src/client/app/config-context.tsx +18 -0
- package/src/client/app/docs-layout.tsx +14 -0
- package/src/client/app/index.tsx +137 -262
- 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 -84
- 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 +102 -82
- package/src/node/routes/sorter.ts +15 -15
- package/src/node/routes/types.ts +24 -24
- package/src/node/ssg/index.ts +73 -47
- 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 +54 -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/ssr.css +0 -2847
- 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,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
|
+
}
|