@linhey/rhine-design 0.4.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 (61) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +11 -1
  3. package/components/AdvancedForms.tsx +184 -0
  4. package/components/AppShell.tsx +71 -0
  5. package/components/Background.tsx +29 -0
  6. package/components/COMMON_COMPONENTS.md +2 -2
  7. package/components/Charts.tsx +94 -0
  8. package/components/Data.tsx +98 -18
  9. package/components/Forms.tsx +14 -8
  10. package/components/Media.tsx +82 -0
  11. package/components/Overlays.tsx +4 -3
  12. package/components/README.md +8 -1
  13. package/components/StatusTimeline.tsx +50 -12
  14. package/components/background.css +43 -0
  15. package/components/charts.css +27 -0
  16. package/components/common.css +5 -5
  17. package/components/data.css +6 -0
  18. package/components/drawer-placement.css +4 -0
  19. package/components/extensions.css +70 -0
  20. package/components/index.ts +12 -2
  21. package/components/media-parity.css +3 -0
  22. package/components/patterns.css +1 -1
  23. package/components/styles.css +4 -1
  24. package/components/theme-arknights.css +10 -5
  25. package/components/theme-neo-brutalism.css +10 -4
  26. package/components/timeline.css +2 -2
  27. package/components/tokens.css +43 -0
  28. package/docs/ANTD_MUI_AUDIT.md +38 -0
  29. package/docs/APPLICATION_COMPONENTS.md +175 -0
  30. package/docs/DESIGN_SYSTEM_AUDIT.md +2 -0
  31. package/docs/ISSUE_4_VALIDATION.md +21 -0
  32. package/docs/ISSUE_VALIDATION.md +52 -0
  33. package/docs/REFERENCE_RESEARCH.md +2 -0
  34. package/docs/THEMING.md +41 -0
  35. package/docs/assets/application-shell.png +0 -0
  36. package/docs/assets/issue4-shell.png +0 -0
  37. package/docs/assets/media-viewer-mobile.png +0 -0
  38. package/docs/assets/monitoring-desktop.png +0 -0
  39. package/docs/audits/DATA_ANTD_MUI.md +75 -0
  40. package/docs/audits/FORMS_ANTD_MUI.md +88 -0
  41. package/docs/audits/MONITORING_ANTD_MUI.md +44 -0
  42. package/docs/audits/SHELL_MEDIA_ANTD_MUI.md +91 -0
  43. package/package.json +1 -1
  44. package/preview/BackgroundGallery.tsx +46 -0
  45. package/preview/ExtensionsGallery.tsx +95 -0
  46. package/preview/GalleryShell.tsx +3 -2
  47. package/preview/MonitoringGallery.tsx +27 -0
  48. package/preview/backgrounds.css +10 -0
  49. package/preview/extensions.css +6 -0
  50. package/preview/main.tsx +10 -1
  51. package/skills/rhine-design/SKILL.md +24 -3
  52. package/skills/rhine-design/references/API_MAP.md +4 -0
  53. package/skills/rhine-design/references/TASK_MAP.md +1 -0
  54. package/skills/rhine-design/references/integration.md +4 -2
  55. package/tests/Background.test.tsx +22 -0
  56. package/tests/DataParity.test.tsx +148 -0
  57. package/tests/Extensions.test.tsx +145 -0
  58. package/tests/FormParity.test.tsx +185 -0
  59. package/tests/Monitoring.test.tsx +38 -0
  60. package/tests/MonitoringParity.test.tsx +125 -0
  61. package/tests/ShellMediaParity.test.tsx +180 -0
@@ -0,0 +1,70 @@
1
+ .rhine-shell { height:100%; min-height:0; min-width:0; display:flex; flex-direction:column; border:1px solid var(--rhine-line); background:var(--rhine-canvas-bg); }
2
+ .rhine-shell__header { flex:none; display:flex; flex-wrap:wrap; align-items:center; gap:8px; padding:12px; border-bottom:1px solid var(--rhine-line); background:var(--rhine-surface); }
3
+ .rhine-shell__body { display:flex; flex:1; min-height:0; min-width:0; }
4
+ .rhine-shell__nav, .rhine-shell__secondary { flex:0 0 224px; min-width:0; overflow:auto; padding:16px; background:var(--rhine-sidebar-bg); border-right:1px solid var(--rhine-line); overflow-wrap:anywhere; }
5
+ .rhine-shell__secondary { flex-basis:256px; }
6
+ .rhine-shell[data-layout=left-right] .rhine-shell__secondary { order:2; border-right:0; border-left:1px solid var(--rhine-line); }
7
+ .rhine-shell__content { flex:1; min-width:0; min-height:0; overflow:auto; padding:24px; overflow-wrap:anywhere; }
8
+ .rhine-date-range { margin:0; min-width:0; padding:16px; border:1px solid var(--rhine-line); background:var(--rhine-field-group-bg); }
9
+ .rhine-date-range legend { padding:0 4px; font-weight:600; }
10
+ .rhine-date-range__fields { display:grid; grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr)); gap:16px; margin-bottom:16px; }
11
+ .rhine-date-range input { max-width:100%; min-width:0; }
12
+ .rhine-number-field { display:grid; gap:8px; min-width:0; }
13
+ .rhine-multi-select { min-width:0; }
14
+ .rhine-multi-select__tags { display:flex; flex-wrap:wrap; gap:8px; list-style:none; padding:0; margin:8px 0; }
15
+ .rhine-multi-select__tags li { display:flex; align-items:center; max-width:100%; padding-left:12px; border:1px solid var(--rhine-line); background:var(--rhine-selected); border-radius:var(--rhine-radius-control); }
16
+ .rhine-multi-select__tags li>span { min-width:0; overflow-wrap:anywhere; }
17
+ .rhine-multi-select__tags .rhine-button { flex:none; }
18
+ .rhine-tree { min-width:0; padding:8px; border:1px solid var(--rhine-line); background:var(--rhine-field-group-bg); }
19
+ .rhine-tree [role=group] { padding-left:var(--rhine-tree-indent); }
20
+ .rhine-tree__row { display:flex; align-items:center; gap:8px; min-height:44px; padding:4px; overflow-wrap:anywhere; cursor:pointer; }
21
+ .rhine-tree__row>span:last-of-type { min-width:0; flex:1; }
22
+ .rhine-tree__row[data-selected=true] { background:var(--rhine-selected); }
23
+ .rhine-tree [aria-disabled=true]>.rhine-tree__row { color:var(--rhine-muted); cursor:default; }
24
+ .rhine-tree__row button { min-height:44px; min-width:44px; border:1px solid var(--rhine-line); background:var(--rhine-surface); color:var(--rhine-ink); font:inherit; cursor:pointer; }
25
+ .rhine-tree__spacer { flex:0 0 12px; }
26
+ .rhine-data[data-density=compact] { --rhine-table-cell-padding:6px 12px; }
27
+ .rhine-data .rhine-table-scroll { max-width:100%; overflow:auto; scroll-padding:48px; }
28
+ .rhine-data .rhine-table :is(th,td) { padding:var(--rhine-table-cell-padding); overflow-wrap:anywhere; font-size:14px; }
29
+ .rhine-table th { background:var(--rhine-table-header-bg); }
30
+ .rhine-table td { background:var(--rhine-surface); }
31
+ .rhine-table tbody tr:hover:not([data-selected=true]) > :is(td,th[scope=row]) { background:var(--rhine-table-hover-bg); }
32
+ .rhine-table tr[data-selected=true] td { background:var(--rhine-table-selected-bg); }
33
+ .rhine-table[data-sticky-header=true] th { position:sticky; top:0; z-index:2; }
34
+ .rhine-table :is(th,td)[data-sticky] { position:sticky; z-index:1; box-shadow:0 0 0 1px var(--rhine-line); }
35
+ .rhine-table[data-sticky-header=true] th[data-sticky] { z-index:3; }
36
+ .rhine-table__depth { display:block; white-space:nowrap; color:var(--rhine-muted); font-size:12px; }
37
+ .rhine-table .rhine-check label { min-height:44px; }
38
+ .rhine-pagination .rhine-select { width:auto; }
39
+ .rhine-media-card { min-width:0; border:1px solid var(--rhine-line); border-radius:var(--rhine-radius-card); background:var(--rhine-surface); }
40
+ .rhine-media-card[data-selected=true] { border:2px solid var(--rhine-green); }
41
+ .rhine-media-card__open { display:block; width:100%; min-width:0; padding:0; border:0; background:none; color:inherit; font:inherit; text-align:left; cursor:pointer; }
42
+ .rhine-media-card__open:disabled { cursor:default; opacity:.6; }
43
+ .rhine-media-card__preview { position:relative; display:flex; align-items:center; justify-content:center; width:100%; overflow:hidden; background:var(--rhine-media-canvas); color:var(--rhine-media-text); border-radius:var(--rhine-radius-card) var(--rhine-radius-card) 0 0; }
44
+ .rhine-media-card__preview :is(img,video) { width:100%; height:100%; object-fit:contain; }
45
+ .rhine-media-card__kind { position:absolute; top:8px; left:8px; padding:4px 8px; background:var(--rhine-media-overlay); color:var(--rhine-media-text); border:1px solid var(--rhine-media-border); font-size:12px; }
46
+ .rhine-media-card__caption { display:grid; gap:4px; padding:16px; overflow-wrap:anywhere; }
47
+ .rhine-media-card__caption>span { font-size:12px; color:var(--rhine-muted); }
48
+ .rhine-media-card>.rhine-check { padding:0 16px 12px; overflow-wrap:anywhere; }
49
+ .rhine-media-card>.rhine-check label { min-height:44px; }
50
+ .rhine-media-viewer { position:fixed; inset:0; z-index:41; width:100%; height:100dvh; display:flex; flex-direction:column; background:var(--rhine-media-canvas); color:var(--rhine-media-text); color-scheme:dark; overflow:auto; }
51
+ .rhine-media-viewer__header { flex:none; display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between; gap:16px; padding:16px; background:var(--rhine-media-overlay); border-bottom:1px solid var(--rhine-media-border); }
52
+ .rhine-media-viewer__header>div { min-width:0; }
53
+ .rhine-media-viewer__header h2 { font-size:18px; line-height:26px; overflow-wrap:anywhere; margin:0; }
54
+ .rhine-media-viewer__header p { color:var(--rhine-media-muted); font-size:12px; margin:4px 0 0; }
55
+ .rhine-media-viewer__actions { display:flex; flex-wrap:wrap; gap:8px; }
56
+ .rhine-media-viewer :is(button,select,a) { min-height:44px; min-width:44px; max-width:100%; padding:8px 12px; border:1px solid var(--rhine-media-border); border-radius:4px; background:var(--rhine-media-overlay); color:var(--rhine-media-text); font:inherit; overflow-wrap:anywhere; }
57
+ .rhine-media-viewer button { cursor:pointer; }
58
+ .rhine-media-viewer button:disabled { color:var(--rhine-media-muted); opacity:.5; cursor:default; }
59
+ .rhine-theme .rhine-media-viewer.rhine-media-viewer :is(button,input,select,textarea,summary,a,[tabindex]):focus-visible { outline:3px solid var(--rhine-media-focus); outline-offset:-3px; }
60
+ .rhine-media-viewer__body { display:grid; grid-template-columns:var(--rhine-media-columns); flex:1; min-height:0; min-width:0; }
61
+ .rhine-media-viewer__canvas { display:flex; align-items:center; justify-content:center; min-height:0; min-width:0; overflow:auto; padding:16px; }
62
+ .rhine-media-viewer__canvas :is(img,video) { display:block; max-width:100%; max-height:100%; object-fit:contain; }
63
+ .rhine-media-viewer__metadata { min-width:0; overflow:auto; padding:24px; border-left:1px solid var(--rhine-media-border); background:var(--rhine-media-overlay); overflow-wrap:anywhere; }
64
+ @media(max-width:760px) {
65
+ .rhine-media-viewer__body { display:flex; flex-direction:column; overflow:auto; }
66
+ .rhine-media-viewer__canvas { flex:1 0 220px; }
67
+ .rhine-media-viewer__canvas :is(img,video) { max-height:50dvh; }
68
+ .rhine-media-viewer__metadata { flex:none; overflow:visible; border-left:0; border-top:1px solid var(--rhine-media-border); }
69
+ .rhine-shell__content { padding:16px; }
70
+ }
@@ -1,6 +1,16 @@
1
1
  import './styles.css';
2
+ export { Background } from './Background';
3
+ export type { BackgroundProps, BackgroundRole, BackgroundDecoration, BackgroundAppearance } from './Background';
4
+ export { AppShell } from './AppShell';
5
+ export type { AppShellProps, ShellPanel } from './AppShell';
6
+ export { MediaCard, MediaViewer } from './Media';
7
+ export type { MediaCardProps, MediaViewerProps } from './Media';
8
+ export { DateField, DateRangePicker, NumberField, RemoteMultiSelect, TreeSelect } from './AdvancedForms';
9
+ export type { DateFieldProps, DateRange, DateRangePickerProps, NumberFieldProps, RemoteMultiSelectProps, TreeNode, TreeSelectProps } from './AdvancedForms';
2
10
  export { StatusTimeline, TimelineLegend } from './StatusTimeline';
3
- export type { TimelineSegment, TimelineStatus } from './StatusTimeline';
11
+ export type { TimelineSegment, TimelineStatus, TimelineDetail, StatusTimelineProps } from './StatusTimeline';
12
+ export { TrendChart, StackedBar } from './Charts';
13
+ export type { ChartPoint, ChartPart, TrendChartProps, StackedBarProps } from './Charts';
4
14
 
5
15
  export { Logo } from './Logo';
6
16
  export type { LogoProps } from './Logo';
@@ -15,7 +25,7 @@ export type { IconName } from './Actions';
15
25
  export { Divider, Stack, Inline, Grid } from './Layout';
16
26
  export type { Spacing } from './Layout';
17
27
  export { Metric, KeyValue, DataTable, Pagination } from './Data';
18
- export type { DataColumn, DataTableProps, TableSort } from './Data';
28
+ export type { DataColumn, DataTableProps, TableSort, PaginationProps } from './Data';
19
29
  export { Checkbox, RadioGroup, Switch, Textarea, SearchField, Combobox } from './Forms';
20
30
  export type { ComboboxProps } from './Forms';
21
31
  export { Dialog, Drawer, ConfirmDialog, Popover, Menu, Tooltip } from './Overlays';
@@ -0,0 +1,3 @@
1
+ .rhine-media-card__actions { display:flex; flex-wrap:wrap; gap:8px; padding:0 16px 12px; }
2
+ .rhine-media-card__asset { position:absolute; inset:0; }
3
+ .rhine-media-card__asset:not([hidden]) { display:flex; align-items:center; justify-content:center; }
@@ -26,7 +26,7 @@
26
26
  .rhine-steps li[data-state=current]>div { background:var(--rhine-selected); color:var(--rhine-green); animation:rhine-pattern-enter var(--rhine-duration-panel) var(--rhine-ease-standard); }
27
27
  .rhine-steps li[data-state=error] { border-color:var(--rhine-error); }
28
28
  .rhine-steps li[data-state=error]>div { color:var(--rhine-error); background:var(--rhine-error-bg); }
29
- .rhine-fieldset { min-width:0; margin:0; padding:20px; border:1px solid var(--rhine-line); border-radius:var(--rhine-radius-card); }
29
+ .rhine-fieldset { background:var(--rhine-surface-bg); color:var(--rhine-surface-text); min-width:0; margin:0; padding:20px; border:1px solid var(--rhine-line); border-radius:var(--rhine-radius-card); }
30
30
  .rhine-fieldset legend { padding:0 8px; font-weight:600; }
31
31
  .rhine-fieldset>p { margin:0 0 20px; color:var(--rhine-muted); font-size:12px; }
32
32
  .rhine-fieldset__body { display:grid; gap:16px; }
@@ -3,6 +3,9 @@
3
3
  @import './common.css';
4
4
  @import './patterns.css';
5
5
  @import './timeline.css';
6
+ @import './extensions.css';
7
+ @import './background.css';
8
+ @import './charts.css';
6
9
 
7
10
  .rhine-theme { color:var(--rhine-ink); background:var(--rhine-bg); font:var(--rhine-text-body)/var(--rhine-leading-body) var(--rhine-font); font-synthesis:none; }
8
11
  .rhine-theme[data-rhine-theme="rhine"] { color-scheme:light; }
@@ -28,7 +31,7 @@
28
31
  .rhine-tone-warning { --rhine-tone:var(--rhine-warning); --rhine-tone-bg:var(--rhine-warning-bg); }
29
32
  .rhine-tone-error { --rhine-tone:var(--rhine-error); --rhine-tone-bg:var(--rhine-error-bg); }
30
33
  .rhine-badge { display:inline-block; padding:2px 8px; border-radius:2px; background:var(--rhine-tone-bg); color:var(--rhine-tone); font:500 12px/20px var(--rhine-font); }
31
- .rhine-surface { padding:var(--rhine-space-6); border:1px solid var(--rhine-line); border-radius:var(--rhine-radius-card); background:var(--rhine-surface); min-width:0; }
34
+ .rhine-surface { padding:var(--rhine-space-6); border:1px solid var(--rhine-line); border-radius:var(--rhine-radius-card); background:var(--rhine-surface-bg); color:var(--rhine-surface-text); min-width:0; }
32
35
  .rhine-selection { display:block; border:1px solid var(--rhine-control); background:var(--rhine-surface); border-radius:var(--rhine-radius-card); padding:var(--rhine-space-6); cursor:pointer; min-width:0; transition:background-color var(--rhine-duration-hover) var(--rhine-ease-standard),border-color var(--rhine-duration-hover) var(--rhine-ease-standard),box-shadow var(--rhine-duration-hover) var(--rhine-ease-standard); }
33
36
  .rhine-selection:hover:not([data-disabled=true]) { border-color:var(--rhine-green); background:#f4f7ef; }
34
37
  .rhine-selection[data-selected=true] { border-color:var(--rhine-green); background:var(--rhine-selected); box-shadow:inset 0 0 0 1px var(--rhine-green); }
@@ -2,12 +2,18 @@
2
2
  .rhine-theme[data-rhine-theme="arknights"] {
3
3
  /* ---- 色彩:暗舱底 + 信号黄 + 警戒橙 ---- */
4
4
  --rhine-bg: #0b0d10;
5
+ --rhine-overlay-bg: #1a1d23;
6
+ --rhine-backdrop-bg: rgba(4, 5, 7, .72);
7
+ --rhine-background-mark: #20242a;
8
+ --rhine-background-texture: linear-gradient(var(--rhine-background-mark) 1px, transparent 1px), linear-gradient(90deg, var(--rhine-background-mark) 1px, transparent 1px);
9
+ --rhine-background-scale: 32px 32px;
5
10
  --rhine-surface: #14161b;
6
11
  --rhine-ink: #eef0ec;
7
12
  --rhine-muted: #878f96;
8
13
  --rhine-line: #272b31;
9
14
  --rhine-control: #454c54;
10
15
  --rhine-selected: #1c2026;
16
+ --rhine-table-hover-bg: color-mix(in srgb, #ffd400 6%, var(--rhine-surface));
11
17
  --rhine-green: #ffd400;
12
18
  --rhine-signal: #ff6a1a;
13
19
  --rhine-warning: #ffb020;
@@ -77,7 +83,7 @@
77
83
  .rhine-theme[data-rhine-theme="arknights"] .rhine-surface,
78
84
  .rhine-theme[data-rhine-theme="arknights"] .rhine-empty,
79
85
  .rhine-theme[data-rhine-theme="arknights"] .rhine-fieldset {
80
- background: var(--ak-panel);
86
+ background: var(--rhine-surface-bg);
81
87
  clip-path: polygon(0 0, calc(100% - var(--ak-cut)) 0, 100% var(--ak-cut), 100% 100%, 0 100%);
82
88
  }
83
89
  /* 左下角小角标点缀 */
@@ -173,7 +179,7 @@
173
179
  .rhine-theme[data-rhine-theme="arknights"] .rhine-combobox__list,
174
180
  .rhine-theme[data-rhine-theme="arknights"] .rhine-popover,
175
181
  .rhine-theme[data-rhine-theme="arknights"] .rhine-menu {
176
- background: var(--ak-panel-hi);
182
+ background: var(--rhine-overlay-bg);
177
183
  border-color: var(--rhine-control);
178
184
  border-radius: 0;
179
185
  box-shadow: 0 12px 32px rgba(0, 0, 0, .55);
@@ -223,7 +229,6 @@
223
229
  letter-spacing: .08em;
224
230
  border-bottom: 2px solid var(--ak-accent);
225
231
  }
226
- .rhine-theme[data-rhine-theme="arknights"] .rhine-table tbody tr:hover { background: rgba(255, 212, 0, .06); }
227
232
  .rhine-theme[data-rhine-theme="arknights"] .rhine-metric { font-variant-numeric: tabular-nums; }
228
233
 
229
234
  /* ================= 反馈 ================= */
@@ -240,9 +245,9 @@
240
245
  .rhine-theme[data-rhine-theme="arknights"] .rhine-disclosure summary>span { color: var(--ak-accent); }
241
246
 
242
247
  /* ================= 浮层 / 对话框 ================= */
243
- .rhine-theme[data-rhine-theme="arknights"] .rhine-overlay { background: rgba(4, 5, 7, .72); }
248
+ .rhine-theme[data-rhine-theme="arknights"] .rhine-overlay { background: var(--rhine-backdrop-bg); }
244
249
  .rhine-theme[data-rhine-theme="arknights"] .rhine-dialog {
245
- background: var(--ak-panel);
250
+ background: var(--rhine-overlay-bg);
246
251
  border-color: var(--rhine-control);
247
252
  border-radius: 0;
248
253
  box-shadow: 0 24px 64px rgba(0, 0, 0, .6);
@@ -2,12 +2,18 @@
2
2
  .rhine-theme[data-rhine-theme="neo-brutalism"] {
3
3
  color-scheme: light;
4
4
  --rhine-bg: #eaf0f4;
5
+ --rhine-backdrop-bg: #20232360;
6
+ --rhine-background-mark: #b7c2cc;
7
+ --rhine-background-texture: radial-gradient(circle, var(--rhine-background-mark) .8px, transparent .9px);
8
+ --rhine-background-scale: 14px 14px;
5
9
  --rhine-surface: #fffdf5;
6
10
  --rhine-ink: #202323;
7
11
  --rhine-muted: #59616a;
8
12
  --rhine-line: #b9c1c7;
9
13
  --rhine-control: #202323;
10
14
  --rhine-selected: #fce4cc;
15
+ --rhine-table-header-bg: #edf0e9;
16
+ --rhine-table-cell-padding: 16px 12px;
11
17
  --rhine-green: #286043;
12
18
  --rhine-signal: #ff8b3d;
13
19
  --rhine-warning: #854216;
@@ -24,7 +30,7 @@
24
30
  --rhine-tag-green: #dcebd7;
25
31
  --rhine-tag-orange: #fce0c4;
26
32
  --rhine-tag-violet: #e3e0f7;
27
- background: radial-gradient(circle, #b7c2cc .8px, transparent .9px) 0 0 / 14px 14px, var(--rhine-bg);
33
+ background: var(--rhine-background-texture) 0 0 / var(--rhine-background-scale), var(--rhine-bg);
28
34
  }
29
35
 
30
36
  .rhine-theme[data-rhine-theme="neo-brutalism"] :is(.rhine-surface, .rhine-selection, .rhine-empty, .rhine-fieldset) {
@@ -67,13 +73,13 @@
67
73
  .rhine-theme[data-rhine-theme="neo-brutalism"] :is(.rhine-dialog, .rhine-popover, .rhine-menu, .rhine-combobox__list) { border: 2px solid var(--rhine-ink); border-radius: 18px; box-shadow: var(--rhine-hard-shadow); }
68
74
  .rhine-theme[data-rhine-theme="neo-brutalism"] .rhine-dialog { border-radius: 24px; box-shadow: 8px 8px 0 var(--rhine-ink); }
69
75
  .rhine-theme[data-rhine-theme="neo-brutalism"] .rhine-drawer { border-radius: 24px 0 0 24px; box-shadow: -5px 0 0 var(--rhine-ink); }
70
- .rhine-theme[data-rhine-theme="neo-brutalism"] .rhine-overlay { background: #20232360; backdrop-filter: none; }
76
+ .rhine-theme[data-rhine-theme="neo-brutalism"] .rhine-overlay { background: var(--rhine-backdrop-bg); backdrop-filter: none; }
71
77
  .rhine-theme[data-rhine-theme="neo-brutalism"] .rhine-menu__item { border-radius: 10px; }
72
78
  .rhine-theme[data-rhine-theme="neo-brutalism"] .rhine-menu__item[data-highlighted] { box-shadow: inset 0 0 0 2px var(--rhine-ink); }
73
79
  .rhine-theme[data-rhine-theme="neo-brutalism"] .rhine-tooltip { border-radius: 8px; }
74
80
  .rhine-theme[data-rhine-theme="neo-brutalism"] :is(.rhine-notice, .rhine-toast, .rhine-error-summary) { border: 2px solid var(--rhine-tone, var(--rhine-error)); border-radius: 14px; }
75
- .rhine-theme[data-rhine-theme="neo-brutalism"] .rhine-table th { color: var(--rhine-ink); background: #edf0e9; font-weight: 700; border-bottom: 2px solid var(--rhine-ink); }
76
- .rhine-theme[data-rhine-theme="neo-brutalism"] .rhine-table :is(th, td) { padding: 16px 12px; }
81
+ .rhine-theme[data-rhine-theme="neo-brutalism"] .rhine-table th { color: var(--rhine-ink); background: var(--rhine-table-header-bg); font-weight: 700; border-bottom: 2px solid var(--rhine-ink); }
82
+ .rhine-theme[data-rhine-theme="neo-brutalism"] .rhine-table :is(th, td) { padding: var(--rhine-table-cell-padding); }
77
83
  .rhine-theme[data-rhine-theme="neo-brutalism"] .rhine-table td { font-size: 14px; }
78
84
  .rhine-theme[data-rhine-theme="neo-brutalism"] .rhine-progress__track { height: 10px; border: 1.5px solid var(--rhine-ink); border-radius: 6px; background: #e7ebee; }
79
85
  .rhine-theme[data-rhine-theme="neo-brutalism"] .rhine-progress__track>span { background: var(--rhine-signal); }
@@ -18,6 +18,6 @@
18
18
  .rhine-timeline--compact { position:relative; gap:0; }
19
19
  .rhine-timeline--compact .rhine-timeline__track { height:20px; }
20
20
  .rhine-timeline--compact .rhine-timeline__track input { top:-12px; bottom:auto; }
21
- .rhine-timeline--compact .rhine-timeline__detail { position:absolute; z-index:2; top:100%; inset-inline:0; padding:4px 8px; border:1px solid var(--rhine-line); background:var(--rhine-surface); box-shadow:0 4px 12px #242b2814; pointer-events:none; visibility:hidden; }
22
- .rhine-timeline--compact:hover .rhine-timeline__detail,.rhine-timeline--compact:focus-within .rhine-timeline__detail { visibility:visible; }
21
+ .rhine-timeline-popover { z-index:50; width:min(360px,calc(100vw - 24px)); max-height:var(--radix-popover-content-available-height); overflow:auto; padding:16px; border:1px solid var(--rhine-control); border-radius:var(--rhine-radius-card); background:var(--rhine-overlay-bg); color:var(--rhine-overlay-text); box-shadow:0 4px 12px #242b2814; overflow-wrap:anywhere; }
22
+ .rhine-timeline-popover .rhine-key-value { margin-block:16px; }
23
23
  .rhine-timeline--compact:not(:hover):not(:focus-within) .rhine-timeline__bars [data-selected=true] { height:20px; outline:none; }
@@ -1,6 +1,25 @@
1
1
  /* Canonical tokens for the component library and design previews. */
2
2
  :root, :where(.rhine-theme[data-rhine-theme]) {
3
3
  --rhine-bg: #f2f1e9;
4
+ --rhine-backdrop-bg: #242b2870;
5
+ --rhine-content-texture: none;
6
+ --rhine-sidebar-texture: none;
7
+ --rhine-surface-texture: none;
8
+ --rhine-overlay-texture: none;
9
+ --rhine-media-texture: none;
10
+ --rhine-background-texture: none;
11
+ --rhine-background-scale: 24px 24px;
12
+ --rhine-background-mark: #c5ccc1;
13
+ --rhine-table-cell-padding: 12px 16px;
14
+ --rhine-tree-indent: 20px;
15
+ --rhine-chart-series-3: #477aa8;
16
+ --rhine-chart-series-4: #8763a7;
17
+ --rhine-media-canvas: #101214;
18
+ --rhine-media-overlay: #202428;
19
+ --rhine-media-text: #f6f7f8;
20
+ --rhine-media-muted: #bcc3ca;
21
+ --rhine-media-border: #68727c;
22
+ --rhine-media-focus: #9bceff;
4
23
  --rhine-surface: #fcfcf8;
5
24
  --rhine-ink: #242b28;
6
25
  --rhine-muted: #59645d;
@@ -61,3 +80,27 @@
61
80
  --rhine-ease-exit: cubic-bezier(.4, 0, 1, 1);
62
81
  --rhine-motion-distance: 3px;
63
82
  }
83
+
84
+ /* Resolve palette aliases at CSS-only roots as well as ThemeProvider roots. */
85
+ :root, :where(.rhine-theme) {
86
+ --rhine-canvas-bg: var(--rhine-bg);
87
+ --rhine-content-bg: var(--rhine-bg);
88
+ --rhine-sidebar-bg: var(--rhine-surface);
89
+ --rhine-surface-bg: var(--rhine-surface);
90
+ --rhine-overlay-bg: var(--rhine-surface);
91
+ --rhine-media-bg: var(--rhine-media-canvas);
92
+ --rhine-canvas-text: var(--rhine-ink);
93
+ --rhine-content-text: var(--rhine-ink);
94
+ --rhine-sidebar-text: var(--rhine-ink);
95
+ --rhine-surface-text: var(--rhine-ink);
96
+ --rhine-overlay-text: var(--rhine-ink);
97
+ --rhine-canvas-texture: var(--rhine-background-texture);
98
+ --rhine-table-header-bg: var(--rhine-surface);
99
+ --rhine-table-hover-bg: var(--rhine-selected);
100
+ --rhine-table-selected-bg: var(--rhine-selected);
101
+ --rhine-field-group-bg: var(--rhine-surface);
102
+ --rhine-chart-series-1: var(--rhine-green);
103
+ --rhine-chart-series-2: var(--rhine-signal);
104
+ --rhine-chart-grid: var(--rhine-line);
105
+ --rhine-chart-missing: var(--rhine-muted);
106
+ }
@@ -0,0 +1,38 @@
1
+ # Ant Design / MUI:应用组件逐项核对
2
+
3
+ 2026-10-24,issues #1–#6 的 0.5.0 API。核对覆盖全部 11 个新组件及 DataTable、Pagination、StatusTimeline 的扩展。依据官方在线文档,再用本库测试和真实展台验证行为;未安装这两套 UI 库,也不声称 API 兼容或完整功能等价。
4
+
5
+ ## 组件覆盖与修复
6
+
7
+ | 本库组件 | Ant Design 对应 | MUI 对应 | 本轮落地 |
8
+ | --- | --- | --- | --- |
9
+ | DateField | DatePicker | X DateField | 只读/表单标识、动态日期范围错误、校验通知 |
10
+ | DateRangePicker | RangePicker | X DateRangePicker | 两端校验、无效预设禁用、只读/清空配置、可定制字段标签 |
11
+ | NumberField | InputNumber | Number Field 官方 Base UI 组合示例 | 只读、Escape 撤销、空值步进修正、动态边界反馈、控制按钮配置 |
12
+ | RemoteMultiSelect | Select multiple | Autocomplete multiple | 连续选择、多选语义、空查询 Backspace、删除后焦点、只读、原生表单提交 |
13
+ | TreeSelect | TreeSelect / Tree | X Tree View | 已选项进入焦点、字符定位、批量键盘操作、结构节点、收起后焦点、只读 |
14
+ | DataTable 扩展 | Table | Table / X Data Grid | 单选/隐藏全选、取消排序、禁止展开约束、稳定详情 ID、行标题、多固定列自动偏移 |
15
+ | Pagination 扩展 | Pagination | TablePagination | 首末页、独立禁用/名称、未知总量和越界恢复、边界焦点 |
16
+ | Background | Layout / ConfigProvider tokens | Box / Paper / theme | 保留六场景语义与局部覆盖;补 DOM ref |
17
+ | AppShell | Layout.Sider / Drawer | permanent / temporary Drawer | 独立受控移动抽屉、正确左右方向、栏宽/断点配置、缩放下容器判断 |
18
+ | MediaCard | Card / Image | CardActionArea / CardMedia / CardActions | 独立重试等 actions、原生属性/ref、打开与选择保持分离 |
19
+ | MediaViewer | Image PreviewGroup / Modal | Dialog + 自定义画廊 | 方向键、首尾禁用与焦点、defaultOpen、可直接切换信息栏、嵌套编辑与播放按键隔离 |
20
+ | StatusTimeline 扩展 | Slider + Popover | Slider + 浮层 | 稳定 ID、受控/默认选中与开关、禁用、键盘进入详情 |
21
+ | TrendChart | 独立 Ant Design Charts Line / Column | X LineChart / BarChart | 键盘/指针逐点查看、受控高亮、格式化、原始数据保留、极值坐标修正 |
22
+ | StackedBar | Ant Design Charts Bar percent | X stacked BarChart | 数值格式化、零值不留假色段;保留未知总量保护 |
23
+
24
+ ## 详细 API 与交互证据
25
+
26
+ - [表单逐项对照](audits/FORMS_ANTD_MUI.md):5 个组件、官方 API 链接、数据类型、事件、键盘、状态、表单关联及保留差异。
27
+ - [数据表与分页逐项对照](audits/DATA_ANTD_MUI.md):选择、排序、展开、固定列、分页与焦点。
28
+ - [背景、外壳与媒体逐项对照](audits/SHELL_MEDIA_ANTD_MUI.md):组合、受控状态、响应式、浮层及媒体导航。
29
+ - [监控逐项对照](audits/MONITORING_ANTD_MUI.md):时段与事件轴区分、图表数值/键盘、缺失数据及格式化。
30
+ - [最终验证](ISSUE_VALIDATION.md):以本轮实测结果为准;官方库自身能力不能替代 Rhine 的验证。
31
+
32
+ ## 接入变化与范围
33
+
34
+ 原有必填参数保持兼容。RemoteMultiSelect 默认选择后保持候选打开,便于连续选择;如需原行为设 `closeOnSelect`。原单选 Combobox 仍默认选完关闭。其他新参数均为可选,现有受控调用无需迁移。新导出的 `PaginationProps`、`StatusTimelineProps` 与原有组件 Props 一同从包入口可用。
35
+
36
+ 保留 ISO 日期字符串、宿主服务端查询与分页、树父子独立选择、原生 table、单序列轻图表及 slot 媒体内容。自绘日历/时区适配器、树级联与虚拟化、可编辑网格、多轴图表、图像旋转缩放/播放引擎未纳入;各分项说明了为何不采用以及对应差异。组件 props 不增加 AntD/MUI 同义别名,不新增运行时依赖,不迁移消费应用。
37
+
38
+ 这些变化自 0.5.0 提供,npm 0.4.0 不包含本页 API;精确制品与消费验证见[版本验收](ISSUE_VALIDATION.md)。
@@ -0,0 +1,175 @@
1
+ # 应用组件与状态契约
2
+
3
+ 本页对应 GitHub issues [#1](https://github.com/linhay/rhine-design/issues/1)、[#2](https://github.com/linhay/rhine-design/issues/2)、[#3](https://github.com/linhay/rhine-design/issues/3)、[#4](https://github.com/linhay/rhine-design/issues/4)、[#5](https://github.com/linhay/rhine-design/issues/5)、[#6](https://github.com/linhay/rhine-design/issues/6)。新增 11 个组件,公共组件总数为 59。
4
+
5
+ **版本边界:以下 API 自 0.5.0 提供,npm 0.4.0 不含这些能力。** 本次不改变现有组件必填参数;DataTable/Pagination 的参数均为新增可选项。使用 `npm install --save-exact @linhey/rhine-design@0.5.0` 并提交 lockfile。组件库升级不代表消费应用已经迁移。
6
+
7
+ 公开类型从包主入口导出,真实实现是 API 权威:`Background.tsx`、`AppShell.tsx`、`AdvancedForms.tsx`、`Media.tsx`、`Data.tsx`。兼容范围沿用 React 19 + Vite;native WebView、其他构建器、SSR 和屏幕阅读器组合需消费项目另行验证。无需新增依赖。
8
+
9
+ 2026-10-24 已对全部新增组件及扩展逐项核对 Ant Design / MUI 官方 API 与交互,修复与保留差异见[对标清单](ANTD_MUI_AUDIT.md)。以下契约包含本轮补齐;原有必填参数保持兼容。
10
+
11
+ ## 背景层级
12
+
13
+ | 区域 | token / API | 规则 |
14
+ | --- | --- | --- |
15
+ | 应用画布 | `--rhine-canvas-bg`,`roleTone="canvas"` | 整体底色,允许主题纹理 |
16
+ | 正文区域 | `--rhine-content-bg`,`roleTone="content"`(默认) | 默认纯色;显式 `decoration="theme"` 可启用主题纹理 |
17
+ | 全局 / 上下文 / 辅助侧栏 | `--rhine-sidebar-bg`,`roleTone="sidebar"` | 不透明纯色,边界使用 `--rhine-line` |
18
+ | 卡片 / 表单 | `--rhine-surface-bg`,`roleTone="surface"` | 不透明阅读表面,默认无纹理 |
19
+ | 弹窗 / 浮层 | `--rhine-overlay-bg`,`roleTone="overlay"` | 默认无纹理,遮罩独立使用 `--rhine-backdrop-bg` |
20
+ | 图片 / 视频 | `--rhine-media-bg`,`roleTone="media"` | 中性深色底与配套浅色文字,默认无纹理 |
21
+
22
+ 纸面默认无纹理;战术终端使用 32px 低对比网格;橙趣使用 14px 点阵。主题统一提供 `--rhine-background-texture`、`--rhine-background-scale`、`--rhine-background-mark`,无需消费者复制渐变。`decoration="auto"` 为默认值,仅 canvas 使用主题纹理,正文和 AppShell 默认纯色。`theme` 显式启用主题纹理,`none` 关闭本区域装饰,主题切换不会重新启用它。Background 另提供 surface/overlay/media 场景及局部 appearance 覆盖,完整六场景配置见[主题契约](THEMING.md#场景背景050)。
23
+
24
+ `decoration` 默认 `auto`,仅画布默认跟随主题纹理,AppShell 正文默认纯色。`appearance` 支持局部底色、文字色、纹理与尺寸覆盖,完整变量及优先级见[场景背景配置](THEMING.md#场景背景050)。展台 `#backgrounds` 仅展示当前主题,随右上角选择器同步切换。
25
+
26
+ ```tsx
27
+ import { Background, Surface } from '@linhey/rhine-design';
28
+ import { ThemeProvider } from '@linhey/rhine-design/theme';
29
+
30
+ <ThemeProvider defaultTheme="neo-brutalism">
31
+ {/* 用 Background 替换原本 background: var(--rhine-bg) 的正文 div。 */}
32
+ <Background roleTone="content">
33
+ <Surface>不透明的内容卡片</Surface>
34
+ </Background>
35
+ </ThemeProvider>
36
+ ```
37
+
38
+ 背景直接绘制在元素上,无伪元素、无点击拦截层、无 transform/contain/position 副作用。纹理从该区域左上角开始,`background-attachment: scroll` 固定在滚动容器的框上:滚动其内部内容时图案不重置、不跟随内容漂移;外层移动整个区域时背景随框移动。各独立区域拥有独立原点,不承诺跨两个元素接缝完全对齐。使用 CSS 默认背景裁剪;内容和焦点在背景上方,裁剪、滚动和 Portal 不受 Background 改变。背景无动画,也无需嵌套 ThemeProvider。
39
+
40
+ ## 三种外壳布局
41
+
42
+ `AppShell` 接收 `layout="single" | "double-left" | "left-right"`;`navigation` 必填、`secondary` 在后两种布局可选。面板形状为 `{ label, content, open, onOpenChange }`;`header` 放当前任务操作、`children` 为正文,`decoration` 控制正文背景。
43
+
44
+ | 方案 | 用途 | 顺序 |
45
+ | --- | --- | --- |
46
+ | single | 简单管理或设置工具 | 全局导航 + 正文 |
47
+ | double-left | 目录、列表与编辑任务 | 全局导航 + 上下文目录 + 正文 |
48
+ | left-right | 检查器、对象属性与预览 | 全局导航 + 正文 + 辅助详情 |
49
+
50
+ 全局栏宽 224px,次栏宽 256px,正文伸展,三栏同时展开时至少留约 638px 正文空间。判断依据是外壳容器宽度,适用于嵌入展台或桌面应用:小于 1120px 先将次栏转为 Drawer,小于 760px 再将全局栏转为 Drawer。抽屉初始关闭;桌面 `open` 偏好保留,回到宽窗后恢复。已经打开的 Drawer 在扩宽窗口时保持到用户关闭,避免中断操作;不会自动丢弃正文或选中项。
51
+
52
+ 外壳需要父级提供明确高度(例如 `height: 100dvh`,展台用固定高度)。header 是不滚动的同级区域,body 填满余高;导航、次栏和正文各自 `overflow:auto`。宿主不应再给这些区域内的整页容器设置第二个垂直滚动。表格的局部横向/有限高度滚动是显式例外。没有拖拽宽度、持久化或停靠管理器。
53
+
54
+ ```tsx
55
+ const [navigationOpen, setNavigationOpen] = useState(true);
56
+ const [detailsOpen, setDetailsOpen] = useState(true);
57
+ // currentId、draft 等业务状态放在 AppShell 的上层。
58
+ <div style={{ height: '100dvh' }}>
59
+ <AppShell layout="left-right"
60
+ navigation={{ label: '全局导航', open: navigationOpen,
61
+ onOpenChange: setNavigationOpen, content: <SideNav {...navigationProps} /> }}
62
+ secondary={{ label: '辅助详情', open: detailsOpen,
63
+ onOpenChange: setDetailsOpen, content: <ObjectDetails value={object} /> }}
64
+ header={<Button onClick={save}>保存</Button>}>
65
+ <Editor value={draft} onChange={setDraft} />
66
+ </AppShell>
67
+ </div>
68
+ ```
69
+
70
+ SideNav 继续负责当前项、长名称和 aria-current;Drawer 继续负责 Tab、Escape 和触发器焦点归还。正文不因切换布局或主题重建。面板在抽屉与内联之间转换时可能重新挂载,因此选中、展开、输入和请求状态必须提升到宿主;不要在面板 render 时启动不可取消的副作用。主题切换不改变 React key。切换或隐藏面板时将位于其中的焦点送回对应操作按钮。
71
+
72
+ `ShellPanel.drawerOpen/defaultDrawerOpen/onDrawerOpenChange` 独立控制临时抽屉,桌面 `open` 偏好不受影响;宿主选完导航项后可设置 drawerOpen=false。`width` 和 `breakpoint` 使用 CSS px,默认仍为上述数值;断点按容器布局宽度判断。全局导航与双左次栏从左侧打开,左右布局的辅助栏从右侧打开。AppShell 支持 className/style,Background 支持原生属性及 ref。受控与非受控模式在组件生命周期内保持一致。
73
+
74
+ ## 表单
75
+
76
+ | 组件 | 主要 API | 数据与键盘契约 |
77
+ | --- | --- | --- |
78
+ | DateField | `label/value/onValueChange/min/max/disabled/error` | 原生 `input type=date`;ISO 日期字符串或空字符串,无 UTC/时区转换,原生日历和键盘由浏览器实现 |
79
+ | DateRangePicker | 上述范围 + `value={start,end}`、`presets=[{label,value}]` | 两个原生日期输入,自定义范围、受控预设、清空;结束日期下界取 start/min 较晚者;反向范围显示错误,不静默调换 |
80
+ | NumberField | `value: number\|null`、`onValueChange/min/max/step/precision/disabled/error` | 编辑中保存字符串;失焦/Enter 提交;无效及越界输入保留并报错;上下键/按钮步进与钳制边界;空字符串提交 null |
81
+ | TreeSelect | `nodes/value/onValueChange/expandedKeys/onExpandedChange/multiple`、loading/error/disabled | 节点为 `{id,label,disabled?,children?}`;父子独立选择,默认单选;调用方提供搜索后的树,不做全量过滤 |
82
+ | RemoteMultiSelect | `query/onQueryChange/value/onValueChange/options`、loading/error/disabled | value 是完整 `{value,label,disabled?}[]`,已选标签不依赖本次结果;不重复选中;清空保留禁选标签 |
83
+
84
+ 日期 min/max 约束原生日历和表单有效性;手动编辑、业务校验及最终提交由宿主验证。原生日期面板位于浏览器 UI,不放在受裁剪的自定义浮层。日历外观、屏幕阅读器和移动 WebView 行为由具体浏览器决定,不声称提供自绘日历。预设必须使用宿主计算好的日期;不得以本机时区代替产品时区。没有“今天”隐式计算。
85
+
86
+ DateField 新增 `readOnly/required/id/name/hint/onValidationChange`;外部日期或 min/max 改变会更新格式/范围错误,不自动回写值。DateRangePicker 同样支持 readOnly/required/hint/onValidationChange,另有 `clearable`(默认 true)、`startLabel/endLabel/startName/endName`。预设含非法日期、反向区间或任一端越界时禁用。onValidationChange 回传格式/范围/显式 error,required 仍走提交时的原生表单校验。只读时保留浏览与聚焦,预设、清空、步进和选择不能更改值。
87
+
88
+ NumberField 支持小数步进(默认 1);precision 限制为 0–15 位,未指定时普通提交保留解析后的 JS 数值,步进清除至 12 位的浮点尾差;IEEE-754 数值精度不适用于高精度货币账本。`-`、`1.` 等草稿在编辑时不改写;外部 value 真正变化时覆盖草稿。未改变值的失焦/Enter 不触发 onValueChange;输入法组词时不提交或步进。
89
+
90
+ NumberField 另有 `readOnly/required/id/name/hint/controls`(按钮默认显示);Escape 取消未提交草稿并恢复受控值,第二次 Escape 可交给外层浮层。min/max 变化会直接标明当前越界,不伪造用户输入或自动修改宿主值。空值步进按有效范围处理,不再把空字符串当成已达到零边界。
91
+
92
+ TreeSelect 使用 tree/treeitem/group,Tab 进入一个可见节点,上下/Home/End 移动,右键展开或进入首个子项,左键收起或返回父项,Space/Enter 选择。禁选节点可导航和展开,但不能勾选。多选用 aria-checked,单选用 aria-selected。节点 ID 全树唯一稳定;过滤后的祖先与展开状态由宿主提供,选择状态不随结果集消失。
93
+
94
+ 首次进入优先已选可见节点;字符前缀定位不改变选择,`*` 展开同层分支,Ctrl/⌘+A 仅切换可见可选节点。外部收起时焦点回可见祖先。节点 `selectable=false` 用于结构分组;readOnly 允许浏览展开而禁止选择。`name` 为选中 key 生成同名 hidden inputs,disabled 不提交。
95
+
96
+ RemoteMultiSelect 复用 Combobox 的方向键、Enter、Escape 和 IME 防误提交。加载或失败时禁止选择旧候选,已选项仍显示且可移除;默认选中后保持候选列表打开,`closeOnSelect=true` 恢复选完关闭。`clearQueryOnSelect` 默认 false;新增 readOnly/clearable/placeholder/hint/name。空查询 Backspace 删除最后一个可移除标签,标签删除/清空后回输入框;锁定标签保留。name 以同名 hidden inputs 提交受控 keys。单选 Combobox 的默认关闭行为不变。异步取消、序列检查、服务端查询和时区解释全部归宿主。展台有可取消的本地延迟结果演示,但不是生产数据层。
97
+
98
+ 分组底色 `--rhine-field-group-bg`;树缩进 `--rhine-tree-indent`;其余输入、选择、错误和焦点继续用既有语义 tokens,字号不低于 12px。
99
+
100
+ ## DataTable 与 Pagination
101
+
102
+ 所有新参数可选,原表格保持原生 table 语义。此处提供可展开的层级表格,不实现可编辑 ARIA treegrid;键盘使用 Tab 与原生 checkbox/button,不冒充网格方向键导航。
103
+
104
+ - `selection={keys,onChange,isDisabled?}`:keys 为稳定 rowKey 数组,可包含其他页/查询中的记录。表头只选择/取消当前可见且未禁选的行;保留不可见和禁选键,部分选中为 mixed。加载时禁用选择。
105
+ - `expansion={keys,onChange,getChildren?,renderDetail?,canExpand?}`:子行按宿主顺序展开,层级有文字说明;renderDetail 为独立 region,可与子行共存。keys 由宿主管理,折叠不清空选择;不请求子数据、不更改导出范围。大树的加载与虚拟化由宿主决定,本组件不内置虚拟表格。
106
+ - `rowLabel` 默认 rowKey,建议传业务可读名称,供选择/展开按钮命名。所有层级 rowKey 必须全局唯一稳定。
107
+ - `stickyHeader/maxHeight`:提供明确局部滚动区;列可设 `width/sticky/stickyOffset`。多列固定默认按实测 CSS 布局宽度自动累计 offset,显式 stickyOffset 优先;需为正文至少保留一个可读列,不将多列固定到遮满窄屏。展台固定右侧两个窄列。
108
+ - `density="comfortable" | "compact"`:默认单元格内距为 12/16px 或 6/12px;橙趣 comfortable 为 16/12px,compact 同为 6/12px。业务文字 14px、层级说明 12px,操作仍为 44px。tokens:`--rhine-table-cell-padding`、`--rhine-table-header-bg`、`--rhine-table-hover-bg`、`--rhine-table-selected-bg`;选中底色优先于悬停,固定列使用不透明底色。
109
+ - `Pagination.onPageSizeChange` 和 `pageSizeOptions`:仅回传页大小意图,宿主决定是否回到第一页、是否取消请求。total 已知时推导页数;未知时依赖 hasNext。库不筛选、排序、请求或切割全量 rows。
110
+
111
+ ```tsx
112
+ <DataTable caption="记录" rows={response.rows} columns={columns}
113
+ rowKey={row => row.id} rowLabel={row => row.name}
114
+ selection={{ keys: selected, onChange: setSelected, isDisabled: row => !row.canSelect }}
115
+ expansion={{ keys: expanded, onChange: setExpanded, getChildren: row => row.children ?? [] }}
116
+ stickyHeader maxHeight={480} loading={loading} error={error} />
117
+ <Pagination page={page} pageSize={pageSize} total={response.total}
118
+ hasNext={response.hasNext} onPageChange={requestPage} onPageSizeChange={requestPageSize} />
119
+ ```
120
+
121
+ 迁移必须保留选择、导出、查询和层级状态。错误与刷新保留已有行,空数据不伪装成错误;已有 sort/onSortChange 不变。
122
+
123
+ `selection.mode` 默认 multiple,可选 single 使用原生 radio(读取 keys 第一项);新选择回传单个 key,替换旧单选。`hideSelectAll` 保留单行操作但不显示全选。`DataColumn.rowHeader` 输出 th scope=row;`onSortReset` 提供显式取消排序,宿主清空 sort 后生效。canExpand=false 同时阻止旧展开 key 渲染子行/详情,详情 ID 按行 key 稳定。
124
+
125
+ Pagination 新增 `disabled/label/showFirstButton/showLastButton`;首末页按钮默认不显示,未知总量时末页不可操作。负数与非有限 total 视作未知;总量缩小导致当前页超界时明确标注,上一页返回已知末页,不自动发请求。翻页到边界时焦点移至相邻可用按钮,页码摘要有状态播报。
126
+
127
+ ## 媒体
128
+
129
+ `MediaCard` 接收 title、date、kind(photo/video/RAW/Live)、preview、state(ready/loading/error/empty)、aspectRatio(默认 4/3)、onOpen;可选 selected/onSelectedChange/disabled。preview 内的 img/video 使用 contain 保持原比例;媒体类型及失败均有文字。打开按钮与选择 checkbox 为兄弟元素,选择不会触发打开;卡片没有内置网络、轮播、虚拟列表或滚轮监听。preview 应为非交互缩略图,不在打开按钮里嵌套视频控制器。loading 时预览保持隐藏挂载,宿主可在 img.onError/onLoad 后设置 state;缺预览默认 empty,日期为空显示未知。
130
+
131
+ `MediaViewer` 接收 trigger/title/description/children、受控 open/onOpenChange、onPrevious/onNext、actions、metadata/metadataOpen/onMetadataOpenChange。复用 Radix Dialog 的焦点范围与 Escape,关闭归还实际打开它的卡片/触发器;宿主若卸载或虚拟化掉触发器,应在 onOpenChange 中安排替代焦点。
132
+
133
+ MediaCard 支持原生 article 属性、ref 与独立 actions(重试等操作不能嵌套在打开按钮里)。MediaViewer 支持 defaultOpen、defaultMetadataOpen 及 className/style;不传 metadataOpen 时,信息栏按钮即可管理内部状态。`previousDisabled/nextDisabled` 显式表达边界,左右键默认触发上/下一项,可用 `keyboardNavigation=false` 关闭;输入框、选择器、媒体播放器和嵌套浮层保留自身按键。导航按钮到达边界变禁用时焦点留在查看器内,可继续反向浏览。
134
+
135
+ 查看器采用同一主题根中的中性媒体 tokens:canvas `#101214`、overlay `#202428`、text `#f6f7f8`、muted `#bcc3ca`、border `#68727c`、focus `#9bceff`。不把战术主题当暗色媒体模式,也不嵌套 Provider。顶部操作可换行,触控目标至少 44px;桌面 metadata 位于右侧,760px 以下移到底部。短窗口内容可滚动,关闭按钮始终在顶部可达。
136
+
137
+ 媒体 children、下载链接和资源版本选择通过 slots 提供;库不持有下载路径、播放状态或路由。actions 中使用原生 button/select/a 可自动使用中性叠加样式。宿主可将 scroll-snap 列表放入画布,并保留原有 Masonic 或其他虚拟列表。仅复用模态的背景滚动管理,没有增加 wheel 事件锁或自定义手势拦截。内部 Dialog/Popover 优先响应 Escape,下一次 Escape 再关闭查看器;媒体自身不播放进场动画,减少动态遵循同一主题根。
138
+
139
+ ## 健康详情与运维图表(issue #6)
140
+
141
+ `TimelineSegment.detail` 可选,形状为 `{ start, end, reason?, counts?: [{ id, label, value: number|null, unit? }], methodology? }`。开始/结束时间是宿主格式化的含时区字符串;计数 0 与 null 分别呈现为零和未知,NaN/Infinity 也视为未知。统计口径缺失时明确标注未提供,不猜测原因或成功率。旧版只有 label/status 的调用仍兼容。
142
+
143
+ 完整与 compact 健康条均复用同一结构化详情。键盘聚焦和方向键、鼠标定位、触屏按下均打开同一个非模态 Popover;默认优先向上,空间不足自动转向下方,距离视口 12px,内容超过可用高度时内部滚动。Escape 或关闭按钮收起,点击外部也可退出。Popovers 挂到同一主题根,不受侧栏/卡片裁剪。保留当前时段的 aria-valuetext,并用 aria-details 指向已展开详情;焦点不因鼠标悬停被抢走。减少动态无新增例外。
144
+
145
+ StatusTimeline 通过 `activeId/defaultActiveId/onActiveChange` 按稳定时段 ID 控制选中;未指定或当前 ID 消失时显示最后一个时段。`open/defaultOpen/onOpenChange` 控制详情,`disabled` 禁止操作及显示详情。Enter/Space 将键盘焦点送入详情关闭按钮,退出后返回健康条且不会立即重开。
146
+
147
+ `TrendChart({ label, unit, rangeLabel, description, points, kind, stale?, error?, loading? })` 提供 `kind="bar" | "line"`,每个 point 是 `{id,label,value:number|null}`。宿主负责时间桶、排序、聚合、单位与统计说明;组件只映射坐标。零是有效采样,未知值不补零、不插值,折线路径在缺失点处断开;图内 ×、SVG 可访问说明和可展开的原始数据表共同表示缺失。单位、范围与图例均为 HTML 文字,避免窄屏 SVG 缩放导致文字小于 12px。多条资源指标使用多个命名图表组合,不内置数据请求或图表引擎。
148
+
149
+ `StackedBar` 使用相同的 label/unit/rangeLabel/description/反馈字段,接收 `parts=[{id,label,value:number|null,tone?:1|2|3|4}]`。只在每项计数均已知、非负、有限且总和大于零时按传入计数绘制占比;任意未知值会停止归一化并显示“总量未知”,仍列出全部已知/未知计数。全零和负数各有说明。百分比是图例中对这组计数的数学占比,保留一位小数,不是健康分数或成功率。宿主必须提供互斥分类,不能把重叠统计相加当作总量。
150
+
151
+ 两类图表新增 `valueFormatter(value:number|null)`;非有限值以 null 传入。TrendChart 刻度与逐点文字采用格式化,原始数据表保留原数值;StackedBar 格式化图例但不改变比例算法,零分段不占色带空间。TrendChart 可聚焦图形后用左右/Home/End,或悬停/轻触查看同一时段。`activePointId/defaultActivePointId/onActivePointChange` 支持受控高亮与多图联动;null 清除高亮,丢失 ID 不替代为别的样本。高亮值以 HTML 文字播报,缺失桶仍可查看。
152
+
153
+ 图表 `stale` 显示采集陈旧说明,`error` 显示错误并保留旧数据,loading 同样保留旧数据;空数组与全未知区分。图例逐项提供名称与数值,不只靠颜色。`--rhine-chart-series-1/2/3/4`、`--rhine-chart-grid`、`--rhine-chart-missing` 是语义 tokens;前两色跟随主题主色/信号色,后两色供独立分类使用。图形不闪烁、不扫光、不内置告警阈值。相关 API 自 0.5.0 提供,npm 0.4.0 不包含。
154
+
155
+ ```tsx
156
+ <StatusTimeline label="请求历史" segments={segmentsWithDetail}
157
+ startLabel="10:00 UTC+8" endLabel="11:00 UTC+8" />
158
+ <TrendChart kind="line" label="CPU 使用率" unit="%" rangeLabel={range}
159
+ description="每分钟采样,缺失保持未知" points={serverPoints}
160
+ stale={staleMessage} error={requestError} />
161
+ <StackedBar label="互斥结果分类" unit="次" rangeLabel={range}
162
+ description="按最终结果分类" parts={serverCounts} />
163
+ ```
164
+
165
+ ## 真实展台入口
166
+
167
+ 开发服务 `npm run dev` 后访问:
168
+
169
+ - `/#app-shell`:三种布局、独立背景、Surface、固定顶部操作、Drawer/Dialog、状态与可编辑草稿。
170
+ - `/#advanced-forms`:日期预设/自定义/清空、数字草稿、树单多选、远程延迟/取消/IME,以及加载/失败/空/禁用。
171
+ - `/#advanced-data`:跨页选中键、禁选、层级/详情、密度、已知/未知总数和局部固定列。
172
+ - `/#media`:默认、长标题、加载、错误、缺预览,媒体查看器中的版本/下载与分层 Escape。
173
+ - `/#monitoring`:结构化时段详情与紧凑条、柱形趋势、缺失点折线、已知/未知占比、文本替代及空/陈旧/错误状态。
174
+
175
+ 页首切换三主题及减少动态不重建当前展台;切换章节会卸载演示。自动测试验证受控行为,浏览器与版本验收单独记录于 [验证记录](ISSUE_VALIDATION.md)。精确版本的制品校验和与消费验证按 [发布流程](RELEASING.md)保留。
@@ -1,5 +1,7 @@
1
1
  # 莱茵设计 · 主流系统对照与补齐顺序
2
2
 
3
+ 本页保留早期组件基线与补齐顺序。2026-10-24 对 issues #1–#6 的 11 个新增组件及 3 个扩展另有[Ant Design / MUI 逐项审计](ANTD_MUI_AUDIT.md),包含本轮修复、验证和有意差异;当前候选 API 见[应用组件契约](APPLICATION_COMPONENTS.md)。
4
+
3
5
  初次调研:2026-10-22;2026-10-23追加[GitHub项目与skills调研](./REFERENCE_RESEARCH.md),main新增六个组件、14类场景指南和两组动效。对照[莱茵设计](../README.md)的`components/`与通用表单、数据展示、反馈和导航场景。以下优先级是本项目判断;引用用于说明参考系统的原则,不代表它们要求采用我们的尺寸、配色或组件清单。独立展台使用18309的`/#…`入口。
4
6
 
5
7
  ## 参考与取舍
@@ -0,0 +1,21 @@
1
+ # Issue #4 主线整合验证
2
+
3
+ 2026-10-23。此记录对应 #4 专项源码整合,不表示 npm 已发布。包版本保持 0.4.0,新增 API 等待下次 npm 发布窗口。
4
+
5
+ ## 范围
6
+
7
+ 从候选提交 `2e08ebb` 提取 Background、AppShell(组合展台依赖)、背景语义 tokens、三主题表面样式、两组真实 React 展台及相关契约。没有纳入复杂表单、受控数据表、媒体查看器和监控图表。原候选分支保留,后续整合需保留本次背景能力并处理共享文件差异。
8
+
9
+ ## 本轮验证
10
+
11
+ - Node 24 下 `npm ci` 与 `npm run check` 通过:55 项测试、TypeScript、Vite 生产构建、CSS/SVG 产物检查。
12
+ - Chromium 使用本次生产构建:三主题 × 场景背景/应用外壳两个展台 × 400/520/880/1600px,共 24 组,无页面、主工作区或外壳横向溢出。
13
+ - 三主题 × 两个展台,在 1040px 视口 CSS `zoom:2` 下无页面横向溢出,共 6 组。此证据不等同于浏览器菜单 200% 缩放,后者留待发布验收。
14
+ - 三主题的正文纹理开关、纯色降级、内部滚动后图案不重置及顶部操作位置不变通过;纸面无纹理,战术网格与橙趣点阵可独立绘制。
15
+ - Dialog 实际可输入,Escape 关闭后焦点回到触发按钮;正文草稿保留,全页唯一主题根。
16
+ - 系统减少动态偏好下背景无动画;浏览器运行时错误为零。
17
+ - 实际截图:[橙趣正文与侧栏](assets/issue4-shell.png)。
18
+
19
+ ## 后续
20
+
21
+ 等下次 npm 窗口完成最终版本号、干净提交制品、registry 回读及仓库外固定版本安装验证,再更新并关闭 #4。当前仅交付 main 源码与展台,不能按 npm 0.4.0 消费新增 Background/AppShell。