@linhey/rhine-design 0.3.0 → 0.5.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.
Files changed (89) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +63 -14
  3. package/components/Actions.tsx +2 -0
  4. package/components/AdvancedForms.tsx +184 -0
  5. package/components/AppShell.tsx +71 -0
  6. package/components/Background.tsx +29 -0
  7. package/components/COMMON_COMPONENTS.md +5 -1
  8. package/components/Charts.tsx +94 -0
  9. package/components/Data.tsx +98 -18
  10. package/components/FormWorkflow.tsx +25 -0
  11. package/components/Forms.tsx +14 -8
  12. package/components/MOTION.md +6 -2
  13. package/components/Media.tsx +82 -0
  14. package/components/Navigation.tsx +42 -0
  15. package/components/Overlays.tsx +10 -3
  16. package/components/README.md +20 -5
  17. package/components/SPACING.md +82 -0
  18. package/components/StatusTimeline.tsx +67 -0
  19. package/components/TYPOGRAPHY.md +34 -2
  20. package/components/background.css +43 -0
  21. package/components/charts.css +27 -0
  22. package/components/common.css +17 -8
  23. package/components/data.css +6 -0
  24. package/components/drawer-placement.css +4 -0
  25. package/components/extensions.css +70 -0
  26. package/components/index.ts +18 -2
  27. package/components/media-parity.css +3 -0
  28. package/components/patterns.css +39 -0
  29. package/components/styles.css +15 -8
  30. package/components/theme-arknights.css +306 -0
  31. package/components/theme-neo-brutalism.css +90 -0
  32. package/components/theme.tsx +58 -0
  33. package/components/timeline.css +23 -0
  34. package/components/tokens.css +49 -1
  35. package/docs/ANTD_MUI_AUDIT.md +38 -0
  36. package/docs/APPLICATION_COMPONENTS.md +175 -0
  37. package/docs/DESIGN_SYSTEM.md +12 -2
  38. package/docs/DESIGN_SYSTEM_AUDIT.md +6 -4
  39. package/docs/INTERACTION_PATTERNS.md +129 -0
  40. package/docs/ISSUE_4_VALIDATION.md +21 -0
  41. package/docs/ISSUE_VALIDATION.md +52 -0
  42. package/docs/REFERENCE_RESEARCH.md +38 -0
  43. package/docs/THEME_NEO_BRUTALISM.md +37 -0
  44. package/docs/THEMING.md +145 -0
  45. package/docs/assets/application-shell.png +0 -0
  46. package/docs/assets/issue4-shell.png +0 -0
  47. package/docs/assets/media-viewer-mobile.png +0 -0
  48. package/docs/assets/monitoring-desktop.png +0 -0
  49. package/docs/audits/DATA_ANTD_MUI.md +75 -0
  50. package/docs/audits/FORMS_ANTD_MUI.md +88 -0
  51. package/docs/audits/MONITORING_ANTD_MUI.md +44 -0
  52. package/docs/audits/SHELL_MEDIA_ANTD_MUI.md +91 -0
  53. package/package.json +5 -1
  54. package/preview/BackgroundGallery.tsx +46 -0
  55. package/preview/CommonGallery.tsx +8 -8
  56. package/preview/ExtensionsGallery.tsx +95 -0
  57. package/preview/GalleryShell.tsx +107 -0
  58. package/preview/LineHeightGallery.tsx +27 -0
  59. package/preview/MonitoringGallery.tsx +27 -0
  60. package/preview/MotionGallery.tsx +6 -2
  61. package/preview/PatternGallery.tsx +52 -0
  62. package/preview/SpacingGallery.tsx +32 -0
  63. package/preview/SubscriptionGallery.tsx +84 -0
  64. package/preview/backgrounds.css +10 -0
  65. package/preview/extensions.css +6 -0
  66. package/preview/line-height.css +2 -0
  67. package/preview/main.tsx +48 -25
  68. package/preview/patterns.css +7 -0
  69. package/preview/preview.css +2 -8
  70. package/preview/shell.css +35 -0
  71. package/preview/spacing.css +2 -0
  72. package/preview/subscriptions.css +27 -0
  73. package/preview/theme-arknights.css +234 -0
  74. package/preview/theme-neo-brutalism.css +11 -0
  75. package/skills/rhine-design/SKILL.md +26 -3
  76. package/skills/rhine-design/references/API_MAP.md +11 -3
  77. package/skills/rhine-design/references/TASK_MAP.md +5 -1
  78. package/skills/rhine-design/references/integration.md +7 -3
  79. package/tests/Background.test.tsx +22 -0
  80. package/tests/DataParity.test.tsx +148 -0
  81. package/tests/Extensions.test.tsx +145 -0
  82. package/tests/FormParity.test.tsx +185 -0
  83. package/tests/GalleryShell.test.tsx +98 -0
  84. package/tests/Monitoring.test.tsx +38 -0
  85. package/tests/MonitoringParity.test.tsx +125 -0
  86. package/tests/Patterns.test.tsx +75 -0
  87. package/tests/ShellMediaParity.test.tsx +180 -0
  88. package/tests/StatusTimeline.test.tsx +15 -0
  89. package/tests/Theme.test.tsx +112 -0
@@ -0,0 +1,37 @@
1
+ # 橙趣 · 粗描边
2
+
3
+ 可选主题,标识 `neo-brutalism`。从包含本文件的源码版本导入 `@linhey/rhine-design/theme-neo-brutalism.css`,置于组件样式之后,再给 `.rhine-theme` 添加 `data-rhine-theme="neo-brutalism"`。自 0.4.0 提供,npm 0.3.0 不包含此主题。
4
+
5
+ React 接入推荐使用 `@linhey/rhine-design/theme` 的 `<ThemeProvider defaultTheme="neo-brutalism">`,它自动加载样式并管理主题根;子组件可通过 `useTheme().setTheme()` 切换。详见[主题 API](THEMING.md)。展台专属布局装饰位于 `preview/theme-neo-brutalism.css`,不包含在公共主题入口中。
6
+
7
+ ## 视觉规则
8
+
9
+ | 元素 | 规范 |
10
+ | --- | --- |
11
+ | 背景 | `#EAF0F4` 浅灰蓝,14px 间距细点阵;不穿入卡片 |
12
+ | 卡片 | `#FFFDF5` 米白,2px `#202323` 描边,22px 圆角,`5px 5px 0` 硬阴影 |
13
+ | 弹窗 | 同色同描边,24px 圆角,`8px 8px 0` 硬阴影;遮罩无模糊 |
14
+ | 按钮 | 主操作 `#FF8B3D` 橙底黑字,次操作白底黑框,12px 圆角,`3px 3px 0` 硬阴影 |
15
+ | 文字 | 深色系统无衬线,辅助 `#59616A`,标题加粗,指标 36/44px;金额等宽数字 |
16
+ | 类型标签 | 绿色 `#DCEBD7`、橙色 `#FCE0C4`、蓝紫色 `#E3E0F7`,均配黑字与类型名称 |
17
+ | 状态 | 成功仍为绿色,警告与失败保留独立语义色和文字,不以橙色主操作代替成功状态 |
18
+
19
+ 主题变量均限定在主题根。既有 `--rhine-*` 尺寸、字体、语义色覆盖见 CSS;新增 `--rhine-hard-shadow`、`--rhine-tag-green`、`--rhine-tag-orange`、`--rhine-tag-violet` 可由宿主调整。
20
+
21
+ 类型标签使用通用样式,不给 Badge 增加产品特定枚举:
22
+
23
+ ```html
24
+ <span class="rhine-type-tag" data-color="green">GPT100</span>
25
+ <span class="rhine-type-tag" data-color="orange">GPT200</span>
26
+ <span class="rhine-type-tag" data-color="violet">Grok</span>
27
+ ```
28
+
29
+ ## 布局与交互
30
+
31
+ 单页内容先展示数量、金额与状态,再以卡片和表格展开。主题 CSS 不强制业务布局;展台 `#subscriptions` 使用静态演示数据呈现此组合,类型、价格和文案由宿主定义,不代表实际产品报价。
32
+
33
+ 详情卡片使用原生 button 作为 Dialog trigger;不将静态 Surface 或带 checkbox 的 SelectionCard 改成伪按钮。卡片内不再嵌套操作,次要管理命令放右上角 Menu。Dialog 保留关闭按钮、Escape、焦点限制与关闭后归还焦点;Portal 继承主题根。主题根不能使用 transform、contain 或裁剪。
34
+
35
+ 按钮按下向右下移动 3px 并收起阴影,松开恢复;禁用与忙碌状态不响应按压。系统减少动态与 `data-rhine-motion="reduced"` 均禁用位移和过渡,反馈保留颜色与阴影变化。键盘焦点为明显黑色轮廓。
36
+
37
+ 卡片按可用空间自动换列,说明自然换行。表格仅在自身区域横向滚动,不让整页超宽;弹窗内容随视口约束纵向滚动。桌面展台目录用于浏览组件,订阅示例自身保持单页,不增加业务路由。
@@ -0,0 +1,145 @@
1
+ # 主题接入
2
+
3
+ `ThemeProvider`、`useTheme`、`themes` 和相关类型从 `@linhey/rhine-design/theme` 导出。此能力自 0.4.0 提供,npm 0.3.0 不包含;建议固定版本并提交 lockfile。组件仍从 `@linhey/rhine-design` 导入。
4
+
5
+ ## 接入一次,随处切换
6
+
7
+ ```tsx
8
+ import { Button } from '@linhey/rhine-design';
9
+ import { ThemeProvider, useTheme } from '@linhey/rhine-design/theme';
10
+
11
+ function Appearance() {
12
+ const { theme, setTheme } = useTheme();
13
+ return <Button onClick={() => setTheme('arknights')}>
14
+ 当前:{theme},切换战术主题
15
+ </Button>;
16
+ }
17
+
18
+ export function App() {
19
+ return <ThemeProvider defaultTheme="rhine"><Appearance /></ThemeProvider>;
20
+ }
21
+ ```
22
+
23
+ 主题入口自动加载基础样式与三套内置主题,不需要另行导入主题 CSS、添加 `.rhine-theme` 或修改 DOM。Provider 渲染一个真实的 `div.rhine-theme`,通过 `data-rhine-theme` 应用主题,不重建子组件。普通组件入口保持独立,不额外加载可选主题。
24
+
25
+ ## API
26
+
27
+ ```ts
28
+ type ThemeName = 'rhine' | 'arknights' | 'neo-brutalism';
29
+
30
+ interface ThemeProviderProps extends HTMLAttributes<HTMLDivElement> {
31
+ defaultTheme?: ThemeName;
32
+ theme?: ThemeName;
33
+ onThemeChange?: (theme: ThemeName) => void;
34
+ }
35
+
36
+ function useTheme(): {
37
+ theme: ThemeName;
38
+ setTheme: (theme: ThemeName) => void;
39
+ };
40
+ ```
41
+
42
+ | 属性 / 行为 | 约定 |
43
+ | --- | --- |
44
+ | `defaultTheme` | 非受控初始值,默认 `rhine`;后续修改该属性不会重置当前选择 |
45
+ | `theme` | 受控值;提供后以宿主传入值为准,`defaultTheme` 不生效 |
46
+ | `onThemeChange` | 请求切换到不同主题时同步调用;初次挂载、宿主更新 props、重复选择当前主题不会调用 |
47
+ | `setTheme(next)` | 非受控时更新内部值并通知;受控时仅通知宿主,宿主回传 `theme` 后生效 |
48
+ | 根节点属性 | 支持 `children`、`className`、`style`、`id`、ARIA、事件及 `data-rhine-motion` 等 div 属性;库保留 `.rhine-theme` 和 `data-rhine-theme` 的控制权 |
49
+ | 无 Provider | `useTheme()` 抛出明确错误 |
50
+ | 无效主题名 | TypeScript 限制为 `ThemeName`;运行时传入非法名字也会抛错,不静默应用不存在的主题 |
51
+
52
+ 受控模式中需由 `onThemeChange` 更新宿主状态,否则选择仅产生请求、界面保持原值。不要在同一个 Provider 生命周期中切换受控和非受控模式。
53
+
54
+ ## 使用主题列表生成选择器
55
+
56
+ `themes` 是只读元数据列表,每项包含稳定标识 `name` 和展示名称 `label`;`ThemeName` 从列表推导。不要根据中文名称保存偏好。
57
+
58
+ ```tsx
59
+ import { Select } from '@linhey/rhine-design';
60
+ import { themes, useTheme } from '@linhey/rhine-design/theme';
61
+
62
+ export function ThemeSelect() {
63
+ const { theme, setTheme } = useTheme();
64
+ return <Select label="界面主题" value={theme} onChange={event => {
65
+ const next = themes.find(item => item.name === event.target.value);
66
+ if (next) setTheme(next.name);
67
+ }}>
68
+ {themes.map(item => <option key={item.name} value={item.name}>{item.label}</option>)}
69
+ </Select>;
70
+ }
71
+ ```
72
+
73
+ ## 宿主管理偏好
74
+
75
+ ```tsx
76
+ import { useState } from 'react';
77
+ import { ThemeProvider, type ThemeName } from '@linhey/rhine-design/theme';
78
+
79
+ export function App() {
80
+ const [theme, setTheme] = useState<ThemeName>('rhine');
81
+ return <ThemeProvider theme={theme} onThemeChange={setTheme}>
82
+ <ThemeSelect />
83
+ </ThemeProvider>;
84
+ }
85
+ ```
86
+
87
+ 库不读写 `localStorage`、Cookie、URL 或账号设置。宿主将这些偏好校验为 `ThemeName` 后传入;读取失败或未知名称时应自行选择默认值。服务端渲染与其他构建器尚未验收;若宿主使用 SSR,需确保服务端和客户端初始值一致。
88
+
89
+ 展台 `preview/GalleryShell.tsx` 是受控接入示例:有效 URL `?theme=` 优先,其次有效本地偏好,最后 `rhine`;主动切换后移除主题查询参数,保留其他参数和章节 hash;存储不可用时仍可切换。上述持久化行为属于展台,不是库默认行为。
90
+
91
+ ## 范围、浮层和动态
92
+
93
+ - 第一版支持多个并列 Provider,各自维护主题。**不支持嵌套 Provider**,会抛错;不要把 Provider 嵌在另一个带主题的 `.rhine-theme` 内,也不要用嵌套手写主题根尝试覆盖风格。现有后代 CSS 选择器尚不具备嵌套隔离能力。
94
+ - Provider 放在页面根部,不添加 `transform`、裁剪或 `contain`。Dialog、Drawer、Menu、Popover、Tooltip、Combobox 的浮层沿用最近 `.rhine-theme` 挂载机制,主题切换时同步继承。
95
+ - 切换只改变视觉属性,不重挂载子树,不清除表单、关闭弹窗或重置滚动。宿主不要给 Provider 或应用子树添加 `key={theme}`。
96
+ - 主题 API 不注入切换动画。系统减少动态和 `data-rhine-motion="reduced"` 沿用组件规则。
97
+ - 内置主题属于视觉风格;不提供 `system` / `dark` / `light` 模式枚举。Logo 的 `theme` 仍是独立配色属性。
98
+ - 公共主题 CSS 只包含组件视觉。展台布局、HUD、终端读数、扫描与开机装饰位于 `preview/theme-*.css`,不会随公共主题入口进入应用。
99
+
100
+ ## 从手写主题根迁移
101
+
102
+ 将 `<div className="rhine-theme" data-rhine-theme="arknights">` 替换为 `<ThemeProvider defaultTheme="arknights">`,删除额外主题 CSS 导入;原根节点的布局类、`id`、动效属性直接传给 Provider。需要动态切换时调用 `useTheme().setTheme()`。
103
+
104
+ 旧的 CSS-only 用法继续支持:先导入基础组件样式,再导入 `theme-arknights.css` 或 `theme-neo-brutalism.css`,在根节点设置属性。两种用法选择一种即可,不要为迁移再包一层主题根。
105
+
106
+ ## 场景背景(0.5.0)
107
+
108
+ `Background` 从组件主入口导出,复用当前主题,不生成主题根。使用 `roleTone` 选择场景;默认是 `content`。主题定义颜色和纹理,业务无需判断当前主题名称。展台 `#backgrounds` 只展示当前选中主题的六类场景;使用右上角主题选择器切换三套内置主题,背景随之同步更新。
109
+
110
+ | `roleTone` | 用途 | 默认纹理 | 底色 / 文字变量 |
111
+ | --- | --- | --- | --- |
112
+ | `canvas` | 页面、工作台底层 | 跟随主题:纸面无纹理、战术网格、橙趣点阵 | `--rhine-canvas-bg` / `--rhine-canvas-text` |
113
+ | `content` | 正文、阅读区 | 无 | `--rhine-content-bg` / `--rhine-content-text` |
114
+ | `sidebar` | 导航、侧栏 | 无 | `--rhine-sidebar-bg` / `--rhine-sidebar-text` |
115
+ | `surface` | 卡片、表单分区 | 无 | `--rhine-surface-bg` / `--rhine-surface-text` |
116
+ | `overlay` | 弹窗、浮层表面 | 无 | `--rhine-overlay-bg` / `--rhine-overlay-text` |
117
+ | `media` | 图片、视频画布 | 无 | `--rhine-media-bg` / `--rhine-media-text` |
118
+
119
+ 每类背景还有 `--rhine-{roleTone}-texture` 变量,仅用于 `Background` 的场景纹理。纹理尺寸使用 `--rhine-background-scale`。媒体底色默认引用已有 `--rhine-media-canvas`,保留中性深色及配套浅色文字;`Surface` / `Fieldset` 使用表面底色,Dialog / Drawer / Popover / Menu 使用浮层底色,模态遮罩使用独立的 `--rhine-backdrop-bg`。Tooltip 保留各主题的强调配色。
120
+
121
+ ```tsx
122
+ import { Background, Surface } from '@linhey/rhine-design';
123
+
124
+ <Background roleTone="canvas">
125
+ <Background roleTone="content">
126
+ <Surface>正文默认关闭纹理,卡片保持不透明。</Surface>
127
+ </Background>
128
+ </Background>
129
+
130
+ // 显式开启当前区域的主题纹理;纸面主题仍然没有纹理。
131
+ <Background roleTone="content" decoration="theme">概览区域</Background>
132
+
133
+ // 局部配色:同时选择能清楚阅读的文字色。
134
+ <Background appearance={{ color: '#e5ece3', textColor: '#242b28' }}>
135
+ 阅读区域
136
+ </Background>
137
+ ```
138
+
139
+ `decoration` 默认 `auto`,使用场景默认值;`theme` 显式使用主题纹理;`none` 强制移除纹理,包括 `appearance.texture`。`appearance` 可局部覆盖 `color`、`textColor`、`texture`、`textureSize`,优先于场景/主题默认值;省略项仍跟随主题,嵌套 `Background` 重置为自身场景,不继承外层局部覆盖。原生 `style` 保持最终 CSS 覆盖权。
140
+
141
+ 背景只负责当前元素的绘制,不添加内边距、固定高度、定位、裁剪、伪元素或鼠标拦截层。`overlay` 只是视觉表面,不创建对话框、遮罩或焦点管理;实际交互使用 Dialog / Drawer 等组件。媒体与局部文字配色不重写子组件自身的颜色变量;输入、按钮等仍使用完整主题样式。
142
+
143
+ 若需全主题配置,在**主题根**通过 CSS 变量覆盖对应场景的底色、文字与纹理;局部调整优先用 `appearance`。局部背景不会传递给挂在主题根的 Portal,浮层仍使用主题的浮层变量。
144
+
145
+ 兼容已有源码调用:`roleTone` 的原有三种值与显式 `theme` / `none` 均保留;省略 `decoration` 的正文及 `AppShell` 改为 `auto`,默认纯色。需要旧版正文纹理的调用方显式传入 `decoration="theme"`。侧栏显式传入 `theme` 现在也会开启纹理;默认仍关闭。以上能力自 0.5.0 提供,不属于 npm 0.4.0。
Binary file
@@ -0,0 +1,75 @@
1
+ # DataTable / Pagination 对标核对
2
+
3
+ 核对日期:2026-10-24。范围:issues #1–#6 的 DataTable 与 Pagination 扩展。以下是 API 与行为核对,不是 Ant Design 或 MUI 的兼容实现;本页扩展自 0.5.0 提供。
4
+
5
+ ## 官方依据
6
+
7
+ - [Ant Design Table API](https://ant.design/components/table/#api):受控排序、选择、展开与固定列;重点为 `selectedRowKeys`、`preserveSelectedRowKeys`、`type`、`hideSelectAll`、`checkStrictly`、`rowExpandable`、`rowScope`。
8
+ - [Ant Design Pagination API](https://ant.design/components/pagination/#api):受控页码/页大小、禁用、快速跳页与总量。
9
+ - [MUI Table](https://mui.com/material-ui/react-table/):原生表格组合、滚动容器、固定表头、分页控件与焦点恢复。
10
+ - [MUI TablePagination API](https://mui.com/material-ui/api/table-pagination/):未知总量、独立页大小事件、首末页与 `disabled`。
11
+ - [MUI X row selection](https://mui.com/x/react-data-grid/row-selection/):受控选择、禁选、跨服务端页保留 key、仅可见行全选。
12
+ - [MUI X tree data](https://mui.com/x/react-data-grid/tree-data/) 与 [master-detail](https://mui.com/x/react-data-grid/master-detail/):树数据、受控详情展开及独立数据所有权。
13
+ - [MUI X sorting](https://mui.com/x/react-data-grid/sorting/) 与 [column pinning](https://mui.com/x/react-data-grid/column-pinning/):受控排序模型与左右固定列的布局约束。
14
+
15
+ ## API 与行为逐项判定
16
+
17
+ | 核对项 | Rhine API / 当前行为 | 判定与本次处理 |
18
+ | --- | --- | --- |
19
+ | 数据与事件所有权 | `rows`、`sort`、`onSortChange`;组件原样展示传入行 | 保留;只回传排序意图,不以本地排序/筛选替代服务端结果 |
20
+ | 多选与禁选 | `selection.keys/onChange/isDisabled` | 已有;只更改可见且可选 key,全选/取消全选均保留跨页及禁选 key |
21
+ | 单选 | `selection.mode='single'`,默认 `multiple` | 补齐;原生同名 radio,选择新行回传仅该 key,不提供全选;单选 keys 只读取第一项 |
22
+ | 不允许批量选择 | `selection.hideSelectAll` | 补齐;保留每行选择与列标题,隐藏全选入口 |
23
+ | 行点击与键盘 | checkbox/radio/button 原生 Tab、Space/Enter | 保留独立操作入口;行内链接、菜单不会隐式改变选择,不伪装 ARIA grid |
24
+ | 父子选择 | 树父、子独立 key | 保留;不推断未加载子树的选择状态,不自动级联 |
25
+ | 排序取消 | 可选 `onSortReset` 显示“清除排序”;原 `onSortChange(TableSort)` 不变 | 补齐显式取消;先将焦点送回排序列按钮(缺失时回到表格滚动区),再发送重置意图,避免按钮卸载丢焦点;宿主清空 `sort` 后才改变排序状态,保留既有升/降循环 |
26
+ | 排序可发现性 | `aria-sort`、文字表头与方向图标、下一方向 `title` | 已有状态语义,补齐下一动作提示;不靠箭头颜色表达方向 |
27
+ | 受控展开与禁展开 | `expansion.keys/onChange/getChildren/renderDetail/canExpand` | 修复:`canExpand=false` 也阻止陈旧 keys 渲染子行及详情,不能只隐藏按钮 |
28
+ | 详情关联 | 展开按钮 `aria-expanded/aria-controls`,详情命名 region | 修复:ID 基于 row key,不因服务端排序更换;数字 `0` 仍是有效详情 |
29
+ | 行标题语义 | `DataColumn.rowHeader` | 补齐可选 `th scope=row`;保持正文背景与固定列层叠,不被固定表头规则吸到顶部 |
30
+ | 多固定列 | `sticky='left'/'right'`、可选 `stickyOffset` | 修复默认多列重叠;按真实布局宽度累计偏移,ResizeObserver 响应尺寸变化,显式 offset 优先;测量使用 CSS 布局宽度,避免 CSS zoom 加倍偏移 |
31
+ | 固定表头与密度 | `stickyHeader/maxHeight/density` | 保留;表格区域内部滚动,操作仍有 44px 命中区 |
32
+ | 加载/失败/空态 | `loading/error/onRetry/emptyTitle` | 保留旧行并说明刷新/错误,初始空态与读取失败分开;禁用会提交新请求的排序/选择 |
33
+ | 页码与页大小 | 1-based `page`、`pageSize`、独立 `onPageChange/onPageSizeChange` | 保留;页大小变化不隐式请求另一页,由宿主原子更新查询条件 |
34
+ | 未知总量 | `total` 缺省,`hasNext` 控制下一页 | 保留;负值/非有限 total 也按未知处理,不把未知写为零;只有 `total=0` 表示零条 |
35
+ | 首页/末页 | `showFirstButton/showLastButton`,均默认 false | 补齐;未知总量的末页按钮禁用,避免捏造最后页 |
36
+ | 全局禁用与多个分页 | `disabled`、`label`(默认“分页”) | 补齐;禁用可与加载分离;不同表格可以提供不同 navigation 名称 |
37
+ | 总量缩小越界 | 显式标注当前页超出总页数;上一页返回已知末页 | 修复;不静默伪装当前行属于另一页,不在 render/effect 自动请求修正 |
38
+ | 分页焦点 | 当当前 action 因页边界禁用时移至相邻可用 action | 补齐;没有可用 action 时不强行移动,外部已获取焦点时不抢回;页码摘要提供 status |
39
+
40
+ ## 有意保留的差异
41
+
42
+ 本库保持原生表格和受控服务端数据边界。AntD 的内置筛选、本地比较器、父子级联、单元格编辑、汇总/合并表头、拖列与虚拟化,以及 MUI X 的网格单元格导航、include/exclude 大数据选择模型、分组聚合,不在本轮 issue 的组件契约内。需要大量数据时宿主提供分页结果;没有自动拉全量模式。树行用明确层级文字与展开按钮,不宣称实现 `treegrid` 的完整键盘模型。
43
+
44
+ 分页为紧凑的服务端前后页控件,补充可选首末页;没有页码枚举、快速跳页或“每页全部”,也不复制两个框架的页码基准差异。Rhine 使用 1-based;适配 MUI TablePagination 的 0-based 状态时由宿主转换。
45
+
46
+ 固定列总宽仍须小于可用表格视口;窄屏由宿主减少固定列,不能用固定列覆盖所有可滚动内容。`stickyOffset` 仍可用于宿主特殊布局。API 以本库原名为准,不同时增加两套同义属性。
47
+
48
+ ## 验证
49
+
50
+ - `tests/DataParity.test.tsx`:12 项针对新增与修复边界的回归,包含单选、隐藏全选、排序取消及焦点、禁展开陈旧 key、详情 ID、行标题、多固定列测量、已知/未知末页、越界恢复、非法数值、禁用/地标及分页焦点。
51
+ - `npm test -- tests/DataParity.test.tsx tests/CommonComponents.test.tsx tests/Extensions.test.tsx`:40 项通过,保留跨页/禁选选择和旧排序/分页契约。
52
+ - `npm run typecheck`:通过。执行环境 Node 24。
53
+ - JSDOM 的列宽测试使用可变 `offsetWidth` 与 ResizeObserver 回调,证明偏移计算及重测。
54
+ - 独立真实组件浏览器 fixture:3 主题 × 400/520/880 共 9 个布局,两个左固定列间距差均为 0px、无正文横向溢出、右列不覆盖左列、固定表头保持顶部且行标题随正文垂直滚动;3 主题在 CSS zoom 200% / 520px 有效宽度下固定列仍相邻。原生 radio 方向键跳过禁选行,分页到末页后焦点回上一页,最终重载回归无 pageerror。该证据不是原生菜单缩放、屏幕阅读器或产品集成验收;真实展台总体验收另记。
55
+
56
+ ## 最小接入
57
+
58
+ ```tsx
59
+ <DataTable
60
+ caption="记录"
61
+ rows={rows}
62
+ rowKey={row => row.id}
63
+ columns={[
64
+ { key: 'name', header: '名称', render: row => row.name, rowHeader: true, sortable: true, sticky: 'left', width: 160 },
65
+ { key: 'status', header: '状态', render: row => row.status, sticky: 'left', width: 120 },
66
+ ]}
67
+ selection={{ mode: 'single', keys: selectedKeys, onChange: setSelectedKeys }}
68
+ sort={sort}
69
+ onSortChange={setSort}
70
+ onSortReset={() => setSort(undefined)}
71
+ />
72
+ <Pagination page={page} pageSize={pageSize} total={total} onPageChange={setPage} showFirstButton showLastButton label="记录分页" />
73
+ ```
74
+
75
+ 示例中的 setState 表示查询意图;实际宿主负责取消旧请求、请求排序/分页结果、更新状态以及处理失败。
@@ -0,0 +1,88 @@
1
+ # 表单组件:Ant Design / MUI API 与交互核对
2
+
3
+ 查阅日期:**2026-10-24**。范围为 issues #1–#6 本次新增的 DateField、DateRangePicker、NumberField、RemoteMultiSelect、TreeSelect,以及它们复用的 Combobox。实现位置为 `components/AdvancedForms.tsx` / `components/Forms.tsx`;这些 API 自 0.5.0 提供。
4
+
5
+ 官方页面当次标注 Ant Design **6.6.7**、Material UI **9.5.0**、MUI X Date Pickers **9.15.0**、Tree View **9.16.0**。版本仅标识这次查阅内容,不表示安装或运行过这些框架。此核对是官方 API/交互文档对照与 Rhine 自身验证,没有声称完成上游源码或跨库像素比较。
6
+
7
+ ## DateField
8
+
9
+ 参考 [Ant Design DatePicker](https://ant.design/components/date-picker/#api) 的值、日期限制、只读输入等契约,以及 [MUI X DateField API](https://mui.com/x/api/date-pickers/date-field/) 的 `readOnly`、`required`、`name`、辅助文本、错误通知。Rhine 保留 date-only ISO 字符串;MUI 的日期适配对象不进入本库 API。
10
+
11
+ | 核对项 | 原实现 | 本次补齐 / 保留 |
12
+ | --- | --- | --- |
13
+ | 受控值和空值 | `value: string` / 空字符串;宿主更新值 | 保留,不新增隐式日期、时区转换或自动钳制 |
14
+ | `min` / `max` 与外部无效值 | 仅传给原生 input;外部改约束后没有可见错误 | 校验 ISO 格式、真实日历日期、上下界及反向边界,显示 `aria-invalid` 和错误;不回写受控值 |
15
+ | 错误通知 | 只能传入 `error` | 新增 `onValidationChange(error: string \| null)`,初次渲染及校验结果变化通知;外部 `error` 优先 |
16
+ | 只读和表单关联 | 缺失 | 新增 `readOnly`、`required`、`id`、`name`、`hint`;只读仍可聚焦,不能改变值 |
17
+ | 键盘 / 移动日期面板 | 浏览器原生 `type=date` | 保留原生选择器;显示格式和日历面板由浏览器/系统决定 |
18
+
19
+ 明确保留差异:没有自绘月历、任意日期禁选回调、月份/年份选择模式、适配器/时区、分段键盘编辑或统一跨浏览器日期显示格式。若产品需要这些能力,应单独定义 Calendar 的无障碍与移动端契约;不能把原生输入包装称为完整 AntD/MUI DatePicker 等价物。`required` 使用浏览器表单校验;`onValidationChange` 报告日期格式、范围及显式 `error`,不代替提交时的 required 校验。
20
+
21
+ ## DateRangePicker
22
+
23
+ 参考 [Ant Design RangePicker](https://ant.design/components/date-picker/#rangepicker) 的区间、预设、空值行为,以及 [MUI X DateRangePicker](https://mui.com/x/react-date-pickers/date-range-picker/) 和 [API](https://mui.com/x/api/date-pickers/date-range-picker/) 的组合、只读、表单和校验能力。
24
+
25
+ | 核对项 | 原实现 | 本次补齐 / 保留 |
26
+ | --- | --- | --- |
27
+ | 区间与顺序 | `{ start, end }`;反向区间提示错误 | 保留两端受控、允许编辑中单端为空;不自动交换用户输入 |
28
+ | 区间边界 | 结束日期 min 跟随开始值 | 继续限制结束端;额外校验两端日期、全局 min/max 与非法边界 |
29
+ | 预设 | 仅检查开始低于 min / 结束高于 max | 反向日期、非法日期、任一端越界的预设都不能提交;`required` 时禁止空端预设 |
30
+ | 只读 / 清空 | 缺只读,清空按钮固定 | 新增 `readOnly`;同时锁住两端、预设和清空;`clearable` 默认 true |
31
+ | 标签和表单 | 开始/结束标签固定,不能命名字段 | 新增 `startLabel`、`endLabel`、`startName`、`endName`、`required`、`hint` |
32
+ | 可组合校验 | 仅页面错误文本 | 新增 `onValidationChange`,宿主可用于错误汇总/阻止提交 |
33
+
34
+ 明确保留差异:Rhine 是两个原生日期字段及快捷预设的组合,没有双月联动月历、拖动范围、移动端确认面板、日期部分选择或适配器功能。预设由宿主提供明确日期,不内建“最近七天”等依赖时区/当前时间的计算。更新一个端点只发变更意图,不清空另一个端点。
35
+
36
+ ## NumberField
37
+
38
+ 参考 [Ant Design InputNumber](https://ant.design/components/input-number/#api) 的只读、步进、精度、控制按钮与动态边界。MUI 官方 [Number Field](https://mui.com/material-ui/react-number-field/) 页面提供的是 **Base UI 组合示例**,不是 `@mui/material` 开箱即用的 NumberField 导出;对应底层契约见 [Base UI NumberField](https://base-ui.com/react/components/number-field)。
39
+
40
+ | 核对项 | 原实现 | 本次补齐 / 保留 |
41
+ | --- | --- | --- |
42
+ | 编辑草稿 / 数值提交 | `-` / `1.` 草稿保留;blur/Enter 提交合法数值 | 保留;新增 Escape 取消尚未提交的草稿并恢复宿主值,不发值变更 |
43
+ | 禁用 vs 只读 | 只有 disabled | 新增 `readOnly`,保留可聚焦输入,阻止输入、步进、提交,并禁用按钮 |
44
+ | 动态范围 | 改 min/max 不会立即标错 | 越界和反向配置立即显示错误;不钳制宿主值、不发送伪用户变更 |
45
+ | 空值步进 | `Number('')` 被视为 0,负值范围的加按钮可能被误禁用 | 空值不参与禁用边界比较;首次步进归入有效范围,非有限 step 回退 1 |
46
+ | 控制按钮 / 表单 | 固定按钮,缺字段身份 | 新增 `controls`(默认 true)、`name`、`id`、`required`、`hint` |
47
+ | 小数、IME、未知值 | 有精度/step/null/IME 契约 | 保留,输入法确认不会误提交或步进 |
48
+
49
+ 明确保留差异:没有任意精度字符串、格式化/解析器、货币本地化、滚轮调值、拖动调值、长按加速或额外修饰键步长。数值仍为 JS `number \| null`,不能承诺财务高精度。越界手工输入保留草稿并拒绝提交;按钮/方向键步进会钳制到界限。`name` 提交当前显示的输入字符串;应用提交前仍应采用受控数值和现有表单校验。
50
+
51
+ ## RemoteMultiSelect / Combobox
52
+
53
+ 参考 [Ant Design Select](https://ant.design/components/select/#api) 的 multiple、选中标签、清空、远程搜索,以及 [MUI Autocomplete](https://mui.com/material-ui/react-autocomplete/#asynchronous-requests) / [API](https://mui.com/material-ui/api/autocomplete/) 的独立 input/value、持续选择、只读和选中语义。
54
+
55
+ | 核对项 | 原实现 | 本次补齐 / 保留 |
56
+ | --- | --- | --- |
57
+ | 服务端结果 / 已选值 | query/options/value 独立受控;完整 Choice 保留跨查询标签 | 保留;组件不本地过滤、请求、防抖或处理响应次序 |
58
+ | 连续多选 | 每选一项就关闭;listbox 无多选语义 | 多选默认 `closeOnSelect=false`,持续选择;listbox 添加 `aria-multiselectable`,选中选项正确 `aria-selected` |
59
+ | 查询重置 | 保留 query | 继续默认保留;新增显式 `clearQueryOnSelect=true` 可在选择后发送空查询 |
60
+ | 删除 / 焦点 | 标签按钮删除后焦点可能落到 body | 删除/清空后回输入框;空查询 Backspace 删除最后一个可移除标签,尊重锁定项和 IME |
61
+ | 只读 / 清空 / 辅助 | 缺只读及配置 | 新增 `readOnly`、`clearable`、`placeholder`、`hint`;只读输入不打开候选,标签不能删除 |
62
+ | 原生 FormData | 缺失 | `name` 为每个受控选中 key 渲染同名 hidden input,包含锁定选中项,disabled 不提交 |
63
+ | 既有单选 Combobox 兼容 | 选完关闭 | 默认仍关闭;新增可选 `selectedValues`、`closeOnSelect`、`onEmptyBackspace` 及 readOnly/id/hint/placeholder 支持多选包装,不改变单选 value 类型 |
64
+
65
+ 明确保留差异:没有 freeSolo、自动 tokenization、本地分组/筛选、虚拟列表或任意 option renderer。已经选中的候选会标记为选中并跳过重复添加;取消选择通过标签、清空或 Backspace,未把选项点击改成反选。loading/error 仍阻止选取过期候选;已选值保持可见并可按宿主规则删除。未提交的输入不成为选中值。
66
+
67
+ ## TreeSelect
68
+
69
+ 参考 [Ant Design TreeSelect API](https://ant.design/components/tree-select/#api) 的节点可选性、受控展开和严格独立选中;交互基线参考 [MUI X Tree View accessibility](https://mui.com/x/react-tree-view/accessibility/)。Rhine 当前是内联树选择视图,形态更接近 MUI Tree View,不是 AntD 的下拉 TreeSelect。
70
+
71
+ | 核对项 | 原实现 | 本次补齐 / 保留 |
72
+ | --- | --- | --- |
73
+ | 选中/展开所有权 | 独立受控,父子不联动 | 保留;不自动传播选中,不隐式展开所有选中祖先 |
74
+ | 初次 Tab 进入 | 总在第一个可见节点 | 优先第一个可见已选项;之后保持用户浏览的节点 |
75
+ | Arrow / Home / End | 已支持层级导航 | 保留;DOM id 基于稳定 node id,避免展开改变数组下标导致标识漂移 |
76
+ | 字符导航 | 缺失 | 添加 500ms 连续前缀匹配;重复字符在同前缀节点间循环;不修改选择,不触发服务端查询 |
77
+ | 批量键盘操作 | 缺失 | `*` 展开同层分支;Ctrl/⌘+A 切换当前可见可选节点,保留不可见/禁选已选 key |
78
+ | 节点可选性 | 只有 disabled | 新增 `selectable=false` 作为结构节点,仍可展开/导航 |
79
+ | 外部收起 / 焦点 | 焦点节点移除后可能丢到 body | 宿主收起分支时恢复到可见祖先;没有祖先时恢复可见已选/首节点 |
80
+ | 只读 / 表单 | 缺失 | 新增 `readOnly`,可继续浏览展开但不能选中;`name` 同名 hidden inputs 提交受控 key |
81
+
82
+ 明确保留差异:没有下拉搜索外壳、虚拟化、节点懒加载调度或级联半选;宿主更新 nodes/expandedKeys 即可显示加载后的节点。多选采用独立勾选模式,不内建 Shift 范围选择。Enter 与 Space 均选择节点,分支展开由方向键/展开按钮负责,保持原有契约;这和 MUI 的分支 Enter 展开行为不同。禁选节点仍可获焦浏览并展开,以免其可选子节点不可达。Ctrl/⌘+A 仅限可见节点,不推断未加载数据。
83
+
84
+ ## 回归证据
85
+
86
+ `tests/FormParity.test.tsx` 新增 **14** 个行为测试,覆盖动态日期错误与通知、通知回调稳定性、反向/非法预设、只读保护、数值取消草稿和空值步进、多选连续选择与 aria、锁定标签/IME、删除焦点、单选兼容、树类型导航/批选/外部收起焦点和字符事件不冒泡。`tests/Extensions.test.tsx` 保留原有受控/IME/跨查询与禁选树行为证据。
87
+
88
+ 本次负责范围已运行 FormParity + CommonComponents(27 个用例通过)及 TypeScript 检查。独立 Chromium 会话 `rhine-forms-parity` 在 `http://127.0.0.1:18309/#advanced-forms` 验证 16 项真实输入/键盘/焦点/只读断言;三主题 × 400/520/880 宽度及三次 CSS 200% 缩放均没有 document/main 横向溢出,控制台 0 错误。局部截图为忽略目录 `output/playwright/forms-parity-400.png`。完整生产构建和跨组件回归由本轮主报告汇总;这里不把 JSDOM 或 Chromium 日期字段测试称为所有系统原生日期面板或屏幕阅读器验证。
@@ -0,0 +1,44 @@
1
+ # 监控组件:Ant Design / MUI 对照
2
+
3
+ 查阅:2026-10-24。对象为 issue #6 的 StatusTimeline 详情、TrendChart、StackedBar。Ant Design 的 Timeline 是事件序列,不能直接等同可拖动的健康历史;其图表由独立的 Ant Design Charts 提供。此处分别对照 Slider + Popover、Charts,避免把相近名称当作相同控件。
4
+
5
+ ## 官方依据
6
+
7
+ - [Ant Design Slider](https://ant.design/components/slider/):value/defaultValue、onChange、disabled 与键盘。
8
+ - [Ant Design Popover](https://ant.design/components/popover/):open/defaultOpen/onOpenChange、触发方式与避让。
9
+ - [Ant Design Timeline](https://ant.design/components/timeline/):items、方向与事件内容;仅用于确认语义差异。
10
+ - [Ant Design Charts 官方 Line 示例](https://charts.ant.design/options/plots/style/)、[Axis](https://charts.ant.design/en/options/plots/axis/)、[Bar](https://ant-design-charts.antgroup.com/en/components/plots/bar):数据字段、轴、交互、堆叠和百分比。
11
+ - [MUI Slider API](https://mui.com/material-ui/api/slider/):受控状态、可访问值描述与禁用。
12
+ - [MUI X Charts 键盘](https://mui.com/x/react-charts/accessibility/):图形焦点、左右键、Home/End。
13
+ - [MUI X Lines](https://mui.com/x/react-charts/lines/):null、缺失断线及可选连接;[Tooltip](https://mui.com/x/react-charts/tooltip/):数值格式化。
14
+
15
+ ## 逐项核对
16
+
17
+ | 组件 / 项目 | 原实现 | 本轮结果 / 验收 |
18
+ | --- | --- | --- |
19
+ | StatusTimeline 选中状态 | 仅内部数组下标 | 新增 `activeId/defaultActiveId/onActiveChange`;插入、重排按稳定 ID 保留,移除时回到最新时段;不回写宿主 |
20
+ | 详情开关 | 仅内部 open | 新增 `open/defaultOpen/onOpenChange`;受控模式仅回传意图,父组件决定是否打开/关闭 |
21
+ | 禁用 | 仅空数组时禁用 | 新增 `disabled`,禁用时不显示详情,也不接受鼠标选择 |
22
+ | 键盘 | range 方向键;Escape | 保留原生方向键/Home/End;Enter/Space 进入详情关闭按钮;关闭焦点回健康条且不重新打开 |
23
+ | 浮层定位 / 叠层 | 已使用 Radix Portal、碰撞检测 | 保留同主题根、视口 12px 避让、内部滚动、非模态、不抢悬停焦点 |
24
+ | 可访问关联 | aria-valuetext/details | 增加展开状态、dialog 类型与 controls;同一份结构化详情供键盘/触屏/鼠标读取 |
25
+ | 业务证据 | 零/未知、原因、统计口径 | 保留;没有证据时不生成成功率或健康评分 |
26
+ | TrendChart 数值格式 | 固定 String | 新增 `valueFormatter(value)`,缺失与非有限值统一传 null;刻度和逐点详情采用格式化,原始数据表保留精确数值 |
27
+ | 逐点交互 | 仅 SVG title + 展开数据表 | 图形可 Tab 聚焦;左右/Home/End、悬停/轻触查看同一个 HTML 数值区;包括零与缺失桶 |
28
+ | 选中同步 | 无 | 新增 `activePointId/defaultActivePointId/onActivePointChange`,受控变化不越过宿主;按 ID 保留,不按数组位置漂移 |
29
+ | 断线 | null/nonfinite 不补零 | 保留缺失断线;没有增加 connectNulls,以免掩盖监控采集缺口 |
30
+ | 坐标 | 大幅正负数相减可能溢出 | 先按数值幅度归一化再映射,有限极值不产生 NaN/Infinity SVG 坐标 |
31
+ | StackedBar 零分段 | flex gap 为零值留下空隙 | 零不绘制色段,文字仍列 0 与 0.0% |
32
+ | 小数分段宽度 | flex-grow 原始计数总和小于 1 时色带无法填满 | 使用归一化权重分配全宽;0.1 / 0.2 与文字 33.3% / 66.7% 一致 |
33
+ | 分类数值格式 | 固定 String | 同样支持 valueFormatter;计数与比例计算仍使用原始值 |
34
+ | 未知/负数/总和溢出 | 不绘制百分比 | 保留;不复制图表引擎对缺失项的默认归一化行为 |
35
+ | 陈旧 / 错误 / 空 | 保留数据并说明 | 保留,loading 不清空已有数据;空数组和全未知不同文案 |
36
+ | 主题 / 动效 / 大量数据 | 同源 CSS tokens、无动画 | 保留;桶排序/聚合/限量在宿主,不新增图表引擎和网络层 |
37
+
38
+ ## 保留差异
39
+
40
+ 只提供单序列柱/折线及一组互斥分类的占比。多坐标轴、图例筛选、多序列、brush、缩放、导出和数据引擎属于另一个扩展范围;不把 MUI X / Ant Design Charts 的全部 API 套到运维轻图表。StatusTimeline 继续表达时段证据,不改成事件轴。受控/非受控模式应在组件生命周期内保持一致。
41
+
42
+ ## 证据
43
+
44
+ `tests/MonitoringParity.test.tsx`:9 个新增行为回归,覆盖受控意图、稳定 ID、键盘、Enter 进入/关闭归还、格式化与原始值、有限极值、零/小数占比及禁用。与既有 Monitoring、StatusTimeline 测试共 14 项通过。真实浏览器矩阵及最终全量结果统一记录于 [ISSUE_VALIDATION](../ISSUE_VALIDATION.md),不以 JSDOM 代替定位或读屏验收。
@@ -0,0 +1,91 @@
1
+ # Background、AppShell 与媒体组件对标
2
+
3
+ 查阅日期:**2026-10-24(Asia/Shanghai)**。对照对象为 Ant Design 与 MUI 官方在线文档;查阅时页面分别标示 Ant Design 6.6.7、Material UI 9.5.0。本记录核对 Rhine Design 0.5.0 的 API 与交互,不表示 npm 0.4.0 已有这些能力,也不宣称与两套组件库完全兼容。
4
+
5
+ ## 官方依据与对应关系
6
+
7
+ | Rhine 组件 | Ant Design 对照 | MUI 对照 | 对应范围 |
8
+ | --- | --- | --- | --- |
9
+ | Background | [Layout](https://ant.design/components/layout/#api) 的区域与样式能力 | [Box](https://mui.com/material-ui/react-box/)、[Theming](https://mui.com/material-ui/customization/theming/) | 没有同名、同职责组件;只对照局部表面样式、主题继承和原生属性,不把 ThemeProvider 当作背景组件 |
10
+ | AppShell | [Layout.Sider](https://ant.design/components/layout/#layout.sider)、[Drawer](https://ant.design/components/drawer/#api) | [响应式 Drawer](https://mui.com/material-ui/react-drawer/#responsive-drawer)、[Drawer API](https://mui.com/material-ui/api/drawer/) | 主体布局、受控折叠、宽度/断点与临时侧栏;MUI 没有同名 AppShell |
11
+ | MediaCard | [Card](https://ant.design/components/card/#api)、[Image](https://ant.design/components/image/#api) 的加载/错误展示 | [Card / CardMedia / CardActionArea](https://mui.com/material-ui/react-card/#primary-action) | 媒体内容、主操作与次操作分区;图片请求不归 Card 容器 |
12
+ | MediaViewer | [Image.PreviewGroup](https://ant.design/components/image/#previewgrouptype) | [全屏 Dialog](https://mui.com/material-ui/react-dialog/#full-screen-dialogs)、[Dialog API](https://mui.com/material-ui/api/dialog/) | 分组浏览、边界、焦点与全屏浮层;MUI Core 没有 Image.PreviewGroup 的直接替代品 |
13
+
14
+ AntD 的 Sider 提供折叠状态与响应式配置;MUI 区分常驻与临时 Drawer。两者均可控制临时面板开关和出现边缘。Rhine 保留已有桌面状态协议,新增独立的抽屉控制,以免导航点击无法关闭手机抽屉。[AntD Layout](https://ant.design/components/layout/#layout.sider)、[AntD Drawer](https://ant.design/components/drawer/#api)、[MUI Drawer](https://mui.com/material-ui/api/drawer/)。
15
+
16
+ MUI Card 将主点击区域与补充操作分开;Rhine 的复选框原已独立,本轮新增同样独立的 `actions` 内容区域。AntD Image 的资源载入、变换和 PreviewGroup 集合管理范围更大,不能把提供一个全屏 Dialog 视为完成这些功能。[MUI Card](https://mui.com/material-ui/react-card/#primary-action)、[AntD Image](https://ant.design/components/image/#previewgrouptype)。
17
+
18
+ ## 逐项核对与结果
19
+
20
+ | 组件 / 核对项 | 原实现 | 本轮处理 / 判定 |
21
+ | --- | --- | --- |
22
+ | Background:主题继承 | 六种语义区域、局部 appearance、单一主题根 | 保留。背景不创建 Provider,不拦截指针,不引入定位上下文 |
23
+ | Background:原生扩展 | HTML 属性、className、style;类型缺少 ref | 补齐 React 19 `ref`,透传到同一 div;样式覆盖优先级不变 |
24
+ | Background:纹理 | auto 仅 canvas;正文默认纯色 | 保留用户已确定的设计;不是 MUI `sx` 的同构实现 |
25
+ | AppShell:布局 | single / double-left / left-right,正文挂载稳定 | 保留,三种布局沿用同一正文树 |
26
+ | AppShell:桌面状态 | `open`、`onOpenChange` 必填 | 保留受控契约。不开辟第二套默认桌面状态,避免持久化状态来源分裂 |
27
+ | AppShell:临时侧栏状态 | 只有内部 state,宿主导航无法关闭 | **补齐** `drawerOpen`、`onDrawerOpenChange`;省略受控值时继续本地管理,并支持 `defaultDrawerOpen` |
28
+ | AppShell:边缘 | 全部复用默认右 Drawer | **修正**:全局导航从左出现;double-left 次栏从左;left-right 次栏从右。共享 Drawer 新增 `side`,其默认右侧保持兼容 |
29
+ | AppShell:尺寸 | 桌面224/256、容器阈值760/1120固定 | **补齐**每栏 `width`(桌面 px)、`breakpoint`(容器 px);省略保持原值;阈值相等时使用桌面栏。以 clientWidth 布局像素判断,不受 CSS zoom/transform 对视觉边界尺寸的放大影响 |
30
+ | AppShell:局部样式 | 根元素无扩展入口 | **补齐** `className` / `style`,不开放替换焦点管理结构的任意 slots |
31
+ | AppShell:焦点 | 栏隐藏/移除返回存活按钮,抽屉由 Radix 管理 | 保留并回归。正文输入不因侧栏折叠而重建 |
32
+ | MediaCard:选择 vs 打开 | 独立 checkbox 与打开按钮 | 保留。禁用同时阻止打开/选择;不会把 loading 自动等同 disabled |
33
+ | MediaCard:错误恢复 | 有加载/错误/空态,但没有次操作位置 | **补齐**独立 `actions`,可以放重试;点击不触发 `onOpen` / 选择 |
34
+ | MediaCard:内容 / 样式 | 预览 ReactNode;缺少根节点属性 | **补齐**原生 article 属性、ref、className、style;修正数值 `0` 预览被误当空态 |
35
+ | MediaViewer:受控 / 默认 | 支持 open、onOpenChange;缺默认打开 | **补齐** `defaultOpen`。受控 open 优先;仍需宿主采纳回调更新状态 |
36
+ | MediaViewer:信息侧栏 | 没有 onMetadataOpenChange 时不能切换 | **补齐**本地状态与 `defaultMetadataOpen`;传入 metadataOpen 时保持受控;按钮与信息区域通过 aria-controls 关联;数值0内容有效 |
37
+ | MediaViewer:上一项 / 下一项 | 按钮动作由宿主提供;缺快捷键 | **补齐** ← / →;`keyboardNavigation=false` 可关闭这些快捷键。Escape 退出由 Radix 保持 |
38
+ | MediaViewer:集合边界 | 缺 callback 即禁用,无独立禁用状态 | **补齐** previousDisabled / nextDisabled;缺 callback 仍禁用;无自动循环,数据索引由宿主维护 |
39
+ | MediaViewer:到达边界后的焦点 | Chrome 将变为 disabled 的已聚焦按钮焦点丢到 body | **修复**:将焦点移到查看器本体,反向方向键仍有效;离开查看器返回实际打开它的媒体卡片 |
40
+ | MediaViewer:快捷键冲突 | 此前没有快捷键 | 新快捷键跳过编辑器、select、视频/音频、slider/tree/listbox/tablist/menu,跳过组合输入、修饰键与嵌套 Portal;不劫持这些控件的方向键 |
41
+ | MediaViewer:内容 / 样式 | children / actions / metadata 已可组合 | 保留这些语义插槽,**补齐** className/style 到全屏 Dialog 内容;不改变暗色媒体画布 |
42
+ | 浮层:主题 / 焦点 / 退出 | Portal 到最近主题根,Radix focus trap、Escape | 保留。点击操作、键盘、嵌套层及实际 opener 返回继续由现有机制负责 |
43
+
44
+ ## 新增 API 的边界
45
+
46
+ ```tsx
47
+ // open 管理桌面折叠;drawerOpen 管理临时浮层,两者互不替代。
48
+ <AppShell
49
+ layout="left-right"
50
+ navigation={{
51
+ label: '导航', content: navigation,
52
+ open: navigationExpanded, onOpenChange: setNavigationExpanded,
53
+ drawerOpen: navigationDrawerOpen, onDrawerOpenChange: setNavigationDrawerOpen,
54
+ width: 240, breakpoint: 800,
55
+ }}
56
+ >
57
+ {body}
58
+ </AppShell>
59
+
60
+ <MediaViewer
61
+ trigger={<button>查看媒体</button>}
62
+ title={current.title} description={`${index + 1} / ${items.length}`}
63
+ onPrevious={() => setIndex(index - 1)} previousDisabled={index === 0}
64
+ onNext={() => setIndex(index + 1)} nextDisabled={index === items.length - 1}
65
+ metadata={metadata} defaultMetadataOpen
66
+ >
67
+ {preview}
68
+ </MediaViewer>
69
+ ```
70
+
71
+ - `defaultOpen`、`defaultMetadataOpen`、`defaultDrawerOpen` 只作为初始值;后续控制使用对应受控属性。抽屉保持打开跨过宽度断点时仍可完成当前操作,关闭后恢复桌面布局。
72
+ - `width` 仅管理桌面侧栏,抽屉仍使用现有 `min(440px, 100%)`。`breakpoint` 使用容器宽度,不能直接替换 AntD 的命名视口断点。
73
+ - 主体子树保持挂载;侧栏内容在桌面与 Portal 抽屉之间会重挂载,需保留的表单/查询状态仍由宿主控制。这不等同 AntD `destroyOnHidden=false` 的内容生命周期。
74
+ - `MediaCard actions` 中的业务按钮由宿主管理 busy/disabled;卡片 `disabled` 控制自身打开与选择。预览区域应提供非交互缩略图;完整视频控件放在 Viewer,避免把交互元素嵌进主按钮。
75
+ - MediaViewer 不持有 items/index,不猜测首尾、循环或资源顺序。完整资源的加载/失败重试内容通过 children/actions 组合;MediaCard 的状态不自动代表完整资源状态。
76
+
77
+ ## 明确保留的差异
78
+
79
+ 1. AntD Image 的缩放、旋转、拖拽、轮播集合 API 未移植;本次解决媒体壳层和已有宿主分页动作。没有对任意 children 强制应用图像变换,以免改变视频及原生媒体交互。
80
+ 2. MUI `sx`、任意组件替换、AntD 语义 DOM 样式对象未照搬;Rhine 保留 CSS tokens、className/style 和既有 ReactNode 插槽,避免引入第二套样式引擎。
81
+ 3. AppShell 没有侧栏拖拽调整宽度、迷你图标栏、触摸滑出手势;Drawer 只提供当前需要的左右边缘,未新增上下抽屉。需要这些行为时应另立完整交互契约,不能只补同名 props。
82
+ 4. Background 不提供 MUI Box 的任意 component/as;根 div 与原生 role/aria 属性已覆盖当前背景容器场景。它不是布局、可点击卡片或新的主题作用域。
83
+
84
+ ## 验证证据
85
+
86
+ - `tests/ShellMediaParity.test.tsx` 新增 **10 项**行为回归;与既有 `tests/Extensions.test.tsx` 一起 **25 项通过**。覆盖受控值不被本地修改、默认状态、左右位置、精确断点、自定义宽度、隐藏栏焦点、错误重试分区、metadata、快捷键边界与冲突。类型检查通过。
87
+ - Chromium 真实开发展台,橙趣主题400px:左导航抽屉 `x=0,width=400`,右侧圆角与向右阴影正确,动画从左进入;媒体从卡片打开,方向键连续 `1→2→3→2`,最后一项禁用后焦点仍留在查看器;Escape 回到实际卡片。
88
+ - 520px、减少动态模式,三套主题的左右抽屉共6次:左侧 `x=0,width=440`,右侧 `x=80,right=520`;全部无抽屉内部横向溢出,Escape 关闭成功。
89
+ - 1600px 浏览器窗口的容器测量:正常 `clientWidth=1134 / visualWidth=1136`,两栏常驻;CSS 200% `502 / 1008`,两栏正确转抽屉;CSS `transform:scale(.5)` 后 `1134 / 568`,两栏仍常驻。证明断点依据布局宽度,而不是变换后的视觉尺寸。这里是 CSS zoom/transform 验证,不声称浏览器菜单缩放实测。
90
+ - 首次浏览器复现明确发现「边界按钮禁用导致焦点回 body」,修复后重复验证反向快捷键成功。JSDOM 回归同时检查焦点目标,但不将其视为真实布局证据。
91
+ - 完整跨主题/宽度、构建产物与包安装验证由本轮总体验收统一记录;这里的开发浏览器结果不代替生产 bundle、原生 WebView 或屏幕阅读器实测。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linhey/rhine-design",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "license": "Apache-2.0",
5
5
  "publishConfig": {
6
6
  "access": "public",
@@ -15,12 +15,16 @@
15
15
  },
16
16
  "exports": {
17
17
  ".": "./components/index.ts",
18
+ "./theme": "./components/theme.tsx",
18
19
  "./tokens.css": "./components/tokens.css",
19
20
  "./styles.css": "./components/styles.css",
21
+ "./theme-arknights.css": "./components/theme-arknights.css",
22
+ "./theme-neo-brutalism.css": "./components/theme-neo-brutalism.css",
20
23
  "./logo.svg": "./components/assets/logo.svg"
21
24
  },
22
25
  "sideEffects": [
23
26
  "./components/index.ts",
27
+ "./components/theme.tsx",
24
28
  "**/*.css"
25
29
  ],
26
30
  "files": [
@@ -0,0 +1,46 @@
1
+ import { useState } from 'react';
2
+ import { Background, Button, Dialog, Select, Stack, Text, TextField, type BackgroundDecoration, type BackgroundRole } from '../components';
3
+ import './backgrounds.css';
4
+
5
+ const scenes: { role: BackgroundRole; label: string; description: string }[] = [
6
+ { role: 'canvas', label: '页面与工作台', description: '主题纹理留在画布上,让工作区有自己的风格。' },
7
+ { role: 'content', label: '正文与阅读', description: '默认纯色,长段文字与密集信息保持清晰。' },
8
+ { role: 'sidebar', label: '导航与侧栏', description: '独立的底色层次,帮助辨认当前位置。' },
9
+ { role: 'surface', label: '卡片与表单', description: '不透明的阅读表面,承载内容和输入。' },
10
+ { role: 'overlay', label: '弹窗与浮层', description: '表面与遮罩分开配置,突出当前任务。' },
11
+ { role: 'media', label: '图片与视频', description: '中性深色画布与浅色文字,让媒体成为焦点。' },
12
+ ];
13
+
14
+ function SceneSamples({ decoration = 'auto' }: { decoration?: BackgroundDecoration }) {
15
+ return <div className="ds-background-scenes">{scenes.map(scene => <div key={scene.role} className="ds-background-example">
16
+ <div className="ds-background-label"><strong>{scene.label}</strong><code>{scene.role}</code></div>
17
+ <div className={scene.role === 'overlay' ? 'ds-background-backdrop' : undefined}>
18
+ <Background roleTone={scene.role} decoration={decoration} className="ds-background-sample">
19
+ <Text variant="heading">{scene.role === 'media' ? '光影之间 / 16:9' : '专注于眼前的工作'}</Text>
20
+ <Text>{scene.description}</Text>
21
+ </Background>
22
+ </div>
23
+ </div>)}</div>;
24
+ }
25
+
26
+ export function BackgroundGallery() {
27
+ const [decoration, setDecoration] = useState<BackgroundDecoration>('auto');
28
+ return <section id="backgrounds">
29
+ <div className="ds-section-title"><span>BACKGROUND</span><h2>背景有层次,阅读有留白</h2></div>
30
+ <p className="ds-caption">主题决定配色,场景决定背景。默认仅画布使用主题纹理;正文、侧栏、卡片、浮层与媒体保持纯色。右上角可切换当前主题。</p>
31
+ <div className="ds-background-controls">
32
+ <Select label="纹理策略" value={decoration} onChange={event => setDecoration(event.target.value as BackgroundDecoration)}>
33
+ <option value="auto">按场景默认</option><option value="theme">全部使用主题纹理</option><option value="none">全部关闭纹理</option>
34
+ </Select>
35
+ <Dialog title="独立的浮层表面" description="弹窗使用主题浮层底色,背后叠加独立遮罩。" trigger={<Button variant="secondary">查看实际弹窗</Button>}>
36
+ <TextField label="浮层草稿" placeholder="试试输入,再按 Escape 返回" />
37
+ </Dialog>
38
+ </div>
39
+ <SceneSamples decoration={decoration} />
40
+ <div className="ds-background-local">
41
+ <Background roleTone="content" appearance={{ color: '#e5ece3', textColor: '#242b28' }} className="ds-background-sample">
42
+ <Stack gap={4}><Text variant="heading">局部覆盖 · 安静的阅读区</Text><Text>仅覆盖这一区域的底色与文字颜色,其他场景继续跟随主题。</Text><TextField label="正文草稿" placeholder="切换主题或纹理,内容仍然保留" /></Stack>
43
+ </Background>
44
+ </div>
45
+ </section>;
46
+ }