@teamix-evo/ui 0.1.1 → 0.3.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 (295) hide show
  1. package/README.md +184 -184
  2. package/manifest.json +680 -492
  3. package/package.json +20 -10
  4. package/src/components/accordion/accordion.meta.md +5 -4
  5. package/src/components/accordion/accordion.stories.tsx +14 -9
  6. package/src/components/accordion/accordion.tsx +104 -8
  7. package/src/components/affix/affix.meta.md +20 -2
  8. package/src/components/affix/affix.stories.tsx +102 -25
  9. package/src/components/affix/affix.tsx +79 -9
  10. package/src/components/alert/alert.meta.md +44 -13
  11. package/src/components/alert/alert.stories.tsx +66 -21
  12. package/src/components/alert/alert.tsx +81 -34
  13. package/src/components/alert-dialog/alert-dialog.meta.md +61 -16
  14. package/src/components/alert-dialog/alert-dialog.stories.tsx +145 -3
  15. package/src/components/alert-dialog/alert-dialog.tsx +60 -13
  16. package/src/components/anchor/anchor.meta.md +8 -3
  17. package/src/components/anchor/anchor.stories.tsx +3 -3
  18. package/src/components/anchor/anchor.tsx +2 -2
  19. package/src/components/app/app.meta.md +9 -4
  20. package/src/components/app/app.stories.tsx +9 -7
  21. package/src/components/aspect-ratio/aspect-ratio.meta.md +4 -3
  22. package/src/components/aspect-ratio/aspect-ratio.stories.tsx +3 -3
  23. package/src/components/auto-complete/auto-complete.meta.md +14 -6
  24. package/src/components/auto-complete/auto-complete.stories.tsx +47 -4
  25. package/src/components/auto-complete/auto-complete.tsx +119 -71
  26. package/src/components/avatar/avatar.meta.md +6 -7
  27. package/src/components/avatar/avatar.stories.tsx +21 -3
  28. package/src/components/avatar/avatar.tsx +24 -23
  29. package/src/components/badge/badge.meta.md +10 -9
  30. package/src/components/badge/badge.stories.tsx +2 -2
  31. package/src/components/badge/badge.tsx +9 -15
  32. package/src/components/breadcrumb/breadcrumb.meta.md +27 -7
  33. package/src/components/breadcrumb/breadcrumb.stories.tsx +127 -4
  34. package/src/components/breadcrumb/breadcrumb.tsx +22 -8
  35. package/src/components/button/button.meta.md +258 -21
  36. package/src/components/button/button.stories.tsx +549 -41
  37. package/src/components/button/button.tsx +335 -33
  38. package/src/components/button/demo/as-child.tsx +24 -0
  39. package/src/components/button/demo/basic.tsx +8 -0
  40. package/src/components/button/demo/block.tsx +16 -0
  41. package/src/components/button/demo/loading.tsx +19 -0
  42. package/src/components/button/demo/shapes.tsx +18 -0
  43. package/src/components/button/demo/sizes.tsx +19 -0
  44. package/src/components/button/demo/variants.tsx +19 -0
  45. package/src/components/button/demo/with-icon.tsx +20 -0
  46. package/src/components/calendar/calendar.meta.md +13 -3
  47. package/src/components/calendar/calendar.stories.tsx +6 -6
  48. package/src/components/calendar/calendar.tsx +73 -8
  49. package/src/components/card/card.meta.md +27 -5
  50. package/src/components/card/card.stories.tsx +42 -3
  51. package/src/components/card/card.tsx +146 -63
  52. package/src/components/carousel/carousel.meta.md +4 -3
  53. package/src/components/carousel/carousel.stories.tsx +11 -6
  54. package/src/components/cascader/cascader.meta.md +47 -17
  55. package/src/components/cascader/cascader.stories.tsx +22 -10
  56. package/src/components/cascader/cascader.tsx +428 -85
  57. package/src/components/checkbox/checkbox.meta.md +75 -7
  58. package/src/components/checkbox/checkbox.stories.tsx +161 -3
  59. package/src/components/checkbox/checkbox.tsx +77 -9
  60. package/src/components/collapsible/collapsible.meta.md +14 -6
  61. package/src/components/collapsible/collapsible.stories.tsx +10 -2
  62. package/src/components/collapsible/collapsible.tsx +93 -6
  63. package/src/components/color-picker/color-picker.meta.md +12 -7
  64. package/src/components/color-picker/color-picker.stories.tsx +86 -7
  65. package/src/components/color-picker/color-picker.tsx +20 -9
  66. package/src/components/command/command.meta.md +29 -13
  67. package/src/components/command/command.stories.tsx +4 -4
  68. package/src/components/command/command.tsx +19 -8
  69. package/src/components/context-menu/context-menu.meta.md +11 -8
  70. package/src/components/context-menu/context-menu.stories.tsx +11 -3
  71. package/src/components/context-menu/context-menu.tsx +21 -8
  72. package/src/components/data-table/data-table.meta.md +6 -5
  73. package/src/components/data-table/data-table.stories.tsx +13 -6
  74. package/src/components/data-table/data-table.tsx +2 -2
  75. package/src/components/date-picker/date-picker.meta.md +88 -19
  76. package/src/components/date-picker/date-picker.stories.tsx +55 -5
  77. package/src/components/date-picker/date-picker.tsx +1489 -91
  78. package/src/components/descriptions/descriptions.meta.md +10 -5
  79. package/src/components/descriptions/descriptions.stories.tsx +3 -3
  80. package/src/components/descriptions/descriptions.tsx +22 -14
  81. package/src/components/dialog/dialog.meta.md +76 -13
  82. package/src/components/dialog/dialog.stories.tsx +182 -20
  83. package/src/components/dialog/dialog.tsx +67 -15
  84. package/src/components/dialog/imperative.tsx +252 -0
  85. package/src/components/drawer/drawer.meta.md +33 -34
  86. package/src/components/drawer/drawer.stories.tsx +29 -12
  87. package/src/components/drawer/drawer.tsx +22 -113
  88. package/src/components/dropdown-menu/dropdown-menu.meta.md +78 -10
  89. package/src/components/dropdown-menu/dropdown-menu.stories.tsx +88 -2
  90. package/src/components/dropdown-menu/dropdown-menu.tsx +24 -10
  91. package/src/components/ellipsis/ellipsis.meta.md +87 -0
  92. package/src/components/ellipsis/ellipsis.stories.tsx +72 -0
  93. package/src/components/ellipsis/ellipsis.tsx +153 -0
  94. package/src/components/empty/empty.meta.md +9 -4
  95. package/src/components/empty/empty.stories.tsx +4 -4
  96. package/src/components/empty/empty.tsx +10 -3
  97. package/src/components/field/field.meta.md +47 -9
  98. package/src/components/field/field.stories.tsx +385 -5
  99. package/src/components/field/field.tsx +263 -35
  100. package/src/components/filter-bar/filter-bar.meta.md +92 -0
  101. package/src/components/filter-bar/filter-bar.stories.tsx +1083 -0
  102. package/src/components/filter-bar/filter-bar.tsx +568 -0
  103. package/src/components/flex/flex.meta.md +54 -6
  104. package/src/components/flex/flex.stories.tsx +107 -20
  105. package/src/components/flex/flex.tsx +27 -4
  106. package/src/components/float-button/float-button.meta.md +8 -3
  107. package/src/components/float-button/float-button.stories.tsx +9 -7
  108. package/src/components/float-button/float-button.tsx +1 -1
  109. package/src/components/form/form.meta.md +39 -17
  110. package/src/components/form/form.stories.tsx +350 -3
  111. package/src/components/form/form.tsx +101 -35
  112. package/src/components/grid/grid.meta.md +7 -2
  113. package/src/components/grid/grid.stories.tsx +6 -4
  114. package/src/components/hover-card/hover-card.meta.md +20 -9
  115. package/src/components/hover-card/hover-card.stories.tsx +34 -5
  116. package/src/components/hover-card/hover-card.tsx +51 -13
  117. package/src/components/icon/DEVELOPMENT.md +809 -0
  118. package/src/components/icon/icon.meta.md +170 -0
  119. package/src/components/icon/icon.stories.tsx +344 -0
  120. package/src/components/icon/icon.tsx +248 -0
  121. package/src/components/image/image.meta.md +9 -4
  122. package/src/components/image/image.stories.tsx +3 -3
  123. package/src/components/image/image.tsx +6 -4
  124. package/src/components/input/demo/basic.tsx +12 -0
  125. package/src/components/input/demo/clearable.tsx +21 -0
  126. package/src/components/input/demo/show-count.tsx +18 -0
  127. package/src/components/input/demo/sizes.tsx +15 -0
  128. package/src/components/input/input.meta.md +39 -33
  129. package/src/components/input/input.stories.tsx +62 -35
  130. package/src/components/input/input.tsx +97 -98
  131. package/src/components/input-group/input-group.meta.md +54 -22
  132. package/src/components/input-group/input-group.stories.tsx +49 -16
  133. package/src/components/input-group/input-group.tsx +44 -8
  134. package/src/components/input-number/input-number.meta.md +64 -7
  135. package/src/components/input-number/input-number.stories.tsx +46 -8
  136. package/src/components/input-number/input-number.tsx +99 -26
  137. package/src/components/input-otp/input-otp.meta.md +4 -3
  138. package/src/components/input-otp/input-otp.stories.tsx +3 -3
  139. package/src/components/input-otp/input-otp.tsx +1 -1
  140. package/src/components/item/item.meta.md +8 -3
  141. package/src/components/item/item.stories.tsx +8 -5
  142. package/src/components/item/item.tsx +7 -6
  143. package/src/components/kbd/kbd.meta.md +13 -4
  144. package/src/components/kbd/kbd.stories.tsx +4 -4
  145. package/src/components/kbd/kbd.tsx +10 -5
  146. package/src/components/label/label.meta.md +18 -10
  147. package/src/components/label/label.stories.tsx +64 -6
  148. package/src/components/label/label.tsx +91 -19
  149. package/src/components/masonry/masonry.meta.md +8 -3
  150. package/src/components/masonry/masonry.stories.tsx +7 -5
  151. package/src/components/masonry/masonry.tsx +1 -0
  152. package/src/components/mentions/mentions.meta.md +36 -6
  153. package/src/components/mentions/mentions.stories.tsx +120 -6
  154. package/src/components/mentions/mentions.tsx +11 -5
  155. package/src/components/menubar/menubar.meta.md +30 -12
  156. package/src/components/menubar/menubar.stories.tsx +62 -2
  157. package/src/components/menubar/menubar.tsx +9 -9
  158. package/src/components/native-select/native-select.meta.md +8 -3
  159. package/src/components/native-select/native-select.stories.tsx +8 -5
  160. package/src/components/native-select/native-select.tsx +1 -1
  161. package/src/components/navigation-menu/navigation-menu.meta.md +19 -9
  162. package/src/components/navigation-menu/navigation-menu.stories.tsx +112 -9
  163. package/src/components/navigation-menu/navigation-menu.tsx +8 -4
  164. package/src/components/notification/notification.meta.md +52 -10
  165. package/src/components/notification/notification.stories.tsx +11 -9
  166. package/src/components/notification/notification.tsx +36 -21
  167. package/src/components/page-header/DEVELOPMENT.md +842 -0
  168. package/src/components/page-header/page-header.meta.md +208 -0
  169. package/src/components/page-header/page-header.stories.tsx +421 -0
  170. package/src/components/page-header/page-header.tsx +281 -0
  171. package/src/components/pagination/pagination.meta.md +140 -37
  172. package/src/components/pagination/pagination.stories.tsx +232 -10
  173. package/src/components/pagination/pagination.tsx +355 -63
  174. package/src/components/popconfirm/popconfirm.meta.md +9 -4
  175. package/src/components/popconfirm/popconfirm.stories.tsx +3 -4
  176. package/src/components/popconfirm/popconfirm.tsx +2 -2
  177. package/src/components/popover/popover.meta.md +62 -5
  178. package/src/components/popover/popover.stories.tsx +83 -7
  179. package/src/components/popover/popover.tsx +77 -28
  180. package/src/components/progress/progress.meta.md +38 -6
  181. package/src/components/progress/progress.stories.tsx +3 -3
  182. package/src/components/progress/progress.tsx +24 -16
  183. package/src/components/radio-group/radio-group.meta.md +79 -7
  184. package/src/components/radio-group/radio-group.stories.tsx +39 -3
  185. package/src/components/radio-group/radio-group.tsx +149 -18
  186. package/src/components/rate/rate.meta.md +35 -4
  187. package/src/components/rate/rate.stories.tsx +13 -5
  188. package/src/components/rate/rate.tsx +37 -10
  189. package/src/components/resizable/resizable.meta.md +7 -4
  190. package/src/components/resizable/resizable.stories.tsx +6 -6
  191. package/src/components/resizable/resizable.tsx +1 -1
  192. package/src/components/result/result.meta.md +7 -2
  193. package/src/components/result/result.stories.tsx +4 -8
  194. package/src/components/result/result.tsx +24 -15
  195. package/src/components/scroll-area/scroll-area.meta.md +4 -3
  196. package/src/components/scroll-area/scroll-area.stories.tsx +12 -4
  197. package/src/components/scroll-area/scroll-area.tsx +3 -3
  198. package/src/components/segmented/segmented.meta.md +7 -4
  199. package/src/components/segmented/segmented.stories.tsx +37 -8
  200. package/src/components/segmented/segmented.tsx +15 -7
  201. package/src/components/select/select.meta.md +197 -52
  202. package/src/components/select/select.stories.tsx +238 -63
  203. package/src/components/select/select.tsx +718 -171
  204. package/src/components/separator/separator.meta.md +4 -3
  205. package/src/components/separator/separator.stories.tsx +3 -3
  206. package/src/components/separator/separator.tsx +3 -7
  207. package/src/components/sheet/sheet.meta.md +32 -16
  208. package/src/components/sheet/sheet.stories.tsx +116 -10
  209. package/src/components/sheet/sheet.tsx +116 -29
  210. package/src/components/sidebar/sidebar.meta.md +37 -18
  211. package/src/components/sidebar/sidebar.stories.tsx +701 -29
  212. package/src/components/sidebar/sidebar.tsx +615 -142
  213. package/src/components/skeleton/skeleton.meta.md +4 -5
  214. package/src/components/skeleton/skeleton.stories.tsx +4 -4
  215. package/src/components/skeleton/skeleton.tsx +7 -7
  216. package/src/components/slider/slider.meta.md +57 -5
  217. package/src/components/slider/slider.stories.tsx +58 -6
  218. package/src/components/slider/slider.tsx +154 -13
  219. package/src/components/sonner/sonner.meta.md +58 -7
  220. package/src/components/sonner/sonner.stories.tsx +78 -5
  221. package/src/components/sonner/sonner.tsx +137 -8
  222. package/src/components/spinner/spinner.meta.md +62 -13
  223. package/src/components/spinner/spinner.stories.tsx +66 -14
  224. package/src/components/spinner/spinner.tsx +111 -9
  225. package/src/components/statistic/statistic.meta.md +7 -2
  226. package/src/components/statistic/statistic.stories.tsx +3 -7
  227. package/src/components/statistic/statistic.tsx +5 -6
  228. package/src/components/steps/steps.meta.md +18 -4
  229. package/src/components/steps/steps.stories.tsx +43 -3
  230. package/src/components/steps/steps.tsx +15 -12
  231. package/src/components/switch/switch.meta.md +51 -5
  232. package/src/components/switch/switch.stories.tsx +6 -6
  233. package/src/components/switch/switch.tsx +109 -41
  234. package/src/components/table/table.meta.md +17 -6
  235. package/src/components/table/table.stories.tsx +10 -5
  236. package/src/components/table/table.tsx +4 -4
  237. package/src/components/tabs/tabs.meta.md +38 -25
  238. package/src/components/tabs/tabs.stories.tsx +111 -25
  239. package/src/components/tabs/tabs.tsx +125 -54
  240. package/src/components/tag/tag.meta.md +105 -40
  241. package/src/components/tag/tag.stories.tsx +189 -16
  242. package/src/components/tag/tag.tsx +222 -21
  243. package/src/components/textarea/textarea.meta.md +35 -19
  244. package/src/components/textarea/textarea.stories.tsx +32 -6
  245. package/src/components/textarea/textarea.tsx +33 -9
  246. package/src/components/time-picker/time-picker.meta.md +124 -32
  247. package/src/components/time-picker/time-picker.stories.tsx +85 -15
  248. package/src/components/time-picker/time-picker.tsx +913 -61
  249. package/src/components/timeline/timeline.meta.md +14 -6
  250. package/src/components/timeline/timeline.stories.tsx +37 -7
  251. package/src/components/timeline/timeline.tsx +35 -14
  252. package/src/components/toggle/toggle.meta.md +5 -4
  253. package/src/components/toggle/toggle.stories.tsx +4 -4
  254. package/src/components/toggle/toggle.tsx +4 -3
  255. package/src/components/toggle-group/toggle-group.meta.md +5 -4
  256. package/src/components/toggle-group/toggle-group.stories.tsx +3 -3
  257. package/src/components/toggle-group/toggle-group.tsx +2 -2
  258. package/src/components/tooltip/tooltip.meta.md +55 -5
  259. package/src/components/tooltip/tooltip.stories.tsx +42 -5
  260. package/src/components/tooltip/tooltip.tsx +81 -21
  261. package/src/components/tour/tour.meta.md +9 -4
  262. package/src/components/tour/tour.stories.tsx +3 -3
  263. package/src/components/tour/tour.tsx +4 -4
  264. package/src/components/transfer/transfer.meta.md +11 -6
  265. package/src/components/transfer/transfer.stories.tsx +4 -8
  266. package/src/components/transfer/transfer.tsx +28 -21
  267. package/src/components/tree/tree.meta.md +63 -5
  268. package/src/components/tree/tree.stories.tsx +31 -12
  269. package/src/components/tree/tree.tsx +9 -8
  270. package/src/components/tree-select/tree-select.meta.md +59 -8
  271. package/src/components/tree-select/tree-select.stories.tsx +3 -3
  272. package/src/components/tree-select/tree-select.tsx +42 -7
  273. package/src/components/typography/typography.meta.md +61 -14
  274. package/src/components/typography/typography.stories.tsx +12 -11
  275. package/src/components/typography/typography.tsx +43 -28
  276. package/src/components/upload/upload.meta.md +49 -4
  277. package/src/components/upload/upload.stories.tsx +72 -12
  278. package/src/components/upload/upload.tsx +170 -37
  279. package/src/components/watermark/watermark.meta.md +7 -2
  280. package/src/components/watermark/watermark.stories.tsx +101 -9
  281. package/src/components/watermark/watermark.tsx +1 -0
  282. package/src/hooks/use-breakpoint.ts +117 -0
  283. package/src/hooks/use-debounce-callback.ts +52 -0
  284. package/src/hooks/use-mobile.ts +23 -0
  285. package/src/stories/theme-tokens.stories.tsx +747 -0
  286. package/src/utils/trigger-input.ts +53 -0
  287. package/src/components/button-group/button-group.meta.md +0 -92
  288. package/src/components/button-group/button-group.stories.tsx +0 -90
  289. package/src/components/button-group/button-group.tsx +0 -75
  290. package/src/components/combobox/combobox.meta.md +0 -93
  291. package/src/components/combobox/combobox.stories.tsx +0 -55
  292. package/src/components/combobox/combobox.tsx +0 -130
  293. package/src/components/space/space.meta.md +0 -94
  294. package/src/components/space/space.stories.tsx +0 -94
  295. package/src/components/space/space.tsx +0 -106
@@ -1,7 +1,18 @@
1
1
  import * as React from 'react';
2
2
 
3
- import { cn } from '@/utils/cn';
3
+ import {
4
+ Command,
5
+ CommandEmpty,
6
+ CommandGroup,
7
+ CommandItem,
8
+ CommandList,
9
+ } from '@/components/command/command';
4
10
  import { Input } from '@/components/input/input';
11
+ import {
12
+ Popover,
13
+ PopoverAnchor,
14
+ PopoverContent,
15
+ } from '@/components/popover/popover';
5
16
 
6
17
  export interface AutoCompleteOption {
7
18
  /** 真实写入到输入框的 value(也作为建议列表 key)。 */
@@ -29,6 +40,7 @@ export interface AutoCompleteProps
29
40
  onChange?: (value: string) => void;
30
41
  /**
31
42
  * 用户输入时回调,用于刷新 `options`(异步建议)。
43
+ * 提供该回调后,内部不再对 `options` 做本地过滤(由外部按 query 自行刷新)。
32
44
  */
33
45
  onSearch?: (query: string) => void;
34
46
  /**
@@ -47,15 +59,23 @@ export interface AutoCompleteProps
47
59
  emptyText?: string;
48
60
  /**
49
61
  * 容器尺寸 — 透传 Input.size。
50
- * @default "default"
62
+ * @default "md"
51
63
  */
52
- size?: 'sm' | 'default' | 'lg';
64
+ size?: 'sm' | 'md' | 'default' | 'lg';
65
+ /**
66
+ * 错误态视觉(校验失败) — 透传给内部 Input,红色 border + ring,自动写 `aria-invalid="true"`。
67
+ * @default false
68
+ */
69
+ error?: boolean;
53
70
  }
54
71
 
55
72
  /**
56
73
  * 输入即建议 — antd 独有补足。**等价 antd `AutoComplete`**。
57
- * 与 `Combobox` 区别:Combobox 是"必选下拉",输入框只接受选项中的值;
58
- * AutoComplete 是"自由输入 + 选项建议",最终 value 可以是任意字符串。
74
+ *
75
+ * `Combobox` 区别:Combobox "必选下拉",输入框只接受选项中的值(closed-list);
76
+ * AutoComplete 是"自由输入 + 选项建议",最终 value 可以是任意字符串(open-list)。
77
+ *
78
+ * 内核与 `Select` 同源 —— 基于 `Popover + cmdk Command`,共享下拉视觉、键盘导航与 a11y。
59
79
  */
60
80
  const AutoComplete = React.forwardRef<HTMLInputElement, AutoCompleteProps>(
61
81
  (
@@ -68,7 +88,8 @@ const AutoComplete = React.forwardRef<HTMLInputElement, AutoCompleteProps>(
68
88
  onSelect,
69
89
  openOnFocus = false,
70
90
  emptyText = '无匹配项',
71
- size = 'default',
91
+ size = 'md',
92
+ error = false,
72
93
  className,
73
94
  onFocus,
74
95
  onBlur,
@@ -84,18 +105,22 @@ const AutoComplete = React.forwardRef<HTMLInputElement, AutoCompleteProps>(
84
105
  const current = isControlled ? value! : internal;
85
106
 
86
107
  const [open, setOpen] = React.useState(false);
87
- const [activeIdx, setActiveIdx] = React.useState(0);
108
+ const [activeValue, setActiveValue] = React.useState<string>('');
88
109
 
110
+ // 提供 onSearch 时,默认认为外部按 query 异步刷过 options;否则内部本地过滤
89
111
  const filtered = React.useMemo(() => {
112
+ if (onSearch) return options;
90
113
  const q = current.toLowerCase();
91
114
  return q === ''
92
115
  ? options
93
116
  : options.filter((o) => o.value.toLowerCase().includes(q));
94
- }, [current, options]);
117
+ }, [current, options, onSearch]);
95
118
 
119
+ // filtered 变化时,默认高亮第一个可用项
96
120
  React.useEffect(() => {
97
- setActiveIdx(0);
98
- }, [current]);
121
+ const first = filtered.find((o) => !o.disabled);
122
+ setActiveValue(first?.value ?? '');
123
+ }, [filtered]);
99
124
 
100
125
  const update = (next: string) => {
101
126
  if (!isControlled) setInternal(next);
@@ -109,20 +134,30 @@ const AutoComplete = React.forwardRef<HTMLInputElement, AutoCompleteProps>(
109
134
  setOpen(false);
110
135
  };
111
136
 
137
+ const moveActive = (dir: 1 | -1) => {
138
+ const enabled = filtered.filter((o) => !o.disabled);
139
+ if (enabled.length === 0) return;
140
+ const idx = enabled.findIndex((o) => o.value === activeValue);
141
+ const nextIdx =
142
+ (idx === -1 ? 0 : idx + dir + enabled.length) % enabled.length;
143
+ const nextOpt = enabled[nextIdx];
144
+ if (nextOpt) setActiveValue(nextOpt.value);
145
+ };
146
+
112
147
  const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
113
- if (open && filtered.length > 0) {
148
+ if (open) {
114
149
  if (e.key === 'ArrowDown') {
115
150
  e.preventDefault();
116
- setActiveIdx((i) => (i + 1) % filtered.length);
151
+ moveActive(1);
117
152
  return;
118
153
  }
119
154
  if (e.key === 'ArrowUp') {
120
155
  e.preventDefault();
121
- setActiveIdx((i) => (i - 1 + filtered.length) % filtered.length);
156
+ moveActive(-1);
122
157
  return;
123
158
  }
124
159
  if (e.key === 'Enter') {
125
- const opt = filtered[activeIdx];
160
+ const opt = filtered.find((o) => o.value === activeValue);
126
161
  if (opt && !opt.disabled) {
127
162
  e.preventDefault();
128
163
  select(opt);
@@ -134,69 +169,82 @@ const AutoComplete = React.forwardRef<HTMLInputElement, AutoCompleteProps>(
134
169
  setOpen(false);
135
170
  return;
136
171
  }
172
+ } else if (e.key === 'ArrowDown' && filtered.length > 0) {
173
+ e.preventDefault();
174
+ setOpen(true);
175
+ return;
137
176
  }
138
177
  onKeyDown?.(e);
139
178
  };
140
179
 
141
- const shouldShow = open && (filtered.length > 0 || current !== '');
142
-
143
180
  return (
144
- <div className={cn('relative', className)}>
145
- <Input
146
- ref={ref}
147
- value={current}
148
- placeholder={placeholder}
149
- disabled={disabled}
150
- size={size}
151
- onChange={(e) => {
152
- update(e.target.value);
153
- onSearch?.(e.target.value);
154
- setOpen(true);
155
- }}
156
- onFocus={(e) => {
157
- if (openOnFocus) setOpen(true);
158
- onFocus?.(e);
159
- }}
160
- onBlur={(e) => {
161
- window.setTimeout(() => setOpen(false), 120);
162
- onBlur?.(e);
163
- }}
164
- onKeyDown={handleKeyDown}
165
- {...props}
166
- />
167
- {shouldShow ? (
168
- <ul
169
- role="listbox"
170
- className="absolute left-0 right-0 top-full z-50 mt-1 max-h-60 overflow-auto rounded-md border bg-popover p-1 text-sm text-popover-foreground shadow-md"
181
+ <Popover open={open} onOpenChange={setOpen}>
182
+ <PopoverAnchor asChild>
183
+ <Input
184
+ ref={ref}
185
+ value={current}
186
+ placeholder={placeholder}
187
+ disabled={disabled}
188
+ size={size}
189
+ error={error}
190
+ className={className}
191
+ role="combobox"
192
+ aria-autocomplete="list"
193
+ aria-expanded={open}
194
+ onChange={(e) => {
195
+ const next = e.target.value;
196
+ update(next);
197
+ onSearch?.(next);
198
+ setOpen(true);
199
+ }}
200
+ onFocus={(e) => {
201
+ if (openOnFocus) setOpen(true);
202
+ onFocus?.(e);
203
+ }}
204
+ onBlur={(e) => {
205
+ window.setTimeout(() => setOpen(false), 120);
206
+ onBlur?.(e);
207
+ }}
208
+ onKeyDown={handleKeyDown}
209
+ {...props}
210
+ />
211
+ </PopoverAnchor>
212
+ <PopoverContent
213
+ align="start"
214
+ sideOffset={4}
215
+ onOpenAutoFocus={(e) => e.preventDefault()}
216
+ // 读 Radix 暴露的 CSS 变量让弹层宽度追平 Input 宽度;主动选 style 而非 Tailwind
217
+ // arbitrary value(`w-[var(--...)]`),避免 `teamix-evo/no-arbitrary-tw-value` lint 告警
218
+ style={{ width: 'var(--radix-popover-trigger-width)' }}
219
+ className="p-0"
220
+ >
221
+ <Command
222
+ shouldFilter={false}
223
+ value={activeValue}
224
+ onValueChange={setActiveValue}
171
225
  >
172
- {filtered.length === 0 ? (
173
- <li className="px-2 py-1.5 text-center text-xs text-muted-foreground">
174
- {emptyText}
175
- </li>
176
- ) : (
177
- filtered.map((opt, i) => (
178
- <li
179
- key={opt.value}
180
- role="option"
181
- aria-selected={i === activeIdx}
182
- onMouseDown={(e) => {
183
- e.preventDefault();
184
- select(opt);
185
- }}
186
- onMouseEnter={() => setActiveIdx(i)}
187
- className={cn(
188
- 'cursor-pointer rounded-sm px-2 py-1.5',
189
- i === activeIdx && !opt.disabled && 'bg-accent text-accent-foreground',
190
- opt.disabled && 'cursor-not-allowed opacity-50',
191
- )}
192
- >
193
- {opt.label ?? opt.value}
194
- </li>
195
- ))
196
- )}
197
- </ul>
198
- ) : null}
199
- </div>
226
+ <CommandList>
227
+ <CommandEmpty>{emptyText}</CommandEmpty>
228
+ {filtered.length > 0 && (
229
+ <CommandGroup>
230
+ {filtered.map((opt) => (
231
+ <CommandItem
232
+ key={opt.value}
233
+ value={opt.value}
234
+ disabled={opt.disabled}
235
+ onSelect={() => select(opt)}
236
+ // 阻止鼠标按下时让 Input 失焦,避免选中前 popover 已关闭
237
+ onMouseDown={(e) => e.preventDefault()}
238
+ >
239
+ {opt.label ?? opt.value}
240
+ </CommandItem>
241
+ ))}
242
+ </CommandGroup>
243
+ )}
244
+ </CommandList>
245
+ </Command>
246
+ </PopoverContent>
247
+ </Popover>
200
248
  );
201
249
  },
202
250
  );
@@ -1,13 +1,14 @@
1
1
  ---
2
2
  id: avatar
3
3
  name: Avatar
4
+ displayName: 头像
4
5
  type: component
5
- category: foundation
6
+ category: data-display
6
7
  since: 0.1.0
7
- package: "@teamix-evo/ui"
8
+ package: '@teamix-evo/ui'
8
9
  ---
9
10
 
10
- # Avatar
11
+ # Avatar 头像
11
12
 
12
13
  头像 — Radix Avatar(图片加载失败自动回退到 Fallback)+ antd Avatar 的 `size / shape / Avatar.Group` 并集。
13
14
 
@@ -29,7 +30,7 @@ package: "@teamix-evo/ui"
29
30
  <!-- auto:props:begin -->
30
31
  | 名称 | 类型 | 默认值 | 必填 | 说明 |
31
32
  | --- | --- | --- | --- | --- |
32
- | `size` | `'sm' \| 'default' \| 'lg' \| 'xl'` | `"default"` | – | 尺寸(antd 并集:sm 32 / default 40 / lg 48 / xl 64)。 |
33
+ | `size` | `'sm' \| 'md' \| 'default' \| 'lg' \| 'xl'` | `"default"` | – | 尺寸(antd 并集:sm 32 / default 40 / lg 48 / xl 64)。 |
33
34
  | `shape` | `'circle' \| 'square'` | `"circle"` | – | 形状(antd 并集)。 |
34
35
  <!-- auto:props:end -->
35
36
 
@@ -51,12 +52,10 @@ package: "@teamix-evo/ui"
51
52
  > 业务侧需要先 `pnpm add` / `npm install` 这些包。CLI 在 `ui add` 完成后会列出此提示。
52
53
 
53
54
  ```bash
54
- pnpm add @radix-ui/react-avatar@^1.1.0 class-variance-authority@^0.7.0
55
+ pnpm add @radix-ui/react-avatar@^1.1.0 class-variance-authority@^0.7.0 lucide-react@^0.460.0
55
56
  ```
56
57
  <!-- auto:deps:end -->
57
58
 
58
- > 子组件:`AvatarImage`(图片源)/ `AvatarFallback`(加载失败 / 无图时的占位)/ `AvatarGroup`(多头像折叠组,带 `max` 与 `size`)。
59
-
60
59
  ## AI 生成纪律
61
60
 
62
61
  - **必须配 Fallback**:`AvatarImage` 加载失败时无 Fallback 会渲染空白;**永远**写 `<AvatarImage /> + <AvatarFallback>{initials}</AvatarFallback>` 的对子
@@ -1,15 +1,15 @@
1
- import type { Meta, StoryObj } from '@storybook/react';
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
2
  import { Avatar, AvatarImage, AvatarFallback, AvatarGroup } from './avatar';
3
3
 
4
4
  const meta: Meta<typeof Avatar> = {
5
- title: '基础原语 · Foundation/Avatar',
5
+ title: '数据展示 · Data Display/Avatar',
6
6
  component: Avatar,
7
7
  tags: ['autodocs'],
8
8
  parameters: {
9
9
  docs: {
10
10
  description: {
11
11
  component:
12
- '头像 — 表示用户或对象,支持图片 / 文字 / 图标三种回退源。基于 Radix Avatar(图片加载失败自动回退到 fallback) + antd 增强:尺寸 sm/default/lg、形状 circle/square、`AvatarGroup` 堆叠。Image / Fallback 通过命名子组件组合。视觉走 OpenTrek semantic tokens,所有样式来自 `@teamix-evo/design`,无 mock。',
12
+ '头像 — 表示用户或对象,支持图片 / 文字 / 图标三种回退源。基于 Radix Avatar(图片加载失败自动回退到 fallback)+ antd 增强:尺寸 sm/default/lg/xl、形状 circle/square、`AvatarGroup` 堆叠。未传 children fallback 默认渲染 lucide `User` 占位图标。',
13
13
  },
14
14
  },
15
15
  },
@@ -66,6 +66,24 @@ export const FallbackOnly: Story = {
66
66
  ),
67
67
  };
68
68
 
69
+ export const DefaultIconFallback: Story = {
70
+ name: '默认图标回退',
71
+ parameters: { controls: { disable: true } },
72
+ render: () => (
73
+ <div className="flex items-end gap-3">
74
+ {(['sm', 'default', 'lg', 'xl'] as const).map((s) => (
75
+ <Avatar key={s} size={s}>
76
+ {/* 未传图片与文字 children, fallback 自动渲染 lucide User 占位 */}
77
+ <AvatarFallback />
78
+ </Avatar>
79
+ ))}
80
+ <Avatar shape="square">
81
+ <AvatarFallback />
82
+ </Avatar>
83
+ </div>
84
+ ),
85
+ };
86
+
69
87
  export const Group: Story = {
70
88
  parameters: { controls: { disable: true } },
71
89
  render: () => (
@@ -1,27 +1,26 @@
1
1
  import * as React from 'react';
2
2
  import * as AvatarPrimitive from '@radix-ui/react-avatar';
3
3
  import { cva, type VariantProps } from 'class-variance-authority';
4
+ import { User } from 'lucide-react';
4
5
 
5
6
  import { cn } from '@/utils/cn';
6
7
 
7
- const avatarVariants = cva(
8
- 'relative flex shrink-0 overflow-hidden bg-muted',
9
- {
10
- variants: {
11
- size: {
12
- sm: 'size-8 text-xs',
13
- default: 'size-10 text-sm',
14
- lg: 'size-12 text-base',
15
- xl: 'size-16 text-lg',
16
- },
17
- shape: {
18
- circle: 'rounded-full',
19
- square: 'rounded-md',
20
- },
8
+ const avatarVariants = cva('relative flex shrink-0 overflow-hidden bg-muted', {
9
+ variants: {
10
+ size: {
11
+ sm: 'size-8 text-xs',
12
+ md: 'size-10 text-xs',
13
+ default: 'size-10 text-xs',
14
+ lg: 'size-12 text-sm',
15
+ xl: 'size-16 text-base',
16
+ },
17
+ shape: {
18
+ circle: 'rounded-full',
19
+ square: 'rounded-md',
21
20
  },
22
- defaultVariants: { size: 'default', shape: 'circle' },
23
21
  },
24
- );
22
+ defaultVariants: { size: 'md', shape: 'circle' },
23
+ });
25
24
 
26
25
  export interface AvatarProps
27
26
  extends React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>,
@@ -30,7 +29,7 @@ export interface AvatarProps
30
29
  * 尺寸(antd 并集:sm 32 / default 40 / lg 48 / xl 64)。
31
30
  * @default "default"
32
31
  */
33
- size?: 'sm' | 'default' | 'lg' | 'xl';
32
+ size?: 'sm' | 'md' | 'default' | 'lg' | 'xl';
34
33
  /**
35
34
  * 形状(antd 并集)。
36
35
  * @default "circle"
@@ -65,7 +64,7 @@ AvatarImage.displayName = AvatarPrimitive.Image.displayName;
65
64
  const AvatarFallback = React.forwardRef<
66
65
  React.ElementRef<typeof AvatarPrimitive.Fallback>,
67
66
  React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
68
- >(({ className, ...props }, ref) => (
67
+ >(({ className, children, ...props }, ref) => (
69
68
  <AvatarPrimitive.Fallback
70
69
  ref={ref}
71
70
  className={cn(
@@ -73,7 +72,9 @@ const AvatarFallback = React.forwardRef<
73
72
  className,
74
73
  )}
75
74
  {...props}
76
- />
75
+ >
76
+ {children ?? <User className="h-3/5 w-3/5" aria-hidden="true" />}
77
+ </AvatarPrimitive.Fallback>
77
78
  ));
78
79
  AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
79
80
 
@@ -106,10 +107,10 @@ const AvatarGroup = React.forwardRef<HTMLDivElement, AvatarGroupProps>(
106
107
  >
107
108
  {visible.map((child, i) =>
108
109
  React.isValidElement(child) && size
109
- ? React.cloneElement(
110
- child as React.ReactElement<AvatarProps>,
111
- { size, key: i },
112
- )
110
+ ? React.cloneElement(child as React.ReactElement<AvatarProps>, {
111
+ size,
112
+ key: i,
113
+ })
113
114
  : child,
114
115
  )}
115
116
  {overflow > 0 ? (
@@ -1,23 +1,24 @@
1
1
  ---
2
2
  id: badge
3
3
  name: Badge
4
+ displayName: 徽标
4
5
  type: component
5
- category: foundation
6
+ category: data-display
6
7
  since: 0.1.0
7
- package: "@teamix-evo/ui"
8
+ package: '@teamix-evo/ui'
8
9
  ---
9
10
 
10
- # Badge
11
+ # Badge 徽标
11
12
 
12
13
  徽标 — shadcn 文本标签风格 + antd 数字徽标 / 红点 / 状态点的合集。
13
14
  **单组件四种形态**,根据 props 自动切换:
14
15
 
15
- | 形态 | 触发条件 | 示例 |
16
- | --- | --- | --- |
17
- | 文本徽标(shadcn) | 无 `count / dot / status`,仅 `children` | `<Badge>New</Badge>` |
18
- | 包裹徽标(antd) | 有 `children` + `count` 或 `dot` | `<Badge count={5}><Avatar /></Badge>` |
19
- | 独立数字气泡 | 仅 `count`,无 `children` | `<Badge count={3} />` |
20
- | 状态点 + 文字 | 有 `status` | `<Badge status="success" text="运行中" />` |
16
+ | 形态 | 触发条件 | 示例 |
17
+ | ---------------- | --------------------------------------- | ------------------------------------------ |
18
+ | 文本徽标(shadcn) | 无 `count / dot / status`,仅 `children` | `<Badge>New</Badge>` |
19
+ | 包裹徽标(antd) | 有 `children` + `count` 或 `dot` | `<Badge count={5}><Avatar /></Badge>` |
20
+ | 独立数字气泡 | 仅 `count`,无 `children` | `<Badge count={3} />` |
21
+ | 状态点 + 文字 | 有 `status` | `<Badge status="success" text="运行中" />` |
21
22
 
22
23
  ## When to use
23
24
 
@@ -1,9 +1,9 @@
1
- import type { Meta, StoryObj } from '@storybook/react';
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
2
  import { Bell, MessageSquare, ShoppingCart } from 'lucide-react';
3
3
  import { Badge } from './badge';
4
4
 
5
5
  const meta: Meta<typeof Badge> = {
6
- title: '基础原语 · Foundation/Badge',
6
+ title: '数据展示 · Data Display/Badge',
7
7
  component: Badge,
8
8
  tags: ['autodocs'],
9
9
  parameters: {
@@ -4,7 +4,7 @@ import { cva, type VariantProps } from 'class-variance-authority';
4
4
  import { cn } from '@/utils/cn';
5
5
 
6
6
  const badgeVariants = cva(
7
- 'inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
7
+ 'inline-flex items-center rounded-md border border-border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
8
8
  {
9
9
  variants: {
10
10
  variant: {
@@ -16,9 +16,9 @@ const badgeVariants = cva(
16
16
  'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80',
17
17
  outline: 'text-foreground',
18
18
  success:
19
- 'border-transparent bg-emerald-500 text-white hover:bg-emerald-500/80',
19
+ 'border-transparent bg-success text-success-foreground hover:bg-success/80',
20
20
  warning:
21
- 'border-transparent bg-amber-500 text-white hover:bg-amber-500/80',
21
+ 'border-transparent bg-warning text-warning-foreground hover:bg-warning/80',
22
22
  },
23
23
  },
24
24
  defaultVariants: {
@@ -27,18 +27,12 @@ const badgeVariants = cva(
27
27
  },
28
28
  );
29
29
 
30
- /**
31
- * 状态点的语义色映射。来自 design `foundations/color/semantic.md`。
32
- * 之所以这里硬编码 hex/tailwind class 而非 design token,
33
- * 是因为 status 是数值化状态枚举(antd 体系),需要稳定一对一映射;
34
- * 切换 design variant 时通过 token 层调整这些颜色,而非组件层。
35
- */
36
30
  const statusDotClasses: Record<BadgeStatus, string> = {
37
31
  default: 'bg-muted-foreground',
38
- success: 'bg-emerald-500',
39
- processing: 'bg-blue-500',
32
+ success: 'bg-success',
33
+ processing: 'bg-info',
40
34
  error: 'bg-destructive',
41
- warning: 'bg-amber-500',
35
+ warning: 'bg-warning',
42
36
  };
43
37
 
44
38
  export type BadgeStatus =
@@ -119,7 +113,7 @@ const Badge = React.forwardRef<HTMLSpanElement, BadgeProps>(
119
113
  return (
120
114
  <span
121
115
  ref={ref}
122
- className={cn('inline-flex items-center gap-2 text-sm', className)}
116
+ className={cn('inline-flex items-center gap-2 text-xs', className)}
123
117
  {...props}
124
118
  >
125
119
  <span
@@ -154,7 +148,7 @@ const Badge = React.forwardRef<HTMLSpanElement, BadgeProps>(
154
148
  aria-hidden="true"
155
149
  />
156
150
  ) : (
157
- <span className="absolute right-0 top-0 inline-flex min-w-[1.25rem] -translate-y-1/2 translate-x-1/2 items-center justify-center rounded-full bg-destructive px-1 text-[10px] font-semibold leading-none text-destructive-foreground ring-2 ring-background">
151
+ <span className="absolute right-0 top-0 inline-flex h-5 min-w-counter -translate-y-1/2 translate-x-1/2 items-center justify-center rounded-full bg-destructive px-1.5 text-xs font-semibold leading-none text-destructive-foreground ring-2 ring-background">
158
152
  {display}
159
153
  </span>
160
154
  )}
@@ -183,7 +177,7 @@ const Badge = React.forwardRef<HTMLSpanElement, BadgeProps>(
183
177
  <span
184
178
  ref={ref}
185
179
  className={cn(
186
- 'inline-flex min-w-[1.25rem] items-center justify-center rounded-full bg-destructive px-1 text-[10px] font-semibold leading-none text-destructive-foreground',
180
+ 'inline-flex h-5 min-w-counter items-center justify-center rounded-full bg-destructive px-1.5 text-xs font-semibold leading-none text-destructive-foreground',
187
181
  className,
188
182
  )}
189
183
  {...props}
@@ -1,15 +1,17 @@
1
1
  ---
2
2
  id: breadcrumb
3
3
  name: Breadcrumb
4
+ displayName: 面包屑
4
5
  type: component
5
6
  category: navigation
6
7
  since: 0.1.0
7
- package: "@teamix-evo/ui"
8
+ package: '@teamix-evo/ui'
8
9
  ---
9
10
 
10
- # Breadcrumb
11
+ # Breadcrumb 面包屑
12
+
13
+ 在多层级页面中显示当前位置并提供回溯路径。默认分隔符 lucide `ChevronRight`,字号 `text-sm`,普通项 `text-muted-foreground` / 当前项 `text-foreground` / 链接 hover 升至 `text-foreground`,能力 = shadcn 原子 ∪ antd `separator`,不收 antd `routes` / `itemRender`。
11
14
 
12
- 面包屑 — 纯组合组件 + antd `separator` 自定义并集。
13
15
  **与 antd Breadcrumb 的差异**:antd 用 `routes` 数组配置;本组件走 shadcn 的**组合式**(每项手写 `<BreadcrumbItem>`),配 React Router / Next.js Link 时更灵活。
14
16
 
15
17
  ## When to use
@@ -31,7 +33,7 @@ package: "@teamix-evo/ui"
31
33
  <!-- auto:props:begin -->
32
34
  | 名称 | 类型 | 默认值 | 必填 | 说明 |
33
35
  | --- | --- | --- | --- | --- |
34
- | `separator` | `React.ReactNode` | | – | 自定义分隔符(antd `separator` 并集)。每个 `<BreadcrumbSeparator />` 默认渲染 `<ChevronRight />`, 通过设置 children 可全局覆盖。 |
36
+ | `separator` | `React.ReactNode` | `<ChevronRight />` | – | 自定义全局分隔符(antd `separator` 并集)。仅作类型层占位 — 实际渲染由 `<BreadcrumbSeparator>` 控制,默认 lucide `ChevronRight`, 通过 `<BreadcrumbSeparator>{节点}</BreadcrumbSeparator>` 覆盖单个分隔符。 |
35
37
  <!-- auto:props:end -->
36
38
 
37
39
  ## 依赖
@@ -56,16 +58,34 @@ pnpm add @radix-ui/react-slot@^1.1.0 lucide-react@^0.460.0
56
58
  ```
57
59
  <!-- auto:deps:end -->
58
60
 
59
- > 子组件:`Breadcrumb`(nav)/ `BreadcrumbList`(ol)/ `BreadcrumbItem`(li)/ `BreadcrumbLink`(支持 asChild)/ `BreadcrumbPage`(当前页,不可点)/ `BreadcrumbSeparator`(默认 ChevronRight,可自定义)/ `BreadcrumbEllipsis`(省略,中间路径折叠用)。
60
-
61
61
  ## AI 生成纪律
62
62
 
63
+ - **默认分隔符是箭头**(`ChevronRight`),不要手写 `/` / `>` / `·` 字符 — 需覆盖时传入 `<BreadcrumbSeparator>{customNode}</BreadcrumbSeparator>`
64
+ - **字号走 token**:所有项默认 `text-sm`(14px),不要在业务层覆写 `text-base` / `text-xs`,跨面包屑不一致会破坏顶部导航识别度
65
+ - **颜色走 semantic**:普通项 `text-muted-foreground` / 当前项 `text-foreground` / 链接 hover 升至 `text-foreground`。不要用 `text-primary` / `text-blue-*` 手动上色
63
66
  - **当前页用 `BreadcrumbPage`**,不是 `BreadcrumbLink`(语义 + 视觉都不同)
64
67
  - **配路由用 `asChild`**:`<BreadcrumbLink asChild><Link to="/x">...</Link></BreadcrumbLink>`
65
- - **每两项之间必有 Separator**:不要手写 `>` 字符
68
+ - **每两项之间必有 Separator**:不要手写文本分隔
66
69
  - **过长路径用 Ellipsis**:超过 4 级时中间收成 `<BreadcrumbEllipsis />`(可配 DropdownMenu 展开完整列表)
67
70
  - **路径文字简短**:每段 ≤ 6 字 / 12 字符,过长导致换行视觉破碎
68
71
 
72
+ ## Breadcrumb 形态 — 旧库 API → 新库映射
73
+
74
+ 旧库(Teamix 1.0 / `@alifd/next`)走数据驱动的 `Breadcrumb.Item` + `maxNode`,新库走 shadcn 的**纯组合**(`BreadcrumbList` / `BreadcrumbItem` / `BreadcrumbLink` / `BreadcrumbPage` / `BreadcrumbSeparator` / `BreadcrumbEllipsis`)。下表给出旧库 API 到新库写法的逐项映射。
75
+
76
+ | 旧库 API | 新库写法 | 备注 |
77
+ | --- | --- | --- |
78
+ | `<Breadcrumb>` 容器 | `<Breadcrumb><BreadcrumbList>...</BreadcrumbList></Breadcrumb>` | 容器 + List 双层是 `nav > ol` 语义,无障碍更准 |
79
+ | `<Breadcrumb.Item link="/x">label</Breadcrumb.Item>` | `<BreadcrumbItem><BreadcrumbLink href="/x">label</BreadcrumbLink></BreadcrumbItem>` | Item 与 Link 拆开,Link 可单独控制 hover / focus 样式 |
80
+ | 末项(当前页) | `<BreadcrumbItem><BreadcrumbPage>label</BreadcrumbPage></BreadcrumbItem>` | `aria-current="page"` 自动注入 |
81
+ | `separator="/"` | `<BreadcrumbSeparator>{customNode}</BreadcrumbSeparator>` | 默认 lucide `ChevronRight`,组件化覆盖更可控 |
82
+ | `maxNode={3}` 自动折叠 | 业务自切片 + `<BreadcrumbEllipsis />` 配 `<DropdownMenu>` 弹出 | 见 `MaxNodeCollapse` Story —— 折叠 UI 完全由你控制(Dropdown / Tooltip / Popover 任选) |
83
+ | `showHiddenItems` 弹层展开 | 同上,用 `<DropdownMenu>` 触发器包 `<BreadcrumbEllipsis />` | — |
84
+ | `popupContainer` / `popupProps` | DropdownMenu 自身的 `modal` / `onOpenChange` | 弹层细节转交 DropdownMenu |
85
+ | `component="span"` 自定义根标签 | 不修复 — 固定 `<nav>`(无障碍语义) | — |
86
+ | `prefix` CSS 前缀 | 不修复 — Tailwind 无 CSS prefix 概念 | — |
87
+ | 路由集成 | `<BreadcrumbLink asChild><Link to="/x">...</Link></BreadcrumbLink>` | shadcn 的 `asChild`(Slot),适配任意路由组件 |
88
+
69
89
  ## Examples
70
90
 
71
91
  ```tsx