Files
UI/packages/skin/ui/src/codeBlock.ts
T
2026-10-26 22:54:55 +08:00

31 lines
1.7 KiB
TypeScript

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);
}