@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,54 +1,182 @@
1
1
  /*
2
2
  * @icen.ai/ui — Component: sidebar(侧栏导航,分组折叠配套 behaviors/sidebar)
3
- * 用法:<aside class="sidebar sidebar--sticky">
4
- * <div class="sidebar-group is-open">
5
- * <button class="sidebar-group-title" aria-expanded="true">分组</button>
6
- * <div class="sidebar-group-panel"><div class="sidebar-group-panel-inner">
7
- * <a class="sidebar-item is-active" href="…">条目</a>
8
- * </div></div>
3
+ *
4
+ * 完整契约:
5
+ * <aside class="sidebar [sidebar--sticky] [sidebar--sm|sidebar--lg]">
6
+ * <div class="sidebar-section"> ← 可选区块标题
7
+ * <span class="sidebar-section-label">主菜单</span>
8
+ * </div>
9
+ * <div class="sidebar-group is-open">
10
+ * <button class="sidebar-group-title" aria-expanded="true">
11
+ * <span class="sidebar-group-title-text">分组</span>
12
+ * </button>
13
+ * <div class="sidebar-group-panel">
14
+ * <div class="sidebar-group-panel-inner">
15
+ * <a class="sidebar-item [.is-active]" href="…" [aria-current="page"]>
16
+ * <svg class="sidebar-item-icon">…</svg>
17
+ * <span class="sidebar-item-label">条目</span>
18
+ * <span class="sidebar-item-badge">3</span> ← 可选徽章
19
+ * </a>
9
20
  * </div>
10
- * </aside>
11
- * 契约:.is-open 由 behaviors/sidebar 切换(同步 aria-expanded);
12
- * 展开动画 = grid-template-rows 0fr↔1fr(同 accordion 约定),内层 overflow:hidden。
13
- * 滚动条样式归 base.css 全局纪律,这里只声明 overflow-y。
21
+ * </div>
22
+ * </div>
23
+ * </aside>
24
+ *
25
+ * behavior:.is-open 由 sidebar.ts 切换(同步 aria-expanded);
26
+ * 展开动画 = grid-template-rows 0fr↔1fr(同 accordion 约定)。
14
27
  */
15
28
 
16
- .sidebar { width: 260px; flex-shrink: 0; overflow-y: auto; padding-right: 4px; }
17
- .sidebar--sticky { position: sticky; top: 80px; max-height: calc(100vh - 100px); }
29
+ .sidebar {
30
+ width: 260px;
31
+ flex-shrink: 0;
32
+ overflow-y: auto;
33
+ overscroll-behavior: contain;
34
+ padding-right: var(--density-space-xs);
35
+ }
36
+ .sidebar--sticky {
37
+ position: sticky;
38
+ top: 80px;
39
+ max-height: calc(100vh - 100px);
40
+ }
41
+ .sidebar--sm { width: 220px; }
42
+ .sidebar--lg { width: 320px; }
43
+
44
+ .sidebar-section {
45
+ padding: var(--density-space-md) var(--density-space-sm) var(--density-space-xs);
46
+ margin-top: var(--density-space-sm);
47
+ }
48
+ .sidebar-section-label {
49
+ font-size: var(--density-font-size-xs);
50
+ font-weight: 600;
51
+ color: var(--token-text-faint);
52
+ text-transform: uppercase;
53
+ letter-spacing: 0.08em;
54
+ }
18
55
 
19
- .sidebar-group { margin-bottom: 8px; }
56
+ .sidebar-group { margin-bottom: var(--density-space-xs); }
20
57
  .sidebar-group-title {
21
- width: 100%; display: flex; align-items: center; justify-content: space-between;
22
- padding: 8px 12px; border-radius: var(--radius-md);
23
- font-family: inherit; font-size: 11px; font-weight: 600;
24
- text-transform: uppercase; letter-spacing: 1px;
58
+ width: 100%;
59
+ display: flex;
60
+ align-items: center;
61
+ justify-content: space-between;
62
+ gap: var(--density-space-xs);
63
+ padding: var(--density-space-sm) var(--density-space-sm);
64
+ border-radius: var(--radius-sm);
65
+ font-family: inherit;
66
+ font-size: var(--density-font-size-xs);
67
+ font-weight: 600;
68
+ text-transform: uppercase;
69
+ letter-spacing: 0.05em;
25
70
  color: var(--token-text-muted);
26
- background: none; border: 0; cursor: pointer;
71
+ background: none;
72
+ border: 0;
73
+ cursor: pointer;
74
+ transition:
75
+ background-color var(--icen-tx-fast),
76
+ color var(--icen-tx-fast);
77
+ }
78
+ .sidebar-group-title:hover {
79
+ background: var(--token-control-hover);
80
+ color: var(--token-text);
81
+ }
82
+ .sidebar-group-title:focus-visible {
83
+ outline: none;
84
+ box-shadow: var(--icen-ring);
85
+ }
86
+ .sidebar-group-title-text {
87
+ flex: 1;
88
+ text-align: left;
27
89
  }
28
- .sidebar-group-title:hover { background: var(--token-control-hover); color: var(--token-text); }
29
90
  /* chevron:闭合态 rotate(-90deg),展开回正 */
30
91
  .sidebar-group-title::after {
31
- content: '▾'; font-size: 12px; line-height: 1; color: var(--token-text-muted);
92
+ content: '▾';
93
+ font-size: var(--density-font-size-sm);
94
+ line-height: 1;
95
+ color: var(--token-text-muted);
32
96
  transform: rotate(-90deg);
33
- transition: transform var(--duration-normal) var(--ease-out);
97
+ transition: transform var(--icen-tx-normal);
34
98
  }
35
99
  .sidebar-group.is-open .sidebar-group-title::after { transform: none; }
36
100
 
37
101
  .sidebar-group-panel {
38
- display: grid; grid-template-rows: 0fr;
39
- transition: grid-template-rows var(--duration-normal) var(--ease-out);
102
+ display: grid;
103
+ grid-template-rows: 0fr;
104
+ transition: grid-template-rows var(--icen-tx-normal);
40
105
  }
41
106
  .sidebar-group.is-open .sidebar-group-panel { grid-template-rows: 1fr; }
42
- .sidebar-group-panel-inner { overflow: hidden; min-height: 0; }
107
+ .sidebar-group-panel-inner {
108
+ overflow: hidden;
109
+ min-height: 0;
110
+ }
43
111
 
44
112
  .sidebar-item {
45
- width: 100%; display: flex; align-items: center; gap: 8px;
46
- padding: 8px 12px; border-radius: var(--radius-lg);
47
- font-size: 13px; color: var(--token-text); text-align: left;
48
- text-decoration: none; background: none; border: 0; cursor: pointer;
49
- }
50
- .sidebar-item:hover { background: var(--token-control-hover); text-decoration: none; }
51
- .sidebar-item.is-active { background: var(--token-accent-subtle); color: var(--token-accent); font-weight: 500; }
52
- .sidebar-item > svg { width: 14px; height: 14px; flex-shrink: 0; color: var(--token-text-muted); }
53
- .sidebar-item.is-active > svg { color: var(--token-accent); }
54
- .sidebar-item > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
113
+ width: 100%;
114
+ display: flex;
115
+ align-items: center;
116
+ gap: var(--density-space-xs);
117
+ padding: var(--density-space-sm) var(--density-space-sm);
118
+ border-radius: var(--radius-md);
119
+ font-size: var(--density-font-size-md);
120
+ color: var(--token-text);
121
+ text-align: left;
122
+ text-decoration: none;
123
+ background: none;
124
+ border: 0;
125
+ cursor: pointer;
126
+ transition:
127
+ background-color var(--icen-tx-fast),
128
+ color var(--icen-tx-fast);
129
+ }
130
+ .sidebar-item:hover {
131
+ background: var(--token-control-hover);
132
+ text-decoration: none;
133
+ color: var(--token-text);
134
+ }
135
+ .sidebar-item:focus-visible {
136
+ outline: none;
137
+ box-shadow: var(--icen-ring);
138
+ }
139
+ .sidebar-item.is-active,
140
+ .sidebar-item[aria-current='page'] {
141
+ background: var(--token-accent-subtle);
142
+ color: var(--token-accent);
143
+ font-weight: 500;
144
+ }
145
+ .sidebar-item.is-disabled,
146
+ .sidebar-item[aria-disabled='true'],
147
+ .sidebar-item[data-disabled] {
148
+ opacity: var(--icen-disabled-opacity);
149
+ pointer-events: none;
150
+ }
151
+ .sidebar-item-icon {
152
+ width: 14px;
153
+ height: 14px;
154
+ flex-shrink: 0;
155
+ color: var(--token-text-muted);
156
+ }
157
+ .sidebar-item.is-active .sidebar-item-icon { color: var(--token-accent); }
158
+ .sidebar-item-label {
159
+ flex: 1;
160
+ overflow: hidden;
161
+ text-overflow: ellipsis;
162
+ white-space: nowrap;
163
+ }
164
+ .sidebar-item-badge {
165
+ display: inline-flex;
166
+ align-items: center;
167
+ justify-content: center;
168
+ min-width: 18px;
169
+ height: 18px;
170
+ padding: 0 5px;
171
+ font-size: var(--density-font-size-xs);
172
+ font-weight: 600;
173
+ color: var(--token-on-accent, #fff);
174
+ background: var(--token-accent);
175
+ border-radius: var(--radius-full);
176
+ font-variant-numeric: tabular-nums;
177
+ }
178
+
179
+ @media (prefers-reduced-motion: reduce) {
180
+ .sidebar-group-panel,
181
+ .sidebar-group-title::after { transition: none; }
182
+ }
@@ -0,0 +1,3 @@
1
+ // @icen.ai/ui — kit: sidebar(CSS + behavior 一行入口;构建产物,勿手改)
2
+ import './sidebar.css';
3
+ export * from '../behaviors/sidebar.mjs';
@@ -0,0 +1,2 @@
1
+ // @icen.ai/ui — kit: skeleton(CSS + behavior 一行入口;构建产物,勿手改)
2
+ import './feedback.css';
@@ -3,67 +3,205 @@
3
3
  * 规格源自 opengal primitives/input.tsx 的 Slider 与 advanced-input.tsx 的 DualRangeSlider;
4
4
  * 与 behaviors/slider.ts 配套(透明原生 range 覆盖层驱动 fill/thumb 定位)。
5
5
  *
6
- * 契约(单值):
7
- * <div class="slider">
6
+ * 完整契约:
7
+ * <div class="slider [--sm|--lg] [--vertical] [.is-disabled]" data-min=0 data-max=100 data-step=1>
8
8
  * <div class="slider-track"></div>
9
- * <div class="slider-fill" style="width:40%"></div>
10
- * <div class="slider-thumb" style="left:calc(40% - 8px)"></div>
11
- * <input class="slider-native" type="range" />
9
+ * <div class="slider-fill" style="width:40%"></div> 单值:from 0
10
+ * OR
11
+ * <div class="slider-range" style="left:20%; right:35%"></div> 双值:选中段
12
+ * <div class="slider-thumb" style="left:calc(40% - 8px)"></div> 单 thumb
13
+ * OR
14
+ * <div class="slider-thumb slider-thumb--lo" …>
15
+ * <div class="slider-thumb slider-thumb--hi" …> 双 thumb
16
+ * <input class="slider-native" type="range" /> 单:1 个;双:2 个 [data-thumb=lo|hi]
12
17
  * </div>
13
- * 双滑块:.slider--dual 内 .slider-range(left/right 定位选中段)
14
- * + .slider-thumb--lo / .slider-thumb--hi + 两个 .slider-native[data-thumb=lo|hi]
15
- * label 行:.slider-row(label + 当前值,tabular-nums)
18
+ *
19
+ * 带标签行:
20
+ * <div class="slider-row">
21
+ * <span class="slider-label">音量</span>
22
+ * <span class="slider-value">40</span> ← behavior 同步当前值,tabular-nums
23
+ * </div>
24
+ *
25
+ * behavior 维护:thumb 位置 % 化、aria-valuenow/min/max、键盘 ←/→ 步进、Shift 十步、双滑块互锁。
16
26
  */
17
27
 
18
- .slider { position: relative; height: 20px; width: 100%; }
28
+ .slider {
29
+ position: relative;
30
+ height: 20px;
31
+ width: 100%;
32
+ --slider-thumb-size: 16px;
33
+ --slider-track-height: 6px;
34
+ }
35
+ .slider--sm {
36
+ height: 16px;
37
+ --slider-thumb-size: 12px;
38
+ --slider-track-height: 4px;
39
+ }
40
+ .slider--lg {
41
+ height: 24px;
42
+ --slider-thumb-size: 20px;
43
+ --slider-track-height: 8px;
44
+ }
45
+ .slider.is-disabled,
46
+ .slider[aria-disabled='true'],
47
+ .slider[data-disabled] {
48
+ opacity: var(--icen-disabled-opacity);
49
+ cursor: not-allowed;
50
+ }
51
+
52
+ /* 垂直方向(orient=vertical),常见于音量/色相 */
53
+ .slider--vertical {
54
+ width: 20px;
55
+ height: 100%;
56
+ min-height: 120px;
57
+ }
58
+ .slider--vertical.slider--sm { width: 16px; }
59
+ .slider--vertical.slider--lg { width: 24px; }
19
60
 
20
61
  .slider-track {
21
- position: absolute; left: 0; right: 0; top: 50%; transform: translateY(-50%);
22
- height: 6px; border-radius: var(--radius-full);
62
+ position: absolute;
63
+ left: 0; right: 0;
64
+ top: 50%;
65
+ transform: translateY(-50%);
66
+ height: var(--slider-track-height);
67
+ border-radius: var(--radius-full);
23
68
  background: color-mix(in srgb, var(--token-line-soft) 50%, transparent);
24
69
  }
70
+ .slider--vertical .slider-track {
71
+ left: 50%; right: auto;
72
+ top: 0; bottom: 0;
73
+ transform: translateX(-50%);
74
+ height: 100%;
75
+ width: var(--slider-track-height);
76
+ }
25
77
 
26
78
  .slider-fill {
27
- position: absolute; left: 0; top: 50%; transform: translateY(-50%);
28
- height: 6px; border-radius: var(--radius-full);
79
+ position: absolute;
80
+ left: 0;
81
+ top: 50%;
82
+ transform: translateY(-50%);
83
+ height: var(--slider-track-height);
84
+ border-radius: var(--radius-full);
29
85
  background: var(--token-accent);
30
- transition: width var(--duration-normal) var(--ease-out);
86
+ transition: width var(--icen-tx-normal);
87
+ }
88
+ .slider--vertical .slider-fill {
89
+ left: 50%;
90
+ top: auto; bottom: 0;
91
+ transform: translateX(-50%);
92
+ width: var(--slider-track-height);
93
+ height: auto;
94
+ transition: height var(--icen-tx-normal);
31
95
  }
32
96
 
33
97
  /* 双滑块选中段:left = lo%、right = (100 - hi)% */
34
98
  .slider-range {
35
- position: absolute; top: 50%; transform: translateY(-50%);
36
- height: 6px; border-radius: var(--radius-full);
99
+ position: absolute;
100
+ top: 50%;
101
+ transform: translateY(-50%);
102
+ height: var(--slider-track-height);
103
+ border-radius: var(--radius-full);
37
104
  background: var(--token-accent);
38
- transition: left var(--duration-normal) var(--ease-out),
39
- right var(--duration-normal) var(--ease-out);
105
+ transition:
106
+ left var(--icen-tx-normal),
107
+ right var(--icen-tx-normal);
108
+ }
109
+ .slider--vertical .slider-range {
110
+ left: 50%;
111
+ top: auto;
112
+ transform: translateX(-50%);
113
+ width: var(--slider-track-height);
114
+ height: auto;
115
+ transition:
116
+ top var(--icen-tx-normal),
117
+ bottom var(--icen-tx-normal);
40
118
  }
41
119
 
42
120
  .slider-thumb {
43
- position: absolute; top: 50%; transform: translateY(-50%);
44
- width: 16px; height: 16px; border-radius: var(--radius-full);
121
+ position: absolute;
122
+ top: 50%;
123
+ transform: translate(-50%, -50%);
124
+ width: var(--slider-thumb-size);
125
+ height: var(--slider-thumb-size);
126
+ border-radius: var(--radius-full);
45
127
  background: var(--token-card);
46
128
  border: 2px solid var(--token-accent);
47
129
  box-shadow: var(--token-shadow-card);
48
130
  pointer-events: none;
49
- transition: transform var(--duration-fast) var(--ease-out),
50
- box-shadow var(--duration-fast) var(--ease-out);
131
+ transition:
132
+ transform var(--icen-tx-fast),
133
+ box-shadow var(--icen-tx-fast),
134
+ border-color var(--icen-tx-fast);
135
+ }
136
+ .slider--vertical .slider-thumb {
137
+ top: auto;
138
+ left: 50%;
139
+ transform: translate(-50%, 50%);
51
140
  }
52
- /* 原生 input 透明,焦点环镜像到可见 thumb 上 */
141
+ .slider:hover .slider-thumb {
142
+ border-color: var(--token-accent-deep, var(--token-accent));
143
+ }
144
+ /* 原生 input 透明,焦点环镜像到可见 thumb 上(消费 --icen-ring-soft) */
53
145
  .slider:focus-within .slider-thumb {
54
- transform: translateY(-50%) scale(1.1);
55
- box-shadow: 0 0 0 2px color-mix(in srgb, var(--token-accent) 50%, transparent),
56
- 0 0 0 3px var(--token-bg);
146
+ transform: translate(-50%, -50%) scale(1.15);
147
+ box-shadow: var(--icen-ring-soft), var(--token-shadow-card);
148
+ }
149
+ .slider--vertical:focus-within .slider-thumb {
150
+ transform: translate(-50%, 50%) scale(1.15);
57
151
  }
58
152
 
59
153
  .slider-native {
60
- position: absolute; inset: 0; width: 100%; height: 100%;
61
- margin: 0; opacity: 0; cursor: pointer; z-index: 1;
154
+ position: absolute;
155
+ inset: 0;
156
+ width: 100%;
157
+ height: 100%;
158
+ margin: 0;
159
+ opacity: 0;
160
+ cursor: pointer;
161
+ z-index: 1;
162
+ -webkit-appearance: none;
163
+ appearance: none;
164
+ background: transparent;
62
165
  }
63
166
  .slider-native:disabled { cursor: not-allowed; }
167
+ .slider-native:focus { outline: none; }
64
168
 
65
- .slider-row {
66
- display: flex; align-items: center; justify-content: space-between;
67
- font-size: 12px; color: var(--token-text-muted);
169
+ /* 双滑块需要两个 native input 叠加,--z 错开避免相互拦截 */
170
+ .slider--dual .slider-native[data-thumb='hi'] { z-index: 2; }
171
+
172
+ /* 当前值标签 */
173
+ .slider-value {
68
174
  font-variant-numeric: tabular-nums;
175
+ color: var(--token-text);
176
+ }
177
+
178
+ .slider-row {
179
+ display: flex;
180
+ align-items: center;
181
+ justify-content: space-between;
182
+ gap: var(--density-space-sm);
183
+ font-size: var(--density-font-size-sm);
184
+ color: var(--token-text-muted);
185
+ }
186
+ .slider-label { color: var(--token-text-muted); }
187
+
188
+ /* 刻度(behavior 可选渲染) */
189
+ .slider-ticks {
190
+ position: absolute;
191
+ left: 0; right: 0;
192
+ top: 50%;
193
+ height: var(--slider-track-height);
194
+ transform: translateY(-50%);
195
+ display: flex;
196
+ justify-content: space-between;
197
+ pointer-events: none;
198
+ }
199
+ .slider-tick {
200
+ width: 1px;
201
+ height: 100%;
202
+ background: color-mix(in srgb, var(--token-text) 12%, transparent);
203
+ }
204
+
205
+ @media (prefers-reduced-motion: reduce) {
206
+ .slider-fill, .slider-range, .slider-thumb { transition: none; }
69
207
  }
@@ -0,0 +1,3 @@
1
+ // @icen.ai/ui — kit: slider(CSS + behavior 一行入口;构建产物,勿手改)
2
+ import './slider.css';
3
+ export * from '../behaviors/slider.mjs';
@@ -0,0 +1,2 @@
1
+ // @icen.ai/ui — kit: spinner(CSS + behavior 一行入口;构建产物,勿手改)
2
+ import './feedback.css';
@@ -1,18 +1,113 @@
1
1
  /*
2
2
  * @icen.ai/ui — Component: stat(指标卡)
3
- * 用法:<div class="stat-grid"><div class="stat-card"><div class="stat-num">0</div><div class="stat-label">用户总数</div></div>…
3
+ *
4
+ * 完整契约:
5
+ * <div class="stat-grid [stat-grid--2|stat-grid--3|stat-grid--4]">
6
+ * <div class="stat-card [.stat-card--interactive]">
7
+ * <div class="stat-head">
8
+ * <span class="stat-label">用户总数</span>
9
+ * <span class="stat-icon">…svg…</span> ← 可选
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>
15
+ * </div>
16
+ * </div>
17
+ * </div>
4
18
  */
5
19
 
6
20
  .stat-grid {
7
- display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
8
- gap: 14px; margin-bottom: 24px;
21
+ display: grid;
22
+ grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
23
+ gap: var(--density-space-md);
24
+ margin-bottom: var(--density-space-lg);
9
25
  }
26
+ .stat-grid--2 { grid-template-columns: repeat(2, 1fr); }
27
+ .stat-grid--3 { grid-template-columns: repeat(3, 1fr); }
28
+ .stat-grid--4 { grid-template-columns: repeat(4, 1fr); }
29
+
10
30
  .stat-card {
11
- background: var(--token-surface); border: 1px solid var(--token-line-soft);
12
- border-radius: var(--radius-lg); padding: 14px 16px;
31
+ display: flex;
32
+ flex-direction: column;
33
+ gap: var(--density-space-xs);
34
+ background: var(--token-surface);
35
+ border: 1px solid var(--token-line-soft);
36
+ border-radius: var(--radius-lg);
37
+ padding: var(--density-space-md);
13
38
  box-shadow: var(--token-shadow-elevated);
39
+ transition:
40
+ border-color var(--icen-tx-fast),
41
+ box-shadow var(--icen-tx-fast),
42
+ transform var(--icen-tx-fast);
43
+ }
44
+ .stat-card--interactive {
45
+ cursor: pointer;
46
+ }
47
+ .stat-card--interactive:hover {
48
+ transform: translateY(-1px);
49
+ border-color: color-mix(in srgb, var(--token-accent) 26%, var(--token-outline-soft));
50
+ box-shadow: var(--token-shadow-card);
51
+ }
52
+
53
+ .stat-head {
54
+ display: flex;
55
+ align-items: center;
56
+ justify-content: space-between;
57
+ gap: var(--density-space-sm);
58
+ }
59
+ .stat-label {
60
+ color: var(--token-text-faint);
61
+ font-size: var(--density-font-size-xs);
62
+ letter-spacing: 0.05em;
63
+ text-transform: uppercase;
64
+ }
65
+ .stat-icon {
66
+ display: inline-flex;
67
+ align-items: center;
68
+ justify-content: center;
69
+ width: 24px;
70
+ height: 24px;
71
+ border-radius: var(--radius-sm);
72
+ background: var(--token-accent-subtle);
73
+ color: var(--token-accent);
74
+ }
75
+ .stat-icon svg { width: 14px; height: 14px; }
76
+
77
+ .stat-num {
78
+ font-size: 24px;
79
+ font-weight: 700;
80
+ line-height: 1.2;
81
+ font-variant-numeric: tabular-nums lining-nums;
82
+ color: var(--token-text);
83
+ word-break: break-all;
14
84
  }
15
- .stat-num { font-size: 24px; font-weight: 700; line-height: 1.25; word-break: break-all; }
16
85
  .stat-num.accent { color: var(--token-accent); }
17
- .stat-num.warn { color: var(--token-warning); }
18
- .stat-label { color: var(--token-text-faint); font-size: 11px; letter-spacing: 1px; margin-top: 2px; }
86
+ .stat-num.warn { color: var(--token-warning); }
87
+ .stat-num.bad { color: var(--token-error); }
88
+ .stat-num.info { color: var(--token-info); }
89
+
90
+ .stat-delta {
91
+ display: inline-flex;
92
+ align-items: center;
93
+ gap: 4px;
94
+ font-size: var(--density-font-size-xs);
95
+ color: var(--token-text-muted);
96
+ font-variant-numeric: tabular-nums;
97
+ }
98
+ .stat-delta svg { width: 12px; height: 12px; }
99
+ .stat-delta.up { color: var(--token-success); }
100
+ .stat-delta.down { color: var(--token-error); }
101
+ .stat-delta.flat { color: var(--token-text-faint); }
102
+ .stat-period {
103
+ color: var(--token-text-faint);
104
+ margin-left: var(--density-space-xs);
105
+ }
106
+
107
+ /* 紧凑变体 */
108
+ .stat-card--sm {
109
+ padding: var(--density-space-sm);
110
+ gap: 2px;
111
+ }
112
+ .stat-card--sm .stat-num { font-size: var(--density-font-size-lg); }
113
+ .stat-card--sm .stat-label { font-size: 10px; }
@@ -0,0 +1,2 @@
1
+ // @icen.ai/ui — kit: stat(CSS + behavior 一行入口;构建产物,勿手改)
2
+ import './stat.css';