@teamix-evo/ui 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/package.json +19 -17
  2. package/src/components/alert/index.tsx +1 -1
  3. package/src/components/alert-dialog/index.tsx +17 -24
  4. package/src/components/alert-dialog/meta.md +102 -8
  5. package/src/components/alert-dialog/stories.tsx +117 -7
  6. package/src/components/avatar/index.tsx +1 -1
  7. package/src/components/badge/index.tsx +1 -1
  8. package/src/components/button/index.tsx +3 -31
  9. package/src/components/button/meta.md +24 -13
  10. package/src/components/button/stories.tsx +24 -12
  11. package/src/components/button-group/meta.md +6 -9
  12. package/src/components/button-group/stories.tsx +2 -6
  13. package/src/components/calendar/index.tsx +12 -7
  14. package/src/components/cascader-select/index.tsx +1 -1
  15. package/src/components/checkbox/index.tsx +1 -1
  16. package/src/components/combobox/index.tsx +54 -10
  17. package/src/components/combobox/meta.md +3 -5
  18. package/src/components/combobox/stories.tsx +104 -25
  19. package/src/components/data-table/index.tsx +10 -5
  20. package/src/components/data-table/stories.tsx +4 -1
  21. package/src/components/date-picker/index.tsx +25 -2
  22. package/src/components/field/index.tsx +1 -1
  23. package/src/components/filter-bar/index.tsx +1 -1
  24. package/src/components/float-button/meta.md +3 -15
  25. package/src/components/icon/index.tsx +3 -4
  26. package/src/components/icon/meta.md +1 -2
  27. package/src/components/input/index.tsx +11 -3
  28. package/src/components/input-group/index.tsx +33 -23
  29. package/src/components/input-group/meta.md +15 -0
  30. package/src/components/input-group/stories.tsx +14 -0
  31. package/src/components/input-ip/index.tsx +1 -1
  32. package/src/components/input-number/index.tsx +5 -5
  33. package/src/components/item/meta.md +10 -42
  34. package/src/components/item/stories.tsx +12 -44
  35. package/src/components/radio-group/index.tsx +1 -1
  36. package/src/components/rate/index.tsx +3 -3
  37. package/src/components/select/index.tsx +2 -2
  38. package/src/components/skeleton/index.tsx +1 -1
  39. package/src/components/skeleton/meta.md +6 -6
  40. package/src/components/skeleton/stories.tsx +8 -8
  41. package/src/components/slider/index.tsx +27 -1
  42. package/src/components/sonner/index.tsx +43 -40
  43. package/src/components/sonner/meta.md +84 -68
  44. package/src/components/sonner/stories.tsx +122 -83
  45. package/src/components/spinner/index.tsx +170 -0
  46. package/src/components/spinner/meta.md +27 -1
  47. package/src/components/spinner/stories.tsx +23 -0
  48. package/src/components/steps/index.tsx +5 -1
  49. package/src/components/switch/index.tsx +1 -1
  50. package/src/components/table/index.tsx +6 -2
  51. package/src/components/tag/index.tsx +14 -0
  52. package/src/components/tag/meta.md +1 -0
  53. package/src/components/tag/stories.tsx +13 -0
  54. package/src/components/textarea/index.tsx +1 -1
  55. package/src/components/textarea/stories.tsx +1 -1
  56. package/src/components/time-picker/index.tsx +3 -1
  57. package/src/components/toggle/index.tsx +1 -1
  58. package/src/components/tooltip/index.tsx +5 -1
  59. package/src/components/tooltip/meta.md +13 -28
  60. package/src/components/tooltip/stories.tsx +11 -28
  61. package/src/components/transfer/index.tsx +12 -10
  62. package/src/components/tree-select/index.tsx +1 -1
  63. package/LICENSE +0 -21
@@ -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-warning',
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-warning"
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-warning')}
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-visible:border-ring focus-visible:ring-1 focus-visible:ring-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",
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 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-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,
@@ -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-4 w-64">
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: <CircleCheckIcon className="size-4 shrink-0 text-success" />,
63
- info: <InfoIcon className="size-4 shrink-0 text-info" />,
64
- warning: <TriangleAlertIcon className="size-4 shrink-0 text-warning" />,
65
- error: <OctagonXIcon className="size-4 shrink-0 text-destructive" />,
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 })` 触发,或参考 stories 演示。
71
+ * 业务侧通过 `toast(msg, { icon: HelpToastIcon })` 触发。
74
72
  */
75
73
  export const HelpToastIcon = (
76
- <CircleHelpIcon className="size-4 shrink-0 text-help" />
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
- // 容器与文字基础变量沿用主题语义 token,dark 自动跟随
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
- // 状态变量桥接开启 richColors 时由 sonner 消费
113
- '--success-bg': 'var(--success-bg)',
114
- '--success-text': 'var(--success)',
115
- '--success-border': 'var(--success-border)',
116
- '--info-bg': 'var(--info-bg)',
117
- '--info-text': 'var(--info)',
118
- '--info-border': 'var(--info-border)',
119
- '--warning-bg': 'var(--warning-bg)',
120
- '--warning-text': 'var(--warning)',
121
- '--warning-border': 'var(--warning-border)',
122
- '--error-bg': 'var(--destructive-bg)',
123
- '--error-text': 'var(--destructive)',
124
- '--error-border': 'var(--destructive-border)',
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
- 'group-[.toast]:bg-transparent group-[.toast]:text-muted-foreground group-[.toast]:border-border hover:group-[.toast]:text-foreground',
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
  }}
@@ -30,15 +30,18 @@
30
30
  标题 + 描述 — 对应 cloud-design `Notification.open({ title, content })`。
31
31
 
32
32
  ```tsx
33
- <Button
34
- onClick={() =>
35
- toast('文件已保存', {
36
- description: '你的修改已自动同步到云端,可在历史版本中找回。',
37
- })
38
- }
39
- >
40
- 触发
41
- </Button>
33
+ <>
34
+ <Button
35
+ onClick={() =>
36
+ toast('文件已保存', {
37
+ description: '你的修改已自动同步到云端,可在历史版本中找回。',
38
+ })
39
+ }
40
+ >
41
+ 触发
42
+ </Button>
43
+ <Toaster />
44
+ </>
42
45
  ```
43
46
 
44
47
  ### WithAction
@@ -46,19 +49,22 @@
46
49
  操作按钮 — 业内主流 `action` props,单击执行下一步行动并关闭。
47
50
 
48
51
  ```tsx
49
- <Button
50
- onClick={() =>
51
- toast('已删除项目「示例」', {
52
- description: '删除后 7 天内可在回收站恢复。',
53
- action: {
54
- label: '撤销',
55
- onClick: () => toast.success('已撤销删除'),
56
- },
57
- })
58
- }
59
- >
60
- 触发
61
- </Button>
52
+ <>
53
+ <Button
54
+ onClick={() =>
55
+ toast('已删除项目「示例」', {
56
+ description: '删除后 7 天内可在回收站恢复。',
57
+ action: {
58
+ label: '撤销',
59
+ onClick: () => toast.success('已撤销删除'),
60
+ },
61
+ })
62
+ }
63
+ >
64
+ 触发
65
+ </Button>
66
+ <Toaster />
67
+ </>
62
68
  ```
63
69
 
64
70
  ### WithCancel
@@ -66,17 +72,20 @@
66
72
  取消按钮 — 与 action 并存的次级行动点,可阻断默认行为。
67
73
 
68
74
  ```tsx
69
- <Button
70
- onClick={() =>
71
- toast('确认离开当前页面?', {
72
- description: '未保存的修改将丢失。',
73
- action: { label: '继续', onClick: () => toast.info('已继续') },
74
- cancel: { label: '取消', onClick: () => toast('已取消') },
75
- })
76
- }
77
- >
78
- 触发
79
- </Button>
75
+ <>
76
+ <Button
77
+ onClick={() =>
78
+ toast('确认离开当前页面?', {
79
+ description: '未保存的修改将丢失。',
80
+ action: { label: '继续', onClick: () => toast.info('已继续') },
81
+ cancel: { label: '取消', onClick: () => toast('已取消') },
82
+ })
83
+ }
84
+ >
85
+ 触发
86
+ </Button>
87
+ <Toaster />
88
+ </>
80
89
  ```
81
90
 
82
91
  ### LoadingThenResolve
@@ -84,19 +93,22 @@
84
93
  按 id 链式更新 — 等价 cloud-design `Message.loading + Message.success({ key })`。
85
94
 
86
95
  ```tsx
87
- <Button
88
- onClick={() => {
89
- const id = toast.loading('正在上传...');
90
- setTimeout(() => {
91
- toast.success('上传完成', {
92
- id,
93
- description: '文件已发布到生产环境。',
94
- });
95
- }, 1500);
96
- }}
97
- >
98
- 触发
99
- </Button>
96
+ <>
97
+ <Button
98
+ onClick={() => {
99
+ const id = toast.loading('正在上传...');
100
+ setTimeout(() => {
101
+ toast.success('上传完成', {
102
+ id,
103
+ description: '文件已发布到生产环境。',
104
+ });
105
+ }, 1500);
106
+ }}
107
+ >
108
+ 触发
109
+ </Button>
110
+ <Toaster />
111
+ </>
100
112
  ```
101
113
 
102
114
  ### Duration
@@ -121,6 +133,7 @@
121
133
  >
122
134
  持久 (Infinity)
123
135
  </Button>
136
+ <Toaster />
124
137
  </div>
125
138
  ```
126
139
 
@@ -129,28 +142,31 @@
129
142
  自定义渲染 — `toast.custom` 完全接管节点,用于嵌入富内容卡片。
130
143
 
131
144
  ```tsx
132
- <Button
133
- onClick={() =>
134
- toast.custom((id) => (
135
- <div className="flex w-80 items-start gap-3 rounded-md border border-border bg-popover p-3 text-popover-foreground shadow-lg">
136
- <CircleHelpIcon className="mt-0.5 size-4 shrink-0 text-help" />
137
- <div className="flex-1">
138
- <div className="text-sm font-medium">自定义卡片</div>
139
- <div className="mt-1 text-xs text-muted-foreground">
140
- 你可以在此渲染任意 JSX:表单、富文本、进度条等。
145
+ <>
146
+ <Button
147
+ onClick={() =>
148
+ toast.custom((id) => (
149
+ <div className="flex w-80 items-start gap-3 rounded-md border border-border bg-popover p-3 text-popover-foreground">
150
+ <HelpFilledIcon className="mt-0.5 size-4 shrink-0 text-help" />
151
+ <div className="flex-1">
152
+ <div className="text-sm font-medium">自定义卡片</div>
153
+ <div className="mt-1 text-xs text-muted-foreground">
154
+ 你可以在此渲染任意 JSX:表单、富文本、进度条等。
155
+ </div>
141
156
  </div>
157
+ <button
158
+ type="button"
159
+ className="cursor-pointer text-xs text-muted-foreground hover:text-foreground"
160
+ onClick={() => toast.dismiss(id)}
161
+ >
162
+ 关闭
163
+ </button>
142
164
  </div>
143
- <button
144
- type="button"
145
- className="cursor-pointer text-xs text-muted-foreground hover:text-foreground"
146
- onClick={() => toast.dismiss(id)}
147
- >
148
- 关闭
149
- </button>
150
- </div>
151
- ))
152
- }
153
- >
154
- 触发
155
- </Button>
165
+ ))
166
+ }
167
+ >
168
+ 触发
169
+ </Button>
170
+ <Toaster />
171
+ </>
156
172
  ```