@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.
- package/manifest.json +16 -7
- package/package.json +4 -4
- package/src/_design-system/theme-tokens/stories.tsx +2 -2
- package/src/components/accordion/index.tsx +1 -1
- package/src/components/affix/meta.md +26 -0
- package/src/components/alert/index.tsx +2 -2
- package/src/components/alert-dialog/index.tsx +3 -3
- package/src/components/alert-dialog/meta.md +52 -0
- package/src/components/alert-dialog/stories.tsx +45 -48
- package/src/components/avatar/index.tsx +1 -1
- package/src/components/badge/index.tsx +2 -2
- package/src/components/badge/meta.md +48 -0
- package/src/components/button/index.tsx +2 -2
- package/src/components/button/meta.md +15 -0
- package/src/components/button/stories.tsx +1 -1
- package/src/components/calendar/index.tsx +2 -2
- package/src/components/card/index.tsx +1 -1
- package/src/components/carousel/index.tsx +2 -2
- package/src/components/carousel/meta.md +34 -2
- package/src/components/carousel/stories.tsx +2 -2
- package/src/components/cascader-select/index.tsx +2 -1
- package/src/components/cascader-select/meta.md +46 -0
- package/src/components/checkbox/meta.md +47 -0
- package/src/components/color-picker/index.tsx +3 -3
- package/src/components/color-picker/meta.md +80 -0
- package/src/components/combobox/index.tsx +2 -2
- package/src/components/combobox/meta.md +130 -0
- package/src/components/data-table/index.tsx +3 -3
- package/src/components/data-table/meta.md +419 -0
- package/src/components/data-table/stories.tsx +4 -4
- package/src/components/date-picker/meta.md +91 -0
- package/src/components/descriptions/index.tsx +1 -1
- package/src/components/descriptions/meta.md +245 -0
- package/src/components/dialog/index.tsx +4 -4
- package/src/components/dialog/meta.md +47 -1
- package/src/components/dialog/stories.tsx +38 -41
- package/src/components/dropdown-menu/index.tsx +5 -5
- package/src/components/empty/index.tsx +2 -2
- package/src/components/field/index.tsx +4 -4
- package/src/components/filter-bar/index.tsx +6 -6
- package/src/components/filter-bar/meta.md +323 -0
- package/src/components/float-button/index.tsx +2 -2
- package/src/components/form/index.tsx +1 -1
- package/src/components/form/meta.md +119 -0
- package/src/components/hover-card/index.tsx +1 -1
- package/src/components/hover-card/meta.md +21 -0
- package/src/components/input/meta.md +16 -0
- package/src/components/input-group/index.tsx +1 -1
- package/src/components/input-group/meta.md +118 -0
- package/src/components/input-group/stories.tsx +6 -6
- package/src/components/input-ip/index.tsx +2 -2
- package/src/components/input-ip/meta.md +30 -0
- package/src/components/input-ip/stories.tsx +2 -2
- package/src/components/input-number/index.tsx +3 -2
- package/src/components/input-number/meta.md +67 -0
- package/src/components/input-number/stories.tsx +2 -2
- package/src/components/item/index.tsx +4 -4
- package/src/components/label/meta.md +8 -0
- package/src/components/mentions/meta.md +15 -0
- package/src/components/menubar/index.tsx +4 -4
- package/src/components/navigation-menu/index.tsx +4 -4
- package/src/components/page-header/index.tsx +2 -2
- package/src/components/page-header/meta.md +145 -0
- package/src/components/page-shell/index.tsx +3 -3
- package/src/components/pagination/index.tsx +1 -1
- package/src/components/pagination/meta.md +203 -0
- package/src/components/popconfirm/meta.md +45 -0
- package/src/components/popover/index.tsx +2 -2
- package/src/components/popover/meta.md +47 -0
- package/src/components/progress/index.tsx +1 -1
- package/src/components/progress/meta.md +36 -0
- package/src/components/progress/stories.tsx +1 -1
- package/src/components/radio-group/meta.md +69 -0
- package/src/components/rate/index.tsx +1 -1
- package/src/components/rate/meta.md +50 -0
- package/src/components/resizable/index.tsx +1 -1
- package/src/components/select/index.tsx +2 -2
- package/src/components/select/meta.md +20 -0
- package/src/components/separator/index.tsx +1 -1
- package/src/components/sheet/index.tsx +13 -14
- package/src/components/sheet/meta.md +124 -0
- package/src/components/sheet/stories.tsx +110 -119
- package/src/components/sidebar/index.tsx +5 -5
- package/src/components/sidebar/meta.md +383 -0
- package/src/components/skeleton/meta.md +13 -0
- package/src/components/slider/index.tsx +2 -2
- package/src/components/sonner/meta.md +86 -0
- package/src/components/spinner/meta.md +46 -0
- package/src/components/spinner/stories.tsx +2 -2
- package/src/components/steps/meta.md +20 -0
- package/src/components/steps/stories.tsx +1 -1
- package/src/components/switch/index.tsx +2 -2
- package/src/components/switch/meta.md +33 -0
- package/src/components/table/index.tsx +4 -4
- package/src/components/table/meta.md +11 -0
- package/src/components/tabs/index.tsx +7 -7
- package/src/components/tabs/meta.md +52 -0
- package/src/components/tag/index.tsx +8 -8
- package/src/components/tag/meta.md +194 -0
- package/src/components/textarea/index.tsx +1 -1
- package/src/components/textarea/meta.md +27 -0
- package/src/components/textarea/stories.tsx +1 -1
- package/src/components/time-picker/index.tsx +3 -3
- package/src/components/time-picker/meta.md +76 -0
- package/src/components/timeline/index.tsx +1 -0
- package/src/components/toggle/index.tsx +1 -1
- package/src/components/toggle-group/index.tsx +1 -1
- package/src/components/tooltip/index.tsx +1 -1
- package/src/components/tooltip/meta.md +23 -0
- package/src/components/transfer/index.tsx +2 -2
- package/src/components/transfer/meta.md +97 -0
- package/src/components/tree/index.tsx +245 -15
- package/src/components/tree/meta.md +151 -0
- package/src/components/tree-select/index.tsx +16 -2
- package/src/components/tree-select/meta.md +150 -0
- package/src/components/typography/index.tsx +3 -3
- package/src/components/upload/index.tsx +3 -3
- package/src/components/upload/meta.md +82 -0
- package/src/components/tree/utils.ts +0 -269
- package/src/examples/built-in-assets/stories.tsx +0 -572
- 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-
|
|
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' ? '
|
|
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' ? '
|
|
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 -
|
|
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
|
|
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:
|
|
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
|
|
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-[
|
|
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:
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
648
|
-
'focus-visible:
|
|
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
|
|
652
|
-
: 'border-border text-foreground
|
|
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 = "tag"(整体可点击关闭)
|
|
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
|
|
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
|
+
```
|
|
@@ -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 [
|
|
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
|
|
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
|
|
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
|
|
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-[
|
|
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)}
|