@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
package/manifest.json CHANGED
@@ -507,7 +507,7 @@
507
507
  }
508
508
  ],
509
509
  "meta": "src/components/dialog/meta.md",
510
- "registryDependencies": ["cn", "button"],
510
+ "registryDependencies": ["cn", "button", "use-radix-popper-guard"],
511
511
  "dependencies": {
512
512
  "radix-ui": "^1.4.3",
513
513
  "class-variance-authority": "^0.7.0",
@@ -551,7 +551,7 @@
551
551
  }
552
552
  ],
553
553
  "meta": "src/components/sheet/meta.md",
554
- "registryDependencies": ["cn"],
554
+ "registryDependencies": ["cn", "use-radix-popper-guard"],
555
555
  "dependencies": {
556
556
  "radix-ui": "^1.4.3",
557
557
  "class-variance-authority": "^0.7.0",
@@ -812,6 +812,20 @@
812
812
  ],
813
813
  "updateStrategy": "frozen"
814
814
  },
815
+ {
816
+ "id": "use-radix-popper-guard",
817
+ "name": "useRadixPopperGuard",
818
+ "type": "hook",
819
+ "description": "防止 Radix DismissableLayer 级联关闭 — Dialog/Sheet 内嵌 Select/Popover 时阻止误关宿主弹窗",
820
+ "files": [
821
+ {
822
+ "source": "src/hooks/use-radix-popper-guard.ts",
823
+ "targetAlias": "hooks",
824
+ "targetName": "use-radix-popper-guard.ts"
825
+ }
826
+ ],
827
+ "updateStrategy": "frozen"
828
+ },
815
829
  {
816
830
  "id": "sidebar",
817
831
  "name": "Sidebar",
@@ -1150,11 +1164,6 @@
1150
1164
  "source": "src/components/tree/index.tsx",
1151
1165
  "targetAlias": "components",
1152
1166
  "targetName": "tree.tsx"
1153
- },
1154
- {
1155
- "source": "src/components/tree/utils.ts",
1156
- "targetAlias": "components",
1157
- "targetName": "utils.ts"
1158
1167
  }
1159
1168
  ],
1160
1169
  "meta": "src/components/tree/meta.md",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teamix-evo/ui",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
4
4
  "description": "Source-injected UI components for Teamix Evo (shadcn-based, antd capabilities)",
5
5
  "type": "module",
6
6
  "files": [
@@ -51,9 +51,9 @@
51
51
  "vite": "^5.4.0",
52
52
  "vite-tsconfig-paths": "^6.1.1",
53
53
  "zod": "^3",
54
- "@teamix-evo/registry": "0.12.0",
55
- "@teamix-evo/eslint-config": "0.2.3",
56
- "@teamix-evo/tokens": "^0.8.0"
54
+ "@teamix-evo/eslint-config": "0.3.0",
55
+ "@teamix-evo/tokens": "^0.8.2",
56
+ "@teamix-evo/registry": "0.14.0"
57
57
  },
58
58
  "publishConfig": {
59
59
  "access": "public",
@@ -655,7 +655,7 @@ function RadiusRow({ k, cls }: { k: string; cls: string }) {
655
655
  {/* 隐形 probe 用来取值,与可见样本同一 class 同一渲染路径 */}
656
656
  <div ref={probeRef} className={cls} style={{ display: 'none' }} />
657
657
  <div
658
- className={`flex h-16 w-16 items-center justify-center border-2 border-foreground ${cls}`}
658
+ className={`flex items-center justify-center border-2 border-foreground size-16 ${cls}`}
659
659
  aria-label={`rounded-${k} sample`}
660
660
  >
661
661
  <span className="font-mono text-xxs uppercase tracking-widest">
@@ -1140,7 +1140,7 @@ function SpacingRow({
1140
1140
  element-A
1141
1141
  </div>
1142
1142
  <div
1143
- className="w-full border-l-2 border-r-2 border-primary"
1143
+ className="w-full border-primary border-x-2"
1144
1144
  style={{ height: `var(${varName})`, minHeight: '2px' }}
1145
1145
  aria-label={`gap ${varName}`}
1146
1146
  />
@@ -121,7 +121,7 @@ function AccordionContent({
121
121
  return (
122
122
  <AccordionPrimitive.Content
123
123
  data-slot="accordion-content"
124
- className="overflow-hidden text-sm data-[state=open]:animate-accordion-down data-[state=closed]:animate-accordion-up"
124
+ className="overflow-hidden text-sm data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
125
125
  {...props}
126
126
  >
127
127
  <div
@@ -21,3 +21,29 @@
21
21
  | `offsetBottom` | `number` | – | – | 距离视口底部指定偏移量后吸底(px)— 与 `offsetTop` 互斥。 |
22
22
  | `container` | `() => HTMLElement \| Window` | `() => window` | – | 滚动容器 — 默认 `window`。传入自定义容器时使用 `position: sticky`。 |
23
23
  | `onAffixChange` | `(affixed: boolean) => void` | – | – | 吸附状态变化回调。 |
24
+
25
+ ## 示例
26
+
27
+ ### Bottom
28
+
29
+ 吸底 — 滚动时固定在容器底部。
30
+
31
+ ```tsx
32
+ <div
33
+ ref={ref}
34
+ className="relative h-[400px] overflow-auto rounded-md border border-border p-4"
35
+ >
36
+ <div className="h-[500px] rounded-md border border-border bg-card p-4 text-sm text-muted-foreground">
37
+ 向下滚动
38
+ </div>
39
+ <div className="mt-3" />
40
+ <Affix offsetBottom={16} container={() => ref.current!}>
41
+ <div className="rounded-md border border-border bg-background p-3 shadow-sm">
42
+ <Button block>立即提交(吸底)</Button>
43
+ </div>
44
+ </Affix>
45
+ <div className="h-[500px] rounded-md border border-border bg-card p-4 mt-3 text-sm text-muted-foreground">
46
+ 底部区域
47
+ </div>
48
+ </div>
49
+ ```
@@ -25,7 +25,7 @@ import {
25
25
  } from '@/components/icon';
26
26
 
27
27
  const alertVariants = cva(
28
- 'group/alert relative grid w-full grid-cols-[auto_1fr] items-center gap-x-2 rounded-md p-2 text-left text-foreground has-data-[slot=alert-title]:items-start has-data-[slot=alert-action]:pr-18 has-data-[slot=alert-close]:pr-7',
28
+ 'group/alert relative grid w-full grid-cols-[auto_1fr] items-center gap-x-2 rounded-md p-2 text-left text-foreground has-data-[slot=alert-action]:pr-18 has-data-[slot=alert-close]:pr-7 has-data-[slot=alert-title]:items-start',
29
29
  {
30
30
  variants: {
31
31
  variant: {
@@ -33,7 +33,7 @@ const alertVariants = cva(
33
33
  success: 'border border-success-bg bg-success-bg',
34
34
  warning: 'border border-warning-bg bg-warning-bg',
35
35
  destructive: 'border border-destructive-bg bg-destructive-bg',
36
- help: 'border border-border border-help-bg bg-help-bg',
36
+ help: 'border border-help-bg bg-help-bg',
37
37
  },
38
38
  },
39
39
  defaultVariants: {
@@ -68,7 +68,7 @@ function AlertDialogOverlay({
68
68
  <AlertDialogPrimitive.Overlay
69
69
  data-slot="alert-dialog-overlay"
70
70
  className={cn(
71
- 'fixed inset-0 z-50 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=closed]:animate-out data-[state=closed]:fade-out-0',
71
+ 'fixed inset-0 z-50 bg-black/10 duration-100 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0 supports-backdrop-filter:backdrop-blur-xs',
72
72
  className,
73
73
  )}
74
74
  {...props}
@@ -101,7 +101,7 @@ function AlertDialogContent({
101
101
  data-size={size}
102
102
  className={cn(
103
103
  // eslint-disable-next-line teamix-evo/no-arbitrary-tw-value -- mobile viewport needs calc(100% - 2rem) for safe inset; box-shadow drives via component-level token --dialog-shadow per ADR 0026.
104
- 'group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 rounded-md bg-popover text-sm text-popover-foreground shadow-[var(--dialog-shadow,0px_6px_24px_0px_rgba(0,0,0,0.1))] duration-100 outline-none data-[size=sm]:sm:max-w-md data-[size=md]:sm:max-w-xl data-[size=lg]:sm:max-w-3xl data-[size=xl]:sm:max-w-6xl 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',
104
+ 'group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-1/2 rounded-md bg-popover text-sm text-popover-foreground shadow-[var(--dialog-shadow,0px_6px_24px_0px_rgba(0,0,0,0.1))] duration-100 outline-none data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[size=lg]:sm:max-w-3xl data-[size=md]:sm:max-w-xl data-[size=sm]:sm:max-w-md data-[size=xl]:sm:max-w-6xl',
105
105
  className,
106
106
  )}
107
107
  {...props}
@@ -139,7 +139,7 @@ function AlertDialogBody({ className, ...props }: React.ComponentProps<'div'>) {
139
139
  return (
140
140
  <div
141
141
  data-slot="alert-dialog-body"
142
- className={cn('pr-5 pl-11 pt-3', className)}
142
+ className={cn('pt-3 pr-5 pl-11', className)}
143
143
  {...props}
144
144
  />
145
145
  );
@@ -178,3 +178,55 @@
178
178
  </AlertDialogContent>
179
179
  </AlertDialog>
180
180
  ```
181
+
182
+ ### Async
183
+
184
+ 异步确认 — onAction 返回 Promise,loading 期间禁止关闭。
185
+
186
+ ```tsx
187
+ const [open, setOpen] = React.useState(false);
188
+ const [loading, setLoading] = React.useState(false);
189
+ const onConfirm = async (e: React.MouseEvent) => {
190
+ e.preventDefault();
191
+ setLoading(true);
192
+ await new Promise((r) => setTimeout(r, 1500));
193
+ setLoading(false);
194
+ setOpen(false);
195
+ };
196
+ return (
197
+ <AlertDialog
198
+ open={open}
199
+ onOpenChange={(next) => {
200
+ if (loading) return;
201
+ setOpen(next);
202
+ }}
203
+ >
204
+ <AlertDialogTrigger asChild>
205
+ <Button variant="destructive">删除</Button>
206
+ </AlertDialogTrigger>
207
+ <AlertDialogContent>
208
+ <AlertDialogHeader>
209
+ <AlertDialogMedia>
210
+ <ErrorFilledIcon className="text-destructive" />
211
+ </AlertDialogMedia>
212
+ <AlertDialogTitle>确认删除?</AlertDialogTitle>
213
+ </AlertDialogHeader>
214
+ <AlertDialogBody>
215
+ <AlertDialogDescription>
216
+ onAction 返回 Promise,loading 期间无法关闭对话框。
217
+ </AlertDialogDescription>
218
+ </AlertDialogBody>
219
+ <AlertDialogFooter>
220
+ <AlertDialogCancel disabled={loading}>取消</AlertDialogCancel>
221
+ <Button
222
+ variant="destructive"
223
+ loading={loading}
224
+ onClick={onConfirm}
225
+ >
226
+ 确认删除
227
+ </Button>
228
+ </AlertDialogFooter>
229
+ </AlertDialogContent>
230
+ </AlertDialog>
231
+ );
232
+ ```
@@ -221,53 +221,50 @@ export const Warning: Story = {
221
221
  export const Async: Story = {
222
222
  name: '异步确认',
223
223
  render: () => {
224
- function Demo() {
225
- const [open, setOpen] = React.useState(false);
226
- const [loading, setLoading] = React.useState(false);
227
- const onConfirm = async (e: React.MouseEvent) => {
228
- e.preventDefault();
229
- setLoading(true);
230
- await new Promise((r) => setTimeout(r, 1500));
231
- setLoading(false);
232
- setOpen(false);
233
- };
234
- return (
235
- <AlertDialog
236
- open={open}
237
- onOpenChange={(next) => {
238
- if (loading) return;
239
- setOpen(next);
240
- }}
241
- >
242
- <AlertDialogTrigger asChild>
243
- <Button variant="destructive">删除</Button>
244
- </AlertDialogTrigger>
245
- <AlertDialogContent>
246
- <AlertDialogHeader>
247
- <AlertDialogMedia>
248
- <ErrorFilledIcon className="text-destructive" />
249
- </AlertDialogMedia>
250
- <AlertDialogTitle>确认删除?</AlertDialogTitle>
251
- </AlertDialogHeader>
252
- <AlertDialogBody>
253
- <AlertDialogDescription>
254
- onAction 返回 Promise,loading 期间无法关闭对话框。
255
- </AlertDialogDescription>
256
- </AlertDialogBody>
257
- <AlertDialogFooter>
258
- <AlertDialogCancel disabled={loading}>取消</AlertDialogCancel>
259
- <Button
260
- variant="destructive"
261
- loading={loading}
262
- onClick={onConfirm}
263
- >
264
- 确认删除
265
- </Button>
266
- </AlertDialogFooter>
267
- </AlertDialogContent>
268
- </AlertDialog>
269
- );
270
- }
271
- return <Demo />;
224
+ const [open, setOpen] = React.useState(false);
225
+ const [loading, setLoading] = React.useState(false);
226
+ const onConfirm = async (e: React.MouseEvent) => {
227
+ e.preventDefault();
228
+ setLoading(true);
229
+ await new Promise((r) => setTimeout(r, 1500));
230
+ setLoading(false);
231
+ setOpen(false);
232
+ };
233
+ return (
234
+ <AlertDialog
235
+ open={open}
236
+ onOpenChange={(next) => {
237
+ if (loading) return;
238
+ setOpen(next);
239
+ }}
240
+ >
241
+ <AlertDialogTrigger asChild>
242
+ <Button variant="destructive">删除</Button>
243
+ </AlertDialogTrigger>
244
+ <AlertDialogContent>
245
+ <AlertDialogHeader>
246
+ <AlertDialogMedia>
247
+ <ErrorFilledIcon className="text-destructive" />
248
+ </AlertDialogMedia>
249
+ <AlertDialogTitle>确认删除?</AlertDialogTitle>
250
+ </AlertDialogHeader>
251
+ <AlertDialogBody>
252
+ <AlertDialogDescription>
253
+ onAction 返回 Promise,loading 期间无法关闭对话框。
254
+ </AlertDialogDescription>
255
+ </AlertDialogBody>
256
+ <AlertDialogFooter>
257
+ <AlertDialogCancel disabled={loading}>取消</AlertDialogCancel>
258
+ <Button
259
+ variant="destructive"
260
+ loading={loading}
261
+ onClick={onConfirm}
262
+ >
263
+ 确认删除
264
+ </Button>
265
+ </AlertDialogFooter>
266
+ </AlertDialogContent>
267
+ </AlertDialog>
268
+ );
272
269
  },
273
270
  };
@@ -91,7 +91,7 @@ function AvatarFallback({
91
91
  className={cn(
92
92
  'flex size-full items-center justify-center bg-border text-muted-foreground',
93
93
  'group-data-[shape=circle]/avatar:rounded-full group-data-[shape=square]/avatar:rounded-md',
94
- 'group-data-[size=sm]/avatar:text-xs group-data-[size=default]/avatar:text-xs group-data-[size=lg]/avatar:text-sm',
94
+ 'group-data-[size=default]/avatar:text-xs group-data-[size=lg]/avatar:text-sm group-data-[size=sm]/avatar:text-xs',
95
95
  '[&>svg]:size-1/2',
96
96
  className,
97
97
  )}
@@ -144,7 +144,7 @@ function Indicator({
144
144
  data-indicator-kind={kind}
145
145
  data-size={size}
146
146
  className={cn(
147
- 'pointer-events-none inline-flex items-center justify-center rounded-full font-medium leading-none select-none',
147
+ 'pointer-events-none inline-flex items-center justify-center rounded-full leading-none font-medium select-none',
148
148
  // 默认色(content 形态走透明,由 color / 自定义 className 决定)
149
149
  !isContent && 'bg-destructive text-destructive-foreground',
150
150
  // content 形态 + 有色非透明底 → 白色文字
@@ -162,7 +162,7 @@ function Indicator({
162
162
  : 'h-4 min-w-4 px-1 text-xs',
163
163
  // wrapped 模式:绝对定位 + ring 描边
164
164
  wrapped &&
165
- 'absolute top-0 right-0 z-10 origin-center -translate-y-1/2 translate-x-1/2 ring-2 ring-background',
165
+ 'absolute top-0 right-0 z-10 origin-center translate-x-1/2 -translate-y-1/2 ring-2 ring-background',
166
166
  )}
167
167
  style={style}
168
168
  >
@@ -235,3 +235,51 @@
235
235
  </Badge>
236
236
  </div>
237
237
  ```
238
+
239
+ ### Dynamic
240
+
241
+ 受控示例:count 与 dot 动态切换。
242
+
243
+ ```tsx
244
+ function DynamicDemo() {
245
+ const [count, setCount] = React.useState(0);
246
+ const [show, setShow] = React.useState(true);
247
+ return (
248
+ <div className="flex flex-col gap-4">
249
+ <div className="flex items-center gap-3">
250
+ <Badge count={count}>
251
+ <Avatar>
252
+ <AvatarFallback>N</AvatarFallback>
253
+ </Avatar>
254
+ </Badge>
255
+ <Badge count={count} showZero>
256
+ <Avatar>
257
+ <AvatarFallback>Z</AvatarFallback>
258
+ </Avatar>
259
+ </Badge>
260
+ <Button size="sm" onClick={() => setCount((c) => c + 1)}>
261
+ +
262
+ </Button>
263
+ <Button
264
+ size="sm"
265
+ variant="outline"
266
+ onClick={() => setCount((c) => Math.max(0, c - 1))}
267
+ >
268
+ -
269
+ </Button>
270
+ </div>
271
+ <div className="flex items-center gap-3">
272
+ <Badge dot={show}>
273
+ <Avatar>
274
+ <AvatarFallback>D</AvatarFallback>
275
+ </Avatar>
276
+ </Badge>
277
+ <Button size="sm" onClick={() => setShow((s) => !s)}>
278
+ Toggle Dot
279
+ </Button>
280
+ </div>
281
+ </div>
282
+ );
283
+ }
284
+ return <DynamicDemo />;
285
+ ```
@@ -24,7 +24,7 @@ import { Spinner } from '@/components/spinner';
24
24
  // - shadow:组件不写,uni-manager 通过 theme.css scoped CSS [data-slot='button'] 注入
25
25
 
26
26
  const buttonVariants = cva(
27
- 'inline-flex shrink-0 cursor-pointer items-center justify-center rounded-md font-normal whitespace-nowrap transition-colors select-none focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-1 aria-invalid:ring-destructive/20 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=size-])]:size-4',
27
+ 'inline-flex shrink-0 cursor-pointer items-center justify-center rounded-md font-normal whitespace-nowrap transition-colors select-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-1 aria-invalid:ring-destructive/20 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=size-])]:size-4',
28
28
  {
29
29
  variants: {
30
30
  variant: {
@@ -54,7 +54,7 @@ const buttonVariants = cva(
54
54
  shape: {
55
55
  default: '',
56
56
  round: 'rounded-full',
57
- circle: 'rounded-full aspect-square p-0',
57
+ circle: 'aspect-square rounded-full p-0',
58
58
  square: 'aspect-square p-0',
59
59
  },
60
60
  block: {
@@ -96,6 +96,21 @@ Button 只有图标时,应该显示为正方形。
96
96
  </div>
97
97
  ```
98
98
 
99
+ ### Loading
100
+
101
+ 通过设置 `loading` 属性即可以让按钮处于加载状态。
102
+
103
+ ```tsx
104
+ <div className="flex gap-2">
105
+ <Button loading variant="secondary">
106
+ Loading
107
+ </Button>
108
+ <Button loading={loading} onClick={() => setLoading(true)}>
109
+ Click to loading
110
+ </Button>
111
+ </div>
112
+ ```
113
+
99
114
  ### Disabled
100
115
 
101
116
  添加 `disabled` 属性即可让按钮处于不可用状态,同时按钮样式也会改变。
@@ -93,7 +93,7 @@ export const IconOnly: Story = {
93
93
 
94
94
  /** 通过设置 `loading` 属性即可以让按钮处于加载状态。 */
95
95
  export const Loading: Story = {
96
- render: function LoadingDemo() {
96
+ render: () => {
97
97
  const [loading, setLoading] = useState(false);
98
98
  return (
99
99
  <div className="flex gap-2">
@@ -117,7 +117,7 @@ function Calendar({
117
117
  defaultClassNames.week_number,
118
118
  ),
119
119
  day: cn(
120
- 'group/day relative aspect-square h-full w-full rounded-md p-0 text-center select-none [&:last-child[data-selected=true]_button]:rounded-r-md',
120
+ 'group/day relative aspect-square size-full rounded-md p-0 text-center select-none [&:last-child[data-selected=true]_button]:rounded-r-md',
121
121
  props.showWeekNumber
122
122
  ? '[&:nth-child(2)[data-selected=true]_button]:rounded-l-md'
123
123
  : '[&:first-child[data-selected=true]_button]:rounded-l-md',
@@ -231,7 +231,7 @@ function CalendarDayButton({
231
231
  data-range-end={modifiers.range_end}
232
232
  data-range-middle={modifiers.range_middle}
233
233
  className={cn(
234
- 'relative isolate z-10 flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 border-0 text-xs leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-1 group-data-[focused=true]/day:ring-ring data-[range-end=true]:rounded-md data-[range-end=true]:rounded-r-md data-[range-middle=true]:rounded-none data-[range-middle=true]:bg-muted data-[range-middle=true]:text-foreground data-[range-start=true]:rounded-md data-[range-start=true]:rounded-l-md hover:text-foreground [&>span]:text-xs [&>span]:opacity-70',
234
+ 'relative isolate z-10 flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 border-0 text-xs leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-1 group-data-[focused=true]/day:ring-ring hover:text-foreground data-[range-end=true]:rounded-md data-[range-end=true]:rounded-r-md data-[range-middle=true]:rounded-none data-[range-middle=true]:bg-muted data-[range-middle=true]:text-foreground data-[range-start=true]:rounded-md data-[range-start=true]:rounded-l-md [&>span]:text-xs [&>span]:opacity-70',
235
235
  defaultClassNames.day,
236
236
  className,
237
237
  isActive &&
@@ -28,7 +28,7 @@ import { Skeleton } from '@/components/skeleton';
28
28
  // - 垂直 padding 20px (`py-5`) / sm 16px (`py-4`)
29
29
  // - normal 无阴影,hover 抬升
30
30
  const cardVariants = cva(
31
- 'group/card flex flex-col overflow-hidden rounded-md bg-card text-card-foreground text-sm has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-md *:[img:last-child]:rounded-b-md',
31
+ 'group/card flex flex-col overflow-hidden rounded-md bg-card text-sm text-card-foreground has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-md *:[img:last-child]:rounded-b-md',
32
32
  {
33
33
  variants: {
34
34
  variant: {
@@ -344,7 +344,7 @@ function CarouselPrevious({
344
344
  className={cn(
345
345
  'absolute z-10 touch-manipulation',
346
346
  arrowPosition === 'inner' &&
347
- 'bg-background/80 backdrop-blur-sm shadow-sm',
347
+ 'bg-background/80 shadow-sm backdrop-blur-sm',
348
348
  positionClass,
349
349
  className,
350
350
  )}
@@ -390,7 +390,7 @@ function CarouselNext({
390
390
  className={cn(
391
391
  'absolute z-10 touch-manipulation',
392
392
  arrowPosition === 'inner' &&
393
- 'bg-background/80 backdrop-blur-sm shadow-sm',
393
+ 'bg-background/80 shadow-sm backdrop-blur-sm',
394
394
  positionClass,
395
395
  className,
396
396
  )}
@@ -172,6 +172,38 @@
172
172
  </Carousel>
173
173
  ```
174
174
 
175
+ ### Controlled
176
+
177
+ 受控当前页:外部按钮跳转。
178
+
179
+ ```tsx
180
+ <div className="flex w-full max-w-xs flex-col gap-3 px-12">
181
+ <Carousel activeIndex={active} onChange={setActive}>
182
+ <CarouselContent>
183
+ {slides.map((i) => (
184
+ <CarouselItem key={i}>
185
+ <Slide index={i - 1}>{i}</Slide>
186
+ </CarouselItem>
187
+ ))}
188
+ </CarouselContent>
189
+ <CarouselPrevious />
190
+ <CarouselNext />
191
+ </Carousel>
192
+ <div className="flex flex-wrap gap-2">
193
+ {slides.map((_, i) => (
194
+ <Button
195
+ key={i}
196
+ size="sm"
197
+ variant={i === active ? 'default' : 'outline'}
198
+ onClick={() => setActive(i)}
199
+ >
200
+ {i + 1}
201
+ </Button>
202
+ ))}
203
+ </div>
204
+ </div>
205
+ ```
206
+
175
207
  ### CustomDots
176
208
 
177
209
  自定义指示点渲染。
@@ -195,8 +227,8 @@
195
227
  onClick={onClick}
196
228
  className={
197
229
  selected
198
- ? 'h-6 w-6 cursor-pointer rounded-md bg-primary text-xs text-primary-foreground'
199
- : 'h-6 w-6 cursor-pointer rounded-md bg-muted text-xs text-muted-foreground hover:bg-accent'
230
+ ? 'cursor-pointer rounded-md bg-primary text-xs text-primary-foreground size-6'
231
+ : 'cursor-pointer rounded-md bg-muted text-xs text-muted-foreground hover:bg-accent size-6'
200
232
  }
201
233
  >
202
234
  {index + 1}
@@ -257,8 +257,8 @@ export const CustomDots: Story = {
257
257
  onClick={onClick}
258
258
  className={
259
259
  selected
260
- ? 'h-6 w-6 cursor-pointer rounded-md bg-primary text-xs text-primary-foreground'
261
- : 'h-6 w-6 cursor-pointer rounded-md bg-muted text-xs text-muted-foreground hover:bg-accent'
260
+ ? 'cursor-pointer rounded-md bg-primary text-xs text-primary-foreground size-6'
261
+ : 'cursor-pointer rounded-md bg-muted text-xs text-muted-foreground hover:bg-accent size-6'
262
262
  }
263
263
  >
264
264
  {index + 1}
@@ -72,6 +72,7 @@ function CascaderPanel({
72
72
  size?: 'sm' | 'default';
73
73
  }) {
74
74
  return (
75
+ // eslint-disable-next-line tailwindcss/no-contradicting-classname -- divide-color ≠ border-color
75
76
  <div className="flex max-h-56 divide-x divide-border overflow-hidden rounded-md border border-border bg-popover shadow-md">
76
77
  {columns.map((column, depth) => (
77
78
  <div key={depth} className="min-w-32 overflow-y-auto p-1">
@@ -291,7 +292,7 @@ function CascaderSelect({
291
292
 
292
293
  {/* Dropdown */}
293
294
  {open && (
294
- <div className="absolute left-0 top-full z-50 mt-1">
295
+ <div className="absolute top-full left-0 z-50 mt-1">
295
296
  <CascaderPanel
296
297
  columns={columns}
297
298
  activeValues={activeValues}
@@ -26,3 +26,49 @@
26
26
  | `size` | `'sm' \| 'default'` | – | – | 尺寸。@default 'default' |
27
27
  | `allowClear` | `boolean` | `true` | – | 是否允许清空:有选中值且未加载/未禁用时,右侧 chevron 切为 × 按钮。 与 Select 一致:右侧图标优先级 loading > clear > chevron,三者互斥、占同一位置。 |
28
28
  | `loading` | `boolean` | `false` | – | 加载态:将右侧图标替换为旋转 spinner,并标记 `data-loading="true"` 供外部样式穿透。 |
29
+
30
+ ## 示例
31
+
32
+ ### Basic
33
+
34
+ ```tsx
35
+ <CascaderSelect options={options} />
36
+ ```
37
+
38
+ ### DefaultValue
39
+
40
+ ```tsx
41
+ <CascaderSelect options={options} defaultValue={['zhejiang'} 'hangzhou'={'hangzhou'} 'xihu']={'xihu']} />
42
+ ```
43
+
44
+ ### SmallSize
45
+
46
+ ```tsx
47
+ <CascaderSelect options={options} size="sm" />
48
+ ```
49
+
50
+ ### Disabled
51
+
52
+ ```tsx
53
+ <CascaderSelect options={options} disabled defaultValue={['jiangsu'} 'nanjing'={'nanjing'} 'xuanwu']={'xuanwu']} />
54
+ ```
55
+
56
+ ### WithClear
57
+
58
+ 有选中值时右侧 chevron 切为 ×,点击清空。优先级:loading > clear > chevron。
59
+
60
+ ```tsx
61
+ <CascaderSelect
62
+ options={options}
63
+ value={value}
64
+ onChange={(next) => setValue(next)}
65
+ />
66
+ ```
67
+
68
+ ### Loading
69
+
70
+ 加载态:`loading` 为 `true` 时右侧图标替换为旋转 spinner。
71
+
72
+ ```tsx
73
+ <CascaderSelect options={options} loading />
74
+ ```