iter(g270): 新增代码块组件

This commit is contained in:
HIsheR
2026-10-26 22:54:55 +08:00
parent 02d7eb3c4d
commit b8f90b8e0d
12 changed files with 272 additions and 11 deletions
+30
View File
@@ -0,0 +1,30 @@
import { h } from '@ui-arch/frame';
import { copyButton } from './copyButton.js';
import type { CodeBlockProps } from './types.js';
/*** 代码块:等宽渲染多行代码,可选行号列与标题行(语言 / 文件名),右上角一键复制原文;只做展示不做高亮 ***/
export function codeBlock(props: CodeBlockProps): HTMLElement {
const lines = props.code.replace(/\r?\n$/, '').split(/\r?\n/); // 末尾换行不生成空行:复制走原文,显示不多一行
const showNo = props.lineNumbers !== false && lines.length > 1; // 单行代码给行号只是噪音
const body = h(
'div',
{ class: 'ua-code-body' },
...lines.map((text, i) => h(
'div',
{ class: 'ua-code-line' },
showNo ? h('span', { class: 'ua-code-no', 'aria-hidden': 'true' }, String(i + 1)) : null, // 行号是装饰,读屏跳过
h('span', { class: 'ua-code-text' }, text || ' '), // 空行补一个空格,撑住行高
)),
);
if (props.maxHeight !== undefined) body.style.maxHeight = `${props.maxHeight}px`; // 只在给了上限时限高,超出内部滚动
const title = props.title ?? props.lang; // 标题优先用文件名,其次语言名
const head = props.copyable === false && !title
? null // 既无标题也不可复制:整个头部省掉,不留空条
: h(
'div',
{ class: 'ua-code-head' },
title ? h('span', { class: 'ua-code-title' }, title) : h('span', null, ''),
props.copyable === false ? null : copyButton({ text: props.code, label: '复制', size: 'sm', variant: 'ghost', onCopied: props.onCopy }),
);
return h('div', { class: 'ua-code', role: 'group', 'aria-label': props.ariaLabel ?? '代码块' }, head, body);
}
+1
View File
@@ -79,3 +79,4 @@ export { inputTag } from './inputTag.js';
export { layout } from './layout.js';
export { jsonViewer } from './jsonViewer.js';
export { diffView } from './diffView.js';
export { codeBlock } from './codeBlock.js';
+12
View File
@@ -1038,3 +1038,15 @@ export interface DiffViewProps {
showStats?: boolean; // 是否在顶部显示 +N / −N 统计条;缺省 true
ariaLabel?: string; // 可访问名;缺省自动播报「新增 N 行,删除 M 行」
}
/*** 代码块入参 ***/
export interface CodeBlockProps {
code: string; // 代码原文:显示与复制都用它(不读 DOM,避免行号混进剪贴板)
lang?: string; // 语言名(如 ts / css);无 title 时作头部标题
title?: string; // 头部标题(如文件名);优先于 lang
lineNumbers?: boolean; // 是否显示行号列;缺省 true(单行代码自动省略)
maxHeight?: number; // 代码区最高高度(px),超出在框内滚动;缺省不限高
copyable?: boolean; // 右上角是否出现复制按钮;缺省 true
onCopy?: (text: string) => void; // 复制成功回调(参数为实际写入剪贴板的内容)
ariaLabel?: string; // 可访问名;缺省「代码块」
}