@icen.ai/ui 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/dist/behaviors/back-top.mjs +84 -0
  2. package/dist/behaviors/charts.mjs +188 -0
  3. package/dist/behaviors/command-palette.mjs +177 -0
  4. package/dist/behaviors/datatable.mjs +244 -24
  5. package/dist/behaviors/date-picker.mjs +228 -0
  6. package/dist/behaviors/input.mjs +170 -1
  7. package/dist/behaviors/notification.mjs +754 -0
  8. package/dist/behaviors/select.mjs +151 -22
  9. package/dist/behaviors/split-pane.mjs +102 -0
  10. package/dist/behaviors/upload.mjs +165 -14
  11. package/dist/components/back-top.css +88 -0
  12. package/dist/components/back-top.mjs +3 -0
  13. package/dist/components/badge.css +131 -0
  14. package/dist/components/badge.mjs +2 -0
  15. package/dist/components/breadcrumb.css +57 -0
  16. package/dist/components/card.css +68 -0
  17. package/dist/components/chart-area.css +15 -0
  18. package/dist/components/chart-area.mjs +4 -0
  19. package/dist/components/chart-bar.css +79 -0
  20. package/dist/components/chart-bar.mjs +4 -0
  21. package/dist/components/chart-gauge.css +38 -0
  22. package/dist/components/chart-gauge.mjs +4 -0
  23. package/dist/components/chart-heatmap.css +52 -0
  24. package/dist/components/chart-heatmap.mjs +4 -0
  25. package/dist/components/chart-line.css +15 -0
  26. package/dist/components/chart-line.mjs +4 -0
  27. package/dist/components/chart-pie.css +26 -0
  28. package/dist/components/chart-pie.mjs +4 -0
  29. package/dist/components/chart-radar.css +19 -0
  30. package/dist/components/chart-radar.mjs +4 -0
  31. package/dist/components/chart-sparkline.css +14 -0
  32. package/dist/components/chart-sparkline.mjs +4 -0
  33. package/dist/components/chart-stack.css +30 -0
  34. package/dist/components/chart-stack.mjs +4 -0
  35. package/dist/components/charts.css +7 -207
  36. package/dist/components/charts.mjs +9 -0
  37. package/dist/components/command-palette.css +246 -0
  38. package/dist/components/command-palette.mjs +3 -0
  39. package/dist/components/content.css +214 -0
  40. package/dist/components/copy.css +93 -0
  41. package/dist/components/copy.mjs +3 -0
  42. package/dist/components/datatable.css +86 -0
  43. package/dist/components/date-picker.css +244 -0
  44. package/dist/components/date-picker.mjs +3 -0
  45. package/dist/components/empty.css +73 -3
  46. package/dist/components/feedback.css +239 -1
  47. package/dist/components/form.css +119 -0
  48. package/dist/components/form.mjs +2 -0
  49. package/dist/components/input.css +24 -1
  50. package/dist/components/media.css +144 -1
  51. package/dist/components/nav.css +99 -0
  52. package/dist/components/notification.css +387 -0
  53. package/dist/components/notification.mjs +3 -0
  54. package/dist/components/panel.css +100 -5
  55. package/dist/components/pill.css +70 -0
  56. package/dist/components/scroll-area.css +112 -0
  57. package/dist/components/scroll-area.mjs +2 -0
  58. package/dist/components/segmented.css +75 -0
  59. package/dist/components/select.css +97 -2
  60. package/dist/components/sidebar.css +118 -0
  61. package/dist/components/slider.css +113 -0
  62. package/dist/components/split-pane.css +112 -0
  63. package/dist/components/split-pane.mjs +3 -0
  64. package/dist/components/stat.css +129 -6
  65. package/dist/components/steps.css +97 -0
  66. package/dist/components/switch.css +142 -0
  67. package/dist/components/table.css +86 -3
  68. package/dist/components/tabs.css +122 -1
  69. package/dist/components/tag.css +164 -0
  70. package/dist/components/tag.mjs +2 -0
  71. package/dist/components/toast.css +114 -0
  72. package/dist/components/toolbar.css +101 -0
  73. package/dist/components/toolbar.mjs +2 -0
  74. package/dist/components/upload.css +61 -4
  75. package/dist/index.mjs +2039 -62
  76. package/dist/registry.json +200 -24
  77. package/dist/ui.css +4865 -622
  78. package/package.json +1 -1
@@ -3,18 +3,31 @@
3
3
  *
4
4
  * 完整契约:
5
5
  * <div class="stat-grid [stat-grid--2|stat-grid--3|stat-grid--4]">
6
- * <div class="stat-card [.stat-card--interactive]">
6
+ * <div class="stat-card [.stat-card--interactive] [.stat-card--horizontal]">
7
7
  * <div class="stat-head">
8
8
  * <span class="stat-label">用户总数</span>
9
- * <span class="stat-icon">…svg…</span> ← 可选
9
+ * <span class="stat-icon [.icon--ok|.icon--warn|.icon--bad|.icon--info]>…svg…</span>
10
10
  * </div>
11
- * <div class="stat-num [.accent|.warn|.bad|.info]">0</div>
12
- * <div class="stat-delta [.up|.down|.flat]"> ← 可选趋势
13
- * <svg>…arrow…</svg> +12.4%
14
- * <span class="stat-period">比上周</span>
11
+ * <div class="stat-body">
12
+ * <div class="stat-num [.accent|.warn|.bad|.info]">0</div>
13
+ * <div class="stat-delta [.up|.down|.flat]"> ← 可选趋势
14
+ * <svg>…arrow…</svg> +12.4%
15
+ * <span class="stat-period">比上周</span>
16
+ * </div>
17
+ * </div>
18
+ * <div class="stat-spark">…svg mini chart…</div> ← 可选:迷你折线/柱状
19
+ * <div class="stat-progress"> ← 可选:进度环
20
+ * <svg class="stat-ring" viewBox="0 0 36 36">
21
+ * <circle class="stat-ring-track" cx="18" cy="18" r="15.5" />
22
+ * <circle class="stat-ring-fill" cx="18" cy="18" r="15.5" style="stroke-dasharray:75,100" />
23
+ * </svg>
15
24
  * </div>
16
25
  * </div>
17
26
  * </div>
27
+ *
28
+ * .stat-card--horizontal:横向布局(数字在右,标签在左),适合 dashboard 顶栏。
29
+ * .stat-spark:内嵌迷你折线/柱状图(消费方可引用 chart-sparkline.css 或自绘 SVG)。
30
+ * .stat-ring:进度环(SVG stroke-dasharray 控制百分比,0-100)。
18
31
  */
19
32
 
20
33
  .stat-grid {
@@ -72,8 +85,19 @@
72
85
  background: var(--token-accent-subtle);
73
86
  color: var(--token-accent);
74
87
  }
88
+ .stat-icon.icon--ok { background: color-mix(in srgb, var(--token-success) 14%, transparent); color: var(--token-success); }
89
+ .stat-icon.icon--warn { background: color-mix(in srgb, var(--token-warning) 14%, transparent); color: var(--token-warning); }
90
+ .stat-icon.icon--bad { background: color-mix(in srgb, var(--token-error) 14%, transparent); color: var(--token-error); }
91
+ .stat-icon.icon--info { background: color-mix(in srgb, var(--token-info) 14%, transparent); color: var(--token-info); }
75
92
  .stat-icon svg { width: 14px; height: 14px; }
76
93
 
94
+ /* ── 内容体(数字 + 趋势) ── */
95
+ .stat-body {
96
+ display: flex;
97
+ flex-direction: column;
98
+ gap: 2px;
99
+ }
100
+
77
101
  .stat-num {
78
102
  font-size: 24px;
79
103
  font-weight: 700;
@@ -111,3 +135,102 @@
111
135
  }
112
136
  .stat-card--sm .stat-num { font-size: var(--density-font-size-lg); }
113
137
  .stat-card--sm .stat-label { font-size: 10px; }
138
+
139
+ /* ── 横向布局(dashboard 顶栏:标签左 + 数字右) ── */
140
+ .stat-card--horizontal {
141
+ flex-direction: row;
142
+ align-items: center;
143
+ justify-content: space-between;
144
+ gap: var(--density-space-md);
145
+ }
146
+ .stat-card--horizontal .stat-head {
147
+ flex-direction: column;
148
+ align-items: flex-start;
149
+ gap: 2px;
150
+ flex: 1;
151
+ min-width: 0;
152
+ }
153
+ .stat-card--horizontal .stat-body {
154
+ text-align: right;
155
+ flex-shrink: 0;
156
+ }
157
+ .stat-card--horizontal .stat-delta {
158
+ justify-content: flex-end;
159
+ }
160
+
161
+ /* ── 迷你图槽(stat-spark)── */
162
+ .stat-spark {
163
+ margin-top: var(--density-space-xs);
164
+ height: 28px;
165
+ width: 100%;
166
+ }
167
+ .stat-spark svg {
168
+ width: 100%;
169
+ height: 100%;
170
+ display: block;
171
+ }
172
+ .stat-card--horizontal .stat-spark {
173
+ margin-top: 0;
174
+ width: 80px;
175
+ flex-shrink: 0;
176
+ }
177
+
178
+ /* ── 进度环 ── */
179
+ .stat-progress {
180
+ display: flex;
181
+ align-items: center;
182
+ justify-content: center;
183
+ margin-top: var(--density-space-xs);
184
+ }
185
+ .stat-card--horizontal .stat-progress {
186
+ margin-top: 0;
187
+ flex-shrink: 0;
188
+ }
189
+ .stat-ring {
190
+ width: 48px;
191
+ height: 48px;
192
+ transform: rotate(-90deg);
193
+ }
194
+ .stat-ring-track {
195
+ fill: none;
196
+ stroke: var(--token-fill-2);
197
+ stroke-width: 3;
198
+ }
199
+ .stat-ring-fill {
200
+ fill: none;
201
+ stroke: var(--token-accent);
202
+ stroke-width: 3;
203
+ stroke-linecap: round;
204
+ stroke-dasharray: 0, 100;
205
+ transition: stroke-dasharray var(--icen-tx-slow) var(--ease-out);
206
+ }
207
+ .stat-card .stat-ring-fill { stroke: var(--token-accent); }
208
+
209
+ /* ── 数字带前缀/后缀 ── */
210
+ .stat-num-prefix,
211
+ .stat-num-suffix {
212
+ font-size: 0.6em;
213
+ font-weight: 500;
214
+ color: var(--token-text-faint);
215
+ margin: 0 1px;
216
+ }
217
+
218
+ /* ── 加载态骨架 ── */
219
+ .stat-card.is-loading .stat-num,
220
+ .stat-card.is-loading .stat-delta {
221
+ color: transparent;
222
+ background: var(--token-fill-2);
223
+ border-radius: var(--radius-xs);
224
+ animation: stat-skeleton 1.5s ease-in-out infinite;
225
+ }
226
+ .stat-card.is-loading .stat-num { height: 28px; }
227
+ .stat-card.is-loading .stat-delta { height: 14px; width: 60%; }
228
+ @keyframes stat-skeleton {
229
+ 0%, 100% { opacity: 1; }
230
+ 50% { opacity: 0.5; }
231
+ }
232
+ @media (prefers-reduced-motion: reduce) {
233
+ .stat-card.is-loading .stat-num,
234
+ .stat-card.is-loading .stat-delta { animation: none; }
235
+ .stat-ring-fill { transition: none; }
236
+ }
@@ -133,3 +133,100 @@
133
133
  @media (prefers-reduced-motion: reduce) {
134
134
  .step-dot, .step-line { transition: none; }
135
135
  }
136
+
137
+ /* ── 纯点变体(无数字/无标签,紧凑进度指示) ── */
138
+ .steps--dots .step-dot {
139
+ width: 10px;
140
+ height: 10px;
141
+ padding: 0;
142
+ font-size: 0;
143
+ }
144
+ .steps--dots .step-line { top: 20px; }
145
+ .steps--dots .step-title,
146
+ .steps--dots .step-desc { display: none; }
147
+ .steps--dots .step { flex: none; }
148
+ .steps--dots { gap: var(--density-space-xs); }
149
+
150
+ /* ── 自定义图标槽(step-dot 内放 svg) ── */
151
+ .step-dot-icon {
152
+ width: 16px;
153
+ height: 16px;
154
+ }
155
+ .step--finish .step-dot-icon { color: var(--token-on-accent, #fff); }
156
+ .step--process .step-dot-icon { color: var(--token-accent); }
157
+ .step--wait .step-dot-icon { color: var(--token-text-faint); }
158
+ .step--error .step-dot-icon { color: var(--token-error); }
159
+
160
+ /* ── 可点击步骤 ── */
161
+ .step--clickable {
162
+ cursor: pointer;
163
+ transition: opacity var(--icen-tx-fast);
164
+ }
165
+ .step--clickable:hover .step-dot {
166
+ transform: scale(1.08);
167
+ box-shadow: var(--icen-ring-soft);
168
+ }
169
+ .step--clickable:hover .step-title {
170
+ color: var(--token-accent);
171
+ }
172
+
173
+ /* ── 进度条变体(步骤间的填充进度,非离散连线) ── */
174
+ .steps--progress .step-line {
175
+ height: 3px;
176
+ border-radius: var(--radius-full);
177
+ background: var(--token-fill-2);
178
+ overflow: hidden;
179
+ }
180
+ .steps--progress .step-line::after {
181
+ content: '';
182
+ position: absolute;
183
+ inset: 0;
184
+ background: var(--token-accent);
185
+ transform-origin: left;
186
+ transform: scaleX(0);
187
+ transition: transform var(--icen-tx-slow) var(--ease-out);
188
+ }
189
+ .steps--progress .step--finish .step-line::after { transform: scaleX(1); }
190
+
191
+ /* ── 垂直模式的进度条变体 ── */
192
+ .steps--vertical.steps--progress .step-line::after {
193
+ transform-origin: top;
194
+ transform: scaleY(0);
195
+ }
196
+ .steps--vertical.steps--progress .step--finish .step-line::after {
197
+ transform: scaleY(1);
198
+ }
199
+
200
+ /* ── 步骤描述富化(可选 icon + text) ── */
201
+ .step-extra {
202
+ display: flex;
203
+ align-items: center;
204
+ gap: 4px;
205
+ margin-top: 4px;
206
+ font-size: var(--density-font-size-xs);
207
+ color: var(--token-text-faint);
208
+ justify-content: center;
209
+ }
210
+ .step--error .step-extra { color: var(--token-error); }
211
+
212
+ /* ── 数字步骤的连接箭头(可选替代纯线条) ── */
213
+ .steps--connected .step-line {
214
+ height: 0;
215
+ background: transparent;
216
+ }
217
+ .steps--connected .step-line::before {
218
+ content: '';
219
+ position: absolute;
220
+ inset: 0;
221
+ border-top: 1px dashed var(--token-outline-soft);
222
+ }
223
+ .steps--connected .step--finish .step-line::before {
224
+ border-top-style: solid;
225
+ border-top-color: var(--token-accent);
226
+ }
227
+
228
+ @media (max-width: 640px) {
229
+ /* 小屏:横向 steps 变为可横向滚动 */
230
+ .steps { overflow-x: auto; flex-wrap: nowrap; }
231
+ .step { min-width: 80px; }
232
+ }
@@ -210,3 +210,145 @@
210
210
  .radio-item[aria-checked='true'] .radio-dot::after {
211
211
  transform: translate(-50%, -50%) scale(1);
212
212
  }
213
+
214
+ /* ── 开关色彩变体 ── */
215
+ .switch--success[aria-checked='true'] { background: var(--token-success); }
216
+ .switch--warning[aria-checked='true'] { background: var(--token-warning); }
217
+ .switch--danger[aria-checked='true'] { background: var(--token-error); }
218
+
219
+ /* ── 开关带文字(on/off 文字嵌入) ── */
220
+ .switch--text {
221
+ --switch-w: 52px;
222
+ }
223
+ .switch--text .switch-thumb {
224
+ display: flex;
225
+ align-items: center;
226
+ justify-content: center;
227
+ font-size: 9px;
228
+ font-weight: 700;
229
+ color: transparent;
230
+ }
231
+ .switch--text[aria-checked='true']::before {
232
+ content: 'ON';
233
+ position: absolute;
234
+ left: 6px;
235
+ top: 50%;
236
+ transform: translateY(-50%);
237
+ font-size: 9px;
238
+ font-weight: 700;
239
+ color: var(--token-on-accent, #fff);
240
+ }
241
+ .switch--text[aria-checked='false']::before {
242
+ content: 'OFF';
243
+ position: absolute;
244
+ right: 6px;
245
+ top: 50%;
246
+ transform: translateY(-50%);
247
+ font-size: 9px;
248
+ font-weight: 700;
249
+ color: var(--token-text-faint);
250
+ }
251
+
252
+ /* ── 带标签的开关行 ── */
253
+ .switch-row {
254
+ display: flex;
255
+ align-items: center;
256
+ justify-content: space-between;
257
+ gap: var(--density-space-sm);
258
+ padding: var(--density-space-sm) 0;
259
+ }
260
+ .switch-row-label {
261
+ display: flex;
262
+ flex-direction: column;
263
+ gap: 1px;
264
+ flex: 1;
265
+ min-width: 0;
266
+ }
267
+ .switch-row-title {
268
+ font-size: var(--density-font-size-md);
269
+ color: var(--token-text);
270
+ }
271
+ .switch-row-desc {
272
+ font-size: var(--density-font-size-xs);
273
+ color: var(--token-text-faint);
274
+ }
275
+
276
+ /* ── 勾选行(checkbox + label 组合) ── */
277
+ .checkbox-row {
278
+ display: flex;
279
+ align-items: flex-start;
280
+ gap: var(--density-space-sm);
281
+ cursor: pointer;
282
+ }
283
+ .checkbox-row-label {
284
+ display: flex;
285
+ flex-direction: column;
286
+ gap: 1px;
287
+ flex: 1;
288
+ min-width: 0;
289
+ }
290
+
291
+ /* ── 单选卡片(富化版 radio-item,带描述与图标) ── */
292
+ .radio-card {
293
+ display: flex;
294
+ flex-direction: column;
295
+ gap: var(--density-space-xs);
296
+ padding: var(--density-space-md);
297
+ border: 1px solid var(--token-outline-soft);
298
+ border-radius: var(--radius-md);
299
+ background: var(--token-surface);
300
+ cursor: pointer;
301
+ transition:
302
+ border-color var(--icen-tx-fast),
303
+ box-shadow var(--icen-tx-fast),
304
+ background-color var(--icen-tx-fast);
305
+ }
306
+ .radio-card:hover {
307
+ border-color: var(--token-hover-border, color-mix(in srgb, var(--token-accent) 30%, transparent));
308
+ }
309
+ .radio-card-head {
310
+ display: flex;
311
+ align-items: center;
312
+ justify-content: space-between;
313
+ gap: var(--density-space-sm);
314
+ }
315
+ .radio-card.is-selected,
316
+ .radio-card[aria-checked='true'] {
317
+ border-color: var(--token-accent);
318
+ background: var(--token-accent-subtle);
319
+ box-shadow: var(--icen-ring-soft);
320
+ }
321
+ .radio-card:focus-visible {
322
+ outline: none;
323
+ box-shadow: var(--icen-ring);
324
+ }
325
+ .radio-card-title {
326
+ font-size: var(--density-font-size-md);
327
+ font-weight: 500;
328
+ color: var(--token-text);
329
+ }
330
+ .radio-card-desc {
331
+ font-size: var(--density-font-size-sm);
332
+ color: var(--token-text-muted);
333
+ line-height: 1.5;
334
+ }
335
+
336
+ /* 单选卡片网格 */
337
+ .radio-card-grid {
338
+ display: grid;
339
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
340
+ gap: var(--density-space-sm);
341
+ }
342
+
343
+ /* ── 复选框组(多列网格) ── */
344
+ .checkbox-grid {
345
+ display: grid;
346
+ grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
347
+ gap: var(--density-space-sm);
348
+ }
349
+
350
+ @media (prefers-reduced-motion: reduce) {
351
+ .switch-thumb { transition: none; }
352
+ .radio-dot::after { transition: none; }
353
+ .radio-card { transition: none; }
354
+ }
@@ -3,22 +3,30 @@
3
3
  *
4
4
  * 完整契约:
5
5
  * <div class="table-wrap [table-wrap--bordered]">
6
- * <table class="admin-table [admin-table--compact|admin-table--dense]">
6
+ * <table class="admin-table [--zebra|--compact|--dense] [--sticky-col]">
7
+ * <caption class="table-caption">标题说明</caption> ← 可选
7
8
  * <thead>
8
- * <tr><th [colspan=rowspan]>列名</th>…</tr>
9
+ * <tr><th [colspan=rowspan] [data-sort]>列名</th>…</tr>
9
10
  * </thead>
10
11
  * <tbody>
11
- * <tr class="row-link [.is-selected]"> ← 整行可点
12
+ * <tr class="row-link [.is-selected] [.is-expanded]> ← 整行可点
13
+ * <td class="col-fixed">…</td> ← 固定列(sticky-col 模式)
12
14
  * <td>…</td>
13
15
  * <td class="ops"><button class="btn btn-sm">…</button></td>
14
16
  * </tr>
17
+ * <tr class="row-expand"> ← 展开行(配合 .is-expanded)
18
+ * <td colspan="99">…展开详情…</td>
19
+ * </tr>
15
20
  * </tbody>
16
21
  * <tfoot><tr><td>合计</td>…</tr></tfoot>
17
22
  * </table>
18
23
  * </div>
19
24
  *
20
25
  * .table-wrap:窄屏横向滚动(滚动条样式见 base.css);--bordered 加外边框。
26
+ * .admin-table--zebra:斑马纹(奇数行底色)。
21
27
  * .admin-table--compact:紧凑(cell padding 缩小);--dense:极紧凑(行高更紧)。
28
+ * .admin-table--sticky-col:首列水平粘滞(配合 td/th .col-fixed,长表横向滚动时保持可见)。
29
+ * .row-expand[hidden]:配合 .row-link.is-expanded 控制展开行显隐。
22
30
  */
23
31
 
24
32
  .table-wrap {
@@ -119,3 +127,78 @@
119
127
  }
120
128
  .admin-table th[data-sort='asc']::after { content: '↑'; opacity: 1; color: var(--token-accent); }
121
129
  .admin-table th[data-sort='desc']::after { content: '↓'; opacity: 1; color: var(--token-accent); }
130
+
131
+ /* ── 斑马纹 ── */
132
+ .admin-table--zebra tbody tr:nth-child(odd) {
133
+ background: color-mix(in srgb, var(--token-bg-soft) 50%, transparent);
134
+ }
135
+ .admin-table--zebra tbody tr:hover {
136
+ background: var(--token-surface-strong);
137
+ }
138
+
139
+ /* ── 表格标题(caption) ── */
140
+ .table-caption {
141
+ text-align: left;
142
+ padding: var(--density-space-sm) calc(var(--density-space-md) + 2px);
143
+ font-size: var(--density-font-size-md);
144
+ font-weight: 600;
145
+ color: var(--token-text);
146
+ caption-side: top;
147
+ }
148
+
149
+ /* ── 首列粘滞(长表横向滚动时保持可见) ── */
150
+ .admin-table--sticky-col th:first-child,
151
+ .admin-table--sticky-col .col-fixed {
152
+ position: sticky;
153
+ left: 0;
154
+ z-index: 2;
155
+ background: var(--token-bg-soft);
156
+ box-shadow: 2px 0 4px color-mix(in srgb, var(--token-shadow-card-base, rgba(0,0,0,0.06)) 40%, transparent);
157
+ }
158
+ .admin-table--sticky-col tbody td.col-fixed {
159
+ background: var(--token-surface);
160
+ }
161
+ .admin-table--sticky-col tbody tr:hover td.col-fixed {
162
+ background: var(--token-surface-strong);
163
+ }
164
+
165
+ /* ── 展开行 ── */
166
+ .admin-table .row-expand > td {
167
+ padding: 0 var(--density-space-md);
168
+ background: var(--token-bg-soft);
169
+ border-bottom: 1px solid var(--token-line-soft);
170
+ font-size: var(--density-font-size-sm);
171
+ color: var(--token-text-muted);
172
+ }
173
+ .admin-table .row-expand[hidden] { display: none; }
174
+ /* 展开行的内容容器(使用方可加 padding/动画) */
175
+ .row-expand-content {
176
+ padding: var(--density-space-sm) 0;
177
+ animation: row-expand-in var(--icen-tx-fast) var(--ease-out);
178
+ }
179
+ @keyframes row-expand-in {
180
+ from { opacity: 0; transform: translateY(-4px); }
181
+ to { opacity: 1; transform: translateY(0); }
182
+ }
183
+
184
+ /* ── 列宽工具类 ── */
185
+ .admin-table .col-narrow { width: 80px; }
186
+ .admin-table .col-wide { width: 240px; }
187
+ .admin-table .col-action { width: 1%; white-space: nowrap; }
188
+
189
+ /* ── 行状态色 ── */
190
+ .admin-table tbody tr.row-success { background: color-mix(in srgb, var(--token-success) 6%, transparent); }
191
+ .admin-table tbody tr.row-warning { background: color-mix(in srgb, var(--token-warning) 6%, transparent); }
192
+ .admin-table tbody tr.row-error { background: color-mix(in srgb, var(--token-error) 6%, transparent); }
193
+
194
+ /* ── 粘滞表头阴影提示 ── */
195
+ .admin-table thead th {
196
+ box-shadow: 0 1px 0 var(--token-line);
197
+ }
198
+
199
+ /* ── 表头分组(rowspan/colspan 样式) ── */
200
+ .admin-table th[rowspan] { vertical-align: bottom; }
201
+
202
+ @media (prefers-reduced-motion: reduce) {
203
+ .row-expand-content { animation: none; }
204
+ }
@@ -108,6 +108,127 @@
108
108
  text-align: center;
109
109
  }
110
110
 
111
+ /* ── 增强 ── */
112
+
113
+ /* 尺寸变体 */
114
+ .page-tabs--sm .page-tab {
115
+ padding: var(--density-space-xs) var(--density-space-sm);
116
+ font-size: var(--density-font-size-sm);
117
+ }
118
+ .page-tabs--lg .page-tab {
119
+ padding: var(--density-space-md) calc(var(--density-space-md) + 4px);
120
+ font-size: var(--density-font-size-lg);
121
+ }
122
+
123
+ /* Tab 带图标 */
124
+ .page-tab-icon {
125
+ width: 14px;
126
+ height: 14px;
127
+ flex-shrink: 0;
128
+ margin-right: 2px;
129
+ }
130
+
131
+ /* Tab 带徽章/计数 */
132
+ .page-tab-badge {
133
+ display: inline-flex;
134
+ align-items: center;
135
+ justify-content: center;
136
+ min-width: 18px;
137
+ height: 18px;
138
+ padding: 0 5px;
139
+ margin-left: var(--density-space-xs);
140
+ font-size: 10px;
141
+ font-weight: 600;
142
+ color: var(--token-text-faint);
143
+ background: var(--token-fill-2);
144
+ border-radius: var(--radius-full);
145
+ font-variant-numeric: tabular-nums;
146
+ line-height: 1;
147
+ }
148
+ .page-tab.active .page-tab-badge,
149
+ .page-tab[aria-selected='true'] .page-tab-badge {
150
+ background: var(--token-accent);
151
+ color: var(--token-on-accent, #fff);
152
+ }
153
+
154
+ /* 可关闭 tab */
155
+ .page-tab-close {
156
+ display: inline-flex;
157
+ align-items: center;
158
+ justify-content: center;
159
+ width: 16px;
160
+ height: 16px;
161
+ margin-left: var(--density-space-xs);
162
+ margin-right: -4px;
163
+ padding: 0;
164
+ border: 0;
165
+ border-radius: var(--radius-full);
166
+ background: transparent;
167
+ color: var(--token-text-faint);
168
+ cursor: pointer;
169
+ transition: color var(--icen-tx-fast), background-color var(--icen-tx-fast);
170
+ }
171
+ .page-tab-close:hover {
172
+ color: var(--token-error);
173
+ background: var(--token-error-muted);
174
+ }
175
+ .page-tab-close svg { width: 10px; height: 10px; }
176
+
177
+ /* 分段式 tab(替代底边线,用 segmented 视觉) */
178
+ .page-tabs--segment {
179
+ display: inline-flex;
180
+ border-bottom: none;
181
+ gap: 2px;
182
+ padding: 2px;
183
+ background: var(--token-panel);
184
+ border: 1px solid var(--token-outline-soft);
185
+ border-radius: var(--radius-md);
186
+ box-shadow: var(--token-shadow-inset);
187
+ }
188
+ .page-tabs--segment .page-tab {
189
+ border-bottom: none;
190
+ margin-bottom: 0;
191
+ padding: var(--density-space-xs) var(--density-space-md);
192
+ border-radius: var(--radius-sm);
193
+ }
194
+ .page-tabs--segment .page-tab.active,
195
+ .page-tabs--segment .page-tab[aria-selected='true'] {
196
+ background: var(--token-accent);
197
+ color: var(--token-on-accent, #fff);
198
+ border-bottom-color: transparent;
199
+ font-weight: 500;
200
+ }
201
+
202
+ /* 垂直 tab(侧栏式) */
203
+ .page-tabs--vertical {
204
+ flex-direction: column;
205
+ border-bottom: none;
206
+ border-right: 1px solid var(--token-line-soft);
207
+ margin-bottom: 0;
208
+ min-width: 160px;
209
+ }
210
+ .page-tabs--vertical .page-tab {
211
+ border-bottom: none;
212
+ border-right: 2px solid transparent;
213
+ margin-bottom: 0;
214
+ margin-right: -1px;
215
+ }
216
+ .page-tabs--vertical .page-tab.active,
217
+ .page-tabs--vertical .page-tab[aria-selected='true'] {
218
+ border-right-color: var(--token-accent);
219
+ border-bottom-color: transparent;
220
+ }
221
+
222
+ /* tab 溢出渐变提示(窄屏溢出时右侧渐变遮罩——纯视觉提示,不违反"不滚动"纪律,仍 flex-wrap) */
223
+ .page-tabs--scroll {
224
+ flex-wrap: nowrap;
225
+ overflow-x: auto;
226
+ overflow-y: hidden;
227
+ scrollbar-width: none;
228
+ -ms-overflow-style: none;
229
+ }
230
+ .page-tabs--scroll::-webkit-scrollbar { display: none; }
231
+
111
232
  @media (prefers-reduced-motion: reduce) {
112
- .page-tab, .admin-tabs a { transition: none; }
233
+ .page-tab, .admin-tabs a, .page-tab-close { transition: none; }
113
234
  }