@teamix-evo/ui 0.7.0 → 0.7.2

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/manifest.json +16 -7
  2. package/package.json +4 -4
  3. package/src/_design-system/theme-tokens/stories.tsx +2 -2
  4. package/src/components/accordion/index.tsx +1 -1
  5. package/src/components/affix/meta.md +26 -0
  6. package/src/components/alert/index.tsx +2 -2
  7. package/src/components/alert-dialog/index.tsx +3 -3
  8. package/src/components/alert-dialog/meta.md +52 -0
  9. package/src/components/alert-dialog/stories.tsx +45 -48
  10. package/src/components/avatar/index.tsx +1 -1
  11. package/src/components/badge/index.tsx +2 -2
  12. package/src/components/badge/meta.md +48 -0
  13. package/src/components/button/index.tsx +2 -2
  14. package/src/components/button/meta.md +15 -0
  15. package/src/components/button/stories.tsx +1 -1
  16. package/src/components/calendar/index.tsx +2 -2
  17. package/src/components/card/index.tsx +1 -1
  18. package/src/components/carousel/index.tsx +2 -2
  19. package/src/components/carousel/meta.md +34 -2
  20. package/src/components/carousel/stories.tsx +2 -2
  21. package/src/components/cascader-select/index.tsx +2 -1
  22. package/src/components/cascader-select/meta.md +46 -0
  23. package/src/components/checkbox/meta.md +47 -0
  24. package/src/components/color-picker/index.tsx +3 -3
  25. package/src/components/color-picker/meta.md +80 -0
  26. package/src/components/combobox/index.tsx +2 -2
  27. package/src/components/combobox/meta.md +130 -0
  28. package/src/components/data-table/index.tsx +3 -3
  29. package/src/components/data-table/meta.md +419 -0
  30. package/src/components/data-table/stories.tsx +4 -4
  31. package/src/components/date-picker/meta.md +91 -0
  32. package/src/components/descriptions/index.tsx +1 -1
  33. package/src/components/descriptions/meta.md +245 -0
  34. package/src/components/dialog/index.tsx +4 -4
  35. package/src/components/dialog/meta.md +47 -1
  36. package/src/components/dialog/stories.tsx +38 -41
  37. package/src/components/dropdown-menu/index.tsx +5 -5
  38. package/src/components/empty/index.tsx +2 -2
  39. package/src/components/field/index.tsx +4 -4
  40. package/src/components/filter-bar/index.tsx +6 -6
  41. package/src/components/filter-bar/meta.md +323 -0
  42. package/src/components/float-button/index.tsx +2 -2
  43. package/src/components/form/index.tsx +1 -1
  44. package/src/components/form/meta.md +119 -0
  45. package/src/components/hover-card/index.tsx +1 -1
  46. package/src/components/hover-card/meta.md +21 -0
  47. package/src/components/input/meta.md +16 -0
  48. package/src/components/input-group/index.tsx +1 -1
  49. package/src/components/input-group/meta.md +118 -0
  50. package/src/components/input-group/stories.tsx +6 -6
  51. package/src/components/input-ip/index.tsx +2 -2
  52. package/src/components/input-ip/meta.md +30 -0
  53. package/src/components/input-ip/stories.tsx +2 -2
  54. package/src/components/input-number/index.tsx +3 -2
  55. package/src/components/input-number/meta.md +67 -0
  56. package/src/components/input-number/stories.tsx +2 -2
  57. package/src/components/item/index.tsx +4 -4
  58. package/src/components/label/meta.md +8 -0
  59. package/src/components/mentions/meta.md +15 -0
  60. package/src/components/menubar/index.tsx +4 -4
  61. package/src/components/navigation-menu/index.tsx +4 -4
  62. package/src/components/page-header/index.tsx +2 -2
  63. package/src/components/page-header/meta.md +145 -0
  64. package/src/components/page-shell/index.tsx +3 -3
  65. package/src/components/pagination/index.tsx +1 -1
  66. package/src/components/pagination/meta.md +203 -0
  67. package/src/components/popconfirm/meta.md +45 -0
  68. package/src/components/popover/index.tsx +2 -2
  69. package/src/components/popover/meta.md +47 -0
  70. package/src/components/progress/index.tsx +1 -1
  71. package/src/components/progress/meta.md +36 -0
  72. package/src/components/progress/stories.tsx +1 -1
  73. package/src/components/radio-group/meta.md +69 -0
  74. package/src/components/rate/index.tsx +1 -1
  75. package/src/components/rate/meta.md +50 -0
  76. package/src/components/resizable/index.tsx +1 -1
  77. package/src/components/select/index.tsx +2 -2
  78. package/src/components/select/meta.md +20 -0
  79. package/src/components/separator/index.tsx +1 -1
  80. package/src/components/sheet/index.tsx +13 -14
  81. package/src/components/sheet/meta.md +124 -0
  82. package/src/components/sheet/stories.tsx +110 -119
  83. package/src/components/sidebar/index.tsx +5 -5
  84. package/src/components/sidebar/meta.md +383 -0
  85. package/src/components/skeleton/meta.md +13 -0
  86. package/src/components/slider/index.tsx +2 -2
  87. package/src/components/sonner/meta.md +86 -0
  88. package/src/components/spinner/meta.md +46 -0
  89. package/src/components/spinner/stories.tsx +2 -2
  90. package/src/components/steps/meta.md +20 -0
  91. package/src/components/steps/stories.tsx +1 -1
  92. package/src/components/switch/index.tsx +2 -2
  93. package/src/components/switch/meta.md +33 -0
  94. package/src/components/table/index.tsx +4 -4
  95. package/src/components/table/meta.md +11 -0
  96. package/src/components/tabs/index.tsx +7 -7
  97. package/src/components/tabs/meta.md +52 -0
  98. package/src/components/tag/index.tsx +8 -8
  99. package/src/components/tag/meta.md +194 -0
  100. package/src/components/textarea/index.tsx +1 -1
  101. package/src/components/textarea/meta.md +27 -0
  102. package/src/components/textarea/stories.tsx +1 -1
  103. package/src/components/time-picker/index.tsx +3 -3
  104. package/src/components/time-picker/meta.md +76 -0
  105. package/src/components/timeline/index.tsx +1 -0
  106. package/src/components/toggle/index.tsx +1 -1
  107. package/src/components/toggle-group/index.tsx +1 -1
  108. package/src/components/tooltip/index.tsx +1 -1
  109. package/src/components/tooltip/meta.md +23 -0
  110. package/src/components/transfer/index.tsx +2 -2
  111. package/src/components/transfer/meta.md +97 -0
  112. package/src/components/tree/index.tsx +245 -15
  113. package/src/components/tree/meta.md +151 -0
  114. package/src/components/tree-select/index.tsx +16 -2
  115. package/src/components/tree-select/meta.md +150 -0
  116. package/src/components/typography/index.tsx +3 -3
  117. package/src/components/upload/index.tsx +3 -3
  118. package/src/components/upload/meta.md +82 -0
  119. package/src/components/tree/utils.ts +0 -269
  120. package/src/examples/built-in-assets/stories.tsx +0 -572
  121. package/src/examples/evaluators/stories.tsx +0 -502
@@ -85,6 +85,28 @@
85
85
  </div>
86
86
  ```
87
87
 
88
+ ### Loading
89
+
90
+ 加载态:`loading` 期间禁用交互,thumb 内显示自旋图标。
91
+
92
+ ```tsx
93
+ <div className="flex items-center gap-4">
94
+ <Switch
95
+ checked={checked}
96
+ loading={loading}
97
+ onCheckedChange={handleChange}
98
+ />
99
+ <Switch size="sm" loading defaultChecked />
100
+ <Button
101
+ variant="outline"
102
+ size="sm"
103
+ onClick={() => handleChange(!checked)}
104
+ >
105
+ 异步切换
106
+ </Button>
107
+ </div>
108
+ ```
109
+
88
110
  ### Disabled
89
111
 
90
112
  禁用态:开/关两种。
@@ -105,3 +127,14 @@
105
127
  </div>
106
128
  </div>
107
129
  ```
130
+
131
+ ### Controlled
132
+
133
+ 受控用法:通过 `checked` + `onCheckedChange` 接管状态。
134
+
135
+ ```tsx
136
+ <div className="flex items-center gap-2">
137
+ <Switch checked={checked} onCheckedChange={setChecked} />
138
+ <Label>{checked ? '开启' : '关闭'}</Label>
139
+ </div>
140
+ ```
@@ -219,8 +219,8 @@ const TableRow = React.forwardRef<
219
219
  className={cn(
220
220
  'border-b border-border-subtle transition-colors',
221
221
  hoverable &&
222
- 'hover:bg-muted/50 has-aria-expanded:bg-muted/50 data-[state=selected]:bg-muted',
223
- crossline && 'group/row',
222
+ 'group/row hover:bg-row-hover has-aria-expanded:bg-row-hover data-[state=selected]:bg-muted',
223
+ crossline && !hoverable && 'group/row',
224
224
  className,
225
225
  )}
226
226
  {...props}
@@ -239,7 +239,7 @@ function TableHead({ className, ...props }: React.ComponentProps<'th'>) {
239
239
  className={cn(
240
240
  'text-left align-middle font-medium whitespace-nowrap',
241
241
  // md:上下 12px / 左右 16px / 行高 18px;sm:上下 8px / 左右 8px
242
- size === 'sm' ? 'px-2 py-2' : 'px-4 py-3 leading-normal',
242
+ size === 'sm' ? 'p-2' : 'px-4 py-3 leading-normal',
243
243
  bordered &&
244
244
  '[&:not(:last-child)]:border-r [&:not(:last-child)]:border-border/60',
245
245
  '[&:has([role=checkbox])]:pr-0',
@@ -261,7 +261,7 @@ function TableCell({ className, ...props }: React.ComponentProps<'td'>) {
261
261
  className={cn(
262
262
  'align-middle whitespace-nowrap text-foreground-body',
263
263
  // md:上下 12px / 左右 16px;sm:上下 8px / 左右 8px
264
- size === 'sm' ? 'px-2 py-2' : 'px-4 py-3',
264
+ size === 'sm' ? 'p-2' : 'px-4 py-3',
265
265
  bordered &&
266
266
  '[&:not(:last-child)]:border-r [&:not(:last-child)]:border-border/60',
267
267
  '[&:has([role=checkbox])]:pr-0',
@@ -96,6 +96,17 @@ stickyHeader / hoverable / crossline / loading / empty / maxBodyHeight 等视觉
96
96
  </Table>
97
97
  ```
98
98
 
99
+ ### StickyHeader
100
+
101
+ 配合 `maxBodyHeight` 使用 `stickyHeader` 实现表头吸顶。
102
+
103
+ ```tsx
104
+ <Table stickyHeader maxBodyHeight={300}>
105
+ {renderHeader()}
106
+ <TableBody>{renderRows(longList)}</TableBody>
107
+ </Table>
108
+ ```
109
+
99
110
  ### Loading
100
111
 
101
112
  `loading` 显示居中蒙层;可通过 `loadingNode` 自定义。
@@ -200,7 +200,7 @@ function TabsList({
200
200
  {variant === 'line' && (
201
201
  <span
202
202
  aria-hidden
203
- className="pointer-events-none absolute -z-10 bg-border bottom-0 left-0 right-0 h-px group-data-[orientation=vertical]/tabs:top-0 group-data-[orientation=vertical]/tabs:left-auto group-data-[orientation=vertical]/tabs:h-auto group-data-[orientation=vertical]/tabs:w-px"
203
+ className="pointer-events-none absolute inset-x-0 bottom-0 -z-10 h-px bg-border group-data-[orientation=vertical]/tabs:top-0 group-data-[orientation=vertical]/tabs:left-auto group-data-[orientation=vertical]/tabs:h-auto group-data-[orientation=vertical]/tabs:w-px"
204
204
  />
205
205
  )}
206
206
  <TabsPrimitive.List
@@ -212,10 +212,10 @@ function TabsList({
212
212
  tabsListVariants({ variant, size }),
213
213
  // line/wrapped: 占满,capsule/text: 自适应
214
214
  (variant === 'line' || variant === 'wrapped') &&
215
- 'min-w-0 flex-1 overflow-x-auto scrollbar-none group-data-[orientation=vertical]/tabs:min-w-fit group-data-[orientation=vertical]/tabs:flex-none group-data-[orientation=vertical]/tabs:overflow-x-visible',
215
+ 'min-w-0 flex-1 scrollbar-none overflow-x-auto group-data-[orientation=vertical]/tabs:min-w-fit group-data-[orientation=vertical]/tabs:flex-none group-data-[orientation=vertical]/tabs:overflow-x-visible',
216
216
  // overflow 隐式 clip → 加 pb-px/pr-px 给 1px 余量让 active border 完整可见
217
217
  variant === 'line' &&
218
- 'pb-px group-data-[orientation=vertical]/tabs:pb-0 group-data-[orientation=vertical]/tabs:pr-px',
218
+ 'pb-px group-data-[orientation=vertical]/tabs:pr-px group-data-[orientation=vertical]/tabs:pb-0',
219
219
  className,
220
220
  )}
221
221
  {...props}
@@ -265,17 +265,17 @@ function TabsList({
265
265
  // ─── TabsTrigger ────────────────────────────────────────────────────────────
266
266
 
267
267
  const tabsTriggerVariants = cva(
268
- 'group/tabs-trigger relative inline-flex shrink-0 cursor-pointer items-center justify-center gap-1.5 whitespace-nowrap font-normal outline-none transition-all hover:text-foreground focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 data-[state=active]:font-semibold [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=size-])]:size-3.5',
268
+ 'group/tabs-trigger relative inline-flex shrink-0 cursor-pointer items-center justify-center gap-1.5 font-normal whitespace-nowrap transition-all outline-none hover:text-foreground focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 data-[state=active]:font-semibold [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=size-])]:size-3.5',
269
269
  {
270
270
  variants: {
271
271
  variant: {
272
- line: '-mb-px border-b-2 border-transparent text-muted-foreground data-[state=active]:border-tab-indicator data-[state=active]:text-foreground group-data-[orientation=vertical]/tabs:mb-0 group-data-[orientation=vertical]/tabs:-mr-px group-data-[orientation=vertical]/tabs:border-b-0 group-data-[orientation=vertical]/tabs:border-r-2 group-data-[orientation=vertical]/tabs:justify-start',
272
+ line: '-mb-px border-b-2 border-transparent text-muted-foreground group-data-[orientation=vertical]/tabs:-mr-px group-data-[orientation=vertical]/tabs:mb-0 group-data-[orientation=vertical]/tabs:justify-start group-data-[orientation=vertical]/tabs:border-r-2 group-data-[orientation=vertical]/tabs:border-b-0 data-[state=active]:border-tab-indicator data-[state=active]:text-foreground',
273
273
  capsule:
274
274
  'rounded-md text-muted-foreground data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm',
275
275
  // text 模式:trigger 间 ::after 竖线分隔(8px 高、1px 宽、垂直居中、border 色)
276
- text: 'text-muted-foreground after:absolute after:right-0 after:top-1/2 after:-translate-y-1/2 after:h-2 after:w-px after:bg-border last:after:hidden data-[state=active]:text-primary',
276
+ text: 'text-muted-foreground after:absolute after:top-1/2 after:right-0 after:h-2 after:w-px after:-translate-y-1/2 after:bg-border last:after:hidden data-[state=active]:text-primary',
277
277
  wrapped:
278
- '-ml-px first:ml-0 first:rounded-l-sm last:rounded-r-sm border border-border text-muted-foreground data-[state=active]:z-10 data-[state=active]:border-primary data-[state=active]:text-foreground',
278
+ '-ml-px border border-border text-muted-foreground first:ml-0 first:rounded-l-sm last:rounded-r-sm data-[state=active]:z-10 data-[state=active]:border-primary data-[state=active]:text-foreground',
279
279
  },
280
280
  size: {
281
281
  sm: 'h-9 px-4 text-xs',
@@ -194,6 +194,32 @@
194
194
  </Tabs>
195
195
  ```
196
196
 
197
+ ### Closable
198
+
199
+ 可关闭选项卡(closable + onClose),适合多文档/工作区场景。
200
+
201
+ ```tsx
202
+ <Tabs value={active} onValueChange={setActive}>
203
+ <TabsList>
204
+ {tabs.map((t) => (
205
+ <TabsTrigger
206
+ key={t.key}
207
+ value={t.key}
208
+ closable
209
+ onClose={handleClose}
210
+ >
211
+ {t.title}
212
+ </TabsTrigger>
213
+ ))}
214
+ </TabsList>
215
+ {tabs.map((t) => (
216
+ <TabsContent key={t.key} value={t.key}>
217
+ <p className="text-muted-foreground">{t.title} 的内容</p>
218
+ </TabsContent>
219
+ ))}
220
+ </Tabs>
221
+ ```
222
+
197
223
  ### Vertical
198
224
 
199
225
  垂直方向选项卡。
@@ -217,6 +243,32 @@
217
243
  </Tabs>
218
244
  ```
219
245
 
246
+ ### Controlled
247
+
248
+ 受控用法 + onValueChange 回调。
249
+
250
+ ```tsx
251
+ <div className="flex flex-col gap-2">
252
+ <Tabs value={value} onValueChange={setValue}>
253
+ <TabsList>
254
+ <TabsTrigger value="home">首页</TabsTrigger>
255
+ <TabsTrigger value="docs">文档</TabsTrigger>
256
+ <TabsTrigger value="api">API</TabsTrigger>
257
+ </TabsList>
258
+ <TabsContent value="home">
259
+ <p className="text-muted-foreground">首页内容。</p>
260
+ </TabsContent>
261
+ <TabsContent value="docs">
262
+ <p className="text-muted-foreground">文档内容。</p>
263
+ </TabsContent>
264
+ <TabsContent value="api">
265
+ <p className="text-muted-foreground">API 参考。</p>
266
+ </TabsContent>
267
+ </Tabs>
268
+ <div className="text-xs text-muted-foreground">当前激活:{value}</div>
269
+ </div>
270
+ ```
271
+
220
272
  ### Overflow
221
273
 
222
274
  溢出滑动:当选项卡超出容器宽度时,显示左右箭头进行滑动。
@@ -159,7 +159,7 @@ const SOLID_BG_MAP: Record<
159
159
 
160
160
  // ─── cva(仅覆盖 solid / outline / ghost 三种背景形态) ───────────────────
161
161
  const tagVariants = cva(
162
- 'inline-flex items-center gap-1 overflow-hidden whitespace-nowrap text-ellipsis border border-border font-normal transition-colors shrink-0 max-w-full',
162
+ 'inline-flex max-w-full shrink-0 items-center gap-1 truncate border border-border font-normal transition-colors',
163
163
  {
164
164
  variants: {
165
165
  variant: {
@@ -403,10 +403,10 @@ function Tag({
403
403
  }}
404
404
  className={cn(
405
405
  '-mr-0.5 inline-flex shrink-0 cursor-pointer rounded-sm opacity-60 transition-opacity hover:opacity-100',
406
- 'focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring',
406
+ 'focus-visible:ring-1 focus-visible:ring-ring focus-visible:outline-none',
407
407
  disabled && 'cursor-not-allowed opacity-30 hover:opacity-30',
408
408
  hoverReveal &&
409
- 'mr-0 overflow-hidden w-0 opacity-0 transition-[width,opacity] duration-150 group-hover/tag:w-3 group-hover/tag:opacity-60',
409
+ 'mr-0 w-0 overflow-hidden opacity-0 transition-[width,opacity] duration-150 group-hover/tag:w-3 group-hover/tag:opacity-60',
410
410
  )}
411
411
  >
412
412
  {renderCloseIcon(closableConfig.closeIcon)}
@@ -429,7 +429,7 @@ function Tag({
429
429
  data-close-area={closableConfig ? closeArea : undefined}
430
430
  aria-disabled={disabled || undefined}
431
431
  className={cn(
432
- 'inline-flex h-6 max-w-full items-center gap-1.5 overflow-hidden whitespace-nowrap text-ellipsis text-xs font-normal shrink-0',
432
+ 'inline-flex h-6 max-w-full shrink-0 items-center gap-1.5 truncate text-xs font-normal',
433
433
  size === 'sm' && 'h-5',
434
434
  size === 'lg' && 'h-7',
435
435
  disabled && 'cursor-not-allowed opacity-50',
@@ -644,12 +644,12 @@ function CheckableTag({
644
644
  }}
645
645
  style={{ borderRadius: 'var(--radius-tag)', ...style }}
646
646
  className={cn(
647
- 'inline-flex h-6 max-w-full cursor-pointer items-center gap-1 overflow-hidden whitespace-nowrap text-ellipsis border border-border px-2 text-xs font-normal transition-colors shrink-0',
648
- 'focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring',
647
+ 'inline-flex h-6 max-w-full shrink-0 cursor-pointer items-center gap-1 truncate border border-border px-2 text-xs font-normal transition-colors',
648
+ 'focus-visible:ring-1 focus-visible:ring-ring focus-visible:outline-none',
649
649
  variant === 'outline'
650
650
  ? checked
651
- ? 'border-primary text-primary bg-primary/10 hover:bg-primary/15'
652
- : 'border-border text-foreground bg-transparent hover:bg-accent'
651
+ ? 'border-primary bg-primary/10 text-primary hover:bg-primary/15'
652
+ : 'border-border bg-transparent text-foreground hover:bg-accent'
653
653
  : checked
654
654
  ? 'border-primary bg-primary text-primary-foreground hover:bg-primary/90'
655
655
  : 'border-border bg-muted text-foreground hover:bg-accent',
@@ -152,6 +152,98 @@ closeArea / aria-label),`onClose(e)` 调用 `e.preventDefault()` 即可
152
152
  </TagGroup>
153
153
  ```
154
154
 
155
+ ### Closable
156
+
157
+ 关闭路径升级: - `closable` 接受 boolean / 对象,可自定义 `closeIcon` / `closeArea` / `aria-label` - `onClose(e)` 调用 `e.preventDefault()` 阻止默认关闭 - `visible` / `defaultVisible` 双形态可见性
158
+
159
+ ```tsx
160
+ <div className="flex flex-col gap-4">
161
+ <div>
162
+ <h4 className="mb-2 text-xs text-muted-foreground">默认 tail</h4>
163
+ <TagGroup>
164
+ <Tag closable color="primary">
165
+ tail close
166
+ </Tag>
167
+ <Tag closable color="success" onClose={() => console.log('closed')}>
168
+ 带回调
169
+ </Tag>
170
+ </TagGroup>
171
+ </div>
172
+ <div>
173
+ <h4 className="mb-2 text-xs text-muted-foreground">
174
+ 自定义 closeIcon
175
+ </h4>
176
+ <TagGroup>
177
+ <Tag
178
+ closable={{ closeIcon: <Trash2 />, 'aria-label': 'Delete' }}
179
+ color="destructive"
180
+ >
181
+ custom icon
182
+ </Tag>
183
+ </TagGroup>
184
+ </div>
185
+ <div>
186
+ <h4 className="mb-2 text-xs text-muted-foreground">
187
+ closeArea = &quot;tag&quot;(整体可点击关闭)
188
+ </h4>
189
+ <TagGroup>
190
+ <Tag closable={{ closeArea: 'tag' }} color="warning">
191
+ click anywhere
192
+ </Tag>
193
+ </TagGroup>
194
+ </div>
195
+ <div>
196
+ <h4 className="mb-2 text-xs text-muted-foreground">
197
+ preventDefault 阻止默认关闭
198
+ </h4>
199
+ <TagGroup>
200
+ <Tag
201
+ closable
202
+ color="primary"
203
+ onClose={(e) => {
204
+ e.preventDefault();
205
+ console.log('blocked');
206
+ }}
207
+ >
208
+ cannot close
209
+ </Tag>
210
+ </TagGroup>
211
+ </div>
212
+ <div>
213
+ <h4 className="mb-2 text-xs text-muted-foreground">受控 visible</h4>
214
+ <TagGroup>
215
+ <Tag
216
+ closable
217
+ visible={visible}
218
+ onClose={(e) => {
219
+ e.preventDefault();
220
+ setVisible(false);
221
+ }}
222
+ color="primary"
223
+ >
224
+ controlled
225
+ </Tag>
226
+ <CheckableTag checked={visible} onChange={setVisible}>
227
+ {visible ? '隐藏' : '显示'}
228
+ </CheckableTag>
229
+ </TagGroup>
230
+ </div>
231
+ <div>
232
+ <h4 className="mb-2 text-xs text-muted-foreground">
233
+ hoverReveal(hover 时才显示 ×)
234
+ </h4>
235
+ <TagGroup>
236
+ <Tag closable={{ hoverReveal: true }} color="primary">
237
+ hover me
238
+ </Tag>
239
+ <Tag closable={{ hoverReveal: true }} color="success">
240
+ hover me too
241
+ </Tag>
242
+ </TagGroup>
243
+ </div>
244
+ </div>
245
+ ```
246
+
155
247
  ### Disabled
156
248
 
157
249
  禁用态:普通 / closable / checkable 三态。
@@ -185,6 +277,59 @@ closeArea / aria-label),`onClose(e)` 调用 `e.preventDefault()` 即可
185
277
  </div>
186
278
  ```
187
279
 
280
+ ### Status
281
+
282
+ status 9 档对照表:彩色标签(tag) / 圆点(statusTag) / 图标(statusIconTag)。 对齐 teamix-pro ProField status demo 矩阵。
283
+
284
+ ```tsx
285
+ <table className="text-sm">
286
+ <thead>
287
+ <tr className="text-left text-muted-foreground">
288
+ <th className="w-20 pb-2 font-normal">类型</th>
289
+ <th className="w-20 pb-2 font-normal">描述</th>
290
+ <th className="px-4 pb-2 font-normal">tag</th>
291
+ <th className="px-4 pb-2 font-normal">statusTag</th>
292
+ <th className="px-4 pb-2 font-normal">statusIconTag</th>
293
+ </tr>
294
+ </thead>
295
+ <tbody>
296
+ {rows.map((row) => (
297
+ <tr key={row.status}>
298
+ <td className="py-1.5 pr-2 font-mono text-xs text-muted-foreground">
299
+ {row.status}
300
+ </td>
301
+ <td className="py-1.5 pr-2 text-muted-foreground">{row.label}</td>
302
+ <td className="px-4 py-1.5">
303
+ {row.noTag ? (
304
+ <span className="text-muted-foreground">-</span>
305
+ ) : (
306
+ <Tag status={row.status}>{row.label}</Tag>
307
+ )}
308
+ </td>
309
+ <td className="px-4 py-1.5">
310
+ {row.noStatus ? (
311
+ <span className="text-muted-foreground">-</span>
312
+ ) : (
313
+ <Tag variant="status" status={row.status}>
314
+ {row.label}
315
+ </Tag>
316
+ )}
317
+ </td>
318
+ <td className="px-4 py-1.5">
319
+ {row.noStatus ? (
320
+ <span className="text-muted-foreground">-</span>
321
+ ) : (
322
+ <Tag variant="status" status={row.status} showIcon>
323
+ {row.label}
324
+ </Tag>
325
+ )}
326
+ </td>
327
+ </tr>
328
+ ))}
329
+ </tbody>
330
+ </table>
331
+ ```
332
+
188
333
  ### Palette
189
334
 
190
335
  装饰色 palette 9 预设 + 任意 CSS 值。
@@ -237,6 +382,55 @@ TagGroup 三档间距。
237
382
  </div>
238
383
  ```
239
384
 
385
+ ### Selectable
386
+
387
+ CheckableTagGroup 多选 / 单选 / outline 三种模式。
388
+
389
+ ```tsx
390
+ <div className="flex flex-col gap-4">
391
+ <div>
392
+ <h4 className="mb-2 text-xs text-muted-foreground">多选(默认)</h4>
393
+ <CheckableTagGroup
394
+ options={[
395
+ { label: 'React', value: 'react' },
396
+ { label: 'Vue', value: 'vue' },
397
+ { label: 'Angular', value: 'angular' },
398
+ { label: 'Svelte', value: 'svelte', disabled: true },
399
+ ]}
400
+ defaultValue={['react']}
401
+ />
402
+ </div>
403
+ <div>
404
+ <h4 className="mb-2 text-xs text-muted-foreground">
405
+ 单选(multiple = false)
406
+ </h4>
407
+ <CheckableTagGroup
408
+ multiple={false}
409
+ value={single}
410
+ onChange={setSingle}
411
+ options={[
412
+ { label: 'React', value: 'react' },
413
+ { label: 'Vue', value: 'vue' },
414
+ { label: 'Angular', value: 'angular' },
415
+ ]}
416
+ />
417
+ </div>
418
+ <div>
419
+ <h4 className="mb-2 text-xs text-muted-foreground">outline 模式</h4>
420
+ <CheckableTagGroup
421
+ variant="outline"
422
+ options={[
423
+ { label: 'React', value: 'react' },
424
+ { label: 'Vue', value: 'vue' },
425
+ { label: 'Angular', value: 'angular' },
426
+ { label: 'Svelte', value: 'svelte' },
427
+ ]}
428
+ defaultValue={['react', 'vue']}
429
+ />
430
+ </div>
431
+ </div>
432
+ ```
433
+
240
434
  ### AsChild
241
435
 
242
436
  asChild — 把 Tag 渲染为子元素(如链接)。
@@ -17,7 +17,7 @@ import { cn } from '@/lib/utils';
17
17
  // ─── Textarea cva ────────────────────────────────────────────────────────────
18
18
 
19
19
  const textareaVariants = cva(
20
- 'flex w-full cursor-text rounded-md border border-input bg-transparent font-normal field-sizing-content transition-colors outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/20 disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-1 aria-invalid:ring-destructive/20',
20
+ 'flex field-sizing-content w-full cursor-text rounded-md border border-input bg-transparent font-normal transition-colors outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/20 disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-1 aria-invalid:ring-destructive/20',
21
21
  {
22
22
  variants: {
23
23
  size: {
@@ -33,6 +33,14 @@
33
33
  </div>
34
34
  ```
35
35
 
36
+ ### Disabled
37
+
38
+ 添加 `disabled` 属性即可让多行输入框处于不可用状态。
39
+
40
+ ```tsx
41
+ <Textarea placeholder="不可编辑" disabled className="w-80" />
42
+ ```
43
+
36
44
  ### Invalid
37
45
 
38
46
  通过 `aria-invalid` 标记错误状态,用于表单校验失败时。
@@ -75,3 +83,22 @@ Textarea 默认使用 `field-sizing-content` 自动撑开高度,也可以通
75
83
  />
76
84
  </div>
77
85
  ```
86
+
87
+ ### WithShowCount
88
+
89
+ 配合 InputGroup + InputGroupShowCount 在右下角展示字符计数; 越界时计数索引会自动变为 destructive 色。
90
+
91
+ ```tsx
92
+ <InputGroup className="w-80">
93
+ <InputGroupTextarea
94
+ placeholder="请输入备注信息..."
95
+ value={value}
96
+ onChange={(e) => setValue(e.target.value)}
97
+ rows={4}
98
+ className="field-sizing-fixed"
99
+ />
100
+ <InputGroupAddon align="block-end" className="justify-end">
101
+ <InputGroupShowCount current={value.length} max={max} />
102
+ </InputGroupAddon>
103
+ </InputGroup>
104
+ ```
@@ -91,7 +91,7 @@ export const WithRows: Story = {
91
91
  * 越界时计数索引会自动变为 destructive 色。
92
92
  */
93
93
  export const WithShowCount: Story = {
94
- render: function WithShowCountStory() {
94
+ render: () => {
95
95
  const [value, setValue] = useState('');
96
96
  const max = 100;
97
97
  return (
@@ -217,7 +217,7 @@ function TimeColumn({
217
217
  ref={containerRef}
218
218
  role="listbox"
219
219
  aria-label={ariaLabel}
220
- className="absolute inset-0 overflow-y-auto px-1 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
220
+ className="absolute inset-0 [scrollbar-width:none] overflow-y-auto px-1 [&::-webkit-scrollbar]:hidden"
221
221
  >
222
222
  {values.map((v) => {
223
223
  const isDisabled = disabledSet.has(v);
@@ -233,7 +233,7 @@ function TimeColumn({
233
233
  disabled={isDisabled}
234
234
  onClick={() => !isDisabled && onChange(v)}
235
235
  className={cn(
236
- 'flex h-8 w-full cursor-pointer items-center justify-center rounded-md text-xs tabular-nums text-foreground transition-colors',
236
+ 'flex h-8 w-full cursor-pointer items-center justify-center rounded-md text-xs text-foreground tabular-nums transition-colors',
237
237
  'hover:bg-accent focus-visible:bg-accent focus-visible:outline-none',
238
238
  'data-[state=checked]:bg-accent data-[state=checked]:font-medium data-[state=checked]:text-primary',
239
239
  isDisabled &&
@@ -490,7 +490,7 @@ function TimePicker({
490
490
  placeholder={placeholder}
491
491
  disabled={disabled}
492
492
  readOnly={inputReadOnly}
493
- className="min-w-0 grow cursor-text bg-transparent text-foreground outline-none tabular-nums placeholder:text-muted-foreground disabled:cursor-not-allowed"
493
+ className="min-w-0 grow cursor-text bg-transparent text-foreground tabular-nums outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed"
494
494
  />
495
495
  {loading ? (
496
496
  <Spinner data-slot="time-picker-loading" size="md" tone="muted" />
@@ -79,6 +79,51 @@
79
79
  </div>
80
80
  ```
81
81
 
82
+ ### Controlled
83
+
84
+ 受控模式
85
+
86
+ ```tsx
87
+ <div className="flex flex-col gap-2">
88
+ <TimePicker value={value} onChange={setValue} />
89
+ <p className="text-xs text-muted-foreground">
90
+ 当前值: {value || '(空)'}
91
+ </p>
92
+ </div>
93
+ ```
94
+
95
+ ### HourMinute
96
+
97
+ 仅小时和分钟
98
+
99
+ ```tsx
100
+ <TimePicker format="HH:mm" placeholder="时:分" />
101
+ ```
102
+
103
+ ### MinuteStep
104
+
105
+ 步长:15 分钟间隔
106
+
107
+ ```tsx
108
+ <TimePicker minuteStep={15} placeholder="15分钟步长" />
109
+ ```
110
+
111
+ ### Disabled
112
+
113
+ 禁用状态
114
+
115
+ ```tsx
116
+ <TimePicker defaultValue="14:30:00" disabled />
117
+ ```
118
+
119
+ ### ErrorStatus
120
+
121
+ 错误状态
122
+
123
+ ```tsx
124
+ <TimePicker status="error" placeholder="校验失败" />
125
+ ```
126
+
82
127
  ### Loading
83
128
 
84
129
  加载态:右侧图标替换为 spinner,loading > clear > Clock 互斥。
@@ -100,3 +145,34 @@
100
145
  disabledHours={() => [0, 1, 2, 3, 4, 5, 6, 7, 8, 22, 23]}
101
146
  />
102
147
  ```
148
+
149
+ ### Panel
150
+
151
+ 独立使用 TimePickerPanel(可嵌入任意弹层或 DatePicker showTime)
152
+
153
+ ```tsx
154
+ <div className="inline-flex rounded-md border border-border">
155
+ <TimePickerPanel value={parts} onChange={setParts} />
156
+ </div>
157
+ ```
158
+
159
+ ### Range
160
+
161
+ 时间范围选择器
162
+
163
+ ```tsx
164
+ <TimeRangePicker />
165
+ ```
166
+
167
+ ### RangeControlled
168
+
169
+ 时间范围 - 受控
170
+
171
+ ```tsx
172
+ <div className="flex flex-col gap-2">
173
+ <TimeRangePicker value={value} onChange={setValue} />
174
+ <p className="text-xs text-muted-foreground">
175
+ {value[0] || '(空)'} ~ {value[1] || '(空)'}
176
+ </p>
177
+ </div>
178
+ ```
@@ -217,6 +217,7 @@ const timelineDotVariants = cva(
217
217
  variants: {
218
218
  state: {
219
219
  done: 'size-3 bg-muted-foreground/40',
220
+ // eslint-disable-next-line tailwindcss/no-contradicting-classname -- TW v4: outline=style, outline-3=width
220
221
  process: 'size-3 bg-primary outline outline-3 outline-primary/20',
221
222
  error: 'size-3 bg-destructive',
222
223
  success: 'size-3 bg-success',
@@ -15,7 +15,7 @@ import { Toggle as TogglePrimitive } from 'radix-ui';
15
15
  import { cn } from '@/lib/utils';
16
16
 
17
17
  const toggleVariants = cva(
18
- 'group/toggle inline-flex items-center justify-center gap-1 rounded-md text-xs font-medium whitespace-nowrap transition-all outline-none hover:bg-muted hover:text-foreground focus-visible:border-ring focus-visible:ring-ring/20 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 aria-pressed:bg-muted data-[state=on]:bg-muted cursor-pointer [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=size-])]:size-4',
18
+ 'group/toggle inline-flex cursor-pointer items-center justify-center gap-1 rounded-md text-xs font-medium whitespace-nowrap transition-all outline-none hover:bg-muted hover:text-foreground focus-visible:border-ring focus-visible:ring-ring/20 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 aria-pressed:bg-muted data-[state=on]:bg-muted [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=size-])]:size-4',
19
19
  {
20
20
  variants: {
21
21
  variant: {
@@ -108,7 +108,7 @@ function ToggleGroup({
108
108
  style={{ '--gap': spacing } as React.CSSProperties}
109
109
  className={cn(
110
110
  // eslint-disable-next-line teamix-evo/no-arbitrary-tw-value -- dynamic gap via CSS var and responsive radius clamping not tokenizable.
111
- 'group/toggle-group flex w-fit flex-row items-center gap-[--spacing(var(--gap))] rounded-md data-[size=sm]:rounded-md data-[orientation=vertical]:flex-col data-[orientation=vertical]:items-stretch',
111
+ 'group/toggle-group flex w-fit flex-row items-center gap-[--spacing(var(--gap))] rounded-md data-[orientation=vertical]:flex-col data-[orientation=vertical]:items-stretch data-[size=sm]:rounded-md',
112
112
  className,
113
113
  )}
114
114
  {...(props as ToggleGroupRootProps)}