@icen.ai/ui 0.2.1 → 0.4.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 (121) 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 +356 -0
  6. package/dist/behaviors/copy.mjs +4 -3
  7. package/dist/behaviors/datatable.mjs +968 -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/chart-area.css +15 -0
  31. package/dist/components/chart-area.mjs +4 -0
  32. package/dist/components/chart-bar.css +79 -0
  33. package/dist/components/chart-bar.mjs +4 -0
  34. package/dist/components/chart-gauge.css +38 -0
  35. package/dist/components/chart-gauge.mjs +4 -0
  36. package/dist/components/chart-heatmap.css +52 -0
  37. package/dist/components/chart-heatmap.mjs +4 -0
  38. package/dist/components/chart-line.css +15 -0
  39. package/dist/components/chart-line.mjs +4 -0
  40. package/dist/components/chart-pie.css +26 -0
  41. package/dist/components/chart-pie.mjs +4 -0
  42. package/dist/components/chart-radar.css +19 -0
  43. package/dist/components/chart-radar.mjs +4 -0
  44. package/dist/components/chart-sparkline.css +14 -0
  45. package/dist/components/chart-sparkline.mjs +4 -0
  46. package/dist/components/chart-stack.css +30 -0
  47. package/dist/components/chart-stack.mjs +4 -0
  48. package/dist/components/charts.css +66 -74
  49. package/dist/components/charts.mjs +12 -0
  50. package/dist/components/content.css +256 -74
  51. package/dist/components/context-menu.mjs +3 -0
  52. package/dist/components/datatable.css +429 -0
  53. package/dist/components/datatable.mjs +4 -0
  54. package/dist/components/desc.mjs +2 -0
  55. package/dist/components/dropdown.mjs +3 -0
  56. package/dist/components/empty.css +50 -10
  57. package/dist/components/empty.mjs +2 -0
  58. package/dist/components/feedback.css +158 -47
  59. package/dist/components/form.css +139 -22
  60. package/dist/components/input.css +165 -45
  61. package/dist/components/input.mjs +3 -0
  62. package/dist/components/kbd.mjs +2 -0
  63. package/dist/components/layout.mjs +2 -0
  64. package/dist/components/list.mjs +2 -0
  65. package/dist/components/media-card.mjs +2 -0
  66. package/dist/components/media.css +187 -40
  67. package/dist/components/menu.css +159 -32
  68. package/dist/components/modal.css +140 -34
  69. package/dist/components/modal.mjs +3 -0
  70. package/dist/components/nav.css +134 -32
  71. package/dist/components/nav.mjs +3 -0
  72. package/dist/components/pagination.css +105 -19
  73. package/dist/components/pagination.mjs +2 -0
  74. package/dist/components/panel.css +61 -6
  75. package/dist/components/panel.mjs +2 -0
  76. package/dist/components/pill.css +74 -14
  77. package/dist/components/pill.mjs +2 -0
  78. package/dist/components/popover.css +109 -7
  79. package/dist/components/popover.mjs +3 -0
  80. package/dist/components/progress.mjs +2 -0
  81. package/dist/components/rating.mjs +2 -0
  82. package/dist/components/result.mjs +2 -0
  83. package/dist/components/segmented.css +199 -50
  84. package/dist/components/segmented.mjs +2 -0
  85. package/dist/components/select.css +140 -39
  86. package/dist/components/select.mjs +3 -0
  87. package/dist/components/sidebar.css +162 -34
  88. package/dist/components/sidebar.mjs +3 -0
  89. package/dist/components/skeleton.mjs +2 -0
  90. package/dist/components/slider.css +169 -31
  91. package/dist/components/slider.mjs +3 -0
  92. package/dist/components/spinner.mjs +2 -0
  93. package/dist/components/stat.css +103 -8
  94. package/dist/components/stat.mjs +2 -0
  95. package/dist/components/steps.css +105 -31
  96. package/dist/components/steps.mjs +2 -0
  97. package/dist/components/switch.css +171 -54
  98. package/dist/components/switch.mjs +2 -0
  99. package/dist/components/table.css +114 -12
  100. package/dist/components/table.mjs +2 -0
  101. package/dist/components/tabs.css +104 -17
  102. package/dist/components/tabs.mjs +3 -0
  103. package/dist/components/tag-input.css +119 -23
  104. package/dist/components/tag-input.mjs +3 -0
  105. package/dist/components/timeline.mjs +2 -0
  106. package/dist/components/toast.css +85 -12
  107. package/dist/components/toast.mjs +3 -0
  108. package/dist/components/tooltip.css +86 -26
  109. package/dist/components/tooltip.mjs +2 -0
  110. package/dist/components/tree.mjs +3 -0
  111. package/dist/components/upload.css +144 -17
  112. package/dist/components/upload.mjs +3 -0
  113. package/dist/index.mjs +811 -42
  114. package/dist/registry.json +310 -2
  115. package/dist/retro-effects.css +264 -0
  116. package/dist/tokens/colors.css +18 -90
  117. package/dist/tokens/retro-effects.css +264 -0
  118. package/dist/tokens/style-profiles.css +47 -47
  119. package/dist/tokens.css +65 -137
  120. package/dist/ui.css +5069 -1026
  121. package/package.json +9 -1
@@ -1,112 +1,261 @@
1
1
  /*
2
2
  * @icen.ai/ui — Component: segmented(分段控件 / 切换组 / 数字步进器 / OTP)
3
+ *
3
4
  * 规格源自 opengal primitives/input.tsx 的 SegmentedControl 与 advanced-input.tsx
4
5
  * 的 ToggleButtonGroup / NumberStepper / OtpInput。
5
- * segmented 与 toggle-group 是纯 CSS 状态组件(.is-active);stepper 由使用方接线;
6
- * otp 单元格交互见 behaviors/input.ts 的 initInput。
6
+ *
7
+ * 完整契约:
8
+ * 1) 分段控件(排他单选):
9
+ * <div class="segmented [segmented--sm|segmented--lg]" role="radiogroup">
10
+ * <button class="segmented-item [.is-active]" role="radio" [aria-checked="true"]>…</button>
11
+ * </div>
12
+ *
13
+ * 2) 切换组(同族变体,更松散):
14
+ * <div class="toggle-group [toggle-group--sm]">
15
+ * <button class="toggle-item [.is-active]">…</button>
16
+ * </div>
17
+ *
18
+ * 3) 数字步进器:
19
+ * <div class="stepper">
20
+ * <button class="stepper-btn" data-step="-1">−</button>
21
+ * <input class="stepper-input" type="number" min="0" max="100" />
22
+ * <button class="stepper-btn" data-step="1">+</button>
23
+ * </div>
24
+ *
25
+ * 4) OTP 验证码单元格:
26
+ * <div class="otp otp--sm" role="group" aria-label="一次性验证码">
27
+ * <input class="otp-cell" maxlength="1" />
28
+ * …
29
+ * </div>
30
+ *
31
+ * segmented/toggle 是纯 CSS 状态组件(.is-active);stepper/otp 由 behaviors/input.ts 接线。
7
32
  */
8
33
 
9
34
  /* ── 分段控件(排他单选) ── */
10
35
  .segmented {
11
- display: inline-flex; align-items: center; gap: 2px; padding: 2px;
36
+ display: inline-flex;
37
+ align-items: center;
38
+ gap: 2px;
39
+ padding: 2px;
12
40
  background: var(--token-panel);
13
41
  border: 1px solid var(--token-outline-soft);
14
- border-radius: var(--radius-lg);
42
+ border-radius: var(--radius-md);
15
43
  box-shadow: var(--token-shadow-inset);
16
44
  }
17
45
 
18
46
  .segmented-item {
19
- display: flex; align-items: center; gap: 6px;
20
- padding: 6px 12px; font-size: 12px; font-weight: 500;
21
- border: none; background: transparent; cursor: pointer;
22
- border-radius: var(--radius-md);
47
+ display: flex;
48
+ align-items: center;
49
+ gap: var(--density-space-xs);
50
+ padding: var(--density-space-xs) calc(var(--density-space-sm) + 2px);
51
+ font-size: var(--density-font-size-sm);
52
+ font-weight: 500;
53
+ border: none;
54
+ background: transparent;
55
+ cursor: pointer;
56
+ border-radius: var(--radius-sm);
23
57
  color: var(--token-text-muted);
58
+ -webkit-tap-highlight-color: transparent;
59
+ transition:
60
+ background-color var(--icen-tx-fast),
61
+ color var(--icen-tx-fast),
62
+ box-shadow var(--icen-tx-fast);
24
63
  }
25
- .segmented-item:hover:not(.is-active) {
26
- color: var(--token-text); background: var(--token-surface-hover);
64
+ .segmented-item:hover:not(.is-active):not(:disabled) {
65
+ color: var(--token-text);
66
+ background: var(--token-surface-hover);
27
67
  }
28
- .segmented-item.is-active {
29
- background: var(--token-accent); color: var(--token-on-accent, #fff);
68
+ .segmented-item:focus-visible {
69
+ outline: none;
70
+ box-shadow: var(--icen-ring);
71
+ }
72
+ .segmented-item.is-active,
73
+ .segmented-item[aria-checked='true'] {
74
+ background: var(--token-accent);
75
+ color: var(--token-on-accent, #fff);
30
76
  box-shadow: var(--token-shadow-elevated);
31
77
  }
32
- .segmented-item:disabled { opacity: 0.4; cursor: not-allowed; }
78
+ .segmented-item:disabled,
79
+ .segmented-item[aria-disabled='true'] {
80
+ opacity: var(--icen-disabled-opacity);
81
+ cursor: not-allowed;
82
+ }
83
+ .segmented-item svg { width: 13px; height: 13px; flex-shrink: 0; }
33
84
 
34
- /* 尺寸:容器修饰符驱动 item 内边距 / 字号 */
35
- .segmented--sm .segmented-item { padding: 4px 10px; font-size: 11px; }
85
+ /* 尺寸 */
86
+ .segmented--sm .segmented-item {
87
+ padding: 2px var(--density-space-sm);
88
+ font-size: var(--density-font-size-xs);
89
+ }
90
+ .segmented--lg .segmented-item {
91
+ padding: var(--density-space-xs) var(--density-space-md);
92
+ font-size: var(--density-font-size-md);
93
+ }
36
94
 
37
- /* ── 切换组(segmented 同族变体:更松的 padding、item 更小圆角、active 阴影更轻) ── */
95
+ /* ── 切换组(segmented 同族变体) ── */
38
96
  .toggle-group {
39
- display: inline-flex; align-items: center; gap: 4px; padding: 4px;
97
+ display: inline-flex;
98
+ align-items: center;
99
+ gap: var(--density-space-xs);
100
+ padding: var(--density-space-xs);
40
101
  background: var(--token-panel);
41
102
  border: 1px solid var(--token-outline-soft);
42
- border-radius: var(--radius-lg);
103
+ border-radius: var(--radius-md);
43
104
  }
44
105
 
45
106
  .toggle-item {
46
- display: flex; align-items: center; gap: 6px;
47
- padding: 6px 12px; font-size: 12px; font-weight: 500;
48
- border: none; background: transparent; cursor: pointer;
107
+ display: flex;
108
+ align-items: center;
109
+ gap: var(--density-space-xs);
110
+ padding: var(--density-space-xs) var(--density-space-sm);
111
+ font-size: var(--density-font-size-sm);
112
+ font-weight: 500;
113
+ border: none;
114
+ background: transparent;
115
+ cursor: pointer;
49
116
  border-radius: var(--radius-xs);
50
117
  color: var(--token-text-muted);
118
+ transition:
119
+ background-color var(--icen-tx-fast),
120
+ color var(--icen-tx-fast);
51
121
  }
52
- .toggle-item:hover:not(.is-active) {
53
- color: var(--token-text); background: var(--token-control-hover);
122
+ .toggle-item:hover:not(.is-active):not(:disabled) {
123
+ color: var(--token-text);
124
+ background: var(--token-control-hover);
54
125
  }
55
- .toggle-item.is-active {
56
- background: var(--token-accent); color: var(--token-on-accent, #fff);
126
+ .toggle-item:focus-visible {
127
+ outline: none;
128
+ box-shadow: var(--icen-ring);
129
+ }
130
+ .toggle-item.is-active,
131
+ .toggle-item[aria-checked='true'] {
132
+ background: var(--token-accent);
133
+ color: var(--token-on-accent, #fff);
57
134
  box-shadow: var(--token-shadow-card);
58
135
  }
59
- .toggle-item:disabled { opacity: 0.4; cursor: not-allowed; }
60
- .toggle-group--sm .toggle-item { padding: 4px 10px; font-size: 11px; }
136
+ .toggle-item:disabled,
137
+ .toggle-item[aria-disabled='true'] {
138
+ opacity: var(--icen-disabled-opacity);
139
+ cursor: not-allowed;
140
+ }
141
+ .toggle-group--sm .toggle-item {
142
+ padding: 2px var(--density-space-sm);
143
+ font-size: var(--density-font-size-xs);
144
+ }
61
145
 
62
146
  /* ── 数字步进器 ── */
63
147
  .stepper {
64
- display: inline-flex; align-items: stretch;
148
+ display: inline-flex;
149
+ align-items: stretch;
65
150
  background: var(--token-control);
66
151
  border: 1px solid var(--token-outline-soft);
67
- border-radius: var(--radius-lg);
152
+ border-radius: var(--radius-md);
68
153
  overflow: hidden;
154
+ transition: border-color var(--icen-tx-fast), box-shadow var(--icen-tx-fast);
155
+ }
156
+ .stepper:hover { border-color: var(--icen-hover-border); }
157
+ .stepper:focus-within {
158
+ border-color: var(--icen-control-focus-border);
159
+ box-shadow: var(--icen-control-focus-shadow);
69
160
  }
70
161
 
71
162
  .stepper-btn {
72
- display: inline-flex; align-items: center; justify-content: center;
73
- padding: 0 10px; border: none; background: transparent; cursor: pointer;
163
+ display: inline-flex;
164
+ align-items: center;
165
+ justify-content: center;
166
+ padding: 0 var(--density-space-sm);
167
+ border: none;
168
+ background: transparent;
169
+ cursor: pointer;
74
170
  color: var(--token-text-muted);
75
- transition: background-color var(--duration-normal) var(--ease-out),
76
- color var(--duration-normal) var(--ease-out);
171
+ font-size: var(--density-font-size-md);
172
+ font-weight: 600;
173
+ -webkit-tap-highlight-color: transparent;
174
+ transition:
175
+ background-color var(--icen-tx-fast),
176
+ color var(--icen-tx-fast);
177
+ }
178
+ .stepper-btn:hover:not(:disabled) {
179
+ background: var(--token-control-hover);
180
+ color: var(--token-text);
181
+ }
182
+ .stepper-btn:focus-visible {
183
+ outline: none;
184
+ box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--token-accent) 50%, transparent);
185
+ }
186
+ .stepper-btn:disabled,
187
+ .stepper-btn[aria-disabled='true'] {
188
+ opacity: var(--icen-disabled-opacity);
189
+ cursor: not-allowed;
77
190
  }
78
- .stepper-btn:hover { background: var(--token-control-hover); color: var(--token-text); }
79
- .stepper-btn:disabled { opacity: 0.4; cursor: not-allowed; }
80
191
 
81
192
  .stepper-input {
82
- width: 56px; padding: 8px 0;
83
- text-align: center; font-variant-numeric: tabular-nums;
84
- background: transparent; outline: none; border: none;
193
+ width: 56px;
194
+ padding: var(--density-space-sm) 0;
195
+ text-align: center;
196
+ font-variant-numeric: tabular-nums;
197
+ background: transparent;
198
+ outline: none;
199
+ border: none;
85
200
  border-left: 1px solid var(--token-outline-soft);
86
201
  border-right: 1px solid var(--token-outline-soft);
87
- color: var(--token-text); font: inherit; font-size: 13px;
202
+ color: var(--token-text);
203
+ font: inherit;
204
+ font-size: var(--density-font-size-md);
88
205
  }
89
206
  /* 隐藏原生 number 箭头(± 交给 .stepper-btn) */
90
207
  .stepper-input::-webkit-outer-spin-button,
91
- .stepper-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
92
- .stepper-input[type='number'] { -moz-appearance: textfield; appearance: textfield; }
208
+ .stepper-input::-webkit-inner-spin-button {
209
+ -webkit-appearance: none;
210
+ margin: 0;
211
+ }
212
+ .stepper-input[type='number'] {
213
+ -moz-appearance: textfield;
214
+ appearance: textfield;
215
+ }
93
216
 
94
217
  /* ── OTP 验证码单元格 ── */
95
- .otp { display: inline-flex; align-items: center; gap: 8px; }
96
-
218
+ .otp {
219
+ display: inline-flex;
220
+ align-items: center;
221
+ gap: var(--density-space-sm);
222
+ }
97
223
  .otp-cell {
98
- width: 40px; height: 48px; padding: 0;
99
- text-align: center; font-size: 18px; font-weight: 600;
224
+ width: 40px;
225
+ height: 48px;
226
+ padding: 0;
227
+ text-align: center;
228
+ font-size: 18px;
229
+ font-weight: 600;
230
+ font-family: var(--font-mono);
100
231
  color: var(--token-text);
101
232
  background: var(--token-control);
102
233
  border: 1px solid var(--token-outline-soft);
103
- border-radius: var(--radius-sm);
234
+ border-radius: var(--radius-md);
104
235
  outline: none;
105
- transition: border-color var(--duration-normal) var(--ease-out),
106
- box-shadow var(--duration-normal) var(--ease-out);
236
+ transition:
237
+ border-color var(--icen-tx-fast),
238
+ box-shadow var(--icen-tx-fast);
239
+ }
240
+ .otp-cell:hover {
241
+ border-color: var(--icen-hover-border);
107
242
  }
108
243
  .otp-cell:focus-visible {
244
+ border-color: var(--icen-control-focus-border);
245
+ box-shadow: var(--icen-control-focus-shadow);
246
+ }
247
+ .otp-cell.is-filled {
109
248
  border-color: var(--token-accent);
110
- box-shadow: 0 0 0 2px var(--token-accent-subtle);
249
+ background: var(--token-accent-subtle);
250
+ }
251
+ .otp-cell:disabled,
252
+ .otp-cell[aria-disabled='true'] {
253
+ opacity: var(--icen-disabled-opacity);
254
+ cursor: not-allowed;
255
+ }
256
+ .otp--sm .otp-cell { width: 32px; height: 40px; font-size: var(--density-font-size-lg); }
257
+ .otp--lg .otp-cell { width: 48px; height: 56px; font-size: 22px; }
258
+
259
+ @media (prefers-reduced-motion: reduce) {
260
+ .segmented-item, .toggle-item, .stepper-btn, .stepper, .otp-cell { transition: none; }
111
261
  }
112
- .otp-cell:disabled { opacity: 0.4; cursor: not-allowed; }
@@ -0,0 +1,2 @@
1
+ // @icen.ai/ui — kit: segmented(CSS + behavior 一行入口;构建产物,勿手改)
2
+ import './segmented.css';
@@ -2,78 +2,179 @@
2
2
  * @icen.ai/ui — Component: select(弹层选择器)
3
3
  * 规格源自 opengal primitives/input.tsx 的 Select;与 behaviors/select.ts 配套。
4
4
  *
5
- * 契约:
6
- * <div class="select" data-select>
7
- * <button class="select-trigger pressable focus-ring" aria-haspopup="listbox" aria-expanded="false">
8
- * <span class="select-leading">…</span> 可选前缀
5
+ * 完整契约:
6
+ * <div class="select [data-disabled] [.is-open]" data-select>
7
+ * <button class="select-trigger pressable focus-ring [--sm|--lg]" aria-haspopup="listbox"
8
+ * aria-expanded="false" [disabled]>
9
+ * <span class="select-leading">…</span> 可选前缀
9
10
  * <span class="select-value is-empty">占位</span>
10
11
  * <svg class="select-chevron">…</svg>
11
12
  * </button>
12
- * <div class="select-panel" hidden>
13
- * <button class="select-option" data-value="x">…</button>
13
+ * <div class="select-panel" hidden role="listbox">
14
+ * <button class="select-option [.is-focused|.is-selected|.is-disabled]" data-value="x">…</button>
14
15
  * </div>
15
16
  * <input type="hidden" data-select-value /> 可选,behavior 同步 value
16
17
  * </div>
17
- * 状态:容器 .is-open;选项 .is-focused / .is-selected(尾部 ✓)/ .is-disabled;
18
- * .select-value.is-empty = 未选占位(faint 色,behavior 选中后移除)。
18
+ *
19
+ * behavior 维护:
20
+ * - .select.is-open ↔ aria-expanded;panel [hidden] 切换;ESC/外部点击关闭
21
+ * - 选项 .is-focused 跟随键盘箭头;Enter 选中 → 写回 .select-value / .is-selected
19
22
  */
20
23
 
21
- .select { position: relative; width: 100%; }
24
+ .select {
25
+ position: relative;
26
+ width: 100%;
27
+ isolation: isolate; /* panel 不被后面兄弟节点的层叠遮挡 */
28
+ }
22
29
 
23
30
  .select-trigger {
24
- display: flex; align-items: center; gap: 8px; width: 100%;
25
- height: 36px; padding: 0 12px; font-size: 13px; text-align: left;
26
- background: var(--token-control); color: var(--token-text);
31
+ display: flex;
32
+ align-items: center;
33
+ gap: var(--density-space-xs);
34
+ width: 100%;
35
+ height: var(--density-input-height-md);
36
+ padding: 0 var(--density-space-sm);
37
+ font-size: var(--density-font-size-md);
38
+ text-align: left;
39
+ background: var(--token-control);
40
+ color: var(--token-text);
27
41
  border: 1px solid var(--token-outline-soft);
28
- border-radius: var(--radius-lg);
42
+ border-radius: var(--radius-md);
29
43
  box-shadow: var(--token-shadow-inset);
30
44
  cursor: pointer;
45
+ transition:
46
+ border-color var(--icen-tx-fast),
47
+ box-shadow var(--icen-tx-fast);
48
+ }
49
+ .select-trigger:hover:not(:disabled) {
50
+ border-color: var(--icen-hover-border);
51
+ }
52
+ .select-trigger:focus-visible {
53
+ outline: none;
54
+ border-color: var(--icen-control-focus-border);
55
+ box-shadow: var(--icen-control-focus-shadow);
56
+ }
57
+ .select-trigger:disabled,
58
+ .select[data-disabled] .select-trigger,
59
+ .select[aria-disabled='true'] .select-trigger {
60
+ opacity: var(--icen-disabled-opacity);
61
+ cursor: not-allowed;
62
+ pointer-events: none;
31
63
  }
32
- .select-trigger:focus-visible { border-color: var(--token-accent); }
33
- .select-trigger:disabled { opacity: 0.4; cursor: not-allowed; }
34
64
 
35
- .select-trigger--sm { height: 32px; padding: 0 10px; font-size: 12px; }
36
- .select-trigger--lg { height: 40px; padding: 0 14px; font-size: 14px; }
65
+ /* 尺寸变体(与 input-wrap 对齐) */
66
+ .select-trigger--sm { height: var(--density-input-height-sm); padding: 0 10px; font-size: var(--density-font-size-sm); }
67
+ .select-trigger--lg { height: var(--density-input-height-lg); padding: 0 var(--density-space-md); font-size: var(--density-font-size-lg); }
37
68
 
38
69
  .select-leading {
39
- display: inline-flex; align-items: center; flex-shrink: 0;
70
+ display: inline-flex;
71
+ align-items: center;
72
+ flex-shrink: 0;
40
73
  color: var(--token-text-faint);
41
74
  }
42
- .select-value { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
43
- .select-value.is-empty { color: var(--token-text-faint); }
75
+ .select-leading svg { width: 14px; height: 14px; }
76
+
77
+ .select-value {
78
+ flex: 1;
79
+ min-width: 0;
80
+ overflow: hidden;
81
+ text-overflow: ellipsis;
82
+ white-space: nowrap;
83
+ }
84
+ .select-value.is-empty {
85
+ color: color-mix(in srgb, var(--token-text-faint) 60%, transparent);
86
+ }
44
87
 
45
88
  .select-chevron {
46
- width: 14px; height: 14px; flex-shrink: 0;
89
+ width: 14px;
90
+ height: 14px;
91
+ flex-shrink: 0;
47
92
  color: var(--token-text-faint);
48
- transition: transform var(--duration-fast) var(--ease-out);
93
+ transition: transform var(--icen-tx-fast);
49
94
  }
50
95
  .select.is-open .select-chevron { transform: rotate(180deg); }
51
96
 
52
97
  .select-panel {
53
- position: absolute; top: calc(100% + 4px); left: 0;
54
- min-width: 100%; max-height: 240px; overflow-y: auto; padding: 4px;
98
+ position: absolute;
99
+ top: calc(100% + 4px);
100
+ left: 0;
101
+ min-width: 100%;
102
+ max-height: 240px;
103
+ overflow-y: auto;
104
+ overscroll-behavior: contain;
105
+ padding: 4px;
55
106
  background: var(--token-card-strong);
56
107
  border: 1px solid var(--token-outline);
57
- border-radius: var(--radius-lg);
108
+ border-radius: var(--radius-md);
58
109
  box-shadow: var(--token-shadow-overlay);
59
110
  z-index: var(--z-popover);
60
111
  transform-origin: top center;
61
- animation: select-panel-in var(--duration-fast) var(--ease-out);
112
+ animation: select-panel-in var(--icen-tx-fast);
62
113
  }
63
114
  @keyframes select-panel-in {
64
- from { opacity: 0; transform: scale(0.95); }
65
- to { opacity: 1; transform: scale(1); }
115
+ from { opacity: 0; transform: scale(0.96) translateY(-4px); }
116
+ to { opacity: 1; transform: scale(1) translateY(0); }
117
+ }
118
+ @media (prefers-reduced-motion: reduce) {
119
+ .select-panel { animation: none; }
66
120
  }
67
121
 
122
+ /* 选项 */
68
123
  .select-option {
69
- display: flex; align-items: center; gap: 8px; width: 100%;
70
- padding: 6px 10px; font-size: 12px; text-align: left;
71
- border: none; background: transparent; color: var(--token-text);
72
- border-radius: var(--radius-sm); cursor: pointer;
73
- transition: background-color var(--duration-fast) var(--ease-out),
74
- color var(--duration-fast) var(--ease-out);
75
- }
76
- .select-option.is-focused { background: var(--token-fill-2); }
77
- .select-option.is-selected { color: var(--token-accent); }
78
- .select-option.is-selected::after { content: '✓'; margin-left: auto; flex-shrink: 0; }
79
- .select-option.is-disabled { opacity: 0.4; cursor: not-allowed; }
124
+ display: flex;
125
+ align-items: center;
126
+ gap: var(--density-space-xs);
127
+ width: 100%;
128
+ padding: 6px var(--density-space-sm);
129
+ font-size: var(--density-font-size-sm);
130
+ text-align: left;
131
+ border: none;
132
+ background: transparent;
133
+ color: var(--token-text);
134
+ border-radius: var(--radius-sm);
135
+ cursor: pointer;
136
+ transition:
137
+ background-color var(--icen-tx-fast),
138
+ color var(--icen-tx-fast);
139
+ }
140
+ .select-option:hover:not(.is-disabled) { background: var(--token-fill-1); }
141
+ .select-option.is-focused {
142
+ background: var(--token-fill-2);
143
+ color: var(--token-text);
144
+ }
145
+ .select-option.is-selected {
146
+ color: var(--token-accent);
147
+ font-weight: 500;
148
+ background: var(--token-accent-subtle);
149
+ }
150
+ .select-option.is-selected::after {
151
+ content: '✓';
152
+ margin-left: auto;
153
+ flex-shrink: 0;
154
+ color: var(--token-accent);
155
+ }
156
+ .select-option.is-disabled {
157
+ opacity: 0.4;
158
+ cursor: not-allowed;
159
+ }
160
+
161
+ /* 分组(行为可选渲染 optgroup) */
162
+ .select-group {
163
+ padding: 0 4px;
164
+ margin-top: 4px;
165
+ }
166
+ .select-group-label {
167
+ padding: 6px var(--density-space-sm) 2px;
168
+ font-size: var(--density-font-size-xs);
169
+ color: var(--token-text-faint);
170
+ text-transform: uppercase;
171
+ letter-spacing: 0.05em;
172
+ }
173
+
174
+ /* 空状态(behavior 渲染 no-match) */
175
+ .select-empty {
176
+ padding: var(--density-space-md);
177
+ font-size: var(--density-font-size-sm);
178
+ color: var(--token-text-faint);
179
+ text-align: center;
180
+ }
@@ -0,0 +1,3 @@
1
+ // @icen.ai/ui — kit: select(CSS + behavior 一行入口;构建产物,勿手改)
2
+ import './select.css';
3
+ export * from '../behaviors/select.mjs';