@teamix-evo/ui 0.2.0 → 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 (282) hide show
  1. package/README.md +184 -184
  2. package/manifest.json +680 -492
  3. package/package.json +15 -9
  4. package/src/components/accordion/accordion.meta.md +5 -9
  5. package/src/components/accordion/accordion.stories.tsx +3 -3
  6. package/src/components/accordion/accordion.tsx +104 -8
  7. package/src/components/affix/affix.meta.md +21 -12
  8. package/src/components/affix/affix.stories.tsx +101 -26
  9. package/src/components/affix/affix.tsx +79 -9
  10. package/src/components/alert/alert.meta.md +52 -26
  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 +48 -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 +10 -14
  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 +10 -14
  20. package/src/components/app/app.stories.tsx +6 -6
  21. package/src/components/aspect-ratio/aspect-ratio.meta.md +4 -8
  22. package/src/components/aspect-ratio/aspect-ratio.stories.tsx +3 -3
  23. package/src/components/auto-complete/auto-complete.meta.md +19 -20
  24. package/src/components/auto-complete/auto-complete.stories.tsx +44 -3
  25. package/src/components/auto-complete/auto-complete.tsx +119 -71
  26. package/src/components/avatar/avatar.meta.md +9 -22
  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 +14 -18
  30. package/src/components/badge/badge.stories.tsx +2 -2
  31. package/src/components/badge/badge.tsx +2 -2
  32. package/src/components/breadcrumb/breadcrumb.meta.md +29 -20
  33. package/src/components/breadcrumb/breadcrumb.stories.tsx +120 -5
  34. package/src/components/breadcrumb/breadcrumb.tsx +22 -8
  35. package/src/components/button/button.meta.md +261 -29
  36. package/src/components/button/button.stories.tsx +549 -41
  37. package/src/components/button/button.tsx +335 -33
  38. package/src/components/calendar/calendar.meta.md +19 -14
  39. package/src/components/calendar/calendar.stories.tsx +5 -5
  40. package/src/components/calendar/calendar.tsx +73 -8
  41. package/src/components/card/card.meta.md +31 -34
  42. package/src/components/card/card.stories.tsx +34 -3
  43. package/src/components/card/card.tsx +146 -63
  44. package/src/components/carousel/carousel.meta.md +10 -14
  45. package/src/components/carousel/carousel.stories.tsx +1 -1
  46. package/src/components/cascader/cascader.meta.md +43 -22
  47. package/src/components/cascader/cascader.stories.tsx +13 -2
  48. package/src/components/cascader/cascader.tsx +427 -84
  49. package/src/components/checkbox/checkbox.meta.md +74 -24
  50. package/src/components/checkbox/checkbox.stories.tsx +160 -2
  51. package/src/components/checkbox/checkbox.tsx +77 -9
  52. package/src/components/collapsible/collapsible.meta.md +7 -6
  53. package/src/components/collapsible/collapsible.stories.tsx +2 -2
  54. package/src/components/collapsible/collapsible.tsx +93 -6
  55. package/src/components/color-picker/color-picker.meta.md +16 -20
  56. package/src/components/color-picker/color-picker.stories.tsx +86 -7
  57. package/src/components/color-picker/color-picker.tsx +19 -9
  58. package/src/components/command/command.meta.md +7 -11
  59. package/src/components/command/command.stories.tsx +4 -4
  60. package/src/components/command/command.tsx +18 -7
  61. package/src/components/context-menu/context-menu.meta.md +5 -25
  62. package/src/components/context-menu/context-menu.stories.tsx +4 -4
  63. package/src/components/context-menu/context-menu.tsx +21 -8
  64. package/src/components/data-table/data-table.meta.md +14 -18
  65. package/src/components/data-table/data-table.stories.tsx +1 -1
  66. package/src/components/data-table/data-table.tsx +2 -2
  67. package/src/components/date-picker/date-picker.meta.md +90 -41
  68. package/src/components/date-picker/date-picker.stories.tsx +55 -5
  69. package/src/components/date-picker/date-picker.tsx +1489 -91
  70. package/src/components/descriptions/descriptions.meta.md +12 -16
  71. package/src/components/descriptions/descriptions.stories.tsx +2 -2
  72. package/src/components/descriptions/descriptions.tsx +22 -14
  73. package/src/components/dialog/dialog.meta.md +67 -17
  74. package/src/components/dialog/dialog.stories.tsx +182 -20
  75. package/src/components/dialog/dialog.tsx +67 -15
  76. package/src/components/dialog/imperative.tsx +252 -0
  77. package/src/components/drawer/drawer.meta.md +27 -39
  78. package/src/components/drawer/drawer.stories.tsx +29 -12
  79. package/src/components/drawer/drawer.tsx +22 -114
  80. package/src/components/dropdown-menu/dropdown-menu.meta.md +64 -24
  81. package/src/components/dropdown-menu/dropdown-menu.stories.tsx +81 -3
  82. package/src/components/dropdown-menu/dropdown-menu.tsx +24 -10
  83. package/src/components/ellipsis/ellipsis.meta.md +87 -0
  84. package/src/components/ellipsis/ellipsis.stories.tsx +72 -0
  85. package/src/components/ellipsis/ellipsis.tsx +153 -0
  86. package/src/components/empty/empty.meta.md +10 -14
  87. package/src/components/empty/empty.stories.tsx +3 -3
  88. package/src/components/empty/empty.tsx +10 -3
  89. package/src/components/field/field.meta.md +46 -25
  90. package/src/components/field/field.stories.tsx +380 -3
  91. package/src/components/field/field.tsx +263 -35
  92. package/src/components/filter-bar/filter-bar.meta.md +92 -0
  93. package/src/components/filter-bar/filter-bar.stories.tsx +1083 -0
  94. package/src/components/filter-bar/filter-bar.tsx +568 -0
  95. package/src/components/flex/flex.meta.md +59 -20
  96. package/src/components/flex/flex.stories.tsx +65 -10
  97. package/src/components/flex/flex.tsx +27 -4
  98. package/src/components/float-button/float-button.meta.md +10 -29
  99. package/src/components/float-button/float-button.stories.tsx +6 -6
  100. package/src/components/form/form.meta.md +31 -52
  101. package/src/components/form/form.stories.tsx +350 -3
  102. package/src/components/form/form.tsx +101 -35
  103. package/src/components/grid/grid.meta.md +4 -24
  104. package/src/components/grid/grid.stories.tsx +2 -2
  105. package/src/components/hover-card/hover-card.meta.md +9 -10
  106. package/src/components/hover-card/hover-card.stories.tsx +29 -4
  107. package/src/components/hover-card/hover-card.tsx +51 -13
  108. package/src/components/icon/DEVELOPMENT.md +809 -0
  109. package/src/components/icon/icon.meta.md +170 -0
  110. package/src/components/icon/icon.stories.tsx +344 -0
  111. package/src/components/icon/icon.tsx +248 -0
  112. package/src/components/image/image.meta.md +14 -18
  113. package/src/components/image/image.stories.tsx +3 -3
  114. package/src/components/image/image.tsx +2 -0
  115. package/src/components/input/demo/sizes.tsx +2 -2
  116. package/src/components/input/input.meta.md +44 -43
  117. package/src/components/input/input.stories.tsx +62 -35
  118. package/src/components/input/input.tsx +96 -101
  119. package/src/components/input-group/input-group.meta.md +53 -39
  120. package/src/components/input-group/input-group.stories.tsx +49 -16
  121. package/src/components/input-group/input-group.tsx +43 -8
  122. package/src/components/input-number/input-number.meta.md +68 -20
  123. package/src/components/input-number/input-number.stories.tsx +33 -6
  124. package/src/components/input-number/input-number.tsx +79 -20
  125. package/src/components/input-otp/input-otp.meta.md +8 -20
  126. package/src/components/input-otp/input-otp.stories.tsx +3 -3
  127. package/src/components/input-otp/input-otp.tsx +1 -1
  128. package/src/components/item/item.meta.md +8 -26
  129. package/src/components/item/item.stories.tsx +3 -3
  130. package/src/components/item/item.tsx +7 -6
  131. package/src/components/kbd/kbd.meta.md +7 -19
  132. package/src/components/kbd/kbd.stories.tsx +4 -4
  133. package/src/components/kbd/kbd.tsx +8 -4
  134. package/src/components/label/label.meta.md +21 -18
  135. package/src/components/label/label.stories.tsx +64 -6
  136. package/src/components/label/label.tsx +91 -19
  137. package/src/components/masonry/masonry.meta.md +8 -12
  138. package/src/components/masonry/masonry.stories.tsx +4 -4
  139. package/src/components/mentions/mentions.meta.md +42 -21
  140. package/src/components/mentions/mentions.stories.tsx +120 -6
  141. package/src/components/mentions/mentions.tsx +10 -5
  142. package/src/components/menubar/menubar.meta.md +4 -8
  143. package/src/components/menubar/menubar.stories.tsx +55 -3
  144. package/src/components/menubar/menubar.tsx +9 -9
  145. package/src/components/native-select/native-select.meta.md +7 -11
  146. package/src/components/native-select/native-select.stories.tsx +4 -4
  147. package/src/components/native-select/native-select.tsx +1 -1
  148. package/src/components/navigation-menu/navigation-menu.meta.md +4 -8
  149. package/src/components/navigation-menu/navigation-menu.stories.tsx +106 -3
  150. package/src/components/navigation-menu/navigation-menu.tsx +6 -3
  151. package/src/components/notification/notification.meta.md +41 -8
  152. package/src/components/notification/notification.stories.tsx +9 -9
  153. package/src/components/notification/notification.tsx +34 -19
  154. package/src/components/page-header/DEVELOPMENT.md +842 -0
  155. package/src/components/page-header/page-header.meta.md +208 -0
  156. package/src/components/page-header/page-header.stories.tsx +421 -0
  157. package/src/components/page-header/page-header.tsx +281 -0
  158. package/src/components/pagination/pagination.meta.md +122 -50
  159. package/src/components/pagination/pagination.stories.tsx +227 -11
  160. package/src/components/pagination/pagination.tsx +355 -63
  161. package/src/components/popconfirm/popconfirm.meta.md +19 -23
  162. package/src/components/popconfirm/popconfirm.stories.tsx +2 -2
  163. package/src/components/popconfirm/popconfirm.tsx +1 -1
  164. package/src/components/popover/popover.meta.md +64 -12
  165. package/src/components/popover/popover.stories.tsx +83 -7
  166. package/src/components/popover/popover.tsx +77 -28
  167. package/src/components/progress/progress.meta.md +43 -26
  168. package/src/components/progress/progress.stories.tsx +2 -2
  169. package/src/components/progress/progress.tsx +19 -11
  170. package/src/components/radio-group/radio-group.meta.md +78 -11
  171. package/src/components/radio-group/radio-group.stories.tsx +38 -2
  172. package/src/components/radio-group/radio-group.tsx +149 -18
  173. package/src/components/rate/rate.meta.md +41 -19
  174. package/src/components/rate/rate.stories.tsx +2 -2
  175. package/src/components/rate/rate.tsx +37 -10
  176. package/src/components/resizable/resizable.meta.md +4 -12
  177. package/src/components/resizable/resizable.stories.tsx +5 -5
  178. package/src/components/resizable/resizable.tsx +1 -1
  179. package/src/components/result/result.meta.md +10 -14
  180. package/src/components/result/result.stories.tsx +2 -2
  181. package/src/components/result/result.tsx +21 -12
  182. package/src/components/scroll-area/scroll-area.meta.md +4 -8
  183. package/src/components/scroll-area/scroll-area.stories.tsx +5 -5
  184. package/src/components/segmented/segmented.meta.md +15 -17
  185. package/src/components/segmented/segmented.stories.tsx +3 -3
  186. package/src/components/segmented/segmented.tsx +15 -7
  187. package/src/components/select/select.meta.md +199 -67
  188. package/src/components/select/select.stories.tsx +238 -63
  189. package/src/components/select/select.tsx +718 -171
  190. package/src/components/separator/separator.meta.md +10 -14
  191. package/src/components/separator/separator.stories.tsx +2 -2
  192. package/src/components/separator/separator.tsx +3 -7
  193. package/src/components/sheet/sheet.meta.md +26 -21
  194. package/src/components/sheet/sheet.stories.tsx +116 -10
  195. package/src/components/sheet/sheet.tsx +116 -29
  196. package/src/components/sidebar/sidebar.meta.md +28 -38
  197. package/src/components/sidebar/sidebar.stories.tsx +696 -29
  198. package/src/components/sidebar/sidebar.tsx +615 -142
  199. package/src/components/skeleton/skeleton.meta.md +7 -31
  200. package/src/components/skeleton/skeleton.stories.tsx +3 -3
  201. package/src/components/skeleton/skeleton.tsx +7 -7
  202. package/src/components/slider/slider.meta.md +60 -13
  203. package/src/components/slider/slider.stories.tsx +58 -6
  204. package/src/components/slider/slider.tsx +154 -13
  205. package/src/components/sonner/sonner.meta.md +54 -8
  206. package/src/components/sonner/sonner.stories.tsx +79 -11
  207. package/src/components/sonner/sonner.tsx +137 -8
  208. package/src/components/spinner/spinner.meta.md +57 -21
  209. package/src/components/spinner/spinner.stories.tsx +66 -14
  210. package/src/components/spinner/spinner.tsx +111 -9
  211. package/src/components/statistic/statistic.meta.md +14 -30
  212. package/src/components/statistic/statistic.stories.tsx +1 -1
  213. package/src/components/statistic/statistic.tsx +4 -5
  214. package/src/components/steps/steps.meta.md +20 -15
  215. package/src/components/steps/steps.stories.tsx +37 -2
  216. package/src/components/steps/steps.tsx +15 -12
  217. package/src/components/switch/switch.meta.md +56 -15
  218. package/src/components/switch/switch.stories.tsx +5 -5
  219. package/src/components/switch/switch.tsx +59 -13
  220. package/src/components/table/table.meta.md +3 -7
  221. package/src/components/table/table.stories.tsx +1 -1
  222. package/src/components/table/table.tsx +4 -4
  223. package/src/components/tabs/tabs.meta.md +40 -32
  224. package/src/components/tabs/tabs.stories.tsx +104 -26
  225. package/src/components/tabs/tabs.tsx +125 -54
  226. package/src/components/tag/tag.meta.md +104 -68
  227. package/src/components/tag/tag.stories.tsx +183 -15
  228. package/src/components/tag/tag.tsx +222 -21
  229. package/src/components/textarea/textarea.meta.md +42 -31
  230. package/src/components/textarea/textarea.stories.tsx +32 -6
  231. package/src/components/textarea/textarea.tsx +32 -8
  232. package/src/components/time-picker/time-picker.meta.md +119 -50
  233. package/src/components/time-picker/time-picker.stories.tsx +65 -33
  234. package/src/components/time-picker/time-picker.tsx +889 -101
  235. package/src/components/timeline/timeline.meta.md +16 -17
  236. package/src/components/timeline/timeline.stories.tsx +24 -4
  237. package/src/components/timeline/timeline.tsx +32 -12
  238. package/src/components/toggle/toggle.meta.md +8 -12
  239. package/src/components/toggle/toggle.stories.tsx +4 -4
  240. package/src/components/toggle/toggle.tsx +4 -3
  241. package/src/components/toggle-group/toggle-group.meta.md +10 -14
  242. package/src/components/toggle-group/toggle-group.stories.tsx +3 -3
  243. package/src/components/toggle-group/toggle-group.tsx +2 -2
  244. package/src/components/tooltip/tooltip.meta.md +63 -18
  245. package/src/components/tooltip/tooltip.stories.tsx +42 -5
  246. package/src/components/tooltip/tooltip.tsx +81 -21
  247. package/src/components/tour/tour.meta.md +16 -20
  248. package/src/components/tour/tour.stories.tsx +3 -3
  249. package/src/components/tour/tour.tsx +3 -3
  250. package/src/components/transfer/transfer.meta.md +18 -22
  251. package/src/components/transfer/transfer.stories.tsx +2 -2
  252. package/src/components/transfer/transfer.tsx +28 -21
  253. package/src/components/tree/tree.meta.md +67 -22
  254. package/src/components/tree/tree.stories.tsx +1 -1
  255. package/src/components/tree/tree.tsx +9 -8
  256. package/src/components/tree-select/tree-select.meta.md +59 -23
  257. package/src/components/tree-select/tree-select.stories.tsx +2 -2
  258. package/src/components/tree-select/tree-select.tsx +42 -7
  259. package/src/components/typography/typography.meta.md +61 -39
  260. package/src/components/typography/typography.stories.tsx +14 -9
  261. package/src/components/typography/typography.tsx +38 -25
  262. package/src/components/upload/upload.meta.md +61 -25
  263. package/src/components/upload/upload.stories.tsx +69 -3
  264. package/src/components/upload/upload.tsx +170 -37
  265. package/src/components/watermark/watermark.meta.md +15 -19
  266. package/src/components/watermark/watermark.stories.tsx +98 -8
  267. package/src/hooks/use-breakpoint.ts +117 -0
  268. package/src/hooks/use-debounce-callback.ts +52 -0
  269. package/src/hooks/use-mobile.ts +23 -0
  270. package/src/stories/theme-tokens.stories.tsx +747 -0
  271. package/src/utils/trigger-input.ts +53 -0
  272. package/src/components/button-group/button-group.meta.md +0 -101
  273. package/src/components/button-group/button-group.stories.tsx +0 -93
  274. package/src/components/button-group/button-group.tsx +0 -75
  275. package/src/components/combobox/combobox.meta.md +0 -102
  276. package/src/components/combobox/combobox.stories.tsx +0 -55
  277. package/src/components/combobox/combobox.tsx +0 -130
  278. package/src/components/input/demo/addon.tsx +0 -15
  279. package/src/components/input/demo/with-prefix-suffix.tsx +0 -19
  280. package/src/components/space/space.meta.md +0 -103
  281. package/src/components/space/space.stories.tsx +0 -108
  282. package/src/components/space/space.tsx +0 -106
@@ -1,16 +1,16 @@
1
1
  import * as React from 'react';
2
- import type { Meta, StoryObj } from '@storybook/react';
2
+ import type { Meta, StoryObj } from '@storybook/react-vite';
3
3
  import { AutoComplete } from './auto-complete';
4
4
 
5
5
  const meta: Meta<typeof AutoComplete> = {
6
- title: '表单与输入 · Form/AutoComplete',
6
+ title: '数据录入 · Data Entry/AutoComplete',
7
7
  component: AutoComplete,
8
8
  tags: ['autodocs'],
9
9
  parameters: {
10
10
  docs: {
11
11
  description: {
12
12
  component:
13
- '输入即建议键入触发候选下拉,可选可改。最终 value 是自由文本,不强制为选项之一(与 Combobox 必选互补)。配 onSearch 实现异步建议。等价 antd `AutoComplete`。',
13
+ '自动补全输入即建议,键入触发候选下拉,可选可改,最终 value 是自由文本(不强制为选项之一)。**Input 为底** + antd `AutoComplete` 的人机交互并集:`options` / `onSearch` / `onSelect` / `openOnFocus` / `size` / `error`,与 Combobox(必选)互补。视觉走 OpenTrek semantic tokens,所有样式来自 `@teamix-evo/tokens`,无 mock。',
14
14
  },
15
15
  },
16
16
  },
@@ -38,6 +38,47 @@ export const Playground: Story = {
38
38
  ),
39
39
  };
40
40
 
41
+ export const Sizes: Story = {
42
+ parameters: { controls: { disable: true } },
43
+ render: () => (
44
+ <div className="flex flex-col gap-3">
45
+ <AutoComplete
46
+ size="sm"
47
+ options={frameworks}
48
+ placeholder="sm"
49
+ className="w-64"
50
+ />
51
+ <AutoComplete
52
+ options={frameworks}
53
+ placeholder="default"
54
+ className="w-64"
55
+ />
56
+ <AutoComplete
57
+ size="lg"
58
+ options={frameworks}
59
+ placeholder="lg"
60
+ className="w-64"
61
+ />
62
+ </div>
63
+ ),
64
+ };
65
+
66
+ export const ErrorState: Story = {
67
+ name: 'Error 错误态',
68
+ parameters: { controls: { disable: true } },
69
+ render: () => (
70
+ <div className="flex flex-col gap-1">
71
+ <AutoComplete
72
+ options={frameworks}
73
+ placeholder="输入框架名(必填)"
74
+ error
75
+ className="w-64"
76
+ />
77
+ <span className="text-xs text-destructive">该字段为必填项</span>
78
+ </div>
79
+ ),
80
+ };
81
+
41
82
  export const OpenOnFocus: Story = {
42
83
  parameters: { controls: { disable: true } },
43
84
  render: () => (
@@ -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
  );
@@ -3,7 +3,7 @@ id: avatar
3
3
  name: Avatar
4
4
  displayName: 头像
5
5
  type: component
6
- category: foundation
6
+ category: data-display
7
7
  since: 0.1.0
8
8
  package: '@teamix-evo/ui'
9
9
  ---
@@ -28,21 +28,10 @@ package: '@teamix-evo/ui'
28
28
  > 以下表格由 `pnpm --filter @teamix-evo/ui gen:meta` 自动生成。
29
29
 
30
30
  <!-- auto:props:begin -->
31
-
32
- #### Avatar
33
-
34
- | 名称 | 类型 | 默认值 | 必填 | 说明 |
35
- | ------- | ----------------------------------- | ----------- | ---- | ---------------------------------------------------- |
36
- | `size` | `'sm' \| 'default' \| 'lg' \| 'xl'` | `"default"` | – | 尺寸(antd 并集:sm 32 / default 40 / lg 48 / xl 64)。 |
37
- | `shape` | `'circle' \| 'square'` | `"circle"` | – | 形状(antd 并集)。 |
38
-
39
- #### AvatarGroup
40
-
41
- | 名称 | 类型 | 默认值 | 必填 | 说明 |
42
- | ------ | ----------------------------------- | ------ | ---- | -------------------------------------------------- |
43
- | `max` | `number` | `3` | – | 显示前 N 个 Avatar,其余收为 `+N` 折叠气泡。 |
44
- | `size` | `'sm' \| 'default' \| 'lg' \| 'xl'` | – | – | Group 内所有 Avatar 共享的尺寸,会覆盖单个 Avatar。 |
45
-
31
+ | 名称 | 类型 | 默认值 | 必填 | 说明 |
32
+ | --- | --- | --- | --- | --- |
33
+ | `size` | `'sm' \| 'md' \| 'default' \| 'lg' \| 'xl'` | `"default"` | – | 尺寸(antd 并集:sm 32 / default 40 / lg 48 / xl 64)。 |
34
+ | `shape` | `'circle' \| 'square'` | `"circle"` | | 形状(antd 并集)。 |
46
35
  <!-- auto:props:end -->
47
36
 
48
37
  ## 依赖
@@ -50,23 +39,21 @@ package: '@teamix-evo/ui'
50
39
  > 以下表格由 `pnpm --filter @teamix-evo/ui gen:meta` 自动生成,数据源是 [`manifest.json`](../../../manifest.json)。**手工编辑 marker 之间的内容会在下次生成时被覆盖**。
51
40
 
52
41
  <!-- auto:deps:begin -->
53
-
54
42
  ### 同库依赖
55
43
 
56
44
  > `teamix-evo ui add avatar` 时,以下 entry 会被自动连带安装(无需手动 add)。
57
45
 
58
- | Entry | 类型 | 描述 |
59
- | ----- | ---- | -------------------------------------------------- |
60
- | `cn` | util | Tailwind className 合并工具(clsx + tailwind-merge) |
46
+ | Entry | 类型 | 描述 |
47
+ | --- | --- | --- |
48
+ | `cn` | util | Tailwind className 合并工具(clsx + tailwind-merge) |
61
49
 
62
50
  ### npm 依赖
63
51
 
64
52
  > 业务侧需要先 `pnpm add` / `npm install` 这些包。CLI 在 `ui add` 完成后会列出此提示。
65
53
 
66
54
  ```bash
67
- 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
68
56
  ```
69
-
70
57
  <!-- auto:deps:end -->
71
58
 
72
59
  ## AI 生成纪律
@@ -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 通过命名子组件组合。',
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 ? (
@@ -3,7 +3,7 @@ id: badge
3
3
  name: Badge
4
4
  displayName: 徽标
5
5
  type: component
6
- category: foundation
6
+ category: data-display
7
7
  since: 0.1.0
8
8
  package: '@teamix-evo/ui'
9
9
  ---
@@ -38,18 +38,16 @@ package: '@teamix-evo/ui'
38
38
  > 以下表格由 `pnpm --filter @teamix-evo/ui gen:meta` 自动生成,数据源是 [`badge.tsx`](./badge.tsx) 的 `BadgeProps` interface JSDoc。**手工编辑 marker 之间的内容会在下次生成时被覆盖**。
39
39
 
40
40
  <!-- auto:props:begin -->
41
-
42
- | 名称 | 类型 | 默认值 | 必填 | 说明 |
43
- | --------------- | ---------------------------------------------------------------------------------- | ----------- | ---- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
44
- | `variant` | `'default' \| 'secondary' \| 'destructive' \| 'outline' \| 'success' \| 'warning'` | `"default"` | – | 视觉风格;`default / secondary / destructive / outline` 来自 shadcn, `success / warning` 来自 antd 状态色补足。 |
45
- | `count` | `number` | | – | 数字徽标内容。配合 `children` 时,数字气泡浮在右上角(包裹模式); `children` 时,渲染为独立小气泡(standalone 模式)。 `0` 默认不显示,需配 `showZero`。 |
46
- | `overflowCount` | `number` | `99` | – | 数字超过此值时显示 `${overflowCount}+`。仅在有 `count` 时生效。 |
47
- | `showZero` | `boolean` | `false` | – | `true` 时即使 `count === 0` 也显示气泡。 |
48
- | `dot` | `boolean` | `false` | – | `true` 时渲染为纯红点(无文字)。配合 `children` 时浮在右上角。 `dot` `count` 同时存在,`dot` 优先。 |
49
- | `status` | `BadgeStatus` | – | – | 状态点 + 文字(`<dot> <text>` 行内组合),用于"运行中 / 已停止"等状态描述。 必须与 `children` 互斥使用 — 这是 standalone 状态条形态。 |
50
- | `text` | `React.ReactNode` | – | – | 配合 `status` 使用,渲染在状态点右侧。无 `status` 时忽略。 |
51
- | `children` | `React.ReactNode` | – | – | 被包裹的元素(包裹模式)或徽标文本内容(standalone 文本模式)。 |
52
-
41
+ | 名称 | 类型 | 默认值 | 必填 | 说明 |
42
+ | --- | --- | --- | --- | --- |
43
+ | `variant` | `'default' \| 'secondary' \| 'destructive' \| 'outline' \| 'success' \| 'warning'` | `"default"` | | 视觉风格;`default / secondary / destructive / outline` 来自 shadcn, `success / warning` 来自 antd 状态色补足。 |
44
+ | `count` | `number` | | – | 数字徽标内容。配合 `children` 时,数字气泡浮在右上角(包裹模式); `children` 时,渲染为独立小气泡(standalone 模式)。 `0` 默认不显示,需配 `showZero`。 |
45
+ | `overflowCount` | `number` | `99` | | 数字超过此值时显示 `${overflowCount}+`。仅在有 `count` 时生效。 |
46
+ | `showZero` | `boolean` | `false` | – | `true` 时即使 `count === 0` 也显示气泡。 |
47
+ | `dot` | `boolean` | `false` | – | `true` 时渲染为纯红点(无文字)。配合 `children` 时浮在右上角。 `dot` 与 `count` 同时存在,`dot` 优先。 |
48
+ | `status` | `BadgeStatus` | | – | 状态点 + 文字(`<dot> <text>` 行内组合),用于"运行中 / 已停止"等状态描述。 必须与 `children` 互斥使用 这是 standalone 状态条形态。 |
49
+ | `text` | `React.ReactNode` | – | – | 配合 `status` 使用,渲染在状态点右侧。无 `status` 时忽略。 |
50
+ | `children` | `React.ReactNode` | – | – | 被包裹的元素(包裹模式)或徽标文本内容(standalone 文本模式)。 |
53
51
  <!-- auto:props:end -->
54
52
 
55
53
  ## 依赖
@@ -57,14 +55,13 @@ package: '@teamix-evo/ui'
57
55
  > 以下表格由 `pnpm --filter @teamix-evo/ui gen:meta` 自动生成,数据源是 [`manifest.json`](../../../manifest.json)。**手工编辑 marker 之间的内容会在下次生成时被覆盖**。
58
56
 
59
57
  <!-- auto:deps:begin -->
60
-
61
58
  ### 同库依赖
62
59
 
63
60
  > `teamix-evo ui add badge` 时,以下 entry 会被自动连带安装(无需手动 add)。
64
61
 
65
- | Entry | 类型 | 描述 |
66
- | ----- | ---- | -------------------------------------------------- |
67
- | `cn` | util | Tailwind className 合并工具(clsx + tailwind-merge) |
62
+ | Entry | 类型 | 描述 |
63
+ | --- | --- | --- |
64
+ | `cn` | util | Tailwind className 合并工具(clsx + tailwind-merge) |
68
65
 
69
66
  ### npm 依赖
70
67
 
@@ -73,7 +70,6 @@ package: '@teamix-evo/ui'
73
70
  ```bash
74
71
  pnpm add class-variance-authority@^0.7.0
75
72
  ```
76
-
77
73
  <!-- auto:deps:end -->
78
74
 
79
75
  > 除上述 props 外,Badge 透传所有 `<span>` 原生属性(`onClick` / `aria-*` / `id` / ...)。
@@ -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: {
@@ -113,7 +113,7 @@ const Badge = React.forwardRef<HTMLSpanElement, BadgeProps>(
113
113
  return (
114
114
  <span
115
115
  ref={ref}
116
- className={cn('inline-flex items-center gap-2 text-sm', className)}
116
+ className={cn('inline-flex items-center gap-2 text-xs', className)}
117
117
  {...props}
118
118
  >
119
119
  <span