@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
@@ -0,0 +1,53 @@
1
+ import { parse as parseDate, isValid as isValidDate } from 'date-fns';
2
+
3
+ import { cn } from '@/utils/cn';
4
+
5
+ /**
6
+ * Trigger wrapper —— input 可输入模式下,wrapper 承担 border + focus-within 视觉态,
7
+ * 内部 input 元素去掉自己的边框 / ring,共享 wrapper 的视觉态。
8
+ * hover / focus 走"灰色 border 加深"(antd 风格),不走蓝色 ring,避免与 popover 弹层视觉抢戏。
9
+ *
10
+ * 被 DatePicker / DateRangePicker / TimePicker / TimeRangePicker 共用,
11
+ * 确保 4 个 picker 的 trigger 视觉完全一致。
12
+ */
13
+ export const triggerWrapperClass = cn(
14
+ 'flex items-center gap-2 rounded-lg border border-input bg-background px-3 shadow-sm transition-colors',
15
+ 'hover:border-foreground/30 focus-within:border-foreground/60 focus-within:outline-none',
16
+ '[&:has(input:disabled)]:cursor-not-allowed [&:has(input:disabled)]:opacity-50',
17
+ );
18
+
19
+ /** Input 元素 className —— 透明、无边框、占满 wrapper 剩余空间。 */
20
+ export const inputElementClass =
21
+ 'min-w-0 grow bg-transparent text-foreground outline-none tabular-nums placeholder:text-muted-foreground disabled:cursor-not-allowed';
22
+
23
+ /** Trigger 高度尺寸(对齐 Input / Button 三档)。 */
24
+ export const triggerSizeClass = {
25
+ sm: 'h-7 text-xs',
26
+ default: 'h-8 text-sm',
27
+ md: 'h-8 text-sm',
28
+ lg: 'h-9 text-base',
29
+ } as const;
30
+
31
+ export type TriggerSize = keyof typeof triggerSizeClass;
32
+
33
+ /**
34
+ * 按指定 format 解析日期文本。
35
+ * - 成功:返回 Date 实例
36
+ * - 失败(非法、空、Invalid Date):返回 undefined
37
+ */
38
+ export const tryParseDate = (
39
+ text: string,
40
+ format: string,
41
+ ): Date | undefined => {
42
+ const trimmed = text.trim();
43
+ if (!trimmed) return undefined;
44
+ const parsed = parseDate(trimmed, format, new Date());
45
+ return isValidDate(parsed) ? parsed : undefined;
46
+ };
47
+
48
+ /** 比较两个 Date 是否相等(都为 undefined 也算相等)。 */
49
+ export const datesEqual = (a: Date | undefined, b: Date | undefined) => {
50
+ if (a == null && b == null) return true;
51
+ if (a == null || b == null) return false;
52
+ return a.getTime() === b.getTime();
53
+ };
@@ -1,101 +0,0 @@
1
- ---
2
- id: button-group
3
- name: ButtonGroup
4
- type: component
5
- category: form
6
- since: 0.1.0
7
- package: '@teamix-evo/ui'
8
- displayName: 按钮组
9
- ---
10
-
11
- # ButtonGroup 按钮组
12
-
13
- 按钮组 — shadcn 2025-10 新增,**等价 antd `Space.Compact`**(v6 替代旧 `Button.Group`)。把多个按钮 / 输入框"粘"在一起共享边线,用于 Split Button、Toolbar、Input + Button 拼装等场景。配 `ButtonGroupText` 渲染只读 addon 文本。
14
-
15
- ## When to use
16
-
17
- - Split Button(主操作 + 右侧下拉箭头)
18
- - Toolbar(同语义按钮集合,如 "上一页 / 下一页 / 跳转")
19
- - Input + 按钮拼装(搜索框、单位选择)
20
- - 多档切换显示(配 `Toggle` 也可,但 ToggleGroup 更专精)
21
-
22
- ## When NOT to use
23
-
24
- - 互斥单选切换 → `ToggleGroup`(语义更强)
25
- - 不相干的按钮 → 普通 `Button` + flex gap
26
-
27
- ## Props
28
-
29
- <!-- auto:props:begin -->
30
-
31
- | 名称 | 类型 | 默认值 | 必填 | 说明 |
32
- | ------------- | ---------------------------- | -------------- | ---- | ----------------------------------------------------------------------------------------- |
33
- | `orientation` | `'horizontal' \| 'vertical'` | `"horizontal"` | – | 排列方向。 |
34
- | `attached` | `boolean` | `true` | – | 是否把按钮"粘"在一起去除边距(antd `Space.Compact` 等价行为)— `false` 时按钮间留 8px gap。 |
35
-
36
- <!-- auto:props:end -->
37
-
38
- ## 依赖
39
-
40
- <!-- auto:deps:begin -->
41
-
42
- ### 同库依赖
43
-
44
- > `teamix-evo ui add button-group` 时,以下 entry 会被自动连带安装(无需手动 add)。
45
-
46
- | Entry | 类型 | 描述 |
47
- | ----- | ---- | -------------------------------------------------- |
48
- | `cn` | util | Tailwind className 合并工具(clsx + tailwind-merge) |
49
-
50
- ### npm 依赖
51
-
52
- > 业务侧需要先 `pnpm add` / `npm install` 这些包。CLI 在 `ui add` 完成后会列出此提示。
53
-
54
- ```bash
55
- pnpm add class-variance-authority@^0.7.0
56
- ```
57
-
58
- <!-- auto:deps:end -->
59
-
60
- ## AI 生成纪律
61
-
62
- - **`attached=true`(默认)子项之间边线共享**:第一项 / 最后一项保留外侧圆角,中间项去除圆角;**不要**在内部再设 `rounded`
63
- - **混用 Input + Button**:把 Input 直接放进 ButtonGroup,它的 `rounded-md` 也会被组件圈圆角(无需特殊处理)
64
- - **`ButtonGroupText`** 用于只读 addon(`https://` 前缀、`/月` 后缀);**不要**把它当按钮点击
65
- - **vertical 模式** 适合工具栏侧边小集合,horizontal 是默认推荐
66
- - **不要**在 ButtonGroup 内放 spinner / icon-only 与文字按钮混排时,高度需要靠 button `size` 对齐 — 子项尺寸应一致
67
-
68
- ## Examples
69
-
70
- ```tsx
71
- import { ButtonGroup, ButtonGroupText } from '@/components/ui/button-group';
72
- import { Button } from '@/components/ui/button';
73
- import { Input } from '@/components/ui/input';
74
- import { ChevronDown } from 'lucide-react';
75
-
76
- // Split button
77
- <ButtonGroup>
78
- <Button>保存</Button>
79
- <Button variant="default" size="icon" icon={<ChevronDown />} aria-label="更多" />
80
- </ButtonGroup>
81
-
82
- // Toolbar
83
- <ButtonGroup>
84
- <Button variant="outline">上一页</Button>
85
- <Button variant="outline">1 / 10</Button>
86
- <Button variant="outline">下一页</Button>
87
- </ButtonGroup>
88
-
89
- // 带 addon 文本
90
- <ButtonGroup>
91
- <ButtonGroupText>https://</ButtonGroupText>
92
- <Input defaultValue="example.com" className="rounded-l-none" />
93
- <Button>访问</Button>
94
- </ButtonGroup>
95
-
96
- // 不粘连(留间距)
97
- <ButtonGroup attached={false}>
98
- <Button variant="outline">取消</Button>
99
- <Button>确定</Button>
100
- </ButtonGroup>
101
- ```
@@ -1,93 +0,0 @@
1
- import type { Meta, StoryObj } from '@storybook/react';
2
- import { ChevronDown } from 'lucide-react';
3
- import { ButtonGroup, ButtonGroupText } from './button-group';
4
- import { Button } from '@/components/button/button';
5
- import { Input } from '@/components/input/input';
6
-
7
- const meta: Meta<typeof ButtonGroup> = {
8
- title: '表单与输入 · Form/ButtonGroup',
9
- component: ButtonGroup,
10
- tags: ['autodocs'],
11
- parameters: {
12
- docs: {
13
- description: {
14
- component:
15
- '按钮组 — 把多个按钮 / 输入框粘在一起共享边线(Split Button、Toolbar、Input + Button 拼装)。配 ButtonGroupText 渲染只读 addon。shadcn 2025-10 新增,等价 antd `Space.Compact`(v6 替代旧 `Button.Group`)。',
16
- },
17
- },
18
- },
19
- argTypes: {
20
- orientation: {
21
- control: 'inline-radio',
22
- options: ['horizontal', 'vertical'],
23
- },
24
- attached: { control: 'boolean' },
25
- },
26
- args: { orientation: 'horizontal', attached: true },
27
- };
28
-
29
- export default meta;
30
- type Story = StoryObj<typeof ButtonGroup>;
31
-
32
- export const Playground: Story = {
33
- render: (args) => (
34
- <ButtonGroup {...args}>
35
- <Button variant="outline">左</Button>
36
- <Button variant="outline">中</Button>
37
- <Button variant="outline">右</Button>
38
- </ButtonGroup>
39
- ),
40
- };
41
-
42
- export const SplitButton: Story = {
43
- parameters: { controls: { disable: true } },
44
- render: () => (
45
- <ButtonGroup>
46
- <Button>保存</Button>
47
- <Button size="icon" icon={<ChevronDown />} aria-label="更多保存选项" />
48
- </ButtonGroup>
49
- ),
50
- };
51
-
52
- export const Pager: Story = {
53
- parameters: { controls: { disable: true } },
54
- render: () => (
55
- <ButtonGroup>
56
- <Button variant="outline">上一页</Button>
57
- <Button variant="outline">1 / 10</Button>
58
- <Button variant="outline">下一页</Button>
59
- </ButtonGroup>
60
- ),
61
- };
62
-
63
- export const InputAddon: Story = {
64
- parameters: { controls: { disable: true } },
65
- render: () => (
66
- <ButtonGroup>
67
- <ButtonGroupText>https://</ButtonGroupText>
68
- <Input defaultValue="example.com" className="rounded-l-none" />
69
- <Button>访问</Button>
70
- </ButtonGroup>
71
- ),
72
- };
73
-
74
- export const Vertical: Story = {
75
- parameters: { controls: { disable: true } },
76
- render: () => (
77
- <ButtonGroup orientation="vertical">
78
- <Button variant="outline">编辑</Button>
79
- <Button variant="outline">复制</Button>
80
- <Button variant="outline">删除</Button>
81
- </ButtonGroup>
82
- ),
83
- };
84
-
85
- export const NotAttached: Story = {
86
- parameters: { controls: { disable: true } },
87
- render: () => (
88
- <ButtonGroup attached={false}>
89
- <Button variant="outline">取消</Button>
90
- <Button>确定</Button>
91
- </ButtonGroup>
92
- ),
93
- };
@@ -1,75 +0,0 @@
1
- import * as React from 'react';
2
- import { cva, type VariantProps } from 'class-variance-authority';
3
-
4
- import { cn } from '@/utils/cn';
5
-
6
- const buttonGroupVariants = cva('inline-flex isolate', {
7
- variants: {
8
- orientation: {
9
- horizontal:
10
- 'flex-row [&>*:not(:first-child)]:rounded-l-none [&>*:not(:last-child)]:rounded-r-none [&>*:not(:first-child)]:-ml-px hover:[&>*]:z-10 focus-visible:[&>*]:z-10',
11
- vertical:
12
- 'flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:last-child)]:rounded-b-none [&>*:not(:first-child)]:-mt-px hover:[&>*]:z-10 focus-visible:[&>*]:z-10',
13
- },
14
- attached: {
15
- true: '',
16
- false: 'gap-2',
17
- },
18
- },
19
- defaultVariants: { orientation: 'horizontal', attached: true },
20
- });
21
-
22
- export interface ButtonGroupProps
23
- extends React.HTMLAttributes<HTMLDivElement>,
24
- VariantProps<typeof buttonGroupVariants> {
25
- /**
26
- * 排列方向。
27
- * @default "horizontal"
28
- */
29
- orientation?: 'horizontal' | 'vertical';
30
- /**
31
- * 是否把按钮"粘"在一起去除边距(antd `Space.Compact` 等价行为)— `false` 时按钮间留 8px gap。
32
- * @default true
33
- */
34
- attached?: boolean;
35
- }
36
-
37
- /**
38
- * 按钮组容器 — shadcn 2025-10 新增。语义类似 antd `Space.Compact`(v6 替代旧 `Button.Group`):
39
- * 把多个 `Button` / `Input` 包在一起共享边线,典型场景:
40
- * - Split button(左主操作 + 右下拉箭头)
41
- * - Toolbar(同语义按钮集合)
42
- * - Input + 按钮拼装(`<Input/><Button>搜索</Button>`)
43
- *
44
- * 内部不限定子组件类型 — 任何带 `rounded-md` 的元素都会被组件圈圆角。
45
- */
46
- const ButtonGroup = React.forwardRef<HTMLDivElement, ButtonGroupProps>(
47
- ({ orientation, attached, className, ...props }, ref) => (
48
- <div
49
- ref={ref}
50
- role="group"
51
- className={cn(buttonGroupVariants({ orientation, attached }), className)}
52
- {...props}
53
- />
54
- ),
55
- );
56
- ButtonGroup.displayName = 'ButtonGroup';
57
-
58
- // ─── ButtonGroupText(antd-style addon text)──────────────────────────────
59
-
60
- const ButtonGroupText = React.forwardRef<
61
- HTMLSpanElement,
62
- React.HTMLAttributes<HTMLSpanElement>
63
- >(({ className, ...props }, ref) => (
64
- <span
65
- ref={ref}
66
- className={cn(
67
- 'inline-flex h-9 select-none items-center border border-input bg-muted px-3 text-sm text-muted-foreground',
68
- className,
69
- )}
70
- {...props}
71
- />
72
- ));
73
- ButtonGroupText.displayName = 'ButtonGroupText';
74
-
75
- export { ButtonGroup, ButtonGroupText, buttonGroupVariants };
@@ -1,102 +0,0 @@
1
- ---
2
- id: combobox
3
- name: Combobox
4
- type: component
5
- category: form
6
- since: 0.1.0
7
- package: '@teamix-evo/ui'
8
- displayName: 组合框
9
- ---
10
-
11
- # Combobox 组合框
12
-
13
- 可搜索单选下拉 — `Command + Popover` 复合,等价 antd `AutoComplete` / `Select showSearch` 的核心场景。
14
- **单选**(刻意不做多选,保持语义简单);多选请直接用 `Command` + 自管 selected 数组。
15
-
16
- ## When to use
17
-
18
- - 选项较多需要搜索筛选(国家 / 时区 / 用户列表 / 标签)
19
- - 选项动态加载(异步 — 在父组件准备数据后传给 `options`)
20
-
21
- ## When NOT to use
22
-
23
- - 选项 ≤ 7 个 → `Select`(无需搜索)
24
- - 多选 → `Command` 自定义,或 `CheckboxGroup`
25
- - 自由文本输入 → `Input` + 自定义 suggestion(超出本组件范围)
26
-
27
- ## Props
28
-
29
- <!-- auto:props:begin -->
30
-
31
- | 名称 | 类型 | 默认值 | 必填 | 说明 |
32
- | ------------------- | --------------------------- | ------------- | ---- | -------------------------------------------- |
33
- | `options` | `ComboboxOption[]` | – | ✓ | 选项列表(antd `AutoComplete.options` 并集)。 |
34
- | `value` | `string` | – | – | 受控 value(单选)。 |
35
- | `onChange` | `(value: string) => void` | – | – | value 变化回调。 |
36
- | `placeholder` | `string` | `"请选择..."` | – | 触发器占位文本(未选)。 |
37
- | `searchPlaceholder` | `string` | `"搜索..."` | – | 搜索框占位文本。 |
38
- | `emptyText` | `string` | `"无匹配项"` | – | 无匹配时的提示文本。 |
39
- | `className` | `string` | `"w-[200px]"` | – | 触发器宽度。 |
40
- | `disabled` | `boolean` | – | – | 是否禁用。 |
41
- | `size` | `'sm' \| 'default' \| 'lg'` | `"default"` | – | 触发器尺寸。 |
42
-
43
- <!-- auto:props:end -->
44
-
45
- ## 依赖
46
-
47
- <!-- auto:deps:begin -->
48
-
49
- ### 同库依赖
50
-
51
- > `teamix-evo ui add combobox` 时,以下 entry 会被自动连带安装(无需手动 add)。
52
-
53
- | Entry | 类型 | 描述 |
54
- | --------- | --------- | --------------------------------------------------------------------------------------- |
55
- | `cn` | util | Tailwind className 合并工具(clsx + tailwind-merge) |
56
- | `button` | component | 通用按钮 — shadcn 实现 + antd 功能扩展(loading / icon / shape / block / dashed variant) |
57
- | `command` | component | 命令面板 — cmdk(Linear / Raycast 风格),全局搜索 / 命令执行;Combobox 的底座 |
58
- | `popover` | component | 可交互浮层 — Radix Popover + antd arrow 并集 |
59
-
60
- ### npm 依赖
61
-
62
- > 业务侧需要先 `pnpm add` / `npm install` 这些包。CLI 在 `ui add` 完成后会列出此提示。
63
-
64
- ```bash
65
- pnpm add lucide-react@^0.460.0
66
- ```
67
-
68
- <!-- auto:deps:end -->
69
-
70
- > 选项类型 `ComboboxOption`:`{ value: string; label: ReactNode; disabled?: boolean }`。
71
-
72
- ## AI 生成纪律
73
-
74
- - **`value` 稳定**:用业务 ID,不要用 index
75
- - **`label` 可富节点**:支持图标 + 文本,不止 string
76
- - **第二次点击当前 value 会清空**:`onChange('')` 触发,业务侧据此实现"取消选择"
77
- - **大量选项(>1000) → 自定义虚拟滚动**:本组件未集成 virtualizer
78
- - **异步加载选项**:父组件 fetch 后传 `options`,加 loading 占位符在 options 列表外
79
-
80
- ## Examples
81
-
82
- ```tsx
83
- import { Combobox, type ComboboxOption } from '@/components/ui/combobox';
84
- import * as React from 'react';
85
-
86
- const frameworks: ComboboxOption[] = [
87
- { value: 'next', label: 'Next.js' },
88
- { value: 'sveltekit', label: 'SvelteKit' },
89
- { value: 'nuxt', label: 'Nuxt' },
90
- { value: 'remix', label: 'Remix' },
91
- { value: 'astro', label: 'Astro' },
92
- ];
93
-
94
- const [v, setV] = React.useState('');
95
- <Combobox
96
- options={frameworks}
97
- value={v}
98
- onChange={setV}
99
- placeholder="选择框架"
100
- searchPlaceholder="搜索框架..."
101
- />;
102
- ```
@@ -1,55 +0,0 @@
1
- import * as React from 'react';
2
- import type { Meta, StoryObj } from '@storybook/react';
3
- import { Combobox, type ComboboxOption } from './combobox';
4
-
5
- const meta: Meta<typeof Combobox> = {
6
- title: '表单与输入 · Form/Combobox',
7
- component: Combobox,
8
- tags: ['autodocs'],
9
- parameters: {
10
- docs: {
11
- description: {
12
- component:
13
- '可搜索单选下拉 — Command + Popover 复合,对标 antd AutoComplete / Select showSearch。键盘可达、模糊搜索、空态、再次选中清空,适合选项数 > 7 的单选场景。',
14
- },
15
- },
16
- },
17
- };
18
-
19
- export default meta;
20
- type Story = StoryObj<typeof Combobox>;
21
-
22
- const frameworks: ComboboxOption[] = [
23
- { value: 'next', label: 'Next.js' },
24
- { value: 'sveltekit', label: 'SvelteKit' },
25
- { value: 'nuxt', label: 'Nuxt' },
26
- { value: 'remix', label: 'Remix' },
27
- { value: 'astro', label: 'Astro' },
28
- { value: 'qwik', label: 'Qwik', disabled: true },
29
- ];
30
-
31
- export const Default: Story = {
32
- render: () => {
33
- const [v, setV] = React.useState('');
34
- return (
35
- <Combobox
36
- options={frameworks}
37
- value={v}
38
- onChange={setV}
39
- placeholder="选择框架"
40
- searchPlaceholder="搜索框架..."
41
- />
42
- );
43
- },
44
- };
45
-
46
- export const Sizes: Story = {
47
- parameters: { controls: { disable: true } },
48
- render: () => (
49
- <div className="flex flex-col gap-3">
50
- <Combobox options={frameworks} placeholder="sm" size="sm" />
51
- <Combobox options={frameworks} placeholder="default" />
52
- <Combobox options={frameworks} placeholder="lg" size="lg" />
53
- </div>
54
- ),
55
- };
@@ -1,130 +0,0 @@
1
- import * as React from 'react';
2
- import { Check, ChevronsUpDown } from 'lucide-react';
3
-
4
- import { cn } from '@/utils/cn';
5
- import { Button } from '@/components/button/button';
6
- import {
7
- Command,
8
- CommandEmpty,
9
- CommandGroup,
10
- CommandInput,
11
- CommandItem,
12
- CommandList,
13
- } from '@/components/command/command';
14
- import {
15
- Popover,
16
- PopoverContent,
17
- PopoverTrigger,
18
- } from '@/components/popover/popover';
19
-
20
- export interface ComboboxOption {
21
- /** 选项值(受控 value 比对的依据,稳定 ID)。 */
22
- value: string;
23
- /** 显示文本。 */
24
- label: React.ReactNode;
25
- /** 禁用此选项。 */
26
- disabled?: boolean;
27
- }
28
-
29
- export interface ComboboxProps {
30
- /** 选项列表(antd `AutoComplete.options` 并集)。 */
31
- options: ComboboxOption[];
32
- /** 受控 value(单选)。 */
33
- value?: string;
34
- /** value 变化回调。 */
35
- onChange?: (value: string) => void;
36
- /** 触发器占位文本(未选)。 @default "请选择..." */
37
- placeholder?: string;
38
- /** 搜索框占位文本。 @default "搜索..." */
39
- searchPlaceholder?: string;
40
- /** 无匹配时的提示文本。 @default "无匹配项" */
41
- emptyText?: string;
42
- /** 触发器宽度。 @default "w-panel-sm" */
43
- className?: string;
44
- /** 是否禁用。 */
45
- disabled?: boolean;
46
- /** 触发器尺寸。 @default "default" */
47
- size?: 'sm' | 'default' | 'lg';
48
- }
49
-
50
- /**
51
- * 可搜索单选下拉 — Command + Popover 复合,等价 antd `AutoComplete` /
52
- * `Select showSearch` 的核心场景。
53
- *
54
- * 多选场景请直接用 Command + 自管 selected 数组(本组件刻意不做多选,保持
55
- * 单选语义清晰)。
56
- */
57
- const Combobox = React.forwardRef<HTMLButtonElement, ComboboxProps>(
58
- (
59
- {
60
- options,
61
- value,
62
- onChange,
63
- placeholder = '请选择...',
64
- searchPlaceholder = '搜索...',
65
- emptyText = '无匹配项',
66
- className,
67
- disabled,
68
- size = 'default',
69
- },
70
- ref,
71
- ) => {
72
- const [open, setOpen] = React.useState(false);
73
- const selected = options.find((o) => o.value === value);
74
-
75
- return (
76
- <Popover open={open} onOpenChange={setOpen}>
77
- <PopoverTrigger asChild>
78
- <Button
79
- ref={ref}
80
- variant="outline"
81
- size={size}
82
- disabled={disabled}
83
- role="combobox"
84
- aria-expanded={open}
85
- className={cn(
86
- 'w-panel-sm justify-between font-normal',
87
- !value && 'text-muted-foreground',
88
- className,
89
- )}
90
- >
91
- {selected ? selected.label : placeholder}
92
- <ChevronsUpDown className="ml-2 size-4 shrink-0 opacity-50" />
93
- </Button>
94
- </PopoverTrigger>
95
- <PopoverContent className="w-panel-sm p-0">
96
- <Command>
97
- <CommandInput placeholder={searchPlaceholder} />
98
- <CommandList>
99
- <CommandEmpty>{emptyText}</CommandEmpty>
100
- <CommandGroup>
101
- {options.map((opt) => (
102
- <CommandItem
103
- key={opt.value}
104
- value={opt.value}
105
- disabled={opt.disabled}
106
- onSelect={(v) => {
107
- onChange?.(v === value ? '' : v);
108
- setOpen(false);
109
- }}
110
- >
111
- <Check
112
- className={cn(
113
- 'mr-2 size-4',
114
- opt.value === value ? 'opacity-100' : 'opacity-0',
115
- )}
116
- />
117
- {opt.label}
118
- </CommandItem>
119
- ))}
120
- </CommandGroup>
121
- </CommandList>
122
- </Command>
123
- </PopoverContent>
124
- </Popover>
125
- );
126
- },
127
- );
128
- Combobox.displayName = 'Combobox';
129
-
130
- export { Combobox };
@@ -1,15 +0,0 @@
1
- import { Input } from '@/components/ui/input';
2
-
3
- /**
4
- * 前后置标签:通过 `addonBefore` / `addonAfter` 形成 input-group。
5
- * 常用于域名拼接、协议前缀、单位后缀。
6
- */
7
- export default function Demo() {
8
- return (
9
- <div className="flex w-96 flex-col gap-3">
10
- <Input addonBefore="https://" placeholder="example.com" />
11
- <Input addonAfter=".teamix.com" placeholder="alice" />
12
- <Input addonBefore="https://" addonAfter=".com" placeholder="my-site" />
13
- </div>
14
- );
15
- }
@@ -1,19 +0,0 @@
1
- import { Search, Mail } from 'lucide-react';
2
- import { Input } from '@/components/ui/input';
3
-
4
- /**
5
- * 前置 / 后置图标:通过 `prefix` / `suffix` 传任意 ReactNode(图标或文本)。
6
- * 视觉对齐由组件内部处理,不要再额外用 padding 兼容图标。
7
- */
8
- export default function Demo() {
9
- return (
10
- <div className="flex w-80 flex-col gap-3">
11
- <Input prefix={<Search />} placeholder="搜索..." />
12
- <Input
13
- prefix={<Mail />}
14
- suffix={<span className="text-xs">@teamix.com</span>}
15
- placeholder="alice"
16
- />
17
- </div>
18
- );
19
- }