@teamix-evo/ui 0.5.2 → 0.6.1
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/package.json +4 -4
- package/src/components/alert/index.tsx +1 -1
- package/src/components/alert-dialog/index.tsx +17 -24
- package/src/components/alert-dialog/meta.md +102 -8
- package/src/components/alert-dialog/stories.tsx +117 -7
- package/src/components/avatar/index.tsx +1 -1
- package/src/components/badge/index.tsx +1 -1
- package/src/components/button/index.tsx +1 -29
- package/src/components/button/meta.md +24 -13
- package/src/components/button/stories.tsx +21 -12
- package/src/components/button-group/meta.md +6 -9
- package/src/components/button-group/stories.tsx +2 -6
- package/src/components/calendar/index.tsx +12 -7
- package/src/components/cascader-select/index.tsx +1 -1
- package/src/components/checkbox/index.tsx +1 -1
- package/src/components/combobox/index.tsx +54 -10
- package/src/components/combobox/meta.md +3 -5
- package/src/components/combobox/stories.tsx +104 -25
- package/src/components/data-table/stories.tsx +4 -1
- package/src/components/date-picker/index.tsx +25 -2
- package/src/components/field/index.tsx +1 -1
- package/src/components/filter-bar/index.tsx +1 -1
- package/src/components/float-button/meta.md +3 -15
- package/src/components/icon/index.tsx +3 -4
- package/src/components/icon/meta.md +1 -2
- package/src/components/input/index.tsx +10 -2
- package/src/components/input-group/index.tsx +3 -3
- package/src/components/input-group/meta.md +15 -0
- package/src/components/input-group/stories.tsx +14 -0
- package/src/components/input-ip/index.tsx +1 -1
- package/src/components/input-number/index.tsx +5 -5
- package/src/components/item/meta.md +11 -11
- package/src/components/radio-group/index.tsx +1 -1
- package/src/components/rate/index.tsx +3 -3
- package/src/components/select/index.tsx +2 -2
- package/src/components/sidebar/index.tsx +4 -4
- package/src/components/skeleton/index.tsx +1 -1
- package/src/components/skeleton/meta.md +6 -6
- package/src/components/skeleton/stories.tsx +8 -8
- package/src/components/slider/index.tsx +27 -1
- package/src/components/sonner/index.tsx +43 -40
- package/src/components/sonner/meta.md +84 -68
- package/src/components/sonner/stories.tsx +122 -83
- package/src/components/spinner/index.tsx +170 -0
- package/src/components/spinner/meta.md +27 -1
- package/src/components/spinner/stories.tsx +23 -0
- package/src/components/steps/index.tsx +5 -1
- package/src/components/switch/index.tsx +1 -1
- package/src/components/tag/index.tsx +14 -0
- package/src/components/tag/meta.md +1 -0
- package/src/components/tag/stories.tsx +13 -0
- package/src/components/textarea/index.tsx +1 -1
- package/src/components/textarea/stories.tsx +1 -1
- package/src/components/time-picker/index.tsx +3 -1
- package/src/components/toggle/index.tsx +1 -1
- package/src/components/tooltip/index.tsx +5 -1
- package/src/components/tooltip/meta.md +13 -28
- package/src/components/tooltip/stories.tsx +11 -28
- package/src/components/tree-select/index.tsx +1 -1
|
@@ -35,16 +35,8 @@
|
|
|
35
35
|
|
|
36
36
|
```tsx
|
|
37
37
|
<DemoContainer>
|
|
38
|
-
<FloatButton
|
|
39
|
-
|
|
40
|
-
aria-label="客服"
|
|
41
|
-
badge="3"
|
|
42
|
-
/>
|
|
43
|
-
<FloatButton
|
|
44
|
-
variant="default"
|
|
45
|
-
icon={<PlusIcon />}
|
|
46
|
-
aria-label="新建"
|
|
47
|
-
/>
|
|
38
|
+
<FloatButton icon={<MessageCircleIcon />} aria-label="客服" badge="3" />
|
|
39
|
+
<FloatButton variant="default" icon={<PlusIcon />} aria-label="新建" />
|
|
48
40
|
</DemoContainer>
|
|
49
41
|
```
|
|
50
42
|
|
|
@@ -54,11 +46,7 @@
|
|
|
54
46
|
|
|
55
47
|
```tsx
|
|
56
48
|
<DemoContainer>
|
|
57
|
-
<FloatButton
|
|
58
|
-
shape="square"
|
|
59
|
-
icon={<PlusIcon />}
|
|
60
|
-
aria-label="新建"
|
|
61
|
-
/>
|
|
49
|
+
<FloatButton shape="square" icon={<PlusIcon />} aria-label="新建" />
|
|
62
50
|
</DemoContainer>
|
|
63
51
|
```
|
|
64
52
|
|
|
@@ -20,8 +20,7 @@
|
|
|
20
20
|
* <span className="text-success"><SuccessFilledIcon /></span>
|
|
21
21
|
* ```
|
|
22
22
|
*
|
|
23
|
-
* 尺寸:默认 `size-
|
|
24
|
-
* viewBox 采用 `-1 -1 22 22`,使图形在容器内自带 1px 内边距留白。
|
|
23
|
+
* 尺寸:默认 `size-4`(16px),通过 `className` 覆盖(如 `size-3.5` / `size-5`)。
|
|
25
24
|
*/
|
|
26
25
|
import * as React from 'react';
|
|
27
26
|
|
|
@@ -37,9 +36,9 @@ function BaseFilledIcon({
|
|
|
37
36
|
return (
|
|
38
37
|
<svg
|
|
39
38
|
data-slot="icon"
|
|
40
|
-
viewBox="
|
|
39
|
+
viewBox="0 0 20 20"
|
|
41
40
|
aria-hidden="true"
|
|
42
|
-
className={cn('size-
|
|
41
|
+
className={cn('size-4 shrink-0', className)}
|
|
43
42
|
{...props}
|
|
44
43
|
>
|
|
45
44
|
{children}
|
|
@@ -22,8 +22,7 @@
|
|
|
22
22
|
- ```tsx
|
|
23
23
|
- <span className="text-success"><SuccessFilledIcon /></span>
|
|
24
24
|
- ```
|
|
25
|
-
- 尺寸:默认 `size-
|
|
26
|
-
- viewBox 采用 `-1 -1 22 22`,使图形在容器内自带 1px 内边距留白。
|
|
25
|
+
- 尺寸:默认 `size-4`(16px),通过 `className` 覆盖(如 `size-3.5` / `size-5`)。
|
|
27
26
|
|
|
28
27
|
## 示例
|
|
29
28
|
|
|
@@ -46,11 +46,19 @@ export interface InputProps
|
|
|
46
46
|
// ─── Input ──────────────────────────────────────────────────────────────────
|
|
47
47
|
|
|
48
48
|
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
49
|
-
({ className, type, size, ...props }, ref) => {
|
|
49
|
+
({ className, type, size, role: roleProp, ...props }, ref) => {
|
|
50
|
+
// <input type="search"> 会被浏览器渲染原生 clear / 放大镜 / IE Reveal 按钮,
|
|
51
|
+
// 与 Input 自带的 `clearable` / `suffix={<Search/>}` 视觉冲突。组件层透明屏蔽:
|
|
52
|
+
// 渲染为 type="text" + role="searchbox",保留无障碍语义,免除业务侧 globals.css
|
|
53
|
+
// 补 webkit/ms 伪元素 reset(原 ADR 0023 §4 “手动加 CSS” 路径)。
|
|
54
|
+
const isSearch = type === 'search';
|
|
55
|
+
const safeType = isSearch ? 'text' : type;
|
|
56
|
+
const role = isSearch ? roleProp ?? 'searchbox' : roleProp;
|
|
50
57
|
return (
|
|
51
58
|
<input
|
|
52
59
|
ref={ref}
|
|
53
|
-
type={
|
|
60
|
+
type={safeType}
|
|
61
|
+
role={role}
|
|
54
62
|
data-slot="input"
|
|
55
63
|
className={cn(inputVariants({ size, className }))}
|
|
56
64
|
{...props}
|
|
@@ -32,7 +32,7 @@ const InputGroup = React.forwardRef<
|
|
|
32
32
|
data-slot="input-group"
|
|
33
33
|
role="group"
|
|
34
34
|
className={cn(
|
|
35
|
-
'group/input-group relative flex h-8 w-full min-w-0 items-center rounded-md border border-input transition-colors outline-none has-disabled:bg-input/50 has-disabled:opacity-50 has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-
|
|
35
|
+
'group/input-group relative flex h-8 w-full min-w-0 items-center rounded-md border border-input transition-colors outline-none has-disabled:bg-input/50 has-disabled:opacity-50 has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-ring/20 has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot][aria-invalid=true]]:ring-1 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>textarea]:h-auto has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=inline-start]]:[&>input]:pl-1.5',
|
|
36
36
|
className,
|
|
37
37
|
)}
|
|
38
38
|
{...props}
|
|
@@ -136,7 +136,7 @@ function InputGroupInput({ className, ...props }: InputProps) {
|
|
|
136
136
|
<Input
|
|
137
137
|
data-slot="input-group-control"
|
|
138
138
|
className={cn(
|
|
139
|
-
'flex-1 rounded-none border-0 bg-transparent shadow-none ring-0 focus-visible:ring-0 disabled:bg-transparent aria-invalid:ring-0',
|
|
139
|
+
'h-full flex-1 rounded-none border-0 bg-transparent shadow-none ring-0 focus-visible:ring-0 disabled:bg-transparent aria-invalid:ring-0',
|
|
140
140
|
className,
|
|
141
141
|
)}
|
|
142
142
|
{...props}
|
|
@@ -278,7 +278,7 @@ function InputGroupShowCount({
|
|
|
278
278
|
data-slot="input-group-show-count"
|
|
279
279
|
data-overflow={overflow || undefined}
|
|
280
280
|
className={cn(
|
|
281
|
-
'flex items-center text-xs tabular-nums text-muted-foreground select-none',
|
|
281
|
+
'flex items-center text-xs tabular-nums text-muted-foreground/60 select-none',
|
|
282
282
|
'data-[overflow]:text-destructive',
|
|
283
283
|
className,
|
|
284
284
|
)}
|
|
@@ -44,6 +44,21 @@
|
|
|
44
44
|
</InputGroup>
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
+
### WithTrailingIcon
|
|
48
|
+
|
|
49
|
+
后置图标插槽:搜索图标在右侧。
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
<InputGroup className="w-64">
|
|
53
|
+
<InputGroupInput placeholder="搜索..." />
|
|
54
|
+
<InputGroupAddon align="inline-end">
|
|
55
|
+
<InputGroupText>
|
|
56
|
+
<SearchIcon />
|
|
57
|
+
</InputGroupText>
|
|
58
|
+
</InputGroupAddon>
|
|
59
|
+
</InputGroup>
|
|
60
|
+
```
|
|
61
|
+
|
|
47
62
|
### Email
|
|
48
63
|
|
|
49
64
|
邮箱输入:左侧邮件图标 + 右侧域名后缀文本。
|
|
@@ -44,6 +44,20 @@ export const WithLeadingIcon: Story = {
|
|
|
44
44
|
),
|
|
45
45
|
};
|
|
46
46
|
|
|
47
|
+
/** 后置图标插槽:搜索图标在右侧。 */
|
|
48
|
+
export const WithTrailingIcon: Story = {
|
|
49
|
+
render: () => (
|
|
50
|
+
<InputGroup className="w-64">
|
|
51
|
+
<InputGroupInput placeholder="搜索..." />
|
|
52
|
+
<InputGroupAddon align="inline-end">
|
|
53
|
+
<InputGroupText>
|
|
54
|
+
<SearchIcon />
|
|
55
|
+
</InputGroupText>
|
|
56
|
+
</InputGroupAddon>
|
|
57
|
+
</InputGroup>
|
|
58
|
+
),
|
|
59
|
+
};
|
|
60
|
+
|
|
47
61
|
/**
|
|
48
62
|
* 搜索场景:左侧 SearchIcon + 右侧 InputGroupClear 受控清除按钮。
|
|
49
63
|
* 仅当 value 非空时显示清除按钮。
|
|
@@ -27,7 +27,7 @@ const inputIpVariants = cva(
|
|
|
27
27
|
// `w-fit` 显式锁定“宽度 = 内容”,避免被 flex/grid 父容器 cross-axis stretch 拉满。
|
|
28
28
|
// 消费方需要铺满父容器时,可通过 className="w-full" 覆盖。
|
|
29
29
|
'inline-flex w-fit items-center rounded-md border border-input bg-transparent transition-colors',
|
|
30
|
-
'focus-within:border-ring focus-within:ring-
|
|
30
|
+
'focus-within:border-ring focus-within:ring-ring/20',
|
|
31
31
|
'aria-[invalid=true]:border-destructive aria-[invalid=true]:ring-1 aria-[invalid=true]:ring-destructive/20',
|
|
32
32
|
].join(' '),
|
|
33
33
|
{
|
|
@@ -21,8 +21,8 @@ import { cn } from '@/lib/utils';
|
|
|
21
21
|
|
|
22
22
|
const inputNumberVariants = cva(
|
|
23
23
|
[
|
|
24
|
-
'inline-flex w-full min-w-0 items-stretch overflow-hidden rounded-md border border-input bg-transparent transition-colors',
|
|
25
|
-
'focus-within:border-ring focus-within:ring-
|
|
24
|
+
'group/input-number inline-flex w-full min-w-0 items-stretch overflow-hidden rounded-md border border-input bg-transparent transition-colors',
|
|
25
|
+
'focus-within:border-ring focus-within:ring-ring/20',
|
|
26
26
|
'has-[input:disabled]:cursor-not-allowed has-[input:disabled]:bg-input/50 has-[input:disabled]:opacity-50',
|
|
27
27
|
'has-[input[aria-invalid=true]]:border-destructive has-[input[aria-invalid=true]]:ring-1 has-[input[aria-invalid=true]]:ring-destructive/20',
|
|
28
28
|
].join(' '),
|
|
@@ -253,7 +253,7 @@ const InputNumber = React.forwardRef<HTMLInputElement, InputNumberProps>(
|
|
|
253
253
|
};
|
|
254
254
|
|
|
255
255
|
const stepBtnClass =
|
|
256
|
-
'flex cursor-pointer items-center justify-center text-muted-foreground hover:bg-
|
|
256
|
+
'flex cursor-pointer items-center justify-center text-muted-foreground hover:bg-muted disabled:cursor-not-allowed disabled:opacity-30 disabled:hover:bg-transparent';
|
|
257
257
|
|
|
258
258
|
const upDisabled =
|
|
259
259
|
disabled ||
|
|
@@ -325,7 +325,7 @@ const InputNumber = React.forwardRef<HTMLInputElement, InputNumberProps>(
|
|
|
325
325
|
{controls && variant === 'default' ? (
|
|
326
326
|
<div
|
|
327
327
|
data-slot="input-number-handlers"
|
|
328
|
-
className="flex flex-col border-l border-input"
|
|
328
|
+
className="flex flex-col divide-y divide-input border-l border-input opacity-0 transition-opacity group-hover/input-number:opacity-100 group-focus-within/input-number:opacity-100"
|
|
329
329
|
>
|
|
330
330
|
<button
|
|
331
331
|
type="button"
|
|
@@ -345,7 +345,7 @@ const InputNumber = React.forwardRef<HTMLInputElement, InputNumberProps>(
|
|
|
345
345
|
onClick={() => stepBy(-1)}
|
|
346
346
|
aria-label="减少"
|
|
347
347
|
data-slot="input-number-decrement"
|
|
348
|
-
className={cn(stepBtnClass, 'h-1/2
|
|
348
|
+
className={cn(stepBtnClass, 'h-1/2 px-1.5')}
|
|
349
349
|
>
|
|
350
350
|
<ChevronDown className="size-3.5" />
|
|
351
351
|
</button>
|
|
@@ -16,20 +16,20 @@
|
|
|
16
16
|
- 图文卡片(horizontal 布局)
|
|
17
17
|
- 通知/消息列表
|
|
18
18
|
- 带操作的资源列表
|
|
19
|
-
- 组合结构:ItemGroup [bordered] [divider] > ItemGroupHeader + Item
|
|
19
|
+
- 组合结构:ItemGroup [bordered] [divider] > ItemGroupHeader + Item* > ItemMedia + ItemContent (> ItemTitle + ItemDescription) + ItemExtra + ItemActions [separated] > ItemGroupFooter
|
|
20
20
|
|
|
21
21
|
## Props
|
|
22
22
|
|
|
23
|
-
| 名称
|
|
24
|
-
|
|
|
25
|
-
| `bordered`
|
|
26
|
-
| `divider`
|
|
27
|
-
| `variant`
|
|
28
|
-
| `size`
|
|
29
|
-
| `layout`
|
|
30
|
-
| `asChild`
|
|
31
|
-
| `variant`
|
|
32
|
-
| `separated` | `boolean`
|
|
23
|
+
| 名称 | 类型 | 默认值 | 必填 | 说明 |
|
|
24
|
+
| --- | --- | --- | --- | --- |
|
|
25
|
+
| `bordered` | `"true" \| "false"` | – | – | – |
|
|
26
|
+
| `divider` | `"true" \| "false"` | – | – | – |
|
|
27
|
+
| `variant` | `"default" \| "outline" \| "muted"` | `"default"` | – | – |
|
|
28
|
+
| `size` | `"default" \| "sm" \| "xs"` | `"default"` | – | – |
|
|
29
|
+
| `layout` | `"vertical" \| "horizontal"` | `"vertical"` | – | – |
|
|
30
|
+
| `asChild` | `boolean` | – | – | 使用 Slot 模式渲染子元素 |
|
|
31
|
+
| `variant` | `"default" \| "icon" \| "image" \| "cover"` | `"default"` | – | – |
|
|
32
|
+
| `separated` | `boolean` | – | – | 子元素之间自动插入竖向分隔线 |
|
|
33
33
|
|
|
34
34
|
## 示例
|
|
35
35
|
|
|
@@ -60,7 +60,7 @@ function RadioGroupItem({ className, ...props }: RadioGroupItemProps) {
|
|
|
60
60
|
<RadioGroupPrimitive.Item
|
|
61
61
|
data-slot="radio-group-item"
|
|
62
62
|
className={cn(
|
|
63
|
-
'peer relative flex aspect-square size-4 shrink-0 cursor-pointer items-center justify-center rounded-full border border-input shadow-xs transition-[color,box-shadow] outline-none after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-
|
|
63
|
+
'peer relative flex aspect-square size-4 shrink-0 cursor-pointer items-center justify-center rounded-full border border-input shadow-xs transition-[color,box-shadow] outline-none after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-ring/20 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-1 aria-invalid:ring-destructive/20 data-[state=checked]:border-primary data-[state=checked]:text-primary',
|
|
64
64
|
className,
|
|
65
65
|
)}
|
|
66
66
|
{...props}
|
|
@@ -232,7 +232,7 @@ function Rate({
|
|
|
232
232
|
rateItemVariants({ size }),
|
|
233
233
|
'inline-flex items-center justify-center',
|
|
234
234
|
state === 'empty' && 'text-border',
|
|
235
|
-
state !== 'empty' && 'text-
|
|
235
|
+
state !== 'empty' && 'text-primary',
|
|
236
236
|
)}
|
|
237
237
|
>
|
|
238
238
|
{state === 'half' ? (
|
|
@@ -241,7 +241,7 @@ function Rate({
|
|
|
241
241
|
{character}
|
|
242
242
|
</span>
|
|
243
243
|
<span
|
|
244
|
-
className="absolute inset-0 inline-flex items-center justify-center text-
|
|
244
|
+
className="absolute inset-0 inline-flex items-center justify-center text-primary"
|
|
245
245
|
style={{ clipPath: 'inset(0 50% 0 0)' }}
|
|
246
246
|
>
|
|
247
247
|
{character}
|
|
@@ -261,7 +261,7 @@ function Rate({
|
|
|
261
261
|
{Icon}
|
|
262
262
|
{state !== 'empty' && (
|
|
263
263
|
<span
|
|
264
|
-
className={cn('absolute inset-0 inline-block text-
|
|
264
|
+
className={cn('absolute inset-0 inline-block text-primary')}
|
|
265
265
|
style={
|
|
266
266
|
state === 'half'
|
|
267
267
|
? { clipPath: 'inset(0 50% 0 0)' }
|
|
@@ -112,7 +112,7 @@ function SelectTrigger({
|
|
|
112
112
|
data-size={size}
|
|
113
113
|
data-loading={loading ? 'true' : undefined}
|
|
114
114
|
className={cn(
|
|
115
|
-
"flex w-fit cursor-pointer items-center justify-between gap-1.5 rounded-md border border-input bg-transparent py-2 pr-2 pl-2.5 text-xs whitespace-nowrap transition-colors outline-none select-none focus
|
|
115
|
+
"flex w-fit cursor-pointer items-center justify-between gap-1.5 rounded-md border border-input bg-transparent py-2 pr-2 pl-2.5 text-xs whitespace-nowrap transition-colors outline-none select-none focus:border-ring focus:ring-ring/20 data-[state=open]:border-ring disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-1 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=lg]:h-9 data-[size=lg]:text-sm *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
116
116
|
className,
|
|
117
117
|
)}
|
|
118
118
|
{...props}
|
|
@@ -198,7 +198,7 @@ function SelectContent({
|
|
|
198
198
|
className={cn(
|
|
199
199
|
// 面板高度上限 `max-h-80`(20rem ≈ 10 个选项),避免选项多时面板过长;
|
|
200
200
|
// 配合 `overflow-y-auto` 使用浏览器原生滚动条,不再渲染 ScrollUp/Down 按钮。
|
|
201
|
-
'relative z-50 max-h-80 min-w-(--radix-select-trigger-width) origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[align-trigger=true]:animate-none data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95
|
|
201
|
+
'relative z-50 max-h-80 min-w-(--radix-select-trigger-width) origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[align-trigger=true]:animate-none data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
|
|
202
202
|
position === 'popper' &&
|
|
203
203
|
'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
|
|
204
204
|
className,
|
|
@@ -33,7 +33,7 @@ import {
|
|
|
33
33
|
TooltipProvider,
|
|
34
34
|
TooltipTrigger,
|
|
35
35
|
} from '@/components/tooltip';
|
|
36
|
-
import {
|
|
36
|
+
import { PanelLeftClose, PanelLeftOpen } from 'lucide-react';
|
|
37
37
|
|
|
38
38
|
const SIDEBAR_COOKIE_NAME = 'sidebar_state';
|
|
39
39
|
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
|
|
@@ -268,7 +268,7 @@ function SidebarTrigger({
|
|
|
268
268
|
onClick,
|
|
269
269
|
...props
|
|
270
270
|
}: React.ComponentProps<typeof Button>) {
|
|
271
|
-
const { toggleSidebar } = useSidebar();
|
|
271
|
+
const { toggleSidebar, open } = useSidebar();
|
|
272
272
|
|
|
273
273
|
return (
|
|
274
274
|
<Button
|
|
@@ -276,14 +276,14 @@ function SidebarTrigger({
|
|
|
276
276
|
data-slot="sidebar-trigger"
|
|
277
277
|
variant="ghost"
|
|
278
278
|
size="icon"
|
|
279
|
-
className={cn(className)}
|
|
279
|
+
className={cn('text-muted-foreground', className)}
|
|
280
280
|
onClick={(event) => {
|
|
281
281
|
onClick?.(event);
|
|
282
282
|
toggleSidebar();
|
|
283
283
|
}}
|
|
284
284
|
{...props}
|
|
285
285
|
>
|
|
286
|
-
<
|
|
286
|
+
{open ? <PanelLeftClose /> : <PanelLeftOpen />}
|
|
287
287
|
<span className="sr-only">Toggle Sidebar</span>
|
|
288
288
|
</Button>
|
|
289
289
|
);
|
|
@@ -204,7 +204,7 @@ function SkeletonImage({
|
|
|
204
204
|
className={cn(skeletonImageVariants({ size, block }), className)}
|
|
205
205
|
{...props}
|
|
206
206
|
>
|
|
207
|
-
{icon ?? <ImageIcon aria-hidden="true" />}
|
|
207
|
+
{icon ?? <ImageIcon strokeWidth={1.5} aria-hidden="true" />}
|
|
208
208
|
</Skeleton>
|
|
209
209
|
);
|
|
210
210
|
}
|
|
@@ -52,19 +52,19 @@ SkeletonInput / SkeletonTitle / SkeletonParagraph(独立子家族,可单独
|
|
|
52
52
|
```tsx
|
|
53
53
|
<div className="flex flex-col gap-3">
|
|
54
54
|
<div className="flex items-center gap-3">
|
|
55
|
-
<span className="w-24 text-muted-foreground">text</span>
|
|
55
|
+
<span className="w-24 text-xs text-muted-foreground">text</span>
|
|
56
56
|
<Skeleton variant="text" className="max-w-64" />
|
|
57
57
|
</div>
|
|
58
58
|
<div className="flex items-center gap-3">
|
|
59
|
-
<span className="w-24 text-muted-foreground">rectangular</span>
|
|
59
|
+
<span className="w-24 text-xs text-muted-foreground">rectangular</span>
|
|
60
60
|
<Skeleton variant="rectangular" className="h-4 w-64" />
|
|
61
61
|
</div>
|
|
62
62
|
<div className="flex items-center gap-3">
|
|
63
|
-
<span className="w-24 text-muted-foreground">rounded</span>
|
|
63
|
+
<span className="w-24 text-xs text-muted-foreground">rounded</span>
|
|
64
64
|
<Skeleton variant="rounded" className="h-4 w-64" />
|
|
65
65
|
</div>
|
|
66
66
|
<div className="flex items-center gap-3">
|
|
67
|
-
<span className="w-24 text-muted-foreground">circular</span>
|
|
67
|
+
<span className="w-24 text-xs text-muted-foreground">circular</span>
|
|
68
68
|
<Skeleton variant="circular" className="size-10" />
|
|
69
69
|
</div>
|
|
70
70
|
</div>
|
|
@@ -77,11 +77,11 @@ pulse / none 两种动画对照。
|
|
|
77
77
|
```tsx
|
|
78
78
|
<div className="flex flex-col gap-3">
|
|
79
79
|
<div className="flex items-center gap-3">
|
|
80
|
-
<span className="w-16 text-muted-foreground">pulse</span>
|
|
80
|
+
<span className="w-16 text-xs text-muted-foreground">pulse</span>
|
|
81
81
|
<Skeleton animation="pulse" className="h-4 w-64" />
|
|
82
82
|
</div>
|
|
83
83
|
<div className="flex items-center gap-3">
|
|
84
|
-
<span className="w-16 text-muted-foreground">none</span>
|
|
84
|
+
<span className="w-16 text-xs text-muted-foreground">none</span>
|
|
85
85
|
<Skeleton animation="none" className="h-4 w-64" />
|
|
86
86
|
</div>
|
|
87
87
|
</div>
|
|
@@ -31,19 +31,19 @@ export const Variants: Story = {
|
|
|
31
31
|
render: () => (
|
|
32
32
|
<div className="flex flex-col gap-3">
|
|
33
33
|
<div className="flex items-center gap-3">
|
|
34
|
-
<span className="w-24 text-muted-foreground">text</span>
|
|
34
|
+
<span className="w-24 text-xs text-muted-foreground">text</span>
|
|
35
35
|
<Skeleton variant="text" className="max-w-64" />
|
|
36
36
|
</div>
|
|
37
37
|
<div className="flex items-center gap-3">
|
|
38
|
-
<span className="w-24 text-muted-foreground">rectangular</span>
|
|
38
|
+
<span className="w-24 text-xs text-muted-foreground">rectangular</span>
|
|
39
39
|
<Skeleton variant="rectangular" className="h-4 w-64" />
|
|
40
40
|
</div>
|
|
41
41
|
<div className="flex items-center gap-3">
|
|
42
|
-
<span className="w-24 text-muted-foreground">rounded</span>
|
|
42
|
+
<span className="w-24 text-xs text-muted-foreground">rounded</span>
|
|
43
43
|
<Skeleton variant="rounded" className="h-4 w-64" />
|
|
44
44
|
</div>
|
|
45
45
|
<div className="flex items-center gap-3">
|
|
46
|
-
<span className="w-24 text-muted-foreground">circular</span>
|
|
46
|
+
<span className="w-24 text-xs text-muted-foreground">circular</span>
|
|
47
47
|
<Skeleton variant="circular" className="size-10" />
|
|
48
48
|
</div>
|
|
49
49
|
</div>
|
|
@@ -55,11 +55,11 @@ export const Animations: Story = {
|
|
|
55
55
|
render: () => (
|
|
56
56
|
<div className="flex flex-col gap-3">
|
|
57
57
|
<div className="flex items-center gap-3">
|
|
58
|
-
<span className="w-16 text-muted-foreground">pulse</span>
|
|
58
|
+
<span className="w-16 text-xs text-muted-foreground">pulse</span>
|
|
59
59
|
<Skeleton animation="pulse" className="h-4 w-64" />
|
|
60
60
|
</div>
|
|
61
61
|
<div className="flex items-center gap-3">
|
|
62
|
-
<span className="w-16 text-muted-foreground">none</span>
|
|
62
|
+
<span className="w-16 text-xs text-muted-foreground">none</span>
|
|
63
63
|
<Skeleton animation="none" className="h-4 w-64" />
|
|
64
64
|
</div>
|
|
65
65
|
</div>
|
|
@@ -84,8 +84,8 @@ export const Loading: Story = {
|
|
|
84
84
|
return (
|
|
85
85
|
<div className="flex flex-col gap-3">
|
|
86
86
|
<Button onClick={() => setLoading((v) => !v)}>切换</Button>
|
|
87
|
-
<Skeleton loading={loading} className="h-
|
|
88
|
-
<span className="text-foreground">真实内容已加载</span>
|
|
87
|
+
<Skeleton loading={loading} className="h-8 w-64">
|
|
88
|
+
<span className="text-xs text-foreground">真实内容已加载</span>
|
|
89
89
|
</Skeleton>
|
|
90
90
|
</div>
|
|
91
91
|
);
|
|
@@ -78,8 +78,9 @@ function Slider({
|
|
|
78
78
|
showTooltip = 'auto',
|
|
79
79
|
tooltipFormatter,
|
|
80
80
|
reverse = false,
|
|
81
|
+
onValueChange,
|
|
81
82
|
...props
|
|
82
|
-
}: SliderProps) {
|
|
83
|
+
}: SliderProps & { onValueChange?: (value: number[]) => void }) {
|
|
83
84
|
const _values = React.useMemo(
|
|
84
85
|
() =>
|
|
85
86
|
Array.isArray(value)
|
|
@@ -90,6 +91,29 @@ function Slider({
|
|
|
90
91
|
[value, defaultValue, min, max],
|
|
91
92
|
);
|
|
92
93
|
|
|
94
|
+
// Track current slider values for mark in-range detection
|
|
95
|
+
const [currentValues, setCurrentValues] = React.useState(_values);
|
|
96
|
+
React.useEffect(() => {
|
|
97
|
+
if (value) setCurrentValues(Array.isArray(value) ? value : [value]);
|
|
98
|
+
}, [value]);
|
|
99
|
+
|
|
100
|
+
const handleValueChange = React.useCallback(
|
|
101
|
+
(vals: number[]) => {
|
|
102
|
+
setCurrentValues(vals);
|
|
103
|
+
onValueChange?.(vals);
|
|
104
|
+
},
|
|
105
|
+
[onValueChange],
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
const isInRange = (markValue: number) => {
|
|
109
|
+
const first = currentValues[0] ?? min;
|
|
110
|
+
if (currentValues.length === 1) {
|
|
111
|
+
return reverse ? markValue >= first : markValue <= first;
|
|
112
|
+
}
|
|
113
|
+
const last = currentValues[currentValues.length - 1] ?? max;
|
|
114
|
+
return markValue >= first && markValue <= last;
|
|
115
|
+
};
|
|
116
|
+
|
|
93
117
|
const parsedMarks = React.useMemo(
|
|
94
118
|
() => parseMarks(marks, min, max),
|
|
95
119
|
[marks, min, max],
|
|
@@ -178,6 +202,7 @@ function Slider({
|
|
|
178
202
|
min={min}
|
|
179
203
|
max={max}
|
|
180
204
|
orientation={orientation}
|
|
205
|
+
onValueChange={handleValueChange}
|
|
181
206
|
className={cn(
|
|
182
207
|
'group/slider relative flex w-full touch-none items-center select-none data-disabled:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-40 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col',
|
|
183
208
|
className,
|
|
@@ -196,6 +221,7 @@ function Slider({
|
|
|
196
221
|
<span
|
|
197
222
|
key={`mark-${m.value}`}
|
|
198
223
|
data-slot="slider-mark"
|
|
224
|
+
data-in-range={isInRange(m.value) || undefined}
|
|
199
225
|
aria-hidden="true"
|
|
200
226
|
style={markDotStyle(m.value)}
|
|
201
227
|
className="pointer-events-none absolute size-1 rounded-full bg-border"
|
|
@@ -21,16 +21,18 @@ import {
|
|
|
21
21
|
toast,
|
|
22
22
|
type ToasterProps as SonnerToasterProps,
|
|
23
23
|
} from 'sonner';
|
|
24
|
-
import {
|
|
25
|
-
CircleCheckIcon,
|
|
26
|
-
CircleHelpIcon,
|
|
27
|
-
InfoIcon,
|
|
28
|
-
Loader2Icon,
|
|
29
|
-
OctagonXIcon,
|
|
30
|
-
TriangleAlertIcon,
|
|
31
|
-
} from 'lucide-react';
|
|
24
|
+
import { Loader2Icon } from 'lucide-react';
|
|
32
25
|
|
|
33
26
|
import { cn } from '@/lib/utils';
|
|
27
|
+
import {
|
|
28
|
+
ErrorFilledIcon,
|
|
29
|
+
HelpFilledIcon,
|
|
30
|
+
InfoFilledIcon,
|
|
31
|
+
SuccessFilledIcon,
|
|
32
|
+
WarningFilledIcon,
|
|
33
|
+
} from '@/components/icon';
|
|
34
|
+
|
|
35
|
+
/* ─── Types ────────────────────────────────────────────────────────────────── */
|
|
34
36
|
|
|
35
37
|
/**
|
|
36
38
|
* 容器级摆放位置 — cloud-design Notification.config({ placement }) 的 6 档形态。
|
|
@@ -52,17 +54,13 @@ export interface ToasterProps
|
|
|
52
54
|
maxToasts?: number;
|
|
53
55
|
}
|
|
54
56
|
|
|
55
|
-
/*
|
|
56
|
-
|
|
57
|
-
/* */
|
|
58
|
-
/* TODO(icon): 项目自研 Icon 组件就绪后,统一替换以下 STATE_ICONS 即可, */
|
|
59
|
-
/* 不影响 Toaster / toast 的 API。 */
|
|
60
|
-
/* ────────────────────────────────────────────────────────────────────────── */
|
|
57
|
+
/* ─── 内置语义图标(对齐 Alert 面性图标体系) ──────────────────────────────── */
|
|
58
|
+
|
|
61
59
|
const STATE_ICONS = {
|
|
62
|
-
success: <
|
|
63
|
-
info: <
|
|
64
|
-
warning: <
|
|
65
|
-
error: <
|
|
60
|
+
success: <SuccessFilledIcon className="size-4 shrink-0 text-success" />,
|
|
61
|
+
info: <InfoFilledIcon className="size-4 shrink-0 text-info" />,
|
|
62
|
+
warning: <WarningFilledIcon className="size-4 shrink-0 text-warning" />,
|
|
63
|
+
error: <ErrorFilledIcon className="size-4 shrink-0 text-destructive" />,
|
|
66
64
|
loading: (
|
|
67
65
|
<Loader2Icon className="size-4 shrink-0 animate-spin text-muted-foreground" />
|
|
68
66
|
),
|
|
@@ -70,12 +68,14 @@ const STATE_ICONS = {
|
|
|
70
68
|
|
|
71
69
|
/**
|
|
72
70
|
* `help` 状态在 sonner 原生类型中不存在,作为业界常见第六档语义保留。
|
|
73
|
-
* 业务侧通过 `toast(msg, { icon: HelpToastIcon })`
|
|
71
|
+
* 业务侧通过 `toast(msg, { icon: HelpToastIcon })` 触发。
|
|
74
72
|
*/
|
|
75
73
|
export const HelpToastIcon = (
|
|
76
|
-
<
|
|
74
|
+
<HelpFilledIcon className="size-4 shrink-0 text-help" />
|
|
77
75
|
);
|
|
78
76
|
|
|
77
|
+
/* ─── Toaster 容器 ─────────────────────────────────────────────────────────── */
|
|
78
|
+
|
|
79
79
|
function Toaster({
|
|
80
80
|
theme,
|
|
81
81
|
placement = 'top-right',
|
|
@@ -104,32 +104,35 @@ function Toaster({
|
|
|
104
104
|
className={cn('toaster group', className)}
|
|
105
105
|
style={
|
|
106
106
|
{
|
|
107
|
-
//
|
|
108
|
-
'--normal-bg': 'var(--popover)',
|
|
109
|
-
'--normal-text': 'var(--popover-foreground)',
|
|
110
|
-
'--normal-border': 'var(--border)',
|
|
107
|
+
// 基础外观 — 桥接 Tailwind v4 主题 token 到 sonner CSS 变量
|
|
108
|
+
'--normal-bg': 'var(--color-popover)',
|
|
109
|
+
'--normal-text': 'var(--color-popover-foreground)',
|
|
110
|
+
'--normal-border': 'var(--color-border)',
|
|
111
111
|
'--border-radius': 'var(--radius)',
|
|
112
|
-
//
|
|
113
|
-
'--
|
|
114
|
-
'--
|
|
115
|
-
'--
|
|
116
|
-
|
|
117
|
-
'--
|
|
118
|
-
'--
|
|
119
|
-
'--
|
|
120
|
-
'--
|
|
121
|
-
'--
|
|
122
|
-
'--
|
|
123
|
-
'--
|
|
124
|
-
'--
|
|
112
|
+
// 关闭按钮定位 — 右上角内部
|
|
113
|
+
'--toast-close-button-start': 'unset',
|
|
114
|
+
'--toast-close-button-end': '8px',
|
|
115
|
+
'--toast-close-button-transform': 'translate(0, 8px)',
|
|
116
|
+
// richColors 状态变量桥接
|
|
117
|
+
'--success-bg': 'var(--color-success-bg)',
|
|
118
|
+
'--success-text': 'var(--color-success)',
|
|
119
|
+
'--success-border': 'var(--color-success-border)',
|
|
120
|
+
'--info-bg': 'var(--color-info-bg)',
|
|
121
|
+
'--info-text': 'var(--color-info)',
|
|
122
|
+
'--info-border': 'var(--color-info-border)',
|
|
123
|
+
'--warning-bg': 'var(--color-warning-bg)',
|
|
124
|
+
'--warning-text': 'var(--color-warning)',
|
|
125
|
+
'--warning-border': 'var(--color-warning-border)',
|
|
126
|
+
'--error-bg': 'var(--color-destructive-bg)',
|
|
127
|
+
'--error-text': 'var(--color-destructive)',
|
|
128
|
+
'--error-border': 'var(--color-destructive-border)',
|
|
125
129
|
...style,
|
|
126
130
|
} as React.CSSProperties
|
|
127
131
|
}
|
|
128
132
|
toastOptions={{
|
|
129
133
|
...toastOptions,
|
|
130
134
|
classNames: {
|
|
131
|
-
toast:
|
|
132
|
-
'group toast group-[.toaster]:bg-popover group-[.toaster]:text-popover-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg',
|
|
135
|
+
toast: 'group toast shadow-none',
|
|
133
136
|
title: 'text-sm font-medium leading-5 text-foreground',
|
|
134
137
|
description: 'text-xs leading-5 text-muted-foreground',
|
|
135
138
|
actionButton:
|
|
@@ -137,7 +140,7 @@ function Toaster({
|
|
|
137
140
|
cancelButton:
|
|
138
141
|
'group-[.toast]:bg-muted group-[.toast]:text-muted-foreground group-[.toast]:rounded-sm group-[.toast]:text-xs group-[.toast]:px-2 group-[.toast]:py-1 group-[.toast]:cursor-pointer hover:group-[.toast]:bg-muted/80',
|
|
139
142
|
closeButton:
|
|
140
|
-
'
|
|
143
|
+
'!size-5 !border-0 !rounded-sm !bg-transparent !text-muted-foreground hover:!text-foreground hover:!bg-transparent [&>svg]:!size-4',
|
|
141
144
|
...toastOptions?.classNames,
|
|
142
145
|
},
|
|
143
146
|
}}
|