diff --git a/.ua/backlog.json b/.ua/backlog.json index 87226f6..e1cd9ba 100644 --- a/.ua/backlog.json +++ b/.ua/backlog.json @@ -1388,6 +1388,46 @@ "acceptance": "npm run changelog 重新生成(329 提交),changelog:check 一致(提交本条后按约定再差一条,属正常);npm run check 全绿", "notes": "自补:发版前刷新步骤;本次唤起收尾轮,顺带提交 g127 漏带的 llms-full.txt", "status": "done" + }, + { + "id": "g129", + "title": "新增排版文本组件 typography", + "motivation": "文档类页面(详情、说明、条款)目前只能手搓 h/p + 内联样式,标题层级、语义色、多行省略、复制都没有统一入口", + "acceptance": "typography 支持 variant(title/text/paragraph) + level + type + 装饰位(strong/underline/delete/mark/code) + align + ellipsis 多行截断 + copyable;标题出语义 h1-h5;样式只消费 --ua-*;manifest/docs/README/demo/llms-full 同步;npm run check 全绿", + "status": "open", + "notes": "自补:组件补充方向" + }, + { + "id": "g130", + "title": "新增栅格布局 row / col", + "motivation": "页面骨架只能靠 space 与手写 flex,24 栅格、列间距、响应式断点缺失,示例页布局各写一套", + "acceptance": "row 提供 gutter 列间距与 justify/align,col 提供 span/offset 与 sm/md 断点;窄屏自动堆叠;样式只消费 --ua-*;文档与演示同步;npm run check 全绿", + "status": "open", + "notes": "自补:组件补充方向" + }, + { + "id": "g131", + "title": "新增悬浮操作按钮 floatButton", + "motivation": "backTop 只能回顶,页面里「新建 / 帮助 / 客服」这类常驻悬浮入口没有统一件,各处自己 fixed 定位", + "acceptance": "floatButton 固定右下角,支持 icon/label/tooltip/badge/点击回调与 position 偏移,键盘可达;样式只消费 --ua-*;文档与演示同步;npm run check 全绿", + "status": "open", + "notes": "自补:组件补充方向" + }, + { + "id": "g132", + "title": "@提及输入补 mentions 组件", + "motivation": "评论 / 协作场景要 @ 选人,目前只能 textarea + 自己接 autoComplete,光标定位与插入逻辑重复实现", + "acceptance": "mentions 在 @ 触发后按光标位置弹候选,↑↓/Enter/Tab 选择、Esc 关闭,选中插入文本并回调 onSelect;受控 value 信号;样式只消费 --ua-*;文档与演示同步;npm run check 全绿", + "status": "open", + "notes": "自补:组件补充方向" + }, + { + "id": "g133", + "title": "树形选择补 treeSelect 组件", + "motivation": "tree 与 select 都在,但「可搜索的层级下拉选择」缺位,级联选择 cascader 不适配任意深度懒加载场景", + "acceptance": "treeSelect 点击开弹层、支持搜索过滤、单选/多选、父子联动、清空,弹层沿用 overlay 的 Esc 栈与外部点击收起;样式只消费 --ua-*;文档与演示同步;npm run check 全绿", + "status": "open", + "notes": "自补:组件补充方向" } ] } diff --git a/.ua/index.json b/.ua/index.json index b665394..ba70278 100644 --- a/.ua/index.json +++ b/.ua/index.json @@ -1,6 +1,6 @@ { "$schema": "ui-arch-registry-index/0.1", - "generatedAt": "2026-10-26T06:49:03.720Z", + "generatedAt": "2026-10-26T07:17:51.824Z", "repoRoot": ".", "packages": [ { @@ -203,7 +203,8 @@ "multiSelect(props):多选下拉,标签式回显 + 可搜索弹层,点选不收起便于连续多选,max 到限后未选项禁用", "anchor(props):锚点导航,竖排区块链接,滚动时按目标位置高亮当前区块(aria-current 同步),点击按 offset 偏移量平滑定位并回写 hash;container 可指定内滚容器", "popconfirm(props, trigger):二次确认气泡,挂在任意触发器上,就地确认危险操作;确定后先收起再回调,取消 / 点外部 / Esc 走 onCancel,展开即聚焦「取消」", - "calendar(props):月历,整月网格内嵌页面(排期 / 值班 / 日程),每日可带 marker 标记文案,方向键漫游 + Enter 选中,翻月走 onMonthChange" + "calendar(props):月历,整月网格内嵌页面(排期 / 值班 / 日程),每日可带 marker 标记文案,方向键漫游 + Enter 选中,翻月走 onMonthChange", + "typography(props):排版文本,variant 决定语义(标题出 h1-h5 / 段落出 p / 行内出 span),支持语义色、装饰位、对齐、多行截断与复制按钮" ], "keyTypes": [ "ButtonProps", @@ -3075,6 +3076,91 @@ "需要显示每天有几件事就传 marker:返回非空文案即渲染小徽标,并拼进 aria-label 让读屏也听得到", "键盘:Tab 进入网格一次,←→ 逐日、↑↓ 逐周、Enter / 空格 选中;跨月会自动翻页并交接焦点" ] + }, + { + "id": "typography", + "title": "排版文本 Typography", + "demo": "sec-typography", + "props": [ + { + "n": "content", + "t": "string | Node", + "r": 1, + "d": "文本内容:字符串按纯文本渲染;传 DOM 则原样插入(此时不参与截断与复制)" + }, + { + "n": "variant", + "t": "'title' | 'text' | 'paragraph'", + "df": "text", + "d": "排版形态,决定语义标签与字号:title 出 h1-h5、paragraph 出 p、text 出 span" + }, + { + "n": "level", + "t": "1 | 2 | 3 | 4 | 5", + "df": "3", + "d": "标题层级,仅 variant=title 生效;越界值会被夹到 1-5" + }, + { + "n": "type", + "t": "'secondary' | 'success' | 'warning' | 'danger'", + "d": "语义色调;缺省主文本色" + }, + { + "n": "strong", + "t": "boolean", + "df": "false", + "d": "加粗强调" + }, + { + "n": "underline", + "t": "boolean", + "df": "false", + "d": "下划线(可为链接式文案保留指引视觉)" + }, + { + "n": "delete", + "t": "boolean", + "df": "false", + "d": "删除线,用于作废文案;会同时弱化文字颜色" + }, + { + "n": "mark", + "t": "boolean", + "df": "false", + "d": "高亮底纹(警示色 24% 混透明),用于搜索命中片段" + }, + { + "n": "code", + "t": "boolean", + "df": "false", + "d": "等宽代码样式,用于行内字段名 / 命令" + }, + { + "n": "align", + "t": "'left' | 'center' | 'right'", + "d": "水平对齐;缺省左对齐" + }, + { + "n": "ellipsis", + "t": "number", + "d": "截断行数(1 起):超出显示省略号,纯文本自动挂 title 供悬停看全文;缺省不截断" + }, + { + "n": "copyable", + "t": "boolean", + "df": "false", + "d": "右侧附复制按钮:点击写剪贴板并回显「已复制 / 复制失败」,1.2 秒后复原" + } + ], + "example": "typography({ variant: 'title', level: 2, content: '部署说明' }); typography({ content: 'RB-2048', code: true, copyable: true }); typography({ variant: 'paragraph', ellipsis: 2, content: '一段很长的说明……' })", + "steps": [ + "正文一律用 variant='text' 或 'paragraph',标题用 variant='title' + level,标签自动出 h1-h5,别自己写 h 标签再叠样式", + "文内强调用装饰位组合:strong 加粗、mark 高亮、code 等宽、underline / delete 表示链接与作废,一个组件内可叠加", + "长描述(卡片摘要、表格说明)用 ellipsis 给行数:超出显示省略号并自动挂 title,悬停即看全文,不必额外做展开按钮", + "订单号 / 密钥 / 命令这类要复制的内容传 copyable:按钮独立于文本节点,复制失败会回显「复制失败」而不是静默", + "content 传 DOM 时组件只负责语义标签与类名:截断与复制对 DOM 内容不生效(取不到稳定文本)", + "段落连续排列时相邻 .ua-type-paragraph 自动留出间距,单个段落不额外撑高,嵌进卡片也不会有空隙" + ] } ] }, diff --git a/AGENTS.md b/AGENTS.md index a58612f..c5c42eb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -21,7 +21,7 @@ - `packages/core/table-core`(@ui-arch/table-core):Headless 表格逻辑内核 - `packages/core/frame`(@ui-arch/frame):signal + hyperscript 微框架 -- `packages/skin/ui`(@ui-arch/ui):60 个 DOM 组件(表单 / 反馈 / 浮层 / 导航 / 表格),样式只消费令牌 +- `packages/skin/ui`(@ui-arch/ui):61 个 DOM 组件(表单 / 反馈 / 浮层 / 导航 / 表格),样式只消费令牌 - `packages/design/tokens`(@ui-arch/tokens):设计令牌 - `packages/tools/cli`(@ui-arch/cli):`ua` 命令行 + MCP 注册表服务(private) - `apps/demo`(@ui-arch/demo):交互演示,产物 apps/demo/dist/demo.html diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 26601bb..64762f3 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -34,7 +34,7 @@ ui-arch-mvp/ │ │ ├── table-core/ # 表格状态内核 │ │ └── frame/ # 信号 + hyperscript 微框架 │ ├── skin/ # 皮肤层(DOM 组件,可发布) -│ │ └── ui/ # 60 个组件 + ui.css +│ │ └── ui/ # 61 个组件 + ui.css │ ├── design/ # 资产层(可发布) │ │ └── tokens/ # --ua-* 设计令牌 │ └── tools/ # 工程层(private,不发布) diff --git a/README.md b/README.md index f732778..45f64c6 100644 --- a/README.md +++ b/README.md @@ -12,13 +12,13 @@ npm run docs # 生成人类文档站 apps/docs/index.html + Agent 全 npm run changelog # 依据 git 提交史重写 CHANGELOG.md(发版前跑;changelog:check 可查漂移) ``` -## 组件(共 60 个) +## 组件(共 61 个) -accordion · affix · alert · anchor · autoComplete · avatar · backTop · badge · breadcrumb · button · calendar · carousel · card · cascader · checkbox · checkboxGroup · colorPicker · dataTable · datePicker · descriptions · divider · drawer · empty · form · image · inputNumber · list · menu · modal · multiSelect · notification · pagination · popover · popconfirm · progress · radioGroup · rate · result · segmented · select · skeleton · slider · space · spinner · splitter · statistic · stepper · switchButton · tabs · tag · textField · textarea · timeline · toast · tooltip · tour · transfer · tree · upload · watermark +accordion · affix · alert · anchor · autoComplete · avatar · backTop · badge · breadcrumb · button · calendar · carousel · card · cascader · checkbox · checkboxGroup · colorPicker · dataTable · datePicker · descriptions · divider · drawer · empty · form · image · inputNumber · list · menu · modal · multiSelect · notification · pagination · popover · popconfirm · progress · radioGroup · rate · result · segmented · select · skeleton · slider · space · spinner · splitter · statistic · stepper · switchButton · tabs · tag · textField · textarea · timeline · toast · tooltip · tour · transfer · tree · typography · upload · watermark -分组:表单(button / textField / inputNumber / textarea / checkbox / checkboxGroup / colorPicker / switchButton / select / multiSelect / radioGroup / slider / rate / autoComplete / datePicker / segmented / upload / form + formItem / transfer / cascader)、反馈(alert / toast / notification / result / spinner / progress / skeleton / badge / empty / statistic)、浮层(tooltip / popover / popconfirm / modal / drawer / menu)、导航(tabs / breadcrumb / pagination / accordion / stepper / tree / anchor)、数据展示(dataTable / descriptions / card / list / timeline / tag / avatar / image / carousel / calendar)、其他(space / divider / splitter / backTop / affix / tour / watermark)。 +分组:表单(button / textField / inputNumber / textarea / checkbox / checkboxGroup / colorPicker / switchButton / select / multiSelect / radioGroup / slider / rate / autoComplete / datePicker / segmented / upload / form + formItem / transfer / cascader)、反馈(alert / toast / notification / result / spinner / progress / skeleton / badge / empty / statistic)、浮层(tooltip / popover / popconfirm / modal / drawer / menu)、导航(tabs / breadcrumb / pagination / accordion / stepper / tree / anchor)、数据展示(dataTable / descriptions / card / list / timeline / tag / avatar / image / carousel / calendar / typography)、其他(space / divider / splitter / backTop / affix / tour / watermark)。 ## 可访问性约定 @@ -32,7 +32,7 @@ accordion · affix · alert · anchor · autoComplete · avatar · backTop · ba |---|---| | `packages/core/frame` | @ui-arch/frame:signal / computed / effect + hyperscript(无虚拟 DOM) | | `packages/core/table-core` | @ui-arch/table-core:headless 表格(filter → sort → paginate 派生管线) | -| `packages/skin/ui` | @ui-arch/ui:60 个 DOM 组件 + ui.css(只消费 `--ua-*` 令牌) | +| `packages/skin/ui` | @ui-arch/ui:61 个 DOM 组件 + ui.css(只消费 `--ua-*` 令牌) | | `packages/design/tokens` | @ui-arch/tokens:设计令牌(暗色只换值不换名) | | `packages/tools/cli` | @ui-arch/cli:`ua` 命令行与 stdio MCP server | | `apps/demo` | 交互演示(esbuild 打包成单文件) | diff --git a/apps/demo/main.ts b/apps/demo/main.ts index c5420d1..2001257 100644 --- a/apps/demo/main.ts +++ b/apps/demo/main.ts @@ -70,6 +70,7 @@ import { toast, tour, tooltip, + typography, } from '@ui-arch/ui'; import type { RowKey } from '@ui-arch/ui'; @@ -1252,6 +1253,42 @@ const content = h( }), h('span', { class: 'out' }, () => `当前选中:${calDay() || '无'}`), ), + section( + 'sec-typography', + '排版文本', + 'typography 一个入口覆盖标题 / 正文 / 行内三种形态:variant 决定语义标签(标题出 h1-h5,读屏可按层级跳读),装饰位可叠加,长描述用 ellipsis 截断并自动挂 title 看全文,copyable 给订单号这类要复制的字段。', + card({ title: '标题层级' }, + typography({ variant: 'title', level: 3, content: '订单详情' }), + typography({ variant: 'title', level: 5, content: '第三节:配送与售后' }), + typography({ variant: 'paragraph', type: 'secondary', content: '标题按 level 出 h1-h5,字号由样式表按标签给,这里没写任何内联字号。' }), + ), + card({ title: '行内装饰' }, + typography({ + variant: 'paragraph', + content: h('span', null, + '本周新增 ', + typography({ content: '2', strong: true, type: 'success' }), + ' 个组件,门禁命令 ', + typography({ content: 'npm run check', code: true }), + ',搜索命中 ', + typography({ content: '排版', mark: true }), + ',已作废 ', + typography({ content: '旧接口', delete: true }), + '。'), + }), + ), + card({ title: '截断与复制' }, + typography({ + variant: 'paragraph', + ellipsis: 2, + content: '这一段故意写得很长用来演示多行截断:组件把行数写进 CSS 变量交给 -webkit-line-clamp,超出的部分显示省略号,同时把全文挂到 title 上,鼠标停留即可读完,调用方不需要自己算宽度或加「展开」按钮。', + }), + space({ size: 'sm' }, + typography({ content: 'RB-2048-0917-3355', code: true, copyable: true }), + typography({ content: '点右侧复制可写进剪贴板', type: 'secondary' }), + ), + ), + ), ); // 引导漫游实例:根节点需挂到 body(fixed 定位,不参与布局) diff --git a/llms-full.txt b/llms-full.txt index 5199320..f9a749c 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -1,6 +1,6 @@ # @ui-arch 组件库 Agent 文档(无图形版) -> 自动生成于 2026-10-26T06:49:03.720Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 +> 自动生成于 2026-10-26T07:17:51.824Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 > 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。 ## 快速上手 @@ -1668,6 +1668,40 @@ const day = signal(''); calendar({ value: day, marker: (d) => (d === '2026-10-26 现场演示:apps/docs/demo.html#sec-calendar +### typography — 排版文本 Typography + +参数: + +- content(必填):string | Node —— 文本内容:字符串按纯文本渲染;传 DOM 则原样插入(此时不参与截断与复制) +- variant(默认 text):'title' | 'text' | 'paragraph' —— 排版形态,决定语义标签与字号:title 出 h1-h5、paragraph 出 p、text 出 span +- level(默认 3):1 | 2 | 3 | 4 | 5 —— 标题层级,仅 variant=title 生效;越界值会被夹到 1-5 +- type:'secondary' | 'success' | 'warning' | 'danger' —— 语义色调;缺省主文本色 +- strong(默认 false):boolean —— 加粗强调 +- underline(默认 false):boolean —— 下划线(可为链接式文案保留指引视觉) +- delete(默认 false):boolean —— 删除线,用于作废文案;会同时弱化文字颜色 +- mark(默认 false):boolean —— 高亮底纹(警示色 24% 混透明),用于搜索命中片段 +- code(默认 false):boolean —— 等宽代码样式,用于行内字段名 / 命令 +- align:'left' | 'center' | 'right' —— 水平对齐;缺省左对齐 +- ellipsis:number —— 截断行数(1 起):超出显示省略号,纯文本自动挂 title 供悬停看全文;缺省不截断 +- copyable(默认 false):boolean —— 右侧附复制按钮:点击写剪贴板并回显「已复制 / 复制失败」,1.2 秒后复原 + +示例: + +``` +typography({ variant: 'title', level: 2, content: '部署说明' }); typography({ content: 'RB-2048', code: true, copyable: true }); typography({ variant: 'paragraph', ellipsis: 2, content: '一段很长的说明……' }) +``` + +手把手: + +1. 正文一律用 variant='text' 或 'paragraph',标题用 variant='title' + level,标签自动出 h1-h5,别自己写 h 标签再叠样式 +2. 文内强调用装饰位组合:strong 加粗、mark 高亮、code 等宽、underline / delete 表示链接与作废,一个组件内可叠加 +3. 长描述(卡片摘要、表格说明)用 ellipsis 给行数:超出显示省略号并自动挂 title,悬停即看全文,不必额外做展开按钮 +4. 订单号 / 密钥 / 命令这类要复制的内容传 copyable:按钮独立于文本节点,复制失败会回显「复制失败」而不是静默 +5. content 传 DOM 时组件只负责语义标签与类名:截断与复制对 DOM 内容不生效(取不到稳定文本) +6. 段落连续排列时相邻 .ua-type-paragraph 自动留出间距,单个段落不额外撑高,嵌进卡片也不会有空隙 + +现场演示:apps/docs/demo.html#sec-typography + API 速览: - button(props):按钮,variant = primary | ghost | danger,disabled 支持布尔或信号禁用;type 缺省 button(表单内不隐式提交) @@ -1732,6 +1766,7 @@ API 速览: - anchor(props):锚点导航,竖排区块链接,滚动时按目标位置高亮当前区块(aria-current 同步),点击按 offset 偏移量平滑定位并回写 hash;container 可指定内滚容器 - popconfirm(props, trigger):二次确认气泡,挂在任意触发器上,就地确认危险操作;确定后先收起再回调,取消 / 点外部 / Esc 走 onCancel,展开即聚焦「取消」 - calendar(props):月历,整月网格内嵌页面(排期 / 值班 / 日程),每日可带 marker 标记文案,方向键漫游 + Enter 选中,翻月走 onMonthChange +- typography(props):排版文本,variant 决定语义(标题出 h1-h5 / 段落出 p / 行内出 span),支持语义色、装饰位、对齐、多行截断与复制按钮 一行用法: diff --git a/llms.txt b/llms.txt index 299eb3f..8f502c2 100644 --- a/llms.txt +++ b/llms.txt @@ -109,6 +109,7 @@ - anchor(props):锚点导航,竖排区块链接,滚动时按目标位置高亮当前区块(aria-current 同步),点击按 offset 偏移量平滑定位并回写 hash;container 可指定内滚容器 - popconfirm(props, trigger):二次确认气泡,挂在任意触发器上,就地确认危险操作;确定后先收起再回调,取消 / 点外部 / Esc 走 onCancel,展开即聚焦「取消」 - calendar(props):月历,整月网格内嵌页面(排期 / 值班 / 日程),每日可带 marker 标记文案,方向键漫游 + Enter 选中,翻月走 onMonthChange + - typography(props):排版文本,variant 决定语义(标题出 h1-h5 / 段落出 p / 行内出 span),支持语义色、装饰位、对齐、多行截断与复制按钮 - 用法: `const checked = signal(false); checkbox({ label: '同意', checked }); const open = signal(false); modal({ title: '确认', open }, '内容')` - 约定: 皮肤不持有业务状态,一律委托 headless 内核或调用方信号 - 约定: 表单类组件统一约定:*Props.value/checked/active 为 Signal 双向绑定,onChange/onInput 为回调 diff --git a/packages/skin/ui/src/index.ts b/packages/skin/ui/src/index.ts index 123833f..02e5bd8 100644 --- a/packages/skin/ui/src/index.ts +++ b/packages/skin/ui/src/index.ts @@ -60,3 +60,4 @@ export { affix } from './affix.js'; export { anchor } from './anchor.js'; export { popconfirm } from './popconfirm.js'; export { calendar } from './calendar.js'; +export { typography } from './typography.js'; diff --git a/packages/skin/ui/src/types.ts b/packages/skin/ui/src/types.ts index a913381..7bbc472 100644 --- a/packages/skin/ui/src/types.ts +++ b/packages/skin/ui/src/types.ts @@ -741,4 +741,20 @@ export interface CalendarProps { onMonthChange?: (month: string) => void; // 翻月回调,参数为 YYYY-MM } +/*** 排版文本入参(variant 决定语义标签:title 出 h1-h5、paragraph 出 p、text 出 span) ***/ +export interface TypographyProps { + content: string | Node; // 文本内容:字符串按纯文本渲染,传 DOM 则原样插入 + variant?: 'title' | 'text' | 'paragraph'; // 排版形态;缺省 text + level?: 1 | 2 | 3 | 4 | 5; // 标题层级,仅 variant=title 生效;缺省 3 + type?: 'secondary' | 'success' | 'warning' | 'danger'; // 语义色调;缺省主文本色 + strong?: boolean; // 加粗强调;缺省 false + underline?: boolean; // 下划线;缺省 false + delete?: boolean; // 删除线(作废文案);缺省 false + mark?: boolean; // 高亮底纹;缺省 false + code?: boolean; // 等宽代码样式;缺省 false + align?: 'left' | 'center' | 'right'; // 水平对齐;缺省左对齐 + ellipsis?: number; // 截断行数(1 起),超出显示省略号且悬停给全文;缺省不截断 + copyable?: boolean; // 附复制按钮:点后写剪贴板并短暂回显结果;缺省 false +} + export type { ColumnDef, RowKey }; // 再导出常用内核类型,方便调用方 diff --git a/packages/skin/ui/src/typography.ts b/packages/skin/ui/src/typography.ts new file mode 100644 index 0000000..6277d0d --- /dev/null +++ b/packages/skin/ui/src/typography.ts @@ -0,0 +1,55 @@ +import { h } from '@ui-arch/frame'; +import type { TypographyProps } from './types.js'; + +// 标题层级 → 语义标签(level 已在入口夹到 1-5) +const HEADING = ['h1', 'h2', 'h3', 'h4', 'h5'] as const; +// 形态 → 缺省标签 +const TAG: Record = { title: 'h3', text: 'span', paragraph: 'p' }; + +/*** 复制到剪贴板:优先异步 API,非安全上下文退回复选式复制,两者都失败返回 false ***/ +function copyText(text: string): Promise { + if (navigator.clipboard?.writeText) return navigator.clipboard.writeText(text).then(() => true, () => false); + const box = h('textarea', { class: 'ua-type-copybox', readonly: true }, text) as HTMLTextAreaElement; + document.body.append(box); + box.select(); // 不可见文本框选中后再走兼容接口 + const ok = document.execCommand('copy'); + box.remove(); + return Promise.resolve(ok); +} + +/*** 类名拼装:形态 + 语义色 + 装饰位,均为纯类名,颜色留给样式表与令牌 ***/ +function classOf(props: TypographyProps): string { + const variant = props.variant ?? 'text'; + return ['ua-type', `ua-type-${variant}`, props.type ? `is-${props.type}` : '', props.strong ? 'is-strong' : '', + props.underline ? 'is-underline' : '', props.delete ? 'is-delete' : '', props.mark ? 'is-mark' : '', + props.code ? 'is-code' : '', props.align ? `is-${props.align}` : '', props.ellipsis ? 'is-clamped' : ''] + .filter(Boolean).join(' '); +} + +/*** 复制按钮:点击后短暂回显结果,1.2 秒复原;按钮自身不参与文本截断 ***/ +function copyButton(el: HTMLElement): HTMLElement { + const btn = h('button', { class: 'ua-type-copy', type: 'button', 'aria-label': '复制文本' }, '复制'); + btn.addEventListener('click', async () => { + const ok = await copyText(el.textContent ?? ''); + btn.textContent = ok ? '已复制' : '复制失败'; + btn.classList.toggle('is-ok', ok); // 成功态换色,失败保持中性 + setTimeout(() => { + btn.textContent = '复制'; + btn.classList.remove('is-ok'); + }, 1200); // 回显窗口 + }); + return btn; +} + +/*** 排版文本:variant 决定语义标签(title 出 h1-h5、paragraph 出 p、text 出 span),ellipsis 走多行截断,copyable 附复制按钮 ***/ +export function typography(props: TypographyProps): HTMLElement { + const variant = props.variant ?? 'text'; + const level = Math.min(Math.max(props.level ?? 3, 1), 5); // 层级夹取,避免越界索引 + const tag = variant === 'title' ? HEADING[level - 1] : TAG[variant]; + const plain = typeof props.content === 'string' ? props.content : null; // 只有字符串可安全截断 + const el = h(tag, { class: classOf(props) }, props.content); + el.style.setProperty('--ua-type-clamp', String(props.ellipsis ?? 0)); // 行数交给 CSS 变量,样式表统一 clamp + if (plain && props.ellipsis) el.title = plain; // 截断后悬停可读全文 + if (!props.copyable) return el; + return h('span', { class: 'ua-type-wrap' }, el, copyButton(el)); +} diff --git a/packages/skin/ui/ua.manifest.json b/packages/skin/ui/ua.manifest.json index dc44116..8b1fb4c 100644 --- a/packages/skin/ui/ua.manifest.json +++ b/packages/skin/ui/ua.manifest.json @@ -74,7 +74,8 @@ "multiSelect(props):多选下拉,标签式回显 + 可搜索弹层,点选不收起便于连续多选,max 到限后未选项禁用", "anchor(props):锚点导航,竖排区块链接,滚动时按目标位置高亮当前区块(aria-current 同步),点击按 offset 偏移量平滑定位并回写 hash;container 可指定内滚容器", "popconfirm(props, trigger):二次确认气泡,挂在任意触发器上,就地确认危险操作;确定后先收起再回调,取消 / 点外部 / Esc 走 onCancel,展开即聚焦「取消」", - "calendar(props):月历,整月网格内嵌页面(排期 / 值班 / 日程),每日可带 marker 标记文案,方向键漫游 + Enter 选中,翻月走 onMonthChange" + "calendar(props):月历,整月网格内嵌页面(排期 / 值班 / 日程),每日可带 marker 标记文案,方向键漫游 + Enter 选中,翻月走 onMonthChange", + "typography(props):排版文本,variant 决定语义(标题出 h1-h5 / 段落出 p / 行内出 span),支持语义色、装饰位、对齐、多行截断与复制按钮" ], "keyTypes": [ "ButtonProps", @@ -2946,6 +2947,91 @@ "需要显示每天有几件事就传 marker:返回非空文案即渲染小徽标,并拼进 aria-label 让读屏也听得到", "键盘:Tab 进入网格一次,←→ 逐日、↑↓ 逐周、Enter / 空格 选中;跨月会自动翻页并交接焦点" ] + }, + { + "id": "typography", + "title": "排版文本 Typography", + "demo": "sec-typography", + "props": [ + { + "n": "content", + "t": "string | Node", + "r": 1, + "d": "文本内容:字符串按纯文本渲染;传 DOM 则原样插入(此时不参与截断与复制)" + }, + { + "n": "variant", + "t": "'title' | 'text' | 'paragraph'", + "df": "text", + "d": "排版形态,决定语义标签与字号:title 出 h1-h5、paragraph 出 p、text 出 span" + }, + { + "n": "level", + "t": "1 | 2 | 3 | 4 | 5", + "df": "3", + "d": "标题层级,仅 variant=title 生效;越界值会被夹到 1-5" + }, + { + "n": "type", + "t": "'secondary' | 'success' | 'warning' | 'danger'", + "d": "语义色调;缺省主文本色" + }, + { + "n": "strong", + "t": "boolean", + "df": "false", + "d": "加粗强调" + }, + { + "n": "underline", + "t": "boolean", + "df": "false", + "d": "下划线(可为链接式文案保留指引视觉)" + }, + { + "n": "delete", + "t": "boolean", + "df": "false", + "d": "删除线,用于作废文案;会同时弱化文字颜色" + }, + { + "n": "mark", + "t": "boolean", + "df": "false", + "d": "高亮底纹(警示色 24% 混透明),用于搜索命中片段" + }, + { + "n": "code", + "t": "boolean", + "df": "false", + "d": "等宽代码样式,用于行内字段名 / 命令" + }, + { + "n": "align", + "t": "'left' | 'center' | 'right'", + "d": "水平对齐;缺省左对齐" + }, + { + "n": "ellipsis", + "t": "number", + "d": "截断行数(1 起):超出显示省略号,纯文本自动挂 title 供悬停看全文;缺省不截断" + }, + { + "n": "copyable", + "t": "boolean", + "df": "false", + "d": "右侧附复制按钮:点击写剪贴板并回显「已复制 / 复制失败」,1.2 秒后复原" + } + ], + "example": "typography({ variant: 'title', level: 2, content: '部署说明' }); typography({ content: 'RB-2048', code: true, copyable: true }); typography({ variant: 'paragraph', ellipsis: 2, content: '一段很长的说明……' })", + "steps": [ + "正文一律用 variant='text' 或 'paragraph',标题用 variant='title' + level,标签自动出 h1-h5,别自己写 h 标签再叠样式", + "文内强调用装饰位组合:strong 加粗、mark 高亮、code 等宽、underline / delete 表示链接与作废,一个组件内可叠加", + "长描述(卡片摘要、表格说明)用 ellipsis 给行数:超出显示省略号并自动挂 title,悬停即看全文,不必额外做展开按钮", + "订单号 / 密钥 / 命令这类要复制的内容传 copyable:按钮独立于文本节点,复制失败会回显「复制失败」而不是静默", + "content 传 DOM 时组件只负责语义标签与类名:截断与复制对 DOM 内容不生效(取不到稳定文本)", + "段落连续排列时相邻 .ua-type-paragraph 自动留出间距,单个段落不额外撑高,嵌进卡片也不会有空隙" + ] } ] } diff --git a/packages/skin/ui/ui.css b/packages/skin/ui/ui.css index e46bd03..eb3b548 100644 --- a/packages/skin/ui/ui.css +++ b/packages/skin/ui/ui.css @@ -3961,3 +3961,87 @@ button.ua-step-dot:hover { pointer-events: auto; box-shadow: 0 0 24px rgba(0, 0, 0, 0.18); } + +/* —— 排版文本:层级靠字号与字重区分,语义色与装饰位只换颜色,不引布局 —— */ +.ua-type { + color: var(--ua-color-text); + font-size: var(--ua-font-size-md); + line-height: 1.65; + margin: 0; +} +.ua-type-paragraph + .ua-type-paragraph { + margin-top: var(--ua-space-2); /* 段落之间留呼吸,单段不额外撑高 */ +} +.ua-type-title { + font-weight: 600; + line-height: 1.35; + letter-spacing: -0.01em; +} +h1.ua-type-title { font-size: calc(var(--ua-font-size-lg) * 1.6); } +h2.ua-type-title { font-size: calc(var(--ua-font-size-lg) * 1.32); } +h3.ua-type-title { font-size: calc(var(--ua-font-size-lg) * 1.12); } +h4.ua-type-title { font-size: var(--ua-font-size-lg); } +h5.ua-type-title { font-size: var(--ua-font-size-md); } +.ua-type.is-secondary { color: var(--ua-color-text-muted); } +.ua-type.is-success { color: var(--ua-color-success); } +.ua-type.is-warning { color: var(--ua-color-warning); } +.ua-type.is-danger { color: var(--ua-color-danger); } +.ua-type.is-strong { font-weight: 600; } +.ua-type.is-underline { text-decoration: underline; text-underline-offset: 2px; } +.ua-type.is-delete { text-decoration: line-through; color: var(--ua-color-text-muted); } +.ua-type.is-underline.is-delete { text-decoration: underline line-through; } +.ua-type.is-mark { + background: color-mix(in srgb, var(--ua-color-warning) 24%, transparent); + border-radius: var(--ua-radius-sm); + padding: 0 2px; +} +.ua-type.is-code { + font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; + font-size: 0.92em; + background: color-mix(in srgb, var(--ua-color-border) 45%, transparent); + border-radius: var(--ua-radius-sm); + padding: 1px var(--ua-space-1); +} +.ua-type.is-center { text-align: center; } +.ua-type.is-right { text-align: right; } +.ua-type.is-clamped { + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: var(--ua-type-clamp, 1); + overflow: hidden; + text-overflow: ellipsis; +} +.ua-type-wrap { + display: inline-flex; + align-items: baseline; + gap: var(--ua-space-1); + max-width: 100%; +} +.ua-type-copy { + border: 0; + background: transparent; + color: var(--ua-color-text-muted); + font: inherit; + font-size: var(--ua-font-size-sm); + padding: 0 2px; + border-radius: var(--ua-radius-sm); + cursor: pointer; + transition: color 0.15s ease, background 0.15s ease; +} +.ua-type-copy:hover { + color: var(--ua-color-accent); + background: color-mix(in srgb, var(--ua-color-accent) 10%, transparent); +} +.ua-type-copy.is-ok { color: var(--ua-color-success); } +.ua-type-copybox { + position: fixed; + left: -9999px; + top: 0; + opacity: 0; /* 兼容复制用的离屏文本框,不参与视觉布局 */ + pointer-events: none; +} +@media (prefers-reduced-motion: reduce) { + .ua-type-copy { + transition: none; + } +}