@linhey/rhine-design 0.10.0 → 0.11.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 +13 -0
- package/CONTRIBUTING.md +3 -1
- package/README.md +17 -14
- package/components/COMMON_COMPONENTS.md +5 -3
- package/components/README.md +13 -5
- package/components/SPACING.md +9 -0
- package/components/Toast.tsx +10 -4
- package/components/common.css +2 -0
- package/components/components.tsx +13 -7
- package/components/styles.css +12 -0
- package/docs/DESIGN_SYSTEM.md +10 -1
- package/docs/DESIGN_SYSTEM_AUDIT.md +4 -4
- package/docs/DISCLOSURE_VERIFICATION.md +49 -0
- package/docs/INTERACTION_PATTERNS.md +9 -1
- package/docs/REVIEW_TOOL.md +128 -0
- package/docs/REVIEW_VERIFICATION.md +56 -0
- package/docs/SUMMARY_TABS.md +77 -0
- package/docs/THEMING.md +1 -1
- package/docs/assets/disclosure/arknights-400.png +0 -0
- package/docs/assets/disclosure/neo-brutalism-400.png +0 -0
- package/docs/assets/disclosure/rhine-400.png +0 -0
- package/docs/assets/disclosure/rhine-native200.png +0 -0
- package/examples/review/http/App.tsx +46 -0
- package/examples/review/http/catalog.tsx +44 -0
- package/examples/review/http/details.ts +1 -0
- package/examples/review/http/vite.config.ts +8 -0
- package/examples/review/platform/Workspace.tsx +34 -0
- package/examples/review/platform/catalog.tsx +46 -0
- package/examples/review/platform/vite.config.ts +8 -0
- package/examples/review/product.css +3 -0
- package/package.json +17 -5
- package/preview/CommonGallery.tsx +1 -1
- package/preview/GalleryShell.tsx +3 -2
- package/preview/OrbitGallery.tsx +1 -1
- package/preview/SpacingGallery.tsx +9 -1
- package/preview/SummaryTabsGallery.tsx +37 -0
- package/preview/main.tsx +7 -3
- package/preview/packageInfo.ts +5 -0
- package/review/canvas.tsx +107 -0
- package/review/dev-client.mjs +17 -0
- package/review/feedback.ts +17 -0
- package/review/index.ts +2 -0
- package/review/isolation.ts +64 -0
- package/review/lifecycle.ts +59 -0
- package/review/protocol.ts +7 -0
- package/review/review.css +41 -0
- package/review/shell.tsx +165 -0
- package/review/state.ts +41 -0
- package/review/types.ts +59 -0
- package/review/vite.d.mts +10 -0
- package/review/vite.mjs +107 -0
- package/scripts/check-review-package.mjs +11 -0
- package/scripts/check-review-recovery.js +47 -0
- package/scripts/check-review.js +153 -0
- package/skills/rhine-design/references/API_MAP.md +4 -0
- package/skills/rhine-design/references/TASK_MAP.md +2 -0
- package/tests/CommonComponents.test.tsx +8 -0
- package/tests/DesignSystem.test.tsx +32 -0
- package/tests/Review.test.tsx +117 -0
- package/tests/ReviewBuild.test.tsx +59 -0
- package/tests/ReviewShell.test.tsx +63 -0
- package/tsconfig.json +5 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# 版本记录
|
|
2
2
|
|
|
3
|
+
## 0.11.0(2026-10-27)
|
|
4
|
+
|
|
5
|
+
本版本新增产品评审工具及以下已合并能力;npm 0.10.0 不包含这些新增 API。组件主入口仍为 67 项,默认行为保持兼容;消费方固定 0.11.0 并提交 lockfile。实际发布可用性以 registry 为准。
|
|
6
|
+
|
|
7
|
+
- #40:新增 `/review`、`/review/vite` 产品评审工具,提供场景协议、目录/控件、URL 状态、合成 iframe、生命周期、独立静态构建与正式包排除检查;附 HTTP 与平台 client 两类例子,静态资源支持项目型站点的嵌套部署路径;补充连接/清理超时恢复、持久错误反馈、折叠高级设置与实际画布尺寸。已在两类真实 App 的隔离副本试用,消费迁移按产品分别验证,见[契约](docs/REVIEW_TOOL.md)。
|
|
8
|
+
|
|
9
|
+
- `Tabs` / `TabItem` 新增 `description`、`status`,提供带身份摘要与只读状态的页签;契约见[Summary Tabs](docs/SUMMARY_TABS.md),展台为 `#summary-tabs`。
|
|
10
|
+
- `Toast` 新增 `placement="floating"`,通过主题根 Portal 呈现浮动通知;默认 inline 保持兼容,见[通知契约](components/COMMON_COMPONENTS.md)。
|
|
11
|
+
- `SegmentedControl` 新增 `size="compact"`,支持卡片局部指标切换;见[间距规范](components/SPACING.md)。0.10.0 已有的 `fullWidth` 不受影响。
|
|
12
|
+
- 相邻 `Disclosure` 共用分隔线;补充上下文帮助与主题背景/文字配对规范。
|
|
13
|
+
- 修复 `Disclosure` 在生产 CSS 将 `200ms` 压缩为 `.2s` 时误用 0.2ms 动画的问题;按单位换算为毫秒,保持 200ms 开合、快速反向和减少动态行为。
|
|
14
|
+
- 展台首页和页脚按当前主入口统一为 67 个组件,源码包版本改从 `package.json` 读取;修正轨道主题艺术的版本标记及文档中的旧数量、候选状态。
|
|
15
|
+
|
|
3
16
|
## 0.10.0(2026-10-26)
|
|
4
17
|
|
|
5
18
|
- #31:MediaViewer 说明补齐 caption 12/18/400,EmptyState 标题采用 heading 16/24 与主题标题字重;同类复制反馈、步骤/表单组说明、错误摘要和主题预览状态补齐语义配对,保留既有密度例外。真实回归入口 `#component-typography`。
|
package/CONTRIBUTING.md
CHANGED
|
@@ -25,7 +25,7 @@ npm run dev
|
|
|
25
25
|
npm run check
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
它包含测试、类型检查、展台构建和 CSS/SVG
|
|
28
|
+
它包含测试、类型检查、展台构建和 CSS/SVG 产物检查,并构建两个独立产品评审示例。产品评审入口、模拟生命周期与浏览器脚本见 [评审工具](docs/REVIEW_TOOL.md) 和 [验收记录](docs/REVIEW_VERIFICATION.md)。视觉变化还需检查 400/520/880 宽度、200% 缩放和相关交互。纯文档修改检查链接与示例即可。
|
|
29
29
|
|
|
30
30
|
`dist/` 是展台构建结果;包导出源码。修改打包配置时,请在仓库外安装实际 tarball 验证。不要删除入口和 CSS 的 `sideEffects`;Logo 依赖 SVG `?no-inline`。本地链接消费项目时,Vite 使用 `resolve.dedupe: ['react', 'react-dom']` 避免重复 React。
|
|
31
31
|
|
|
@@ -35,6 +35,8 @@ README 面向首次接触项目的开发者,优先解释用途、安装、示
|
|
|
35
35
|
|
|
36
36
|
组件 API 变更时,更新契约、示例以及 skill 的 `references/API_MAP.md` 或 `TASK_MAP.md`。索引指向真实源码,不复制第二份 API 定义;skill 的版本信息随包版本更新。
|
|
37
37
|
|
|
38
|
+
展台包版本由 `preview/packageInfo.ts` 从 `package.json` 读取;组件数量只统计 `components/index.ts` 的公开组件,不计类型与独立主题入口。新增导出时同步该数量、README 完整清单与组件入口。发布后的源码新增能力写入 CHANGELOG 的“未发布”部分,并在相关契约标明旧包不包含;不要因为源码仍使用同一版本字段,就将新能力归入已发布版本。历史版本介绍与验收记录保留原时点。
|
|
39
|
+
|
|
38
40
|
## 提交 Pull Request
|
|
39
41
|
|
|
40
42
|
说明解决的问题、变更后的行为,以及已执行的验证。涉及视觉或动效时附上截图或录屏;涉及破坏性 API 变更时提供迁移说明并更新 [CHANGELOG](CHANGELOG.md)。
|
package/README.md
CHANGED
|
@@ -16,29 +16,25 @@
|
|
|
16
16
|
<a href="CHANGELOG.md">版本记录</a>
|
|
17
17
|
</p>
|
|
18
18
|
|
|
19
|
-
Rhine Design 是面向 React 应用的开源设计系统,适合桌面工具、管理界面和数据密集型应用。它将设计规范、
|
|
19
|
+
Rhine Design 是面向 React 应用的开源设计系统,适合桌面工具、管理界面和数据密集型应用。它将设计规范、67 个组件和动效示例放在一起,帮助团队建立一致、可复用的界面语言。
|
|
20
20
|
|
|
21
21
|
- **一致的视觉语言**:色彩、字体、文字层级、间距、几何与焦点规则。
|
|
22
|
-
- **
|
|
22
|
+
- **67 个可组合组件**:覆盖布局、表单、数据、导航、浮层和反馈。
|
|
23
23
|
- **可验证的动效**:12 组播放 / 重播 / 复位演示,以及三角构架启动编排。
|
|
24
24
|
- **面向开发协作**:组件契约、交互展台和 AI skill 帮助设计与实现保持一致。
|
|
25
25
|
|
|
26
|
-
**0.4.0 新增能力:**主题 API、三套内置主题,以及导航、表单流程与健康时间轴组件;在线展台跟随 main,安装前请核对[版本记录](CHANGELOG.md)。新增组件及选型见[交互规范](docs/INTERACTION_PATTERNS.md),参考项目见[GitHub 调研](docs/REFERENCE_RESEARCH.md)。
|
|
27
|
-
|
|
28
26
|
基于 React 与 Radix UI,采用 [Apache-2.0](LICENSE) 许可证。
|
|
29
27
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
**0.8.0 新增能力**:三主题 Three.js 艺术分别为折纸鸟、源石晶簇与草莓甜甜圈,保留原始轨道切换;新增 `NeutronStarBackdrop`、`StatusTimelineList` 、`AppShellToggle` 与 `ButtonLink`,公共组件总数为 66。包含暂停、减少动态、静态降级与资源清理。安装前核对[版本记录](CHANGELOG.md)与 registry;在线展台跟随 main。
|
|
28
|
+
## 版本与源码范围
|
|
33
29
|
|
|
34
|
-
0.
|
|
30
|
+
- **本源码包版本:0.11.0**。主入口导出 67 个组件,统计不含类型及独立 `/theme` 入口的 ThemeProvider、hook 和元数据。发布是否可安装以 npm registry 为准。
|
|
31
|
+
- **0.11.0 新增**:产品评审 `/review`、`/review/vite`,摘要页签 `Tabs description/status`、`Toast placement="floating"`、`SegmentedControl size="compact"` 及相邻 Disclosure 分隔线与生产动效修正。npm 0.10.0 不包含这些新增 API。
|
|
32
|
+
- **展台随源码更新**,在线部署跟随 main;安装包的能力以精确版本和实际导出/实现为准。上述源码变更、历史版本与接入边界统一见[版本记录](CHANGELOG.md)。
|
|
35
33
|
|
|
36
|
-
|
|
34
|
+
三套主题、轨道艺术、设置导航与主题选择等已有能力见下方组件和规范入口;应用组件的 Ant Design / MUI 对照见[对标清单](docs/ANTD_MUI_AUDIT.md)。
|
|
37
35
|
|
|
38
36
|
<a id="quick-start"></a>
|
|
39
37
|
|
|
40
|
-
**0.8.0 新增**:新增 [主题预览与选择](docs/THEME_PICKER.md)、[设置弹窗导航](docs/SETTINGS_DIALOG.md)及[浮层缩放修复](docs/FLOATING_OVERLAYS.md)。0.7.0 候选未发布;本版承接其全部已接受功能。
|
|
41
|
-
|
|
42
38
|
## 快速接入
|
|
43
39
|
|
|
44
40
|
需要 **React 19**。当前已验证的构建环境为 **Vite**,开发建议使用 **Node 24**。
|
|
@@ -46,7 +42,7 @@ Rhine Design 是面向 React 应用的开源设计系统,适合桌面工具、
|
|
|
46
42
|
在你的 React 项目中安装:
|
|
47
43
|
|
|
48
44
|
```sh
|
|
49
|
-
npm install --save-exact @linhey/rhine-design@0.
|
|
45
|
+
npm install --save-exact @linhey/rhine-design@0.11.0
|
|
50
46
|
```
|
|
51
47
|
|
|
52
48
|
包与版本信息见 [npm](https://www.npmjs.com/package/@linhey/rhine-design)。如需使用未发版的源码,可安装 `git+https://github.com/linhay/rhine-design.git#<commit>`;建议固定版本并提交 lockfile。
|
|
@@ -135,13 +131,14 @@ export function App() {
|
|
|
135
131
|
| 品牌与文字 | Logo · Text |
|
|
136
132
|
| 操作 | Button · ButtonLink⁴ · Icon · IconButton · Link · CopyButton |
|
|
137
133
|
| 布局与背景 | Surface · Toolbar · Divider · Stack · Inline · Grid · Background² · AppShell² · AppShellToggle⁴ · OrbitBackdrop³ · NeutronStarBackdrop⁴ |
|
|
138
|
-
| 表单与选择 | TextField · Fieldset¹ · ErrorSummary¹ · Textarea · Select · Checkbox · RadioGroup · Switch · SearchField · Combobox · SelectionCard · DateField² · DateRangePicker² · NumberField² · TreeSelect² · RemoteMultiSelect² |
|
|
134
|
+
| 表单与选择 | TextField · Fieldset¹ · ErrorSummary¹ · Textarea · Select · Checkbox · RadioGroup · Switch · SearchField · Combobox · SelectionCard · DateField² · DateRangePicker² · NumberField² · TreeSelect² · RemoteMultiSelect² · ThemePicker⁴ · ThemePreview⁴ |
|
|
139
135
|
| 导航与展开 | SideNav¹ · Breadcrumbs¹ · Steps¹ · Tabs · SegmentedControl · Disclosure |
|
|
136
|
+
| 搜索与筛选组合 | ListFilterBar⁵ |
|
|
140
137
|
| 数据与媒体 | Metric · KeyValue · DataTable · Pagination · StatusTimeline¹ · StatusTimelineList⁴ · TimelineLegend¹ · TrendChart² · StackedBar² · MediaCard² · MediaViewer² |
|
|
141
138
|
| 浮层 | Drawer¹ · Dialog · ConfirmDialog · Popover · Menu · Tooltip |
|
|
142
139
|
| 状态与反馈 | Badge · Notice · Toast · Progress · Skeleton · EmptyState |
|
|
143
140
|
|
|
144
|
-
¹ 自 0.4.0 提供。² 自 0.5.0 提供。³ 自 0.6.0 提供。⁴ 自 0.8.0 提供。
|
|
141
|
+
¹ 自 0.4.0 提供。² 自 0.5.0 提供。³ 自 0.6.0 提供。⁴ 自 0.8.0 提供。⁵ 自 0.9.0 提供。
|
|
145
142
|
|
|
146
143
|
参数、状态和组合示例见[组件 API](components/README.md)与[常用组件契约](components/COMMON_COMPONENTS.md)。
|
|
147
144
|
|
|
@@ -208,6 +205,12 @@ skill 中提供以下参考入口:
|
|
|
208
205
|
|
|
209
206
|
升级组件时请同步更新 skill;如果已做本地定制,先比较差异再合并。其他工具请将同一 skill 目录放入其支持的技能路径。
|
|
210
207
|
|
|
208
|
+
## 产品设计稿评审工具(自 0.11.0)
|
|
209
|
+
|
|
210
|
+
新增独立 `/review` 与 `/review/vite` 入口,统一产品场景目录、主题/尺寸/状态控制、URL 分享、隔离画布和静态构建。产品保留页面、HTTP/平台模拟与业务知识,无需复制通用预览壳。**npm 0.10.0 尚不包含此工具**。
|
|
211
|
+
|
|
212
|
+
源码可运行 `npm run review:http`、`npm run review:platform`,或 `npm run review:build` 构建两个合成消费示例。接入、生命周期、部署与迁移见[产品评审契约](docs/REVIEW_TOOL.md),实际覆盖与剩余边界见[验收记录](docs/REVIEW_VERIFICATION.md)。
|
|
213
|
+
|
|
211
214
|
## 参与贡献
|
|
212
215
|
|
|
213
216
|
欢迎提交问题、文档改进和组件提案。遇到问题时,请在 [Issues](https://github.com/linhay/rhine-design/issues) 提供组件名、版本、预期行为及最小复现。
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# 莱茵设计 · 常用组件契约
|
|
1
|
+
# 莱茵设计 · 常用组件契约
|
|
2
2
|
|
|
3
3
|
本文件定义[莱茵设计](../README.md)的常用组件契约。先固定契约和组件展台,再验证;默认优先复用本目录,页面先改设计稿,再按授权接入App。具体业务由宿主定义,不进入通用契约。
|
|
4
4
|
|
|
@@ -31,7 +31,7 @@ SideNav、Breadcrumbs、Steps、Drawer、Fieldset、ErrorSummary、StatusTimelin
|
|
|
31
31
|
| Menu | 操作项/禁用/危险项;方向键/Home/End/输入字符跳转;不是表单选择器 |
|
|
32
32
|
| Divider / Stack / Inline / Grid | 语义分隔线、纵排、可换行横排、自适应网格;只消费间距tokens |
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
上表以早期 V1.4 文档的 24 个扩展组件为基础,当时与已有 16 个合计 40 个;V1.4 是历史文档阶段编号,不是 npm 包版本。当前完整清单与版本边界见[组件入口](README.md)。复杂浮层使用Radix无样式基础,避免维护重复焦点/菜单/碰撞实现;样式由Rhine Design定义。原生表单、数据与布局不引入额外框架。
|
|
35
35
|
|
|
36
36
|
## 动效与状态
|
|
37
37
|
|
|
@@ -72,4 +72,6 @@ SideNav、Breadcrumbs、Steps、Drawer、Fieldset、ErrorSummary、StatusTimelin
|
|
|
72
72
|
|
|
73
73
|
## 设计展台验收
|
|
74
74
|
|
|
75
|
-
|
|
75
|
+
展台使用当前源码的真实组件;本页重点覆盖数据、表单、操作/布局、反馈/浮层四组。合成数据只演示事件与状态;Clipboard仅复制明确的演示字符串。测试覆盖分页边界、请求状态、异步复制失败、IME、候选列表、通知计时、浮层焦点/Escape与窄窗;原生宿主原生平台和屏幕阅读器仍需App接入后单独验收。
|
|
76
|
+
|
|
77
|
+
Toast 浮动通知(自 0.11.0,npm 0.10.0 不含,见[版本记录](../CHANGELOG.md#01102026-10-27)):`placement="floating"` 通过 Portal 挂到最近主题根,位于视口右下角,距边缘 16px、最大宽度 400px,窄窗两侧各留 16px。默认 inline 行为保持;浮动通知不占内容流、不抢焦点,关闭后不拦截点击;继承主题与减少动态。反馈展台使用浮动模式。成功沿用 5 秒关闭及悬停/聚焦/隐藏暂停,关键错误仍持久。
|
package/components/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
本目录提供[莱茵设计 · Rhine Design](../README.md)的 React 组件、设计变量和 API 契约。组件接收应用传入的数据与动作,不绑定特定后端或原生平台。包名为`@linhey/rhine-design`;样式变量和类名使用`rhine-`命名空间。
|
|
4
4
|
|
|
5
|
-
0.8.0
|
|
5
|
+
当前主入口 `components/index.ts` 共 67 个组件(不含类型及独立 `/theme` 入口);0.9.0 新增 ListFilterBar 后,0.10.0 保持此数量。0.8.0 为 66 个,包含 NeutronStarBackdrop、StatusTimelineList、AppShellToggle、ButtonLink、ThemePicker 与 ThemePreview;0.6.0 为 60 个,0.5.0 为 59 个,0.4.0 为 48 个,0.3.0 为 40 个。当前源码中的摘要 Tabs、浮动 Toast、紧凑 SegmentedControl 和相邻 Disclosure 分隔线调整不在 npm 0.10.0 中,见[0.11.0 记录](../CHANGELOG.md#01102026-10-27)。新增导航、表单流程及健康时间轴组件见[场景与交互规范](../docs/INTERACTION_PATTERNS.md),原24个扩展组件见[常用组件契约](./COMMON_COMPONENTS.md)。[字体与文字规范](./TYPOGRAPHY.md)与[主流系统对照与缺口](../docs/DESIGN_SYSTEM_AUDIT.md)共同约束复用。静态页面样稿也使用同源Toolbar/Select CSS,不运行React。
|
|
6
6
|
|
|
7
7
|
中子星艺术、主题物品和生命周期见[中子星契约](../docs/NEUTRON_STAR_BACKDROP.md)与[轨道背景契约](../docs/ORBIT_BACKDROP.md)。安装版本与源码导出分别核对。
|
|
8
8
|
|
|
@@ -64,12 +64,12 @@ export function Example() {
|
|
|
64
64
|
| Background(0.5.0) | roleTone=canvas/content/sidebar/surface/overlay/media;decoration=auto/theme/none;appearance={color,textColor,texture,textureSize};HTML div 属性 | 默认正文纯色;仅画布默认使用主题纹理;局部覆盖不创建主题根或定位上下文;完整配置见[场景背景](../docs/THEMING.md#场景背景050) |
|
|
65
65
|
| SelectionCard | label;selected;onSelectedChange;disabled;showIndicator=true;children | 原生 checkbox 可取消;是否单选由上层控制;showIndicator=false 仅视觉隐藏勾选框,保留键盘与读屏,卡片边界表达选中;内部不嵌套按钮/链接/其他输入 |
|
|
66
66
|
| TextField | label;hint;error;原生 input 属性 | label 与 id 自动关联;错误与提示组成 aria-describedby;输入值由调用方管理 |
|
|
67
|
-
| Tabs | label;items[{value,label,content,disabled?}];value;onValueChange | value 必须指向有效可用项;唯一 value;左右/Home/End
|
|
68
|
-
| SegmentedControl | label;items[{value,label,disabled?}];value;onValueChange;fullWidth
|
|
67
|
+
| Tabs | label;items[{value,label,content,disabled?,description?,status?}];value;onValueChange | value 必须指向有效可用项;唯一 value;左右/Home/End,跳过禁用项;隐藏面板保留挂载但退出可访问树;description/status自0.11.0新增,npm 0.10.0 不含,见下文 |
|
|
68
|
+
| SegmentedControl | label;items[{value,label,disabled?}];value;onValueChange;fullWidth?;size=default/compact | compact为卡片局部操作的32px视觉/44px命中(自 0.11.0,npm 0.10.0 不含);原生 radio,独立 name 防多组干扰;方向键和禁用行为由浏览器提供;fullWidth 为整行等宽选项,标签和选中底色撑满且文字居中 |
|
|
69
69
|
| Notice | tone;title;children;action | error 使用 alert,其余 status;调用方决定显示/移除,不自动倒计时关闭错误 |
|
|
70
70
|
| Progress | label;value:number/null;valueText? | null/NaN 为未知进度;数值钳制 0–100;组件不执行或估算真实进度 |
|
|
71
71
|
| Skeleton | label;lines | 1–8 行,默认 3 行;单一可访问加载描述,装饰行隐藏 |
|
|
72
|
-
| Disclosure | title;children;defaultOpen | 非受控原生 details;挂载后 defaultOpen
|
|
72
|
+
| Disclosure | title;children;defaultOpen | 非受控原生 details;挂载后 defaultOpen 不作远程控制;支持开合中断与反向,关闭内容退出键盘顺序;连续分组不加组间 gap,相邻 Disclosure 共用一条分隔线(自 0.11.0,npm 0.10.0 不含) |
|
|
73
73
|
| EmptyState | title;description;action | 说明当前状态并可给一个下一步动作;不将错误伪装成空数据 |
|
|
74
74
|
|
|
75
75
|
新增24项API与组合示例见[常用组件契约](./COMMON_COMPONENTS.md)。TextField封装单行输入,Select使用原生单选;需要查询候选时用Combobox。复杂浮层采用Radix Dialog、AlertDialog、Popover、DropdownMenu、Tooltip的无样式基础,莱茵设计提供视觉与动效。Toast只处理单条通知;不加入全局队列、表格引擎、图表包或业务数据层。
|
|
@@ -141,6 +141,14 @@ export function Example() {
|
|
|
141
141
|
- 动画不承诺异步任务一定成功;不使用定时器模拟业务完成。预览中的 900ms 保存仅为明确标记的本地演示。
|
|
142
142
|
- 组件不提供全局开启/关闭业务状态的开关;动画是视觉层的可选增强。
|
|
143
143
|
|
|
144
|
+
### 带说明与状态的 Tabs(自 0.11.0,npm 0.10.0 不含)
|
|
145
|
+
|
|
146
|
+
完整规格见[带摘要的页签](../docs/SUMMARY_TABS.md):名称14/22/600、摘要12/18/400,12px上下内距、8px名称/状态、4px摘要间隔、16px页签间距及24px面板间距。独立可交互展台 `/#summary-tabs` 提供局部宽度与状态切换;普通Tabs保持原有间距。
|
|
147
|
+
|
|
148
|
+
`description?: string` 显示对象身份等辅助信息,`status?: ReactNode` 用只读 Badge 或纯文本表达状态,不嵌套按钮、链接等交互元素。存在这两项时页签等分可用宽度,标题/状态及辅助文字自然换行;普通纯标题 Tabs 布局保持不变。页签可访问名称始终为 `label`,描述和状态经 `aria-describedby` 关联;选中与焦点保持原语义和动效,减少动态沿用现有规则。
|
|
149
|
+
|
|
150
|
+
状态由宿主持有,每项同时呈现自己的摘要,不能用当前选中项的摘要填充所有页签。身份变化不得改变 `value` 或重建焦点;状态变化不自动切页。展台 `/#selection` 保留普通Tabs,`/#summary-tabs` 集中提供摘要页签的正常、禁用/加载、错误和长说明,支持局部宽度、键盘、主题及减少动态控件。
|
|
151
|
+
|
|
144
152
|
## 页面采用顺序
|
|
145
153
|
|
|
146
154
|
先 Button、TextField、Notice、Badge;再 Tabs、SegmentedControl 与 Surface;随后 SelectionCard、Progress、Skeleton、Disclosure、EmptyState。逐页替换时保持业务 DTO/动作调用不变。
|
|
@@ -178,4 +186,4 @@ export function Example() {
|
|
|
178
186
|
|
|
179
187
|
- [浮层缩放与边界](../docs/FLOATING_OVERLAYS.md):Popover、Menu、Tooltip 与时间轴共享 CSS zoom 修正。
|
|
180
188
|
- [设置 Dialog](../docs/SETTINGS_DIALOG.md):`navigation` 插槽及窄窗/缩放重排。
|
|
181
|
-
- [主题选择](../docs/THEME_PICKER.md):`ThemePicker` / `ThemePreview
|
|
189
|
+
- [主题选择](../docs/THEME_PICKER.md):`ThemePicker` / `ThemePreview`,自 0.8.0 提供,当时组件总数增至 66;0.9.0 新增 ListFilterBar 后为 67。
|
package/components/SPACING.md
CHANGED
|
@@ -52,6 +52,14 @@
|
|
|
52
52
|
|
|
53
53
|
页面已有h1时卡片通常用h2;标题等级由结构决定,不能用字号决定。不要为布局引入空Text、空段落或连续br。
|
|
54
54
|
|
|
55
|
+
### 卡片标题与局部指标切换
|
|
56
|
+
|
|
57
|
+
卡片继续使用 Surface 24px 内距和 Stack gap=4 的头部/内容间距。头部用 Inline gap=4、justifyContent: space-between:标题使用 Text heading(通常 h2),操作靠右、按内容宽度排列;空间不足自然换行,不挤压标题或固定卡高。只有说明时才加标题/说明 8px 间距。
|
|
58
|
+
|
|
59
|
+
局部指标切换采用 `SegmentedControl size="compact"`(自 0.11.0,npm 0.10.0 不含,见[版本记录](../CHANGELOG.md#01102026-10-27)):可见选项最小32px高,文字12/18,水平内距8px;上下各6px透明命中区域保持44px操作高度,单项最小44px宽。控件不使用业务CSS缩放;默认尺寸和整行模式保持原契约。视觉大小与点击范围分别验收,透明命中区计入头部布局,不覆盖相邻内容。三主题沿用选中、禁用与焦点语义。
|
|
60
|
+
|
|
61
|
+
真实组合见 `#spacing` 的“统计分布”卡片:默认、加载/禁用、错误、长标题与窄窗,以及键盘选择、200%缩放和减少动态。
|
|
62
|
+
|
|
55
63
|
### 只承载表格的 Surface
|
|
56
64
|
|
|
57
65
|
纯表格使用 `<Surface padding="none"><DataTable … /></Surface>`,取消容器外围24px内距,表头和单元格仍使用 DataTable 自身的阅读间距。该公开 prop 自 0.9.1 提供,见 [#24](https://github.com/linhay/rhine-design/issues/24);默认 `padding="default"` 保持原来行为。
|
|
@@ -70,6 +78,7 @@
|
|
|
70
78
|
| 数据列表 | 同项说明4–8px | 列表项16px;筛选/表格/分页16–24px | Stack +DataTable/Pagination |
|
|
71
79
|
| 浮层 | 标题/说明由浮层组件负责 | 正文组16px;操作区24px | Dialog/Drawer +Stack/Inline |
|
|
72
80
|
| 反馈 | 提示内部由Notice负责 | 与前后内容16px | Notice放入Stack,不覆盖其内部样式 |
|
|
81
|
+
| 带摘要的页签 | 名称/状态8px,名称组/身份4px,上下各12px | 页签16px;面板顶部24px | Tabs description/status;[完整规格](../docs/SUMMARY_TABS.md) |
|
|
73
82
|
|
|
74
83
|
这些是组合默认值,不重新覆盖现有组件内部的padding、gap或状态样式。要改变组件密度,先在库中定义并验证,不能在业务页用后代选择器临时压缩。
|
|
75
84
|
|
package/components/Toast.tsx
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import { useEffect, useRef, useState } from 'react';
|
|
1
|
+
import { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
|
2
|
+
import { createPortal } from 'react-dom';
|
|
2
3
|
import { IconButton } from './Actions';
|
|
3
4
|
import type { Tone } from './components';
|
|
4
5
|
|
|
5
|
-
export function Toast({ open, onOpenChange, title, description, tone = 'success', duration = 5000 }: {
|
|
6
|
-
open: boolean; onOpenChange: (open: boolean) => void; title: string; description?: string; tone?: Tone; duration?: number;
|
|
6
|
+
export function Toast({ open, onOpenChange, title, description, tone = 'success', duration = 5000, placement = 'inline' }: {
|
|
7
|
+
open: boolean; onOpenChange: (open: boolean) => void; title: string; description?: string; tone?: Tone; duration?: number; placement?: 'inline' | 'floating';
|
|
7
8
|
}) {
|
|
9
|
+
const anchor = useRef<HTMLSpanElement>(null);
|
|
10
|
+
const [container, setContainer] = useState<HTMLElement>();
|
|
11
|
+
// Escape scrolling panels while inheriting the host theme and reduced-motion setting.
|
|
12
|
+
useLayoutEffect(() => { if (placement === 'floating') setContainer((anchor.current?.closest('.rhine-theme') as HTMLElement) || document.body); }, [placement]);
|
|
8
13
|
const [hover, setHover] = useState(false), [focus, setFocus] = useState(false), [hidden, setHidden] = useState(false);
|
|
9
14
|
const remaining = useRef(duration), close = useRef(onOpenChange);
|
|
10
15
|
close.current = onOpenChange;
|
|
@@ -16,10 +21,11 @@ export function Toast({ open, onOpenChange, title, description, tone = 'success'
|
|
|
16
21
|
const started = Date.now(); const timer = setTimeout(() => close.current(false), Math.max(0, remaining.current));
|
|
17
22
|
return () => { clearTimeout(timer); remaining.current = Math.max(0, remaining.current - (Date.now() - started)); };
|
|
18
23
|
}, [open, hover, focus, hidden, tone, duration, title, description]);
|
|
19
|
-
|
|
24
|
+
const notification = <div className="rhine-toast-region" data-placement={placement} aria-label="通知">
|
|
20
25
|
<div role={tone === 'error' ? 'alert' : 'status'} aria-atomic="true" className={open ? `rhine-toast rhine-tone-${tone}` : 'rhine-sr-only'}
|
|
21
26
|
onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)} onFocus={() => setFocus(true)} onBlur={event => { if (!event.currentTarget.contains(event.relatedTarget)) setFocus(false); }}>
|
|
22
27
|
{open && <><div><strong>{title}</strong>{description && <p>{description}</p>}</div><IconButton label="关闭通知" icon="close" onClick={() => onOpenChange(false)} /></>}
|
|
23
28
|
</div>
|
|
24
29
|
</div>;
|
|
30
|
+
return placement === 'floating' ? <><span ref={anchor} hidden />{container && createPortal(notification, container)}</> : notification;
|
|
25
31
|
}
|
package/components/common.css
CHANGED
|
@@ -72,6 +72,8 @@
|
|
|
72
72
|
.rhine-tooltip { overflow:auto; z-index:60; background:var(--rhine-ink); color:var(--rhine-surface); padding:8px 12px; border-radius:3px; max-width:min(280px,calc(100vw - 24px)); font-size:12px; line-height:18px; overflow-wrap:anywhere; }
|
|
73
73
|
.rhine-toast-region { position:relative; }.rhine-toast { display:flex; align-items:start; gap:12px; justify-content:space-between; padding:16px; background:var(--rhine-tone-bg); border-left:3px solid var(--rhine-tone); color:var(--rhine-ink); animation:rhine-enter var(--rhine-duration-panel) var(--rhine-ease-standard); }
|
|
74
74
|
.rhine-toast p { margin:4px 0 0; overflow-wrap:anywhere; }.rhine-toast strong { overflow-wrap:anywhere; }.rhine-toast>div { min-width:0; }
|
|
75
|
+
.rhine-toast-region[data-placement="floating"] { position:fixed; z-index:60; inset-inline-end:16px; bottom:calc(16px + env(safe-area-inset-bottom, 0px)); width:min(400px,calc(100% - 32px)); pointer-events:none; }
|
|
76
|
+
.rhine-toast-region[data-placement="floating"] .rhine-toast { pointer-events:auto; box-shadow:0 8px 24px #242b2820; }
|
|
75
77
|
.rhine-dialog[data-state=open],.rhine-overlay[data-state=open] { animation:rhine-overlay-fade var(--rhine-duration-overlay) var(--rhine-ease-standard); }
|
|
76
78
|
.rhine-popover[data-state=open],.rhine-menu[data-state=open],.rhine-combobox__list[data-state=open],.rhine-tooltip[data-state=delayed-open],.rhine-tooltip[data-state=instant-open] { animation:rhine-enter var(--rhine-duration-overlay) var(--rhine-ease-standard); }
|
|
77
79
|
@keyframes rhine-overlay-fade { from { opacity:0; } to { opacity:1; } }
|
|
@@ -45,12 +45,12 @@ export function TextField({ label, hint, error, id: providedId, className, ...pr
|
|
|
45
45
|
</div>;
|
|
46
46
|
}
|
|
47
47
|
export interface Choice { value: string; label: string; disabled?: boolean }
|
|
48
|
-
export interface TabItem extends Choice { content: ReactNode }
|
|
48
|
+
export interface TabItem extends Choice { content: ReactNode; description?: string; status?: ReactNode }
|
|
49
49
|
export function Tabs({ label, items, value, onValueChange }: { label: string; items: TabItem[]; value: string; onValueChange: (value: string) => void }) {
|
|
50
50
|
const id = useId();
|
|
51
51
|
const buttons = useRef<(HTMLButtonElement | null)[]>([]);
|
|
52
52
|
return <div className="rhine-tabs">
|
|
53
|
-
<div role="tablist" aria-label={label} className=
|
|
53
|
+
<div role="tablist" aria-label={label} className={classes('rhine-tabs__list', items.some(item => item.description || item.status) && 'rhine-tabs__list--detailed')} onKeyDown={event => {
|
|
54
54
|
if (!['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key)) return;
|
|
55
55
|
const available = items.map((item, index) => ({ ...item, index })).filter(item => !item.disabled);
|
|
56
56
|
if (!available.length) return;
|
|
@@ -61,15 +61,19 @@ export function Tabs({ label, items, value, onValueChange }: { label: string; it
|
|
|
61
61
|
}}>
|
|
62
62
|
{items.map((item, index) => <button key={item.value} ref={node => { buttons.current[index] = node; }}
|
|
63
63
|
type="button" role="tab" disabled={item.disabled} id={`${id}-tab-${index}`} aria-controls={`${id}-panel-${index}`}
|
|
64
|
-
aria-
|
|
64
|
+
aria-label={item.label} aria-describedby={[item.description && `${id}-description-${index}`, item.status && `${id}-status-${index}`].filter(Boolean).join(' ') || undefined}
|
|
65
|
+
aria-selected={value === item.value} tabIndex={value === item.value ? 0 : -1} onClick={() => onValueChange(item.value)}>
|
|
66
|
+
{item.description || item.status ? <><span className="rhine-tabs__title">{item.label}{item.status && <span id={`${id}-status-${index}`} className="rhine-tabs__status">{item.status}</span>}</span>
|
|
67
|
+
{item.description && <span id={`${id}-description-${index}`} className="rhine-tabs__description">{item.description}</span>}</> : item.label}
|
|
68
|
+
</button>)}
|
|
65
69
|
</div>
|
|
66
70
|
{items.map((item, index) => <div key={item.value} role="tabpanel" id={`${id}-panel-${index}`} aria-labelledby={`${id}-tab-${index}`}
|
|
67
71
|
hidden={value !== item.value} tabIndex={0} className="rhine-tabs__panel">{item.content}</div>)}
|
|
68
72
|
</div>;
|
|
69
73
|
}
|
|
70
|
-
export function SegmentedControl({ label, items, value, onValueChange, fullWidth = false }: { label: string; items: Choice[]; value: string; onValueChange: (value: string) => void; fullWidth?: boolean }) {
|
|
74
|
+
export function SegmentedControl({ label, items, value, onValueChange, fullWidth = false, size = 'default' }: { label: string; items: Choice[]; value: string; onValueChange: (value: string) => void; fullWidth?: boolean; size?: 'default' | 'compact' }) {
|
|
71
75
|
const name = useId();
|
|
72
|
-
return <fieldset className={classes('rhine-segmented', fullWidth && 'rhine-segmented--full-width')}><legend className="rhine-sr-only">{label}</legend>
|
|
76
|
+
return <fieldset className={classes('rhine-segmented', fullWidth && 'rhine-segmented--full-width', size === 'compact' && 'rhine-segmented--compact')}><legend className="rhine-sr-only">{label}</legend>
|
|
73
77
|
{items.map(item => <label key={item.value} className="rhine-segmented__option">
|
|
74
78
|
<input type="radio" name={name} value={item.value} checked={value === item.value} disabled={item.disabled} onChange={() => onValueChange(item.value)} />
|
|
75
79
|
<span>{item.label}</span>
|
|
@@ -133,8 +137,10 @@ export function Disclosure({ title, children, defaultOpen = false }: { title: st
|
|
|
133
137
|
const reduced = window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;
|
|
134
138
|
if (reduced || !el.animate) { el.open = expanded; setOpen(expanded); return; }
|
|
135
139
|
const style = getComputedStyle(el);
|
|
136
|
-
|
|
137
|
-
const
|
|
140
|
+
// Production CSS may normalize 200ms to .2s; Web Animations always takes milliseconds.
|
|
141
|
+
const durationValue = style.getPropertyValue('--rhine-duration-notice').trim();
|
|
142
|
+
const parsedDuration = parseFloat(durationValue);
|
|
143
|
+
const duration = Number.isFinite(parsedDuration) ? parsedDuration * (durationValue.endsWith('s') && !durationValue.endsWith('ms') ? 1000 : 1) : 200;
|
|
138
144
|
if (duration === 0) { el.open = expanded; setOpen(expanded); return; }
|
|
139
145
|
const to = expanded ? el.getBoundingClientRect().height : summary.getBoundingClientRect().height + parseFloat(style.borderTopWidth) + parseFloat(style.borderBottomWidth);
|
|
140
146
|
const next = el.animate([{ height: `${from}px` }, { height: `${to}px` }], { duration, easing: style.getPropertyValue(expanded ? '--rhine-ease-standard' : '--rhine-ease-exit').trim() || 'ease' });
|
package/components/styles.css
CHANGED
|
@@ -60,7 +60,14 @@
|
|
|
60
60
|
.rhine-tabs__list button[aria-selected=true] { color:var(--rhine-ink); }
|
|
61
61
|
.rhine-tabs__list button[aria-selected=true]::after { transform:scaleX(1); }
|
|
62
62
|
.rhine-tabs__list button:disabled { color:var(--rhine-muted); opacity:.6; cursor:default; }
|
|
63
|
+
.rhine-tabs__list--detailed { align-items:stretch; gap:var(--rhine-space-4); }
|
|
64
|
+
.rhine-tabs__list--detailed>button { display:flex; flex-direction:column; align-items:stretch; flex:1 1 0; min-width:0; padding-block:var(--rhine-space-3); text-align:start; overflow-wrap:anywhere; }
|
|
65
|
+
.rhine-tabs__list--detailed>button:hover:not(:disabled) { color:var(--rhine-ink); }
|
|
66
|
+
.rhine-tabs__title { display:flex; align-items:center; flex-wrap:wrap; gap:var(--rhine-space-2); font-weight:var(--rhine-weight-heading); }
|
|
67
|
+
.rhine-tabs__status { display:inline-flex; }
|
|
68
|
+
.rhine-tabs__description { display:block; margin-top:var(--rhine-space-1); color:var(--rhine-muted); font:var(--rhine-weight-regular) var(--rhine-text-caption)/var(--rhine-leading-caption) var(--rhine-font); }
|
|
63
69
|
.rhine-tabs__panel { padding-top:20px; animation:rhine-enter var(--rhine-duration-panel) var(--rhine-ease-standard); }
|
|
70
|
+
.rhine-tabs__list--detailed~.rhine-tabs__panel { padding-top:var(--rhine-space-6); }
|
|
64
71
|
.rhine-segmented { display:inline-flex; flex-wrap:wrap; gap:3px; padding:3px; border:1px solid var(--rhine-control); border-radius:var(--rhine-radius-control); margin:0; min-width:0; }
|
|
65
72
|
.rhine-segmented__option { position:relative; display:flex; flex:1 0 auto; cursor:pointer; }
|
|
66
73
|
.rhine-segmented input { position:absolute; opacity:0; width:1px; height:1px; }
|
|
@@ -71,6 +78,10 @@
|
|
|
71
78
|
.rhine-segmented input:checked+span { background:var(--rhine-ink); color:var(--rhine-surface); }
|
|
72
79
|
.rhine-segmented input:focus-visible+span { outline:2px solid var(--rhine-green); outline-offset:3px; }
|
|
73
80
|
.rhine-segmented input:disabled+span { opacity:.6; cursor:default; }
|
|
81
|
+
.rhine-theme .rhine-segmented--compact { padding:0; border:0; gap:0; background:transparent; }
|
|
82
|
+
.rhine-segmented--compact .rhine-segmented__option { min-width:44px; min-height:44px; padding:6px 0; }
|
|
83
|
+
.rhine-segmented--compact .rhine-segmented__option span { min-height:32px; padding:6px 8px; font-size:12px; line-height:18px; background:var(--rhine-surface); box-shadow:inset 0 0 0 1px var(--rhine-control); }
|
|
84
|
+
.rhine-segmented--compact input:checked+span { background:var(--rhine-ink); color:var(--rhine-surface); }
|
|
74
85
|
.rhine-notice { padding:16px; border-left:3px solid var(--rhine-tone); background:var(--rhine-tone-bg); color:var(--rhine-tone); display:flex; align-items:center; justify-content:space-between; gap:16px; flex-wrap:wrap; animation:rhine-enter var(--rhine-duration-notice) var(--rhine-ease-standard); }
|
|
75
86
|
.rhine-notice>div { flex:1 1 200px; min-width:0; overflow-wrap:anywhere; }
|
|
76
87
|
.rhine-notice strong { font-size:14px; font-weight:600; }
|
|
@@ -84,6 +95,7 @@
|
|
|
84
95
|
.rhine-skeleton>span[aria-hidden] { display:block; height:16px; background:var(--rhine-line); animation:rhine-pulse var(--rhine-duration-skeleton) ease-in-out infinite; }
|
|
85
96
|
.rhine-skeleton>span[aria-hidden]:last-child { width:65%; }
|
|
86
97
|
.rhine-disclosure { border-top:1px solid var(--rhine-line); border-bottom:1px solid var(--rhine-line); overflow:hidden; }
|
|
98
|
+
.rhine-disclosure + .rhine-disclosure { border-top:0; }
|
|
87
99
|
.rhine-disclosure summary { min-height:48px; padding:12px 0; list-style:none; cursor:pointer; display:flex; justify-content:space-between; align-items:center; gap:16px; font-weight:500; }
|
|
88
100
|
.rhine-disclosure summary::-webkit-details-marker { display:none; }
|
|
89
101
|
.rhine-theme .rhine-disclosure summary:focus-visible { outline-offset:-3px; }
|
package/docs/DESIGN_SYSTEM.md
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
- 组件优先:先更新规范、组件与真实展台,再由消费项目选择版本;业务流程不进入通用组件。
|
|
15
15
|
- 动效克制:反馈服务于状态理解,就绪不等待装饰,减少动态不削弱功能。
|
|
16
16
|
- 以用户任务为中心消融:先保留身份、当前动作、结果与必要后果;去掉重复状态和不影响决策的机制长文,历史按需展开。已有外层 Surface 时,只读额度与事实优先无框紧凑组合;压缩不靠缩小字号、裁数字或隐藏未知/失败。具体取舍与反例见[紧凑摘要与凭证操作](COMPACT_ACCOUNT_PATTERNS.md)。
|
|
17
|
+
- 机制说明靠近任务:计算口径、排序机制等补充说明放到相关标签或动作旁的问号帮助,支持悬停、键盘聚焦与触屏点按;不为一段重复说明永久占用独立行。失败、权限限制、不可逆后果和影响决策的数据不完整仍就地常显,不能移入帮助而失去可见性。
|
|
17
18
|
|
|
18
19
|
密集任务浮层的落地规则:先按用户决策划分常显/按需信息;区域标题、数量和操作集中到导航行;短内容自然撑高、默认单层滚动;可见间距与44px操作范围分别验证。详见[场景与交互规范](INTERACTION_PATTERNS.md#弹窗高度与滚动归属)和[间距组合](../components/SPACING.md#视觉间距与操作范围),选择即保存仅按其适用边界使用。
|
|
19
20
|
|
|
@@ -53,6 +54,12 @@
|
|
|
53
54
|
|
|
54
55
|
本稿测算:正文 / 底色 12.77:1,次级文字 / 底色 5.44:1;品牌橙 / 底色仅 3.72:1,因此只用于非文字标记或符合大字要求的展示文字。重要状态同时有文字。数据未知显示「— / 暂不可用」,不显示 0%,未知进度不画百分比。
|
|
55
56
|
|
|
57
|
+
### 背景与文字必须成对消费
|
|
58
|
+
|
|
59
|
+
区域设置底色时同时明确其文字色,优先使用 `--rhine-content-bg` / `--rhine-content-text`、`--rhine-surface-bg` / `--rhine-surface-text` 或 `--rhine-overlay-bg` / `--rhine-overlay-text`;通用场景由 `Background` / `Surface` 直接表达。不能只换外层深色背景,却让嵌套代码、Markdown、统计说明、报表单元格继续使用 `#fff`、`#f8fafc` 等浅色常量与继承的浅色字。代码区也属于可读内容,边框、正文、链接、占位符、选中态及 hover 都随主题核对;媒体等确需固定画布的区域必须同时定义自己的文字配对。
|
|
60
|
+
|
|
61
|
+
选择状态必须有清楚的边界、勾选或下划线等结构信号,不能只用轻微底色差。选中背景和前景成对选择,保持名称、数值与操作文字可读;焦点与选中是不同状态,键盘焦点仍持续可见。检查所有三主题下的默认、hover、selected、focus、disabled,以及长文本和空/错误态;“外层主题已经切换”不等于所有后代已适配。
|
|
62
|
+
|
|
56
63
|
### 3.2 字体与密度
|
|
57
64
|
|
|
58
65
|
完整规则见 [字体与文字规范](../components/TYPOGRAPHY.md);涵盖9档文字样式、中文混排、数字单位、换行截断、内容语义和动效边界。主流系统对照与待补能力见 [补齐顺序](./DESIGN_SYSTEM_AUDIT.md)。
|
|
@@ -88,7 +95,9 @@
|
|
|
88
95
|
|
|
89
96
|
## 4. 组件与内容契约
|
|
90
97
|
|
|
91
|
-
|
|
98
|
+
带对象摘要与状态的页签遵循[Summary Tabs](SUMMARY_TABS.md):稳定名称、只读状态和身份摘要分层;12/8/4/16/24间距及窄窗/三主题/键盘要求由库统一实现,产品页不覆盖。API 与展台属于 0.10.0 之后的未发布变更,npm 0.10.0 不含;普通 Tabs 维持原契约,见[版本记录](../CHANGELOG.md#未发布)。
|
|
99
|
+
|
|
100
|
+
使用[组件契约](../components/README.md)、[常用组合](../components/COMMON_COMPONENTS.md)及[场景与交互选型](./INTERACTION_PATTERNS.md)。当前主入口导出 67 个组件,统计不含类型及独立主题入口;数量自 0.9.0 新增 ListFilterBar 后保持至 0.10.0。接入时核对安装版本的实际导出与所需属性,源码未发布变更不由相同包版本字段保证。表单标签持续可见,提示与错误关联;交互元素使用原生语义或可访问基础。选择、筛选、排序和分页向宿主回传意图,宿主决定数据来源及操作结果。
|
|
92
101
|
|
|
93
102
|
错误说明发生了什么及可执行的下一步,避免内部堆栈和秘密;成功、警告、未知与空态有文字区分。通知不得依赖颜色或短暂动画才能理解。品牌、实体名、时区和业务单位由宿主定义,具体文案规则见[字体与文字](../components/TYPOGRAPHY.md)。
|
|
94
103
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# 莱茵设计 · 主流系统对照与补齐顺序
|
|
2
2
|
|
|
3
|
-
本页保留早期组件基线与补齐顺序。2026-10-24 对 issues #1–#6 的 11 个新增组件及 3 个扩展另有[Ant Design / MUI 逐项审计](ANTD_MUI_AUDIT.md)
|
|
3
|
+
本页保留早期组件基线与补齐顺序。2026-10-24 对 issues #1–#6 的 11 个新增组件及 3 个扩展另有[Ant Design / MUI 逐项审计](ANTD_MUI_AUDIT.md),包含当时修复、验证和有意差异。下文 V1.3 / V1.4 是历史文档阶段编号,不是 npm 版本;数量、优先级与接入状态均保留当时语境。当前 67 个主入口组件及发布/源码边界见[组件入口](../components/README.md),应用 API 见[应用组件契约](APPLICATION_COMPONENTS.md)。
|
|
4
4
|
|
|
5
5
|
初次调研:2026-10-22;2026-10-23追加[GitHub项目与skills调研](./REFERENCE_RESEARCH.md),main新增六个组件、14类场景指南和两组动效。对照[莱茵设计](../README.md)的`components/`与通用表单、数据展示、反馈和导航场景。以下优先级是本项目判断;引用用于说明参考系统的原则,不代表它们要求采用我们的尺寸、配色或组件清单。独立展台使用18309的`/#…`入口。
|
|
6
6
|
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
| [Carbon Data table](https://www.carbondesignsystem.com/building-blocks/core/components/data-table/guidelines) | 明确表头、工具栏、数据行和分页的职责 | 后续优先提取请求明细与分页;不预建电子表格、批量编辑或表格引擎 |
|
|
16
16
|
| [Radix Accessibility](https://www.radix-ui.com/primitives/docs/overview/accessibility) | 标签、键盘和焦点管理是组件契约 | 每个控件写清语义和焦点行为;复杂浮层出现时评估成熟基础实现,不手写只在鼠标下有效的弹层 |
|
|
17
17
|
|
|
18
|
-
##
|
|
18
|
+
## 历史基线:当时已经落地
|
|
19
19
|
|
|
20
20
|
2026-10-22追加用户指定的[Transitions.dev](https://transitions.dev/)参考,具体UX、动效映射和已实现/候选边界见[MOTION.md](../components/MOTION.md)。随后按用户授权在`/#motion`实现10组可播放/重播、4倍慢放及减少动态对照;新动画限定在展台,组件数量仍为40,未迁移App。
|
|
21
21
|
|
|
@@ -23,7 +23,7 @@ V1.4共40个React组件。原16项为Button、Badge、Surface、SelectionCard、
|
|
|
23
23
|
|
|
24
24
|
V1.3新增Text/Select/Toolbar、9档文字样式和[字体/文案规范](../components/TYPOGRAPHY.md)。静态样稿工具栏复用同源CSS,修正箭头并移除App内重复品牌顶栏。已有Logo深浅版本不等于完整深色主题。
|
|
25
25
|
|
|
26
|
-
##
|
|
26
|
+
## 历史补齐顺序
|
|
27
27
|
|
|
28
28
|
| 优先级 | 补充项 | 具体范围与验收 | 状态 |
|
|
29
29
|
| --- | --- | --- | --- |
|
|
@@ -60,6 +60,6 @@ V1.3新增Text/Select/Toolbar、9档文字样式和[字体/文案规范](../comp
|
|
|
60
60
|
|
|
61
61
|
## 消融与范围
|
|
62
62
|
|
|
63
|
-
- 独立通用库现已提供SideNav、Breadcrumbs、Steps、Drawer、Fieldset、ErrorSummary
|
|
63
|
+
- 独立通用库现已提供SideNav、Breadcrumbs、Steps、Drawer、Fieldset、ErrorSummary,不再沿用原产品仅有两个页面的限制。DateField / DateRangePicker 与 TrendChart / StackedBar 自 0.5.0 已提供,见[应用组件契约](APPLICATION_COMPONENTS.md);命令面板、拖拽、富文本与通用图表引擎仍需具体需求后评估。
|
|
64
64
|
- 不引入动画框架、主题生成器或整体UI库。V1.4用户已授权完整浮层能力,采用Radix的5个无样式primitive包处理焦点/键盘/边缘避让;视觉和动效仍使用Rhine Design CSS。
|
|
65
65
|
- 不把每种视觉模式封装成组件:先出现稳定复用,再抽取;业务数据契约与服务端状态继续保留在调用方。
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Disclosure 连续分组验收
|
|
2
|
+
|
|
3
|
+
对应 [issue #37](https://github.com/linhay/rhine-design/issues/37) 与 [PR #38](https://github.com/linhay/rhine-design/pull/38)。相邻边框、零 gap 契约和双组展台已在 `e7461af` 实现;本轮补齐验收,并修复生产 CSS 时间单位归一化导致的动画过快。两项均为 npm 0.10.0 之后的未发布源码变更。
|
|
4
|
+
|
|
5
|
+
## 源码与环境
|
|
6
|
+
|
|
7
|
+
- 日期:2026-10-27,Asia/Shanghai。
|
|
8
|
+
- 行为源码:`e86aeea412b33f739e2e01531ae82c96ed5fbe4c`;本记录及截图的后续提交不改变组件、样式、展台或测试。
|
|
9
|
+
- Node 24.21.0、npm 11.19.0、macOS 27.0.1、Chrome 155.0.8059.85。
|
|
10
|
+
- 使用 `npm run build` 的生产展台,通过本机 Vite preview 访问 `#disclosure`,内容均为内置合成示例。
|
|
11
|
+
- JavaScript `index-BS5CgdA1.js` SHA-256:`b67e184dd6a8eb06ea95e35fc17165bbcbfd786613a3ad6f5b510f70547d6c66`。
|
|
12
|
+
- CSS `index-ZqNLTCyY.css` SHA-256:`869b49dd6c91c604169311a85bf635e34732406a3386c48eddcc2d31754ec473`。
|
|
13
|
+
|
|
14
|
+
## 结果
|
|
15
|
+
|
|
16
|
+
| 场景 | 结果与证据 |
|
|
17
|
+
| --- | --- |
|
|
18
|
+
| 全量检查 | PASS:Node 24 下 `VITEST_MAX_WORKERS=1 npm run check`;34 文件、265 测试,类型与生产 CSS/SVG 制品检查通过。未修改测试超时或断言。 |
|
|
19
|
+
| 三主题 × 400/520/880 CSS px × 四种开合 | PASS:36 组均保持 gap 0、前组上下边界 1px/1px、后组上下边界 0/1px;开合独立。 |
|
|
20
|
+
| 单组件边界 | PASS:每个主题/宽度临时移除第二组,第一组上下边界仍为 1px/1px,共 9 组。随后完整重载展台。 |
|
|
21
|
+
| 换行与可读性 | PASS:三主题窄窗标题与正文换行、正文无水平溢出、页面无横向滚动;见下方截图。 |
|
|
22
|
+
| 原生网页缩放 200% | PASS:隔离 Chrome 的 `chrome://settings/appearance` → 网页缩放选择 200%;CSS zoom 为 1,visualViewport.scale 为 1。预缩放视口 800/1040/1760 对应 innerWidth 400/520/880,再跑上述 45 组布局检查全部通过。 |
|
|
23
|
+
| Enter / Space 与 Tab | PASS:三主题 Enter 展开、Tab 进入内部按钮、Shift+Tab 回标题、Space 关闭、Tab 跳到第二组标题;关闭内容退出键盘顺序。100% 与原生 200% 均覆盖。 |
|
|
24
|
+
| 真实动画与快速反向 | PASS:生产计算样式 `.2s`,Web Animation duration 实测 200ms。70ms 中途存在活动动画及中间高度;反向从当前高度开始、旧动画取消,再次反向后最终展开且无残留动画。100% 与原生 200% 均覆盖三主题。 |
|
|
25
|
+
| 系统与手动减少动态 | PASS:系统偏好通过真实 Chrome 的 media emulation 触发;手动使用展台“减少动态”。两者均立即到达开合结果;系统偏好切换中断在途关闭,手动切换中断在途展开。100% 与原生 200% 均覆盖三主题。 |
|
|
26
|
+
| CSS zoom 200% | N/A:本轮 200% 使用浏览器原生网页缩放,不以 CSS zoom 替代。 |
|
|
27
|
+
| 其他浏览器、系统设置面板、消费方 | SKIPPED:未覆盖 Safari/Firefox、真实 OS 偏好面板或消费应用;系统媒体偏好由 Chrome 模拟。 |
|
|
28
|
+
| npm 发布 / iOS | N/A:不在本轮范围。 |
|
|
29
|
+
|
|
30
|
+
交互矩阵每个缩放级别为三主题 × 九项,共 27 项;两种缩放合计 90 项布局与 54 项交互结果。截图仅证明外观,动画结论来自运行时量测。
|
|
31
|
+
|
|
32
|
+
## 保留的失败与验证限制
|
|
33
|
+
|
|
34
|
+
- 修复前生产 `.2s` 被直接 `parseFloat`,实际 duration 为 0.2ms;三主题快速反向检查均 FAIL。新增单测覆盖 `200ms`、`.2s`、`0ms`、`0s`。
|
|
35
|
+
- 首次候选全量检查为 29 个 5 秒超时及一个 worker 启动超时;限制两 worker 后仍有 7 项失败(含超时及等待状态断言)。最终单 worker 完整检查通过。同期存在主机并行负载,但未确认这些失败的根因,不能视为产品修复证据。
|
|
36
|
+
- 首次新增动画桩类型不完整,类型检查与对应 CI FAIL;补齐测试桩类型后通过。失败修订保留在 PR 历史。
|
|
37
|
+
- 首次布局脚本临时移除第二组后未重载同 URL,导致定位第二组超时;已改为每组完整 reload。旋转图标的包围盒产生约 6px scrollWidth 差值,逐项核对后确认正文没有溢出,不能把装饰变换直接当作正文裁切。
|
|
38
|
+
- 原生应用控制接口曾无响应;最终缩放矩阵改用隔离 Chrome 内置设置完成。元素截图在原生缩放下裁剪不准确,原生缩放外观使用整页视口截图复核。
|
|
39
|
+
- 本地原始结果位于忽略目录 `output/playwright/issue-37/`;公开内容仅保留脱敏摘要与组件截图。
|
|
40
|
+
|
|
41
|
+
## 组件截图(100%,400 CSS px)
|
|
42
|
+
|
|
43
|
+
| 莱茵 | 战术终端 | 橙趣 |
|
|
44
|
+
| --- | --- | --- |
|
|
45
|
+
|  |  |  |
|
|
46
|
+
|
|
47
|
+
[原生 200% 网页缩放视口截图(莱茵主题)](assets/disclosure/rhine-native200.png)。
|
|
48
|
+
|
|
49
|
+
重验时先构建最终源码,再在 `#disclosure` 依次检查全收起、前开后关、前关后开、全展开;量测 `getBoundingClientRect()` 间距与计算边框,并执行上表键盘和动态偏好步骤。生产动画须检查 `Animation.effect.getTiming().duration`,只看源码 token 或最终截图不足以判定正确。
|
|
@@ -31,7 +31,15 @@
|
|
|
31
31
|
| 某时段的时间与状态 | 悬停、键盘聚焦时显示 | 同时提供可访问名称;触屏也能聚焦/操作 |
|
|
32
32
|
| 失败、未知、数据不完整、关键陈旧状态 | 相关区域常显 | 不用减少文字来掩盖数据质量问题 |
|
|
33
33
|
|
|
34
|
-
##
|
|
34
|
+
## 补充说明与局部布局
|
|
35
|
+
|
|
36
|
+
### 与操作相邻的帮助
|
|
37
|
+
|
|
38
|
+
计算口径、匹配/排序方式、筛选解释等非阻断机制说明,入口放在对应标签或操作旁,用有明确可访问名称的问号按钮表达。44px 命中区与可见问号尺寸分开,不扩大到覆盖旁边的输入或按钮;不把说明拆成重复的独立常驻行。按钮名称说明对象,例如“查看费用计算说明”,而非只读出“问号”。
|
|
39
|
+
|
|
40
|
+
复用现有 Tooltip / Popover:简短文字由悬停与键盘聚焦可见,触屏点按必须能查看同一说明;长内容或有链接/操作的说明用可关闭 Popover。组件组合须实际验证 hover、focus、click/tap、Escape 与焦点归还,不把原生 title 或只响应 hover 的 Tooltip 当作触屏方案,也不为 hover 打开而抢走正在输入的焦点。不可逆影响、权限不足、当前失败、结果过期/不完整仍常显;帮助用于解释机制,不隐藏必要风险。
|
|
41
|
+
|
|
42
|
+
### 局部宽度与输入可用性
|
|
35
43
|
|
|
36
44
|
400px 视口并不代表控件有400px可用:需扣除宿主侧栏、卡片、Drawer 与字段分组的逐层内距。在 200% CSS zoom 下分别测控件边框、实际文本编辑区与邻接按钮,检查完整值、placeholder、标签、错误和焦点;原生日期必须实际聚焦并编辑最后一段,核对年月日与日历入口均未被挤掉。
|
|
37
45
|
|