@icen.ai/ui 0.2.1 → 0.3.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 (103) hide show
  1. package/README.md +29 -16
  2. package/dist/base.css +389 -48
  3. package/dist/behaviors/accordion.mjs +20 -0
  4. package/dist/behaviors/carousel.mjs +30 -1
  5. package/dist/behaviors/charts.mjs +168 -0
  6. package/dist/behaviors/copy.mjs +4 -3
  7. package/dist/behaviors/datatable.mjs +748 -0
  8. package/dist/behaviors/input.mjs +20 -1
  9. package/dist/behaviors/modal.mjs +14 -0
  10. package/dist/behaviors/nav.mjs +31 -4
  11. package/dist/behaviors/sidebar.mjs +12 -0
  12. package/dist/behaviors/slider.mjs +35 -3
  13. package/dist/behaviors/tabs.mjs +100 -9
  14. package/dist/behaviors/tag-input.mjs +11 -0
  15. package/dist/behaviors/theme.mjs +26 -0
  16. package/dist/behaviors/toast.mjs +83 -15
  17. package/dist/behaviors/tree.mjs +57 -1
  18. package/dist/behaviors/upload.mjs +2 -3
  19. package/dist/cli.mjs +74 -0
  20. package/dist/components/accordion.mjs +3 -0
  21. package/dist/components/alert.mjs +2 -0
  22. package/dist/components/avatar.mjs +2 -0
  23. package/dist/components/breadcrumb.css +91 -18
  24. package/dist/components/breadcrumb.mjs +2 -0
  25. package/dist/components/btn.css +174 -14
  26. package/dist/components/btn.mjs +2 -0
  27. package/dist/components/card.css +118 -18
  28. package/dist/components/card.mjs +2 -0
  29. package/dist/components/carousel.mjs +3 -0
  30. package/dist/components/charts.css +225 -33
  31. package/dist/components/charts.mjs +3 -0
  32. package/dist/components/content.css +256 -74
  33. package/dist/components/context-menu.mjs +3 -0
  34. package/dist/components/datatable.css +343 -0
  35. package/dist/components/datatable.mjs +4 -0
  36. package/dist/components/desc.mjs +2 -0
  37. package/dist/components/dropdown.mjs +3 -0
  38. package/dist/components/empty.css +50 -10
  39. package/dist/components/empty.mjs +2 -0
  40. package/dist/components/feedback.css +158 -47
  41. package/dist/components/form.css +139 -22
  42. package/dist/components/input.css +165 -45
  43. package/dist/components/input.mjs +3 -0
  44. package/dist/components/kbd.mjs +2 -0
  45. package/dist/components/layout.mjs +2 -0
  46. package/dist/components/list.mjs +2 -0
  47. package/dist/components/media-card.mjs +2 -0
  48. package/dist/components/media.css +187 -40
  49. package/dist/components/menu.css +159 -32
  50. package/dist/components/modal.css +140 -34
  51. package/dist/components/modal.mjs +3 -0
  52. package/dist/components/nav.css +134 -32
  53. package/dist/components/nav.mjs +3 -0
  54. package/dist/components/pagination.css +105 -19
  55. package/dist/components/pagination.mjs +2 -0
  56. package/dist/components/panel.css +61 -6
  57. package/dist/components/panel.mjs +2 -0
  58. package/dist/components/pill.css +74 -14
  59. package/dist/components/pill.mjs +2 -0
  60. package/dist/components/popover.css +109 -7
  61. package/dist/components/popover.mjs +3 -0
  62. package/dist/components/progress.mjs +2 -0
  63. package/dist/components/rating.mjs +2 -0
  64. package/dist/components/result.mjs +2 -0
  65. package/dist/components/segmented.css +199 -50
  66. package/dist/components/segmented.mjs +2 -0
  67. package/dist/components/select.css +140 -39
  68. package/dist/components/select.mjs +3 -0
  69. package/dist/components/sidebar.css +162 -34
  70. package/dist/components/sidebar.mjs +3 -0
  71. package/dist/components/skeleton.mjs +2 -0
  72. package/dist/components/slider.css +169 -31
  73. package/dist/components/slider.mjs +3 -0
  74. package/dist/components/spinner.mjs +2 -0
  75. package/dist/components/stat.css +103 -8
  76. package/dist/components/stat.mjs +2 -0
  77. package/dist/components/steps.css +105 -31
  78. package/dist/components/steps.mjs +2 -0
  79. package/dist/components/switch.css +171 -54
  80. package/dist/components/switch.mjs +2 -0
  81. package/dist/components/table.css +114 -12
  82. package/dist/components/table.mjs +2 -0
  83. package/dist/components/tabs.css +104 -17
  84. package/dist/components/tabs.mjs +3 -0
  85. package/dist/components/tag-input.css +119 -23
  86. package/dist/components/tag-input.mjs +3 -0
  87. package/dist/components/timeline.mjs +2 -0
  88. package/dist/components/toast.css +85 -12
  89. package/dist/components/toast.mjs +3 -0
  90. package/dist/components/tooltip.css +86 -26
  91. package/dist/components/tooltip.mjs +2 -0
  92. package/dist/components/tree.mjs +3 -0
  93. package/dist/components/upload.css +144 -17
  94. package/dist/components/upload.mjs +3 -0
  95. package/dist/index.mjs +623 -42
  96. package/dist/registry.json +197 -2
  97. package/dist/retro-effects.css +264 -0
  98. package/dist/tokens/colors.css +18 -90
  99. package/dist/tokens/retro-effects.css +264 -0
  100. package/dist/tokens/style-profiles.css +47 -47
  101. package/dist/tokens.css +65 -137
  102. package/dist/ui.css +4861 -1019
  103. package/package.json +9 -1
@@ -1,70 +1,136 @@
1
1
  /*
2
2
  * @icen.ai/ui — Component: feedback(反馈族)
3
+ *
3
4
  * 包含:alert(四态内联横幅)/ result(终态页)/ progress(线性进度)/ spinner(加载圈)/ skeleton(占位块)
4
- * 规格源自 opengal src/presentation/ui/feedback/toast.tsx 的 Alert 与 data/exhibit.tsx 的 Result、
5
- * data/display.tsx 的 Progress / Spinner / Skeleton。
6
- * base.css 未提供 spin / skeleton-pulse 关键帧,在本文件自带。
5
+ * 规格源自 opengal feedback/toast.tsx 与 data/exhibit.tsx、data/display.tsx。
7
6
  */
8
7
 
9
8
  /* ══════════════ alert · 内联横幅 ══════════════ */
10
9
  .alert {
11
- display: flex; align-items: flex-start; gap: 12px;
12
- padding: 12px;
13
- border-radius: var(--radius-lg);
10
+ display: flex;
11
+ align-items: flex-start;
12
+ gap: var(--density-space-sm);
13
+ padding: var(--density-space-sm);
14
+ border-radius: var(--radius-md);
14
15
  box-shadow: var(--token-shadow-inset);
15
16
  border: 1px solid transparent;
17
+ font-size: var(--density-font-size-sm);
16
18
  }
17
- .alert--info { background: var(--token-info-muted); border-color: color-mix(in srgb, var(--token-info) 15%, transparent); }
18
- .alert--success { background: var(--token-success-muted); border-color: color-mix(in srgb, var(--token-success) 15%, transparent); }
19
- .alert--warning { background: var(--token-warning-muted); border-color: color-mix(in srgb, var(--token-warning) 15%, transparent); }
20
- .alert--error { background: var(--token-error-muted); border-color: color-mix(in srgb, var(--token-error) 15%, transparent); }
19
+ .alert--info { background: var(--token-info-muted); border-color: color-mix(in srgb, var(--token-info) 18%, transparent); }
20
+ .alert--success { background: var(--token-success-muted); border-color: color-mix(in srgb, var(--token-success) 18%, transparent); }
21
+ .alert--warning { background: var(--token-warning-muted); border-color: color-mix(in srgb, var(--token-warning) 18%, transparent); }
22
+ .alert--error { background: var(--token-error-muted); border-color: color-mix(in srgb, var(--token-error) 18%, transparent); }
21
23
 
22
- .alert-icon { flex-shrink: 0; margin-top: 2px; display: flex; }
24
+ .alert-icon {
25
+ flex-shrink: 0;
26
+ margin-top: 1px;
27
+ display: flex;
28
+ }
23
29
  .alert-icon svg { width: 16px; height: 16px; }
24
- .alert--info .alert-icon, .alert--info .alert-title { color: var(--token-info); }
25
- .alert--success .alert-icon, .alert--success .alert-title { color: var(--token-success); }
26
- .alert--warning .alert-icon, .alert--warning .alert-title { color: var(--token-warning); }
27
- .alert--error .alert-icon, .alert--error .alert-title { color: var(--token-error); }
30
+ .alert--info .alert-icon, .alert--info .alert-title { color: var(--token-info); }
31
+ .alert--success .alert-icon, .alert--success .alert-title { color: var(--token-success); }
32
+ .alert--warning .alert-icon, .alert--warning .alert-title { color: var(--token-warning); }
33
+ .alert--error .alert-icon, .alert--error .alert-title { color: var(--token-error); }
28
34
 
29
35
  .alert-content { flex: 1; min-width: 0; }
30
- .alert-title { font-size: 13px; font-weight: 500; margin: 0; }
31
- .alert-body { font-size: 12px; color: var(--token-text-muted); line-height: 1.6; }
36
+ .alert-title {
37
+ font-size: var(--density-font-size-md);
38
+ font-weight: 500;
39
+ margin: 0;
40
+ line-height: 1.4;
41
+ }
42
+ .alert-body {
43
+ font-size: var(--density-font-size-sm);
44
+ color: var(--token-text-muted);
45
+ line-height: 1.5;
46
+ margin-top: 2px;
47
+ }
32
48
  .alert-close {
33
- margin-left: auto; flex-shrink: 0;
34
- display: flex; align-items: center; justify-content: center;
35
- padding: 2px; border: 0; border-radius: var(--radius-xs);
36
- background: none; color: var(--token-text-faint); cursor: pointer;
49
+ margin-left: auto;
50
+ flex-shrink: 0;
51
+ display: inline-flex;
52
+ align-items: center;
53
+ justify-content: center;
54
+ width: 22px;
55
+ height: 22px;
56
+ padding: 0;
57
+ border: 0;
58
+ border-radius: var(--radius-xs);
59
+ background: none;
60
+ color: var(--token-text-faint);
61
+ cursor: pointer;
62
+ transition:
63
+ background-color var(--icen-tx-fast),
64
+ color var(--icen-tx-fast);
65
+ }
66
+ .alert-close:hover {
67
+ color: var(--token-text);
68
+ background: color-mix(in srgb, var(--token-text) 6%, transparent);
69
+ }
70
+ .alert-close:focus-visible {
71
+ outline: none;
72
+ box-shadow: var(--icen-ring);
37
73
  }
38
- .alert-close:hover { color: var(--token-text); background: color-mix(in srgb, var(--token-text) 6%, transparent); }
39
74
  .alert-close svg { width: 14px; height: 14px; }
40
75
 
41
76
  /* ══════════════ result · 终态页 ══════════════ */
42
77
  .result {
43
- display: flex; flex-direction: column; align-items: center; justify-content: center;
78
+ display: flex;
79
+ flex-direction: column;
80
+ align-items: center;
81
+ justify-content: center;
44
82
  text-align: center;
45
- padding: 40px 24px;
83
+ padding: 40px var(--density-space-md);
46
84
  }
47
85
  .result-icon {
48
- width: 64px; height: 64px;
86
+ width: 64px;
87
+ height: 64px;
49
88
  border-radius: var(--radius-full);
50
- display: flex; align-items: center; justify-content: center;
89
+ display: flex;
90
+ align-items: center;
91
+ justify-content: center;
51
92
  box-shadow: var(--token-shadow-inset);
52
93
  border: 1px solid transparent;
53
- margin-bottom: 16px;
94
+ margin-bottom: var(--density-space-md);
54
95
  }
55
96
  .result-icon svg { width: 28px; height: 28px; }
56
- .result--success .result-icon { background: var(--token-success-muted); color: var(--token-success); border-color: color-mix(in srgb, var(--token-success) 10%, transparent); }
57
- .result--error .result-icon { background: var(--token-error-muted); color: var(--token-error); border-color: color-mix(in srgb, var(--token-error) 10%, transparent); }
58
- .result--warning .result-icon { background: var(--token-warning-muted); color: var(--token-warning); border-color: color-mix(in srgb, var(--token-warning) 10%, transparent); }
59
- .result--info .result-icon { background: var(--token-info-muted); color: var(--token-info); border-color: color-mix(in srgb, var(--token-info) 10%, transparent); }
60
- .result-title { font-size: 17px; font-weight: 600; color: var(--token-text); margin: 0; }
61
- .result-desc { font-size: 13px; color: var(--token-text-muted); margin: 4px 0 0; max-width: 360px; }
62
- .result-extra { display: flex; align-items: center; gap: 8px; margin-top: 20px; }
97
+ .result--success .result-icon { background: var(--token-success-muted); color: var(--token-success); border-color: color-mix(in srgb, var(--token-success) 12%, transparent); }
98
+ .result--error .result-icon { background: var(--token-error-muted); color: var(--token-error); border-color: color-mix(in srgb, var(--token-error) 12%, transparent); }
99
+ .result--warning .result-icon { background: var(--token-warning-muted); color: var(--token-warning); border-color: color-mix(in srgb, var(--token-warning) 12%, transparent); }
100
+ .result--info .result-icon { background: var(--token-info-muted); color: var(--token-info); border-color: color-mix(in srgb, var(--token-info) 12%, transparent); }
101
+
102
+ .result-title {
103
+ font-size: var(--density-font-size-lg);
104
+ font-weight: 600;
105
+ color: var(--token-text);
106
+ margin: 0;
107
+ font-family: var(--font-heading);
108
+ line-height: 1.3;
109
+ }
110
+ .result-desc {
111
+ font-size: var(--density-font-size-sm);
112
+ color: var(--token-text-muted);
113
+ margin: var(--density-space-xs) 0 0;
114
+ max-width: 360px;
115
+ line-height: 1.5;
116
+ }
117
+ .result-extra {
118
+ display: flex;
119
+ align-items: center;
120
+ gap: var(--density-space-sm);
121
+ margin-top: var(--density-space-md);
122
+ }
63
123
 
64
124
  /* ══════════════ progress · 线性进度 ══════════════ */
65
- .progress { display: flex; align-items: center; gap: 8px; }
125
+ .progress {
126
+ display: flex;
127
+ align-items: center;
128
+ gap: var(--density-space-sm);
129
+ }
66
130
  .progress-track {
67
- flex: 1; height: 6px; overflow: hidden;
131
+ flex: 1;
132
+ height: 6px;
133
+ overflow: hidden;
68
134
  border-radius: var(--radius-full);
69
135
  background: color-mix(in srgb, var(--token-line-soft) 40%, transparent);
70
136
  }
@@ -72,35 +138,80 @@
72
138
  height: 100%;
73
139
  border-radius: var(--radius-full);
74
140
  background: var(--token-accent);
75
- transition: width 500ms var(--ease-out);
141
+ transition: width var(--icen-tx-slow);
76
142
  }
143
+ .progress--success .progress-bar { background: var(--token-success); }
144
+ .progress--warning .progress-bar { background: var(--token-warning); }
145
+ .progress--error .progress-bar { background: var(--token-error); }
146
+
147
+ /* 不定进度(spinner 替代品):bar 来回扫动 */
148
+ .progress--indeterminate .progress-bar {
149
+ width: 35% !important;
150
+ animation: progress-sweep 1.4s var(--ease-in-out, var(--ease-out)) infinite;
151
+ }
152
+ @keyframes progress-sweep {
153
+ 0% { transform: translateX(-100%); }
154
+ 100% { transform: translateX(300%); }
155
+ }
156
+
77
157
  .progress--sm .progress-track { height: 4px; }
78
- .progress-label { font-size: 11px; color: var(--token-text-muted); font-variant-numeric: tabular-nums; }
158
+ .progress--lg .progress-track { height: 8px; }
159
+ .progress-label {
160
+ font-size: var(--density-font-size-xs);
161
+ color: var(--token-text-muted);
162
+ font-variant-numeric: tabular-nums;
163
+ flex-shrink: 0;
164
+ }
79
165
 
80
166
  /* ══════════════ spinner · 加载圈 ══════════════ */
81
167
  .spinner {
82
- width: 20px; height: 20px;
83
- border: 2px solid var(--token-accent);
84
- border-top-color: transparent;
168
+ display: inline-block;
169
+ width: 20px;
170
+ height: 20px;
171
+ border: 2px solid color-mix(in srgb, var(--token-accent) 30%, transparent);
172
+ border-top-color: var(--token-accent);
85
173
  border-radius: var(--radius-full);
86
- animation: icen-spin 1s linear infinite;
174
+ animation: icen-spin 0.8s linear infinite;
87
175
  }
88
- .spinner--sm { width: 16px; height: 16px; }
89
- .spinner--lg { width: 32px; height: 32px; }
176
+ .spinner--sm { width: 14px; height: 14px; border-width: 2px; }
177
+ .spinner--lg { width: 32px; height: 32px; border-width: 3px; }
178
+ .spinner--success { border-color: color-mix(in srgb, var(--token-success) 30%, transparent); border-top-color: var(--token-success); }
179
+ .spinner--error { border-color: color-mix(in srgb, var(--token-error) 30%, transparent); border-top-color: var(--token-error); }
180
+ .spinner--muted { border-color: color-mix(in srgb, var(--token-text-faint) 30%, transparent); border-top-color: var(--token-text-faint); }
90
181
 
91
- @keyframes icen-spin {
92
- to { transform: rotate(360deg); }
182
+ @keyframes icen-spin { to { transform: rotate(360deg); } }
183
+
184
+ /* 文字+spinner 组合 */
185
+ .spinner-wrap {
186
+ display: inline-flex;
187
+ align-items: center;
188
+ gap: var(--density-space-xs);
189
+ font-size: var(--density-font-size-sm);
190
+ color: var(--token-text-muted);
93
191
  }
94
192
 
95
193
  /* ══════════════ skeleton · 占位块 ══════════════ */
96
194
  .skeleton {
97
- background: var(--token-control-hover);
195
+ display: block;
196
+ background: color-mix(in srgb, var(--token-text) 8%, transparent);
98
197
  border-radius: var(--radius-sm);
99
198
  animation: icen-skeleton-pulse 2s ease-in-out infinite;
100
199
  }
101
200
  .skeleton--circle { border-radius: var(--radius-full); }
201
+ .skeleton--text {
202
+ height: 0.85em;
203
+ margin: 0.25em 0;
204
+ }
205
+ .skeleton--text:last-child { width: 60%; }
102
206
 
103
207
  @keyframes icen-skeleton-pulse {
104
208
  0%, 100% { opacity: 1; }
105
209
  50% { opacity: 0.5; }
106
210
  }
211
+
212
+ @media (prefers-reduced-motion: reduce) {
213
+ .progress-bar { transition: none; }
214
+ .progress--indeterminate .progress-bar { animation: none; width: 100% !important; }
215
+ .spinner { animation-duration: 2s; }
216
+ .skeleton { animation: none; opacity: 0.6; }
217
+ }
@@ -1,31 +1,148 @@
1
1
  /*
2
- * @icen.ai/ui — Component: form(表单字段/输入/错误与成功提示)
2
+ * @icen.ai/ui — Component: form(表单结构:字段组 / label / hint / 横幅提示)
3
+ *
4
+ * 与 input.css 的 wrapper 基元互补:
5
+ * - input.css 处理"单个 input 的视觉外壳"
6
+ * - form.css 处理"字段→label→hint 的纵向布局"和"页面级结果横幅"
7
+ *
8
+ * 契约:
9
+ * <form class="form">
10
+ * <fieldset class="form-field">
11
+ * <label class="form-label" for="x">姓名</label>
12
+ * <span class="control"><input id="x" class="form-input" /></span>
13
+ * <span class="form-hint">必填,3-20 个字符</span> <!-- 可选 -->
14
+ * <span class="form-error">姓名长度不足</span> <!-- 可选,aria-live=polite -->
15
+ * </fieldset>
16
+ * </form>
17
+ *
18
+ * <div class="banner-ok [.banner-block]">已保存</div>
19
+ * <div class="banner-err [.banner-block]">提交失败:网络错误</div>
20
+ *
21
+ * form-input 同时支持 :invalid 与 [data-invalid=true],behavior 二选一驱动。
3
22
  */
4
23
 
5
- .form-field { margin-bottom: 18px; }
6
- .form-label { display: block; font-size: 12px; color: var(--token-text-muted); margin-bottom: 6px; letter-spacing: 1px; }
7
- .form-input {
8
- width: 100%; padding: 11px 14px;
9
- background: var(--token-bg); color: var(--token-text);
10
- border: 1px solid var(--token-line-soft); border-radius: var(--radius-sm);
11
- font-family: var(--font-mono); font-size: 14px;
12
- outline: none; transition: border-color var(--duration-fast) var(--ease-out);
24
+ .form { display: flex; flex-direction: column; gap: var(--density-space-md); }
25
+
26
+ .form-field {
27
+ display: flex;
28
+ flex-direction: column;
29
+ gap: var(--density-space-xs);
30
+ margin: 0;
31
+ padding: 0;
32
+ border: 0;
33
+ }
34
+
35
+ .form-label {
36
+ font-size: var(--density-font-size-sm);
37
+ color: var(--token-text-muted);
38
+ letter-spacing: 0.02em;
39
+ font-weight: 500;
40
+ }
41
+ .form-label .req { color: var(--token-error); margin-left: 2px; }
42
+
43
+ /* 裸 input(非 .control wrapper 用法) */
44
+ .form-input,
45
+ .form-textarea {
46
+ width: 100%;
47
+ padding: 0 var(--density-space-md);
48
+ height: var(--density-input-height-md);
49
+ background: var(--token-control);
50
+ color: var(--token-text);
51
+ border: 1px solid var(--token-outline-soft);
52
+ border-radius: var(--radius-md);
53
+ font-family: var(--font-body);
54
+ font-size: var(--density-font-size-md);
55
+ outline: none;
56
+ transition:
57
+ border-color var(--icen-tx-fast),
58
+ box-shadow var(--icen-tx-fast),
59
+ background-color var(--icen-tx-fast);
60
+ }
61
+ .form-textarea {
62
+ height: auto;
63
+ min-height: calc(4 * 1.5em + 24px);
64
+ padding: var(--density-space-sm) var(--density-space-md);
65
+ line-height: 1.5;
66
+ resize: vertical;
67
+ }
68
+ .form-input::placeholder,
69
+ .form-textarea::placeholder {
70
+ color: color-mix(in srgb, var(--token-text-faint) 60%, transparent);
71
+ }
72
+ .form-input:hover,
73
+ .form-textarea:hover {
74
+ border-color: var(--icen-hover-border);
75
+ }
76
+ .form-input:focus-visible,
77
+ .form-textarea:focus-visible {
78
+ border-color: var(--icen-control-focus-border);
79
+ box-shadow: var(--icen-control-focus-shadow);
80
+ }
81
+ .form-input:disabled,
82
+ .form-textarea:disabled,
83
+ .form-input[aria-disabled='true'],
84
+ .form-textarea[aria-disabled='true'] {
85
+ opacity: var(--icen-disabled-opacity);
86
+ cursor: not-allowed;
87
+ background: var(--token-bg-soft);
13
88
  }
14
- .form-input:focus { border-color: var(--token-accent); }
15
- .form-input::placeholder { color: var(--token-text-faint); }
16
89
 
90
+ /* 校验态:原生 :invalid 与显式 [data-invalid] 同时支持 */
91
+ .form-input:where(:invalid:not(:placeholder-shown), [data-invalid='true']),
92
+ .form-textarea:where(:invalid:not(:placeholder-shown), [data-invalid='true']) {
93
+ border-color: var(--token-error);
94
+ }
95
+ .form-input:where(:invalid:not(:placeholder-shown), [data-invalid='true']):focus-visible,
96
+ .form-textarea:where(:invalid:not(:placeholder-shown), [data-invalid='true']):focus-visible {
97
+ border-color: var(--token-error);
98
+ box-shadow: var(--icen-control-error-shadow);
99
+ }
100
+
101
+ /* 尺寸变体 */
102
+ .form-input--sm, .form-textarea--sm { height: var(--density-input-height-sm); font-size: var(--density-font-size-sm); }
103
+ .form-input--lg, .form-textarea--lg { height: var(--density-input-height-lg); font-size: var(--density-font-size-lg); }
104
+
105
+ .form-hint {
106
+ font-size: var(--density-font-size-xs);
107
+ color: var(--token-text-faint);
108
+ line-height: 1.4;
109
+ }
17
110
  .form-error {
18
- background: color-mix(in srgb, var(--token-error) 7%, transparent); color: var(--token-error);
19
- border: 1px solid color-mix(in srgb, var(--token-error) 25%, transparent); border-radius: var(--radius-sm);
20
- padding: 10px 14px; font-size: 12.5px; margin-bottom: 16px;
21
- font-family: var(--font-heading);
111
+ font-size: var(--density-font-size-xs);
112
+ color: var(--token-error);
113
+ line-height: 1.4;
22
114
  }
23
- .form-ok {
24
- background: color-mix(in srgb, var(--token-success) 8%, transparent); color: var(--token-success);
25
- border: 1px solid color-mix(in srgb, var(--token-success) 25%, transparent); border-radius: var(--radius-sm);
26
- padding: 10px 14px; font-size: 12.5px; margin-bottom: 16px;
27
- font-family: var(--font-heading);
115
+ .form-field[data-invalid='true'] .form-label { color: var(--token-error); }
116
+
117
+ /* ── 页面级横幅:操作结果 / 引导提示 ── */
118
+ .banner-ok,
119
+ .banner-err {
120
+ display: flex;
121
+ align-items: flex-start;
122
+ gap: var(--density-space-sm);
123
+ padding: var(--density-space-sm) var(--density-space-md);
124
+ margin-bottom: var(--density-space-md);
125
+ border-radius: var(--radius-md);
126
+ font-size: var(--density-font-size-sm);
127
+ line-height: 1.5;
128
+ border: 1px solid transparent;
129
+ }
130
+ .banner-ok {
131
+ background: var(--token-success-muted);
132
+ color: var(--token-success);
133
+ border-color: color-mix(in srgb, var(--token-success) 25%, transparent);
134
+ }
135
+ .banner-err {
136
+ background: var(--token-error-muted);
137
+ color: var(--token-error);
138
+ border-color: color-mix(in srgb, var(--token-error) 25%, transparent);
139
+ }
140
+ /* block 模式:横向占满,带图标位 */
141
+ .banner-block {
142
+ align-items: center;
143
+ padding: var(--density-space-md);
28
144
  }
29
145
 
30
- /* 页面级横幅(与表单提示同族,用于操作结果/引导提示) */
31
- .banner-ok, .banner-err { margin-bottom: 20px; }
146
+ @media (prefers-reduced-motion: reduce) {
147
+ .banner-ok, .banner-err { transition: none; }
148
+ }