feat(docs): enhance code presentation

This commit is contained in:
Maofeng
2026-09-28 16:00:01 +08:00
parent 91cfb3d068
commit 93373adb9a
6 changed files with 430 additions and 20 deletions
+124 -19
View File
@@ -5,6 +5,7 @@ import { useTranslate } from "../../lib/locale"
import { getSandboxDefinition } from "./registry"
import { SandboxErrorBoundary } from "./sandbox-error-boundary"
import { SandboxFrame } from "./sandbox-frame"
import { highlightSource, type HighlightedSource } from "./highlight-source"
import type {
SandboxDefinition,
SandboxPreviewModule,
@@ -13,6 +14,8 @@ import type {
type SandboxView = "code" | "preview"
const codeFontSizes = [12, 13, 14, 16] as const
export type SandboxProps = {
className?: string
example: string
@@ -87,14 +90,13 @@ function SandboxViewer({
[definition.files]
)
const [view, setView] = React.useState<SandboxView>("preview")
const [selectedFile, setSelectedFile] = React.useState(
definition.defaultFile ?? filenames[0] ?? ""
)
const [previewModule, setPreviewModule] =
React.useState<SandboxPreviewModule | null>(null)
const [selectedFile, setSelectedFile] = React.useState(definition.defaultFile ?? filenames[0] ?? "")
const [previewModule, setPreviewModule] = React.useState<SandboxPreviewModule | null>(null)
const [previewError, setPreviewError] = React.useState<Error | null>(null)
const [source, setSource] = React.useState<string | null>(null)
const [sourceError, setSourceError] = React.useState<Error | null>(null)
const [highlightedSource, setHighlightedSource] = React.useState<HighlightedSource | null>(null)
const [codeFontSizeIndex, setCodeFontSizeIndex] = React.useState(1)
const [reloadKey, setReloadKey] = React.useState(0)
const [copied, setCopied] = React.useState(false)
const resolvedTitle = title ?? definition.title ?? definition.id
@@ -131,6 +133,7 @@ function SandboxViewer({
let active = true
setSource(null)
setSourceError(null)
setHighlightedSource(null)
resolveSource(file).then(
(value) => {
if (active) setSource(value)
@@ -145,6 +148,25 @@ function SandboxViewer({
}
}, [definition.files, selectedFile, view])
React.useEffect(() => {
if (!source || view !== "code") return
let active = true
setHighlightedSource(null)
highlightSource(source, selectedFile).then(
(value) => {
if (active) setHighlightedSource(value)
},
() => {
if (active) setHighlightedSource(null)
}
)
return () => {
active = false
}
}, [selectedFile, source, view])
React.useEffect(() => {
setSelectedFile(definition.defaultFile ?? filenames[0] ?? "")
}, [definition.defaultFile, filenames])
@@ -182,16 +204,24 @@ function SandboxViewer({
<div className="ml-auto flex items-center gap-1">
{view === "code" ? (
<SandboxAction
disabled={!source}
label={t({ zh: "复制源码", us: "Copy source" })}
onClick={() => void copySource()}
>
<Icon
name={copied ? "lucide:check" : "lucide:copy"}
className="size-4"
<>
<CodeFontSizeControl
decreaseLabel={t({ zh: "减小字号", us: "Decrease font size" })}
increaseLabel={t({ zh: "增大字号", us: "Increase font size" })}
index={codeFontSizeIndex}
onIndexChange={setCodeFontSizeIndex}
/>
</SandboxAction>
<SandboxAction
disabled={!source}
label={t({ zh: "复制源码", us: "Copy source" })}
onClick={() => void copySource()}
>
<Icon
name={copied ? "lucide:check" : "lucide:copy"}
className="size-4"
/>
</SandboxAction>
</>
) : (
<SandboxAction
label={t({ zh: "重置预览", us: "Reset preview" })}
@@ -241,11 +271,21 @@ function SandboxViewer({
))}
</nav>
) : null}
<pre className="m-0 min-w-0 overflow-auto bg-slate-950 p-4 text-sm leading-6 text-slate-100">
<code>
{sourceError
? sourceError.message
: (source ?? t({ zh: "正在加载源码…", us: "Loading source…" }))}
<pre
className="m-0 min-w-0 overflow-auto bg-slate-50 p-4 font-mono text-slate-800 dark:bg-slate-950 dark:text-slate-100"
style={{
fontSize: `${codeFontSizes[codeFontSizeIndex]}px`,
lineHeight: 1.55,
}}
>
<code className="block min-w-max">
{sourceError ? (
sourceError.message
) : highlightedSource ? (
<HighlightedCode source={highlightedSource} />
) : (
(source ?? t({ zh: "正在加载源码…", us: "Loading source…" }))
)}
</code>
</pre>
</div>
@@ -254,6 +294,71 @@ function SandboxViewer({
)
}
function CodeFontSizeControl({
decreaseLabel,
increaseLabel,
index,
onIndexChange,
}: {
decreaseLabel: string
increaseLabel: string
index: number
onIndexChange: React.Dispatch<React.SetStateAction<number>>
}) {
return (
<div
className="inline-flex h-8 items-center rounded-md border border-slate-200 bg-white dark:border-slate-700 dark:bg-slate-900"
aria-label={`${codeFontSizes[index]}px`}
>
<SandboxAction
disabled={index === 0}
label={decreaseLabel}
onClick={() => onIndexChange((value) => Math.max(0, value - 1))}
>
<Icon name="lucide:minus" className="size-3.5" />
</SandboxAction>
<span className="w-9 text-center font-mono text-[11px] text-slate-500 tabular-nums dark:text-slate-400">
{codeFontSizes[index]}
</span>
<SandboxAction
disabled={index === codeFontSizes.length - 1}
label={increaseLabel}
onClick={() =>
onIndexChange((value) =>
Math.min(codeFontSizes.length - 1, value + 1)
)
}
>
<Icon name="lucide:plus" className="size-3.5" />
</SandboxAction>
</div>
)
}
function HighlightedCode({ source }: { source: HighlightedSource }) {
return source.map((line, lineIndex) => (
<React.Fragment key={line.offset}>
<span className="sandbox-code-line">
{line.tokens.map((token) => (
<span
key={token.offset}
className="sandbox-code-token"
style={
{
"--sandbox-token-dark": token.dark,
"--sandbox-token-light": token.light,
} as React.CSSProperties
}
>
{token.content}
</span>
))}
</span>
{lineIndex < source.length - 1 ? "\n" : null}
</React.Fragment>
))
}
function SandboxTab({
active,
children,