@linhey/rhine-design 0.9.1 → 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 +21 -0
- package/CONTRIBUTING.md +3 -1
- package/README.md +18 -15
- package/components/AdvancedForms.tsx +22 -8
- package/components/COMMON_COMPONENTS.md +6 -3
- package/components/Forms.tsx +14 -1
- package/components/ListFilterBar.tsx +5 -3
- package/components/Overlays.tsx +3 -2
- package/components/README.md +13 -5
- package/components/SPACING.md +9 -0
- package/components/TYPOGRAPHY.md +2 -0
- package/components/Toast.tsx +10 -4
- package/components/checkbox-row.css +12 -0
- package/components/common.css +3 -1
- package/components/compact-forms.css +20 -0
- package/components/components.tsx +13 -7
- package/components/index.ts +1 -0
- package/components/media-parity.css +4 -4
- package/components/patterns.css +3 -3
- package/components/styles.css +17 -2
- package/components/theme-picker.css +2 -2
- package/docs/APPLICATION_COMPONENTS.md +2 -2
- package/docs/CHECKBOX_ROWS.md +36 -0
- package/docs/COMPACT_FILTER_CONTROLS.md +38 -0
- package/docs/COMPONENT_TYPOGRAPHY_AUDIT.md +44 -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/LIST_FILTER_BAR.md +5 -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/CheckboxRowGallery.tsx +26 -0
- package/preview/CommonGallery.tsx +1 -1
- package/preview/CompactFilterBarGallery.tsx +19 -0
- package/preview/ComponentTypographyGallery.tsx +23 -0
- package/preview/DenseFilterControlsGallery.tsx +21 -0
- package/preview/GalleryShell.tsx +4 -3
- package/preview/OrbitGallery.tsx +1 -1
- package/preview/SpacingGallery.tsx +9 -1
- package/preview/SummaryTabsGallery.tsx +37 -0
- package/preview/main.tsx +14 -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-checkbox-rows.js +65 -0
- package/scripts/check-component-typography.js +76 -0
- package/scripts/check-dense-filters.js +60 -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/SKILL.md +2 -2
- package/skills/rhine-design/references/API_MAP.md +6 -0
- package/skills/rhine-design/references/TASK_MAP.md +2 -0
- package/skills/rhine-design/references/integration.md +1 -1
- package/tests/CheckboxRow.test.tsx +53 -0
- package/tests/CommonComponents.test.tsx +8 -0
- package/tests/CompactFilterBar.test.tsx +50 -0
- package/tests/DenseFilterControls.test.tsx +79 -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
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
.rhine-media-card__footer .rhine-check { min-width:0; overflow-wrap:anywhere; }
|
|
18
18
|
.rhine-media-card__footer .rhine-check label { min-height:44px; }
|
|
19
19
|
.rhine-media-card__actions { display:flex; flex-wrap:wrap; gap:8px; }
|
|
20
|
-
.rhine-media-card__status { display:grid; justify-items:center; gap:12px; padding:40px 16px; font-size:var(--rhine-text-body); }
|
|
20
|
+
.rhine-media-card__status { display:grid; justify-items:center; gap:12px; padding:40px 16px; font-size:var(--rhine-text-body); line-height:var(--rhine-leading-body); font-weight:var(--rhine-weight-regular); }
|
|
21
21
|
.rhine-media-card__status-mark { display:grid; place-items:center; width:44px; height:44px; border:1px solid var(--rhine-control); border-radius:var(--rhine-radius-control); font:24px var(--rhine-mono); }
|
|
22
22
|
.rhine-media-card[data-state=error] .rhine-media-card__status { color:var(--rhine-error); }
|
|
23
23
|
.rhine-theme[data-rhine-theme=neo-brutalism] .rhine-media-card { --media-card-border:var(--rhine-ink); --media-card-shadow:var(--rhine-hard-shadow); border-width:2px; }
|
|
@@ -28,10 +28,10 @@
|
|
|
28
28
|
.rhine-media-viewer { position:fixed; inset:0; z-index:41; width:100%; height:100%; display:flex; flex-direction:column; background:var(--rhine-surface); color:var(--rhine-ink); overflow:auto; }
|
|
29
29
|
.rhine-media-viewer__header { position:sticky; top:0; z-index:1; flex:none; display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between; gap:16px; padding:16px 24px; background:var(--rhine-surface); border-bottom:1px solid var(--rhine-control); }
|
|
30
30
|
.rhine-media-viewer__header>div { min-width:0; }
|
|
31
|
-
.rhine-media-viewer__header h2 { font-size:var(--rhine-text-section); line-height:var(--rhine-leading-section); overflow-wrap:anywhere; margin:0; }
|
|
32
|
-
.rhine-media-viewer__header p { color:var(--rhine-muted); font-size:var(--rhine-text-caption); margin:4px 0 0; }
|
|
31
|
+
.rhine-media-viewer__header h2 { font-size:var(--rhine-text-section); line-height:var(--rhine-leading-section); font-weight:var(--rhine-weight-heading); overflow-wrap:anywhere; margin:0; }
|
|
32
|
+
.rhine-media-viewer__header p { color:var(--rhine-muted); font-size:var(--rhine-text-caption); line-height:var(--rhine-leading-caption); font-weight:var(--rhine-weight-regular); overflow-wrap:anywhere; margin:4px 0 0; }
|
|
33
33
|
.rhine-media-viewer__actions { display:flex; align-items:center; flex-wrap:wrap; gap:8px; }
|
|
34
|
-
.rhine-media-viewer__actions label { display:flex; flex-wrap:wrap; align-items:center; gap:8px; font-size:var(--rhine-text-caption); }
|
|
34
|
+
.rhine-media-viewer__actions label { display:flex; flex-wrap:wrap; align-items:center; gap:8px; font-size:var(--rhine-text-caption); line-height:var(--rhine-leading-caption); font-weight:var(--rhine-weight-regular); }
|
|
35
35
|
.rhine-media-viewer :is(button,select,a):not(.rhine-button) { min-height:44px; min-width:44px; max-width:100%; padding:8px 12px; border:1px solid var(--rhine-control); border-radius:var(--rhine-radius-control); background:var(--rhine-surface); color:var(--rhine-ink); font:inherit; overflow-wrap:anywhere; }
|
|
36
36
|
.rhine-media-viewer button { cursor:pointer; }
|
|
37
37
|
.rhine-media-viewer button:disabled { cursor:default; }
|
package/components/patterns.css
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
.rhine-steps button { border:0; background:none; font:inherit; cursor:pointer; }
|
|
21
21
|
.rhine-steps button:hover { background:var(--rhine-selected); }
|
|
22
22
|
.rhine-steps strong,.rhine-steps small { display:block; overflow-wrap:anywhere; }
|
|
23
|
-
.rhine-steps small { margin-top:4px; font-size:
|
|
23
|
+
.rhine-steps small { margin-top:4px; font-size:var(--rhine-text-caption); line-height:var(--rhine-leading-caption); font-weight:var(--rhine-weight-regular); }
|
|
24
24
|
.rhine-steps__number { flex:0 0 28px; font:14px/24px var(--rhine-mono); }
|
|
25
25
|
.rhine-steps li[data-state=current],.rhine-steps li[data-state=complete] { border-color:var(--rhine-green); }
|
|
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); }
|
|
@@ -28,10 +28,10 @@
|
|
|
28
28
|
.rhine-steps li[data-state=error]>div { color:var(--rhine-error); background:var(--rhine-error-bg); }
|
|
29
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
|
-
.rhine-fieldset>p { margin:0 0 20px; color:var(--rhine-muted); font-size:
|
|
31
|
+
.rhine-fieldset>p { margin:0 0 20px; color:var(--rhine-muted); font-size:var(--rhine-text-caption); line-height:var(--rhine-leading-caption); font-weight:var(--rhine-weight-regular); }
|
|
32
32
|
.rhine-fieldset__body { display:grid; gap:16px; }
|
|
33
33
|
.rhine-error-summary { padding:20px; border:1px solid var(--rhine-error); border-inline-start:3px solid var(--rhine-error); background:var(--rhine-error-bg); }
|
|
34
|
-
.rhine-error-summary h3 { margin:0 0 8px; font-size:
|
|
34
|
+
.rhine-error-summary h3 { margin:0 0 8px; font-size:var(--rhine-text-heading); line-height:var(--rhine-leading-heading); font-weight:var(--rhine-weight-heading); }
|
|
35
35
|
.rhine-error-summary ul { padding-inline-start:20px; margin:0; }
|
|
36
36
|
.rhine-error-summary a { display:inline-block; padding:10px 0; color:var(--rhine-error); text-underline-offset:3px; overflow-wrap:anywhere; }
|
|
37
37
|
.rhine-dialog.rhine-drawer { inset-block:0; inset-inline-end:0; inset-inline-start:auto; width:min(440px,100%); max-height:100dvh; margin:0; transform:none; border-radius:0; animation:rhine-drawer-enter var(--rhine-duration-overlay) var(--rhine-ease-standard); }
|
package/components/styles.css
CHANGED
|
@@ -60,14 +60,28 @@
|
|
|
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; }
|
|
67
|
-
.rhine-segmented__option span { display:flex; justify-content:center; align-items:center; min-height:40px; padding:8px 12px; color:var(--rhine-muted); font-size:14px; transition:background-color var(--rhine-duration-hover) var(--rhine-ease-standard),color var(--rhine-duration-hover) var(--rhine-ease-standard); }
|
|
74
|
+
.rhine-segmented__option span { display:flex; flex:1; justify-content:center; align-items:center; min-height:40px; padding:8px 12px; text-align:center; color:var(--rhine-muted); font-size:14px; transition:background-color var(--rhine-duration-hover) var(--rhine-ease-standard),color var(--rhine-duration-hover) var(--rhine-ease-standard); }
|
|
75
|
+
.rhine-segmented--full-width { display:flex; width:100%; flex-wrap:nowrap; }
|
|
76
|
+
.rhine-segmented--full-width .rhine-segmented__option { flex:1 1 0; min-width:0; }
|
|
77
|
+
.rhine-segmented--full-width .rhine-segmented__option span { min-width:0; overflow-wrap:anywhere; }
|
|
68
78
|
.rhine-segmented input:checked+span { background:var(--rhine-ink); color:var(--rhine-surface); }
|
|
69
79
|
.rhine-segmented input:focus-visible+span { outline:2px solid var(--rhine-green); outline-offset:3px; }
|
|
70
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); }
|
|
71
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); }
|
|
72
86
|
.rhine-notice>div { flex:1 1 200px; min-width:0; overflow-wrap:anywhere; }
|
|
73
87
|
.rhine-notice strong { font-size:14px; font-weight:600; }
|
|
@@ -81,6 +95,7 @@
|
|
|
81
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; }
|
|
82
96
|
.rhine-skeleton>span[aria-hidden]:last-child { width:65%; }
|
|
83
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; }
|
|
84
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; }
|
|
85
100
|
.rhine-disclosure summary::-webkit-details-marker { display:none; }
|
|
86
101
|
.rhine-theme .rhine-disclosure summary:focus-visible { outline-offset:-3px; }
|
|
@@ -88,7 +103,7 @@
|
|
|
88
103
|
.rhine-disclosure[data-expanded=true] summary>span { transform:rotate(45deg); }
|
|
89
104
|
.rhine-disclosure__content { padding:4px 0 20px; }
|
|
90
105
|
.rhine-empty { padding:32px 24px; text-align:center; border:1px solid var(--rhine-line); background:var(--rhine-surface); }
|
|
91
|
-
.rhine-empty strong { font-size:
|
|
106
|
+
.rhine-empty strong { display:block; font-size:var(--rhine-text-heading); line-height:var(--rhine-leading-heading); font-weight:var(--rhine-weight-heading); overflow-wrap:anywhere; }.rhine-empty p { max-width:400px; margin:8px auto 20px; color:var(--rhine-muted); }
|
|
92
107
|
@keyframes rhine-spin { to { transform:rotate(360deg); } }
|
|
93
108
|
.rhine-logo { display:inline-flex; flex:none; align-items:center; vertical-align:middle; color:var(--rhine-green); line-height:1; }
|
|
94
109
|
.rhine-logo__symbol { display:block; width:var(--rhine-logo-size); height:var(--rhine-logo-size); flex:none; fill:currentColor; }
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
.rhine-theme-option:hover:not(:has(input:disabled)) { background:var(--rhine-selected); }
|
|
9
9
|
.rhine-theme-option__copy { display:grid; gap:var(--rhine-space-2); min-width:0; overflow-wrap:anywhere; }
|
|
10
10
|
.rhine-theme-option__description { font-size:var(--rhine-text-caption); line-height:var(--rhine-leading-caption); color:var(--rhine-muted); }
|
|
11
|
-
.rhine-theme-option__status { font-size:var(--rhine-text-caption); min-height:24px; }
|
|
12
|
-
.rhine-theme-preview { position:relative; display:grid; place-items:center; aspect-ratio:8/5; min-width:0; overflow:hidden; border:1px solid var(--rhine-line); border-radius:var(--rhine-radius-control); background:var(--rhine-bg); color:var(--rhine-muted); font-size:var(--rhine-text-caption); }
|
|
11
|
+
.rhine-theme-option__status { font-size:var(--rhine-text-caption); line-height:var(--rhine-leading-caption); font-weight:var(--rhine-weight-regular); min-height:24px; }
|
|
12
|
+
.rhine-theme-preview { position:relative; display:grid; place-items:center; aspect-ratio:8/5; min-width:0; overflow:hidden; border:1px solid var(--rhine-line); border-radius:var(--rhine-radius-control); background:var(--rhine-bg); color:var(--rhine-muted); font-size:var(--rhine-text-caption); line-height:var(--rhine-leading-caption); font-weight:var(--rhine-weight-regular); }
|
|
13
13
|
.rhine-theme-preview img { display:block; width:100%; height:100%; object-fit:contain; }
|
|
14
14
|
.rhine-theme-preview__loading { position:absolute; inset:0; display:grid; place-items:center; background:var(--rhine-bg); }
|
|
15
15
|
.rhine-theme[data-rhine-theme=neo-brutalism] .rhine-theme-option { border:2px solid var(--rhine-ink); box-shadow:var(--rhine-hard-shadow); }
|
|
@@ -105,10 +105,10 @@ SideNav 继续负责当前项、长名称和 aria-current;Drawer 继续负责
|
|
|
105
105
|
| 组件 | 主要 API | 数据与键盘契约 |
|
|
106
106
|
| --- | --- | --- |
|
|
107
107
|
| DateField | `label/value/onValueChange/min/max/disabled/error` | 原生 `input type=date`;ISO 日期字符串或空字符串,无 UTC/时区转换,原生日历和键盘由浏览器实现 |
|
|
108
|
-
| DateRangePicker | 上述范围 + `value={start,end}`、`presets=[{label,value}]
|
|
108
|
+
| DateRangePicker | 上述范围 + `value={start,end}`、`presets=[{label,value}]`、`compact?` | 两个原生日期输入,自定义范围、受控预设、清空;compact 合并为一体区间框;结束日期下界取 start/min 较晚者;反向范围显示错误,不静默调换 |
|
|
109
109
|
| NumberField | `value: number\|null`、`onValueChange/min/max/step/precision/disabled/error` | 编辑中保存字符串;失焦/Enter 提交;无效及越界输入保留并报错;上下键/按钮步进与钳制边界;空字符串提交 null |
|
|
110
110
|
| TreeSelect | `nodes/value/onValueChange/expandedKeys/onExpandedChange/multiple`、loading/error/disabled | 节点为 `{id,label,disabled?,children?}`;父子独立选择,默认单选;调用方提供搜索后的树,不做全量过滤 |
|
|
111
|
-
| RemoteMultiSelect | `query/onQueryChange/value/onValueChange/options`、loading/error/disabled | value 是完整 `{value,label,disabled?}[]
|
|
111
|
+
| RemoteMultiSelect | `query/onQueryChange/value/onValueChange/options`、loading/error/disabled、`maxVisibleTags?` | value 是完整 `{value,label,disabled?}[]`,已选标签不依赖本次结果;不重复选中;清空保留禁选标签;maxVisibleTags 限制常显数量并提供展开/收起,展开区域有界滚动 |
|
|
112
112
|
|
|
113
113
|
日期 min/max 约束原生日历和表单有效性;手动编辑、业务校验及最终提交由宿主验证。原生日期面板位于浏览器 UI,不放在受裁剪的自定义浮层。日历外观、屏幕阅读器和移动 WebView 行为由具体浏览器决定,不声称提供自绘日历。预设必须使用宿主计算好的日期;不得以本机时区代替产品时区。没有“今天”隐式计算。
|
|
114
114
|
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# 侧栏双行多选
|
|
2
|
+
|
|
3
|
+
`Checkbox layout="row"` 自 0.10.0 提供,npm 0.9.1 尚不包含。适用于侧栏、来源列表和筛选项;导航仍用 SideNav,需要内容卡片时用 SelectionCard。
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
<Checkbox layout="row" label="示例项目 A" hint="内存卡 · 14 项"
|
|
7
|
+
checked={selectedIds.includes('a')}
|
|
8
|
+
onChange={event => updateSelection('a', event.target.checked)} />
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
- 默认 `layout="form"` 保留原表单布局;`CheckboxProps` 从公共入口导出,继续接受原生 name/value、受控与非受控状态及事件。
|
|
12
|
+
- row 的复选框单独占 18px 列,与文字列间隔 8px。名称 14/22px,使用标签字重 token(默认 500,橙趣主题 600);说明 12/18px、400,二者相隔 4px,具有同一文字起点。长名称与长连续字符串自然换行,不缩字或截断。
|
|
13
|
+
- 行使用上下左右对称 8px 内距、最小 44px 命中高度,随文字自然增高。两行基础高度为 62px(22+4+18+16+2),不用 44px 名称行的透明余量充当文字间距。宿主用 Stack gap={2} 排列,避免覆盖内部 label CSS。
|
|
14
|
+
- 外边框内的说明、文字及留白全部属于同一个原生 label;一次点击仅触发一次原生 change,Space 保留原生键盘选择。说明用 aria-describedby 关联,默认可访问名称只包含名称文字;调用方的 aria-label / aria-labelledby 继续生效。
|
|
15
|
+
- checked 与 indeterminate 使用主题选中背景、强调边界及原生勾选/混合态;focus-visible 使用行内焦点框,紧贴滚动边缘也不会因外扩而裁切。disabled 保持可读并禁止选择,仍可展示已选状态。
|
|
16
|
+
- 混合态由宿主根据选中 IDs 计算;原生 checkbox 没有只读行为,禁用交互请使用 disabled。label/hint 保持纯文本,不在整行 label 内嵌链接或其他交互控件。
|
|
17
|
+
|
|
18
|
+
真实展台 `#checkbox-rows` 提供 160/240/320px 侧栏、单行/长名称、说明有无、混合态、禁用与默认表单对照。验收以实际文字行框、对称内距、完整点击区域和键盘焦点为准;CSS zoom 与原生浏览器菜单缩放分别记录。
|
|
19
|
+
|
|
20
|
+
## 验证(2026-10-26)
|
|
21
|
+
|
|
22
|
+
Node 24 下 259 测试、类型检查与生产构建通过。Chromium 三主题 × 400/520/880px 视口 × 100%/200% CSS zoom × 160/240/320px 侧栏,共 54 组几何检查通过;侧栏实际宽度受可用空间限制。验证名称/说明行框、字重、4px 间隔、同列起点、对称留白、最小 44px、无横溢出,并执行说明点击、行内留白点击、Tab 焦点、Space、混合态与禁用检查。三主题 400px / 200% 截图已检查,长名称、说明与焦点可见。未覆盖原生浏览器菜单缩放或所有浏览器。
|
|
23
|
+
|
|
24
|
+
先执行 `npm run check`,再在一个终端启动并保留预览:
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
npx vite preview --host 127.0.0.1 --port 18529 --strictPort
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
在另一终端运行浏览器回归:
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
playwright-cli -s=checkbox-rows open 'http://127.0.0.1:18529/#checkbox-rows'
|
|
34
|
+
mkdir -p output/playwright/issue-29
|
|
35
|
+
playwright-cli -s=checkbox-rows run-code --filename=scripts/check-checkbox-rows.js
|
|
36
|
+
```
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# 紧凑筛选控件
|
|
2
|
+
|
|
3
|
+
本节 API 自 0.10.0 提供,0.9.1 不包含这些选项。真实组件组合为 `preview/CompactFilterBarGallery.tsx`。
|
|
4
|
+
|
|
5
|
+
统一展台入口 `#compact-filters`,同时运行 `preview/DenseFilterControlsGallery.tsx`:100 个候选、90 个已选项及一体日期区间。`RemoteMultiSelect maxVisibleTags={2}` 默认只显示两个标签与总数,展开后使用有界滚动,不将大量已选项常驻到筛选抽屉。`DateRangePicker compact` 将两端原生日期放在同一边框内,保留无障碍标签、原生日期键盘与错误关联;极窄容器允许内部换行,不能裁切日期。
|
|
6
|
+
|
|
7
|
+
- `SegmentedControl fullWidth` 占满可用行宽,选项等宽;长标签在所属选项中换行。选中背景和可见文本区域共同撑满 label,文字水平/垂直居中,不把扩大点击区域误认为扩大选中背景。
|
|
8
|
+
- 默认 `fullWidth=false` 保持既有按内容布局。原生 radio 的 checked、禁用、Tab 和方向键行为不变;独立实例继续使用独立 name。
|
|
9
|
+
- 有限标签在极窄容器内允许换行,保留完整的移除按钮;展开只影响呈现,不裁减表单值。清空或宿主将选择数降至限制以内后,再次批量选择恢复折叠。
|
|
10
|
+
- `ListFilterBar drawerDescription={null} drawerResult={null}` 删除可省略说明与重复统计。没有内容时不留下空段落/反馈容器;关闭后仍显示栏内完整摘要。
|
|
11
|
+
- 正在更新和错误不是冗余说明,抽屉打开时继续提供反馈。减少文字不能掩盖请求失败或把旧结果解释为当前条件成功。
|
|
12
|
+
- `Dialog` / `Drawer` 的 description 接收 ReactNode,`null` 不渲染描述,同时移除 aria-describedby;标题和关闭入口始终保留。
|
|
13
|
+
|
|
14
|
+
验收三主题、400/520/880px、200% 内容缩放及长标签;检查每个选项的几何宽度和文字中心、整个选中底色的宽度、键盘原生单选、抽屉关闭归焦、无描述时的可访问名称。JSDOM 验证语义与回调,真实浏览器验证几何。
|
|
15
|
+
|
|
16
|
+
## 回归记录(2026-10-25)
|
|
17
|
+
|
|
18
|
+
Node 24:254 项测试、类型检查和生产构建通过。生产预览下三主题 × 400/520/880px × 100%/200% CSS zoom 的 18 组检查通过,覆盖有限标签与完整移除入口、有界展开、清空后重新折叠、原生日期最后一段编辑、等宽单选键盘操作、可省略摘要及保留加载/错误、关闭归焦。400px / 200% 的三主题日期截图人工核对完整年月日与日历入口;不外推为原生菜单缩放或所有浏览器验收。
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
npm run check
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
在一个终端启动预览,并在浏览器检查期间保持运行:
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
npx vite preview --host 127.0.0.1 --port 18527 --strictPort
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
预览就绪后,在另一个终端执行:
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
playwright-cli -s=dense-filters open 'http://127.0.0.1:18527/#compact-filters'
|
|
34
|
+
mkdir -p output/playwright/issue-27
|
|
35
|
+
playwright-cli -s=dense-filters run-code --filename=scripts/check-dense-filters.js
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
每个矩阵场景使用独立导航参数重建示例状态,避免前次未关闭浮层导致下轮读到隐藏的底层内容。全程只使用手写合成数据。
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# 公共组件文字配对复核(#31)
|
|
2
|
+
|
|
3
|
+
2026-10-26。本轮修复自 npm 0.10.0 提供,0.9.1 不包含。范围为 `components/*.css` 中的字号声明及对应真实组件,重点检查组件从正文切换到 caption/heading 后是否仍依赖父级行高或浏览器默认字重;不全局重写 strong、p 或所有字体。
|
|
4
|
+
|
|
5
|
+
| 组件位置 | 本轮处理 |
|
|
6
|
+
| --- | --- |
|
|
7
|
+
| MediaViewer header 说明、操作标签 | caption 12/18/400,长说明自然换行 |
|
|
8
|
+
| EmptyState 标题 | heading 16/24,显式块级行框与自然换行 |
|
|
9
|
+
| MediaViewer header 标题 | section 18/26,显式主题标题字重 |
|
|
10
|
+
| MediaCard 加载/空/错误文案 | body 14/22/400,符号标记继续独立 |
|
|
11
|
+
| CopyButton 反馈、Steps 说明、Fieldset 提示 | caption 12/18/400 |
|
|
12
|
+
| ErrorSummary 标题 | heading 16/24 与主题标题字重 |
|
|
13
|
+
| ThemePicker 状态、ThemePreview 加载/失败文案 | caption 12/18/400,保留最小区域高度 |
|
|
14
|
+
|
|
15
|
+
标题字重使用 `--rhine-weight-heading`:莱茵与战术 600,橙趣主题已有覆盖为 750。这里保留主题语义,不强制全部主题变成 600。辅助说明使用 `--rhine-weight-regular`,避免宿主强调字重泄入。
|
|
16
|
+
|
|
17
|
+
## 保留与排除
|
|
18
|
+
|
|
19
|
+
- Text、Checkbox row、表格 caption、媒体卡片 caption、导航、筛选反馈等已有字号/行高配对,保持不变。
|
|
20
|
+
- Badge、Notice 说明、图表辅助说明的既有 12/20、表格普通文本的既有密度,以及主题表格/表单标签覆盖不机械统一。正文同层的分段控件与 Notice 标题没有切换到 caption/heading,不在本次跨层级修复中重设字重。
|
|
21
|
+
- Metric 单位属于指标与辅助字号的行内混排;整行由指标控制,不把整体压成 caption 高度。状态符号、编号、Logo 等图形/品牌表达不当作正文。
|
|
22
|
+
- 消费应用自己的标题与辅助文字组合不在设计库修复范围;本轮组件修复不迁移业务页面;npm 发布单独执行版本流程。
|
|
23
|
+
|
|
24
|
+
## 复现与回归
|
|
25
|
+
|
|
26
|
+
真实展台 `#component-typography` 使用纯合成内容,覆盖上述规则。复制动作使用异步空实现,不访问剪贴板;主题预览使用故意损坏的内嵌图片验证失败文案,不请求外部资源。
|
|
27
|
+
|
|
28
|
+
先运行 `npm run check`,然后在一个终端保持预览运行:
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
npx vite preview --host 127.0.0.1 --port 18531 --strictPort
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
另一终端执行:
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
playwright-cli -s=component-type open 'http://127.0.0.1:18531/#component-typography'
|
|
38
|
+
mkdir -p output/playwright/issue-31
|
|
39
|
+
playwright-cli -s=component-type run-code --filename=scripts/check-component-typography.js
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
回归直接断言计算字号、行高与字重,并改变宿主行高/字重确认局部配对;检查三主题、400/520/880px、100%/200% CSS zoom、长文、44px 操作区域、关闭与焦点归还。400px、200% CSS zoom 下额外测量 EmptyState 标题与 MediaViewer 说明的文字 Range,确认实际多行且文字边界无水平或垂直裁切,不仅检查外围容器。不将 CSS zoom 外推为原生菜单缩放或所有浏览器覆盖。
|
|
43
|
+
|
|
44
|
+
本轮结果:259 项组件测试、类型检查与生产构建通过;上述 18 组 Chromium 场景通过,无页面异常。另人工复核三主题在 400px、200% CSS zoom 下的空状态与媒体头部截图,长文和操作仍完整可读。
|
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
|
|
package/docs/LIST_FILTER_BAR.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
- `primary` 只常显前两项,超出的项目自动进入“更多筛选”。选择高频且互相独立的条件,例如状态和排序;不要为了展开所有控件增加第二个筛选栏。
|
|
12
12
|
- 其他条件进入同一个 Drawer。字段自己的 label 持续可见,组件不重复加可见标题,也不提供任意 `children` 让页面插回一排控件。
|
|
13
13
|
- 非默认条件用短摘要说明当前限制;单项清除与整体重置都由宿主定义。默认排序、默认统计日期等是否需要摘要由宿主的 `activeLabel` 决定,不把所有非空值都视为筛选。
|
|
14
|
-
- 结果数量、更新中与错误反馈在栏内呈现;Drawer
|
|
14
|
+
- 结果数量、更新中与错误反馈在栏内呈现;Drawer 默认重复结果摘要,信息较多时用 `drawerResult={null}` 移除重复统计。更新中与错误仍必须在 Drawer 内反馈,失败不能藏在遮罩后的页面上,也不能把旧数量当作新条件已经成功。
|
|
15
15
|
- 刷新之外最多一个业务操作。多记录的选择、批量操作属于结果区,录制启停等对象设置也不塞进筛选栏。
|
|
16
16
|
- 平级工作区已有侧栏身份时不重复大标题。组件的 `label` 提供可访问分组名称,宿主仍负责页面标题、主内容名称及路由焦点。
|
|
17
17
|
|
|
@@ -29,6 +29,8 @@
|
|
|
29
29
|
| `refresh` | `{onClick,busy?,disabled?}`;原生共享 Button,不自动执行请求 |
|
|
30
30
|
| `action` | 可选单个 ReactElement,代表一个业务操作;消费方不能用 Fragment 或自建按钮组绕过数量限制 |
|
|
31
31
|
| `result` | 宿主提供的结果摘要;数量未知时明确显示未知,不用已加载条数冒充总数 |
|
|
32
|
+
| `drawerDescription` | 可选 ReactNode;省略时保留默认即时更新说明,传 `null` 不渲染说明,也不留下空段落或失效的 aria-describedby |
|
|
33
|
+
| `drawerResult` | 可选 ReactNode;省略时沿用 `result`,传 `null` 仅移除抽屉重复摘要;pending/error 始终保留 |
|
|
32
34
|
| `pending` / `error` | 正在更新 / 失败说明;在栏与已展开 Drawer 中保持一致 |
|
|
33
35
|
| `className` | 只用于外层组合布局,不覆盖组件内部视觉或状态 |
|
|
34
36
|
|
|
@@ -59,6 +61,8 @@
|
|
|
59
61
|
|
|
60
62
|
示例中的状态、函数与额外字段由宿主提供。使用受控 `search.value` 才能准确决定搜索是否需要重置;组件不解析其他控件的值。
|
|
61
63
|
|
|
64
|
+
`drawerDescription` / `drawerResult` 自 0.10.0 提供,0.9.1 不支持。紧凑示例见 `preview/CompactFilterBarGallery.tsx`。无必要解释时,标题后直接展示筛选字段,不以另一段帮助文字替代被移除的重复说明。
|
|
65
|
+
|
|
62
66
|
## 状态与查询边界
|
|
63
67
|
|
|
64
68
|
更多筛选是同一组状态的展开视图,不建立另一套草稿/应用流程。普通选择即时回传宿主;关闭、点击外部与 Escape 只收起抽屉,不撤销已经接受的条件。业务本来要求完整范围显式应用的日期控件可以保留其局部草稿,但应在 Drawer 内内联展示,不再嵌套日期 Drawer。
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# 产品设计稿评审工具(自 0.11.0)
|
|
2
|
+
|
|
3
|
+
对应 [#40](https://github.com/linhay/rhine-design/issues/40)。`@linhey/rhine-design/review` 与 `/review/vite` 自 **0.11.0** 提供;npm 0.10.0 不包含它们。安装时固定 `@linhey/rhine-design@0.11.0` 并提交 lockfile,确认 registry 已提供该版本。历史同为 0.10.0 字段的本地候选快照不等同 npm 正式制品。
|
|
4
|
+
|
|
5
|
+
工具统一维护目录、搜索、评审控件、深链接、独立 iframe、模拟边界和静态构建。产品维护页面、路由、权限、状态维度、业务模拟、说明和验收记录。普通 React 设计稿、真实 App + HTTP mock、真实组件 + 平台 client 均可接入,不要求把平台接口转换成 HTTP。
|
|
6
|
+
|
|
7
|
+
## 运行两个合成例子
|
|
8
|
+
|
|
9
|
+
在源码仓使用 Node 24 安装依赖。两个 dev 命令分别在独立终端运行;构建/静态服务是另一种运行方式:
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
npm run review:http # http://127.0.0.1:18340/review-http/
|
|
13
|
+
npm run review:platform # http://127.0.0.1:18341/review-platform/
|
|
14
|
+
npm run review:build # dist/review-http 与 dist/review-platform
|
|
15
|
+
python3 -m http.server 18342 --bind 127.0.0.1 --directory dist
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
示例开发服务保留各自 `/review-http/`、`/review-platform/` 路径;构建产物使用相对资源路径,可放在任意嵌套目录(例如项目型 GitHub Pages)。构建后访问 `/review-http/`、`/review-platform/`。这是本地进程地址,不是已部署的持久在线服务。关闭进程后链接失效。也可以使用 `vite preview --config examples/review/http/vite.config.ts --host 127.0.0.1` 预览对应制品;以启动输出为准。
|
|
19
|
+
|
|
20
|
+
HTTP 示例有正常、加载、空、错误、密集长文及只读角色;支持搜索、弹窗保存与延迟模块。平台示例注入内存 client,覆盖独立连接/用量维度、慢初始化、渲染异常、清理错误/挂起恢复和 Canvas 动效。示例只展示工具契约,不代表桌面原生或 iOS 验收。
|
|
21
|
+
|
|
22
|
+
开发服务没有 HMR/WebSocket:Vite 6/8 的 CSS 导入由官方样式客户端处理,修改页面后手动刷新;修改配置或工具插件后重启服务。不要用放开业务代理来换取热更新。
|
|
23
|
+
|
|
24
|
+
## 最小消费配置
|
|
25
|
+
|
|
26
|
+
在消费项目安装固定的 0.11.0 正式制品,并把 `vite`、`@vitejs/plugin-react` 放在开发依赖中。只在独立预览配置中导入 Node/Vite 入口,正式 App 不导入它:
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
// design/vite.config.ts
|
|
30
|
+
import { defineConfig } from 'vite';
|
|
31
|
+
import react from '@vitejs/plugin-react';
|
|
32
|
+
import { reviewPlugin } from '@linhey/rhine-design/review/vite';
|
|
33
|
+
|
|
34
|
+
export default defineConfig({
|
|
35
|
+
root: import.meta.dirname,
|
|
36
|
+
base: '/product-review/',
|
|
37
|
+
plugins: [react(), reviewPlugin({ catalog: './catalog.tsx', routerBase: '/app/' })],
|
|
38
|
+
build: { outDir: '../dist-review', emptyOutDir: true },
|
|
39
|
+
});
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
运行 `vite --config design/vite.config.ts`、`vite build --config design/vite.config.ts`、`vite preview --config design/vite.config.ts`。插件生成 `index.html` 和 `canvas.html`,消费方不复制评审外壳、URL 状态代码或工具栏。只复制/编写自身目录与页面;两个合成例子分别在 `examples/review/http`、`examples/review/platform`,共同的示例样式在 `examples/review/product.css`。
|
|
43
|
+
|
|
44
|
+
`base` 是静态资源部署路径;可用相对 `./` 或固定子路径。`routerBase` 仅覆盖产品使用的 `import.meta.env.BASE_URL`,不会改写资源路径,也不替产品建立路由。产品可在 setup 中设置 frame 内 history 并触发自己的路由更新;外层评审 hash 与产品路由各自独立。静态服务器提供带尾斜杠的目录 URL,不必为每个场景配置服务端路由。
|
|
45
|
+
|
|
46
|
+
## 场景与生命周期
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
import { defineReview } from '@linhey/rhine-design/review';
|
|
50
|
+
|
|
51
|
+
export default defineReview({
|
|
52
|
+
title: '产品评审',
|
|
53
|
+
widths: [400, 520, 880, 1280],
|
|
54
|
+
scenes: [{
|
|
55
|
+
id: 'workspace', title: '工作区', group: '产品页面',
|
|
56
|
+
themeRoot: 'host', // 真实 App 已有 ThemeProvider 时必须声明
|
|
57
|
+
source: 'src/Workspace.tsx', // 产品仓相对路径
|
|
58
|
+
dimensions: [{ id: 'role', label: '角色', options: [
|
|
59
|
+
{ id: 'editor', label: '编辑者' }, { id: 'reader', label: '只读成员' },
|
|
60
|
+
] }],
|
|
61
|
+
async setup(context) {
|
|
62
|
+
const client = createSyntheticClient(context.signal); // 产品实现
|
|
63
|
+
context.onDispose(() => client.dispose());
|
|
64
|
+
// 必须在模拟适配与清理回调安装后,再求值真实 App 模块。
|
|
65
|
+
const { App } = await import('../src/App');
|
|
66
|
+
context.signal.throwIfAborted();
|
|
67
|
+
return {
|
|
68
|
+
render: environment => <App client={client} theme={environment.theme}
|
|
69
|
+
reducedMotion={environment.reducedMotion} paused={environment.paused} />,
|
|
70
|
+
dispose() {}, // 已登记的清理由 onDispose 承担,避免重复释放
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
}],
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
这是接入结构示例;`createSyntheticClient` 和 `App` 属性由产品定义,完整可运行代码见两个合成例子。
|
|
78
|
+
|
|
79
|
+
- 目录模块必须无业务副作用。App、真实平台模块、首次 API 请求放在 `setup` 安装适配之后的动态 import 内。静态 import App 会先于场景 mock 求值,属于错误接入。
|
|
80
|
+
- `setup(context)` 可异步。`context` 提供只读维度、`signal`、`reason`、`onDispose`、`mockHttp` 和 `report`。初始化前期获得的监听器、计时器、连接、动画和临时路由状态立即登记 `onDispose`,不能等 Promise 返回后才登记。
|
|
81
|
+
- `render(environment)` 返回普通 React 树;组件类型和 key 要保持稳定。主题、宽度、CSS zoom、动态偏好更新只重渲染同一实例,不重新 setup;用户自然将焦点移到外层控件时,工具不会抢回焦点。
|
|
82
|
+
- 场景或任一维度变化:先卸载 React 树、abort 旧请求,依次执行清理与 session.dispose,再 setup 新场景。快速切换只接纳最新选择;迟到 session 不挂载并执行 dispose。
|
|
83
|
+
- 显式重置在上述过程中额外调用旧 session.reset(若提供),随后重新 setup、重置内存存储与模拟状态。`reset` 是通知,不是任意业务交互的历史恢复。
|
|
84
|
+
- setup/渲染/异步操作失败在评审反馈区显示;不要用失败 mock 返回业务成功。清理失败保留原场景归属并继续其余清理。界面就绪与错误记录独立:新场景就绪、主题更新和重新载入都不会抹掉此前错误,需用户“确认并清除反馈”。显示最近 20 条反馈;更早错误仍计入待确认数量。
|
|
85
|
+
- 首次连接或场景初始化/清理超过 10 秒时显示恢复入口;超时只说明尚未确认完成,可继续等待。显式“重新载入画布”销毁旧 iframe 上下文,清空未保存草稿并保留 URL 评审条件;不会绕过挂起的清理,在旧上下文继续 setup。就绪确认绑定选择序号和 React 实际提交,旧画布消息及迟到确认不更新新状态。
|
|
86
|
+
- 产品清理应尽快完成且幂等;浏览器关闭/整页卸载无法保证异步清理完成,持久远端资源不属于本工具的生命周期保障。重新载入也不是远端资源清理保证。
|
|
87
|
+
- 产品未遵守 AbortSignal 的任意后台副作用无法被框架自动撤销;框架拒绝迟到挂载和 fetch 结果,不等于能拦截所有用户代码。目录不是第二套路由/权限系统,业务列表仍经产品模拟服务按原筛选契约查询。
|
|
88
|
+
|
|
89
|
+
## HTTP、主题与动效
|
|
90
|
+
|
|
91
|
+
`context.mockHttp(handler)` 在当前场景替换 fetch。handler 接收标准 `Request`,保留 method、body、调用方 signal,并合并场景取消,credentials 固定 omit;返回 `Response` 或 Promise。未匹配返回 undefined 会 reject 并显示失败,永不回退真实 fetch。产品负责 URL/method 白名单和合成增删改;不支持 XHR 的真实请求回退。旧 mock 若采用 `(input, options) => Response`,需薄适配传入 `request.url`、method、headers、signal,以及非 GET/HEAD 时的 `await request.text()`,或改为直接消费 Request;不能用类型断言把旧函数直接接上。
|
|
92
|
+
|
|
93
|
+
默认 `themeRoot: 'runtime'` 为普通组件提供单一 Rhine ThemeProvider。真实 App 已有 Provider 时使用 `themeRoot: 'host'`,通过 render environment 更新自己的受控 Provider,并传播减少动态属性。现有宿主若只支持内部持久化主题,需要先暴露受控主题或桥接入口;`themeRoot: host` 不自动提供该能力,示意中的 App 属性必须按真实产品接口适配。不要用写 localStorage、重建 App 或再嵌套 Provider 来冒充原位切换。主题和尺寸不进入 iframe key,Portal 与表单保留同一 DOM 实例。
|
|
94
|
+
|
|
95
|
+
默认动态选项只有 system/reduced。只有完整适配了场景中全部动画的产品才声明 `motionControls: ['full', 'paused']`,对应消费 `environment.reducedMotion`、`environment.paused`。Canvas/WebGL 必须暂停帧循环,清理时取消 rAF/监听器并释放 GPU 资源;只禁 CSS 动画不够。
|
|
96
|
+
|
|
97
|
+
平台例子证明自有 Canvas 适配,不证明 OrbitBackdrop/NeutronStarBackdrop 全局受控暂停。它们仍遵循各自契约并尊重系统减少动态;未完成适配的场景不要声明 full/paused。工具不改变这两个现有组件的公开 API。
|
|
98
|
+
|
|
99
|
+
## 分享、隔离与正式产物
|
|
100
|
+
|
|
101
|
+
主工具栏保留主题、宽度、场景维度与重置;CSS 缩放、动态偏好和手动重新载入收在“更多设置”。画布尺寸显示 iframe 可用区域(不含边框)的实际宽高,以及按 CSS zoom 折算的内容宽度;例如 400px 外宽有 2px 边框时,可用宽度为 398px,200% 下约 199 CSS px。
|
|
102
|
+
|
|
103
|
+
外层 URL hash 只允许稳定 scene ID、声明的维度值、三主题、宽度预设/fit、1/2 倍 CSS zoom、动态选项。刷新、前进/后退恢复这些评审条件;未知值稳定回退,未知字段和任意表单内容不进入 URL。切换后保留的草稿不等于可序列化业务会话。CSS zoom 不代表浏览器原生缩放。
|
|
104
|
+
|
|
105
|
+
预览配置禁止读取项目 dotenv/public 目录及业务代理;环境前缀限定为显式公开的 `RHINE_REVIEW_PUBLIC_`。不要继承带秘密 define 的生产配置。iframe 先安装内存 localStorage/sessionStorage、空 Cookie 视图和请求/平台防护,再加载目录;XHR、WebSocket、Worker、IndexedDB、CacheStorage、ServiceWorker、Beacon、业务剪贴板等均拒绝或报告未实现。需要这些能力的产品必须提供自己的内存适配。
|
|
106
|
+
|
|
107
|
+
CSP 使用 `connect-src 'none'`,脚本/样式/媒体仅本地资源及允许的 data/blob;真实网络请求没有兜底。表单使用 `allow-forms` 并由 React onSubmit preventDefault,CSP 仍禁止原生提交。普通链接仅允许当前文档 hash;SPA 路由需由产品 preventDefault 并操作 history。下载、外链、整页导航、window.open 被阻止并报告。外层“复制链接”是用户主动执行的评审工具操作,不属于业务剪贴板。
|
|
108
|
+
|
|
109
|
+
**这不是执行任意不可信代码的安全沙箱。** `allow-scripts + allow-same-origin` 用于保留正常 React/路由/浮层,浏览器会提示该组合不能约束恶意同源脚本。应在专用静态站点/源托管,只装合成数据,不接真实会话;产品主动绕过防护、直接 location 导航、服务端同源 Cookie 等不由 JS 替换 API 全面隔绝。不得将产品秘密或真实后端环境注入预览。
|
|
110
|
+
|
|
111
|
+
正式构建可启用独立排除检查:
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
import { reviewProductionGuard } from '@linhey/rhine-design/review/vite';
|
|
115
|
+
// 加入正式 App 的 Vite plugins;这些是相对正式 root 的精确文件/目录。
|
|
116
|
+
reviewProductionGuard({ forbidden: ['./design', './src/preview-fixtures'] });
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
默认只拒绝官方 review runtime;不会把所有名为 design/fixtures 的正常生产目录误判。消费方必须登记自己的预览目录。检查基于实际输出 chunk 的模块图,随包附带的 tests/examples 不会因主入口引用自动进入 App。生产与预览使用不同 outDir;静态托管只上传对应 dist-review,不上传源码目录、环境文件或本地日志。
|
|
120
|
+
|
|
121
|
+
## 静态托管与迁移
|
|
122
|
+
|
|
123
|
+
1. 保留现有可用预览,先固定并安装通过验证的官方候选/发布制品。
|
|
124
|
+
2. 将产品场景与多维状态映射到 catalog;先接 HTTP/平台模拟,再动态载入页面。保留 router base、Provider 所有权与已有视觉。
|
|
125
|
+
3. 通过主题切换保状态、重置/清理、深链接、键盘、状态矩阵、出站检查与正式产物排除后,删除本地评审外壳/工具栏/URL 同步实现。
|
|
126
|
+
4. 用独立 CI 构建,在静态服务器或 GitHub Pages 上传对应 outDir;设置与部署路径一致的 base。用实际部署 URL 重验资源、画布与 hash 刷新,才能称为“已上线”。私有设计稿由消费方管理访问控制;首版没有托管 SaaS 或权限中心。
|
|
127
|
+
|
|
128
|
+
兼容与实测范围见 [验收记录](REVIEW_VERIFICATION.md)。SSR、其他构建器、实时多人协作、自动截图差异、原生平台验证均不属于首版。最终关闭 #40 还需要固定版本发布及真实消费迁移证据,不能用本地工具通过代替。
|