@teamix-evo/ui 0.1.1

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 (270) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +336 -0
  3. package/_data.json +12 -0
  4. package/manifest.json +1688 -0
  5. package/package.json +90 -0
  6. package/src/components/accordion/accordion.meta.md +87 -0
  7. package/src/components/accordion/accordion.stories.tsx +67 -0
  8. package/src/components/accordion/accordion.tsx +58 -0
  9. package/src/components/affix/affix.meta.md +80 -0
  10. package/src/components/affix/affix.stories.tsx +57 -0
  11. package/src/components/affix/affix.tsx +97 -0
  12. package/src/components/alert/alert.meta.md +101 -0
  13. package/src/components/alert/alert.stories.tsx +93 -0
  14. package/src/components/alert/alert.tsx +132 -0
  15. package/src/components/alert-dialog/alert-dialog.meta.md +107 -0
  16. package/src/components/alert-dialog/alert-dialog.stories.tsx +81 -0
  17. package/src/components/alert-dialog/alert-dialog.tsx +136 -0
  18. package/src/components/anchor/anchor.meta.md +87 -0
  19. package/src/components/anchor/anchor.stories.tsx +74 -0
  20. package/src/components/anchor/anchor.tsx +130 -0
  21. package/src/components/app/app.meta.md +86 -0
  22. package/src/components/app/app.stories.tsx +62 -0
  23. package/src/components/app/app.tsx +58 -0
  24. package/src/components/aspect-ratio/aspect-ratio.meta.md +81 -0
  25. package/src/components/aspect-ratio/aspect-ratio.stories.tsx +59 -0
  26. package/src/components/aspect-ratio/aspect-ratio.tsx +22 -0
  27. package/src/components/auto-complete/auto-complete.meta.md +102 -0
  28. package/src/components/auto-complete/auto-complete.stories.tsx +93 -0
  29. package/src/components/auto-complete/auto-complete.tsx +205 -0
  30. package/src/components/avatar/avatar.meta.md +94 -0
  31. package/src/components/avatar/avatar.stories.tsx +80 -0
  32. package/src/components/avatar/avatar.tsx +126 -0
  33. package/src/components/badge/badge.meta.md +119 -0
  34. package/src/components/badge/badge.stories.tsx +153 -0
  35. package/src/components/badge/badge.tsx +210 -0
  36. package/src/components/breadcrumb/breadcrumb.meta.md +107 -0
  37. package/src/components/breadcrumb/breadcrumb.stories.tsx +84 -0
  38. package/src/components/breadcrumb/breadcrumb.tsx +122 -0
  39. package/src/components/button/button.meta.md +98 -0
  40. package/src/components/button/button.stories.tsx +235 -0
  41. package/src/components/button/button.tsx +160 -0
  42. package/src/components/button-group/button-group.meta.md +92 -0
  43. package/src/components/button-group/button-group.stories.tsx +90 -0
  44. package/src/components/button-group/button-group.tsx +75 -0
  45. package/src/components/calendar/calendar.meta.md +118 -0
  46. package/src/components/calendar/calendar.stories.tsx +68 -0
  47. package/src/components/calendar/calendar.tsx +107 -0
  48. package/src/components/card/card.meta.md +117 -0
  49. package/src/components/card/card.stories.tsx +112 -0
  50. package/src/components/card/card.tsx +222 -0
  51. package/src/components/carousel/carousel.meta.md +117 -0
  52. package/src/components/carousel/carousel.stories.tsx +84 -0
  53. package/src/components/carousel/carousel.tsx +224 -0
  54. package/src/components/cascader/cascader.meta.md +110 -0
  55. package/src/components/cascader/cascader.stories.tsx +108 -0
  56. package/src/components/cascader/cascader.tsx +198 -0
  57. package/src/components/checkbox/checkbox.meta.md +99 -0
  58. package/src/components/checkbox/checkbox.stories.tsx +130 -0
  59. package/src/components/checkbox/checkbox.tsx +125 -0
  60. package/src/components/collapsible/collapsible.meta.md +80 -0
  61. package/src/components/collapsible/collapsible.stories.tsx +35 -0
  62. package/src/components/collapsible/collapsible.tsx +18 -0
  63. package/src/components/color-picker/color-picker.meta.md +84 -0
  64. package/src/components/color-picker/color-picker.stories.tsx +80 -0
  65. package/src/components/color-picker/color-picker.tsx +160 -0
  66. package/src/components/combobox/combobox.meta.md +93 -0
  67. package/src/components/combobox/combobox.stories.tsx +55 -0
  68. package/src/components/combobox/combobox.tsx +130 -0
  69. package/src/components/command/command.meta.md +104 -0
  70. package/src/components/command/command.stories.tsx +59 -0
  71. package/src/components/command/command.tsx +147 -0
  72. package/src/components/context-menu/context-menu.meta.md +90 -0
  73. package/src/components/context-menu/context-menu.stories.tsx +46 -0
  74. package/src/components/context-menu/context-menu.tsx +191 -0
  75. package/src/components/data-table/data-table.meta.md +149 -0
  76. package/src/components/data-table/data-table.stories.tsx +125 -0
  77. package/src/components/data-table/data-table.tsx +185 -0
  78. package/src/components/date-picker/date-picker.meta.md +106 -0
  79. package/src/components/date-picker/date-picker.stories.tsx +58 -0
  80. package/src/components/date-picker/date-picker.tsx +156 -0
  81. package/src/components/descriptions/descriptions.meta.md +78 -0
  82. package/src/components/descriptions/descriptions.stories.tsx +60 -0
  83. package/src/components/descriptions/descriptions.tsx +129 -0
  84. package/src/components/dialog/dialog.meta.md +105 -0
  85. package/src/components/dialog/dialog.stories.tsx +93 -0
  86. package/src/components/dialog/dialog.tsx +128 -0
  87. package/src/components/drawer/drawer.meta.md +96 -0
  88. package/src/components/drawer/drawer.stories.tsx +54 -0
  89. package/src/components/drawer/drawer.tsx +114 -0
  90. package/src/components/dropdown-menu/dropdown-menu.meta.md +103 -0
  91. package/src/components/dropdown-menu/dropdown-menu.stories.tsx +112 -0
  92. package/src/components/dropdown-menu/dropdown-menu.tsx +195 -0
  93. package/src/components/empty/empty.meta.md +81 -0
  94. package/src/components/empty/empty.stories.tsx +46 -0
  95. package/src/components/empty/empty.tsx +47 -0
  96. package/src/components/field/field.meta.md +116 -0
  97. package/src/components/field/field.stories.tsx +117 -0
  98. package/src/components/field/field.tsx +164 -0
  99. package/src/components/flex/flex.meta.md +94 -0
  100. package/src/components/flex/flex.stories.tsx +112 -0
  101. package/src/components/flex/flex.tsx +122 -0
  102. package/src/components/float-button/float-button.meta.md +87 -0
  103. package/src/components/float-button/float-button.stories.tsx +78 -0
  104. package/src/components/float-button/float-button.tsx +143 -0
  105. package/src/components/form/form.meta.md +131 -0
  106. package/src/components/form/form.stories.tsx +122 -0
  107. package/src/components/form/form.tsx +194 -0
  108. package/src/components/grid/grid.meta.md +87 -0
  109. package/src/components/grid/grid.stories.tsx +99 -0
  110. package/src/components/grid/grid.tsx +130 -0
  111. package/src/components/hover-card/hover-card.meta.md +92 -0
  112. package/src/components/hover-card/hover-card.stories.tsx +68 -0
  113. package/src/components/hover-card/hover-card.tsx +29 -0
  114. package/src/components/image/image.meta.md +94 -0
  115. package/src/components/image/image.stories.tsx +55 -0
  116. package/src/components/image/image.tsx +138 -0
  117. package/src/components/input/input.meta.md +109 -0
  118. package/src/components/input/input.stories.tsx +117 -0
  119. package/src/components/input/input.tsx +213 -0
  120. package/src/components/input-group/input-group.meta.md +92 -0
  121. package/src/components/input-group/input-group.stories.tsx +88 -0
  122. package/src/components/input-group/input-group.tsx +107 -0
  123. package/src/components/input-number/input-number.meta.md +91 -0
  124. package/src/components/input-number/input-number.stories.tsx +87 -0
  125. package/src/components/input-number/input-number.tsx +210 -0
  126. package/src/components/input-otp/input-otp.meta.md +105 -0
  127. package/src/components/input-otp/input-otp.stories.tsx +65 -0
  128. package/src/components/input-otp/input-otp.tsx +97 -0
  129. package/src/components/item/item.meta.md +116 -0
  130. package/src/components/item/item.stories.tsx +113 -0
  131. package/src/components/item/item.tsx +171 -0
  132. package/src/components/kbd/kbd.meta.md +85 -0
  133. package/src/components/kbd/kbd.stories.tsx +70 -0
  134. package/src/components/kbd/kbd.tsx +81 -0
  135. package/src/components/label/label.meta.md +91 -0
  136. package/src/components/label/label.stories.tsx +87 -0
  137. package/src/components/label/label.tsx +66 -0
  138. package/src/components/masonry/masonry.meta.md +85 -0
  139. package/src/components/masonry/masonry.stories.tsx +66 -0
  140. package/src/components/masonry/masonry.tsx +59 -0
  141. package/src/components/mentions/mentions.meta.md +89 -0
  142. package/src/components/mentions/mentions.stories.tsx +75 -0
  143. package/src/components/mentions/mentions.tsx +237 -0
  144. package/src/components/menubar/menubar.meta.md +100 -0
  145. package/src/components/menubar/menubar.stories.tsx +81 -0
  146. package/src/components/menubar/menubar.tsx +232 -0
  147. package/src/components/native-select/native-select.meta.md +88 -0
  148. package/src/components/native-select/native-select.stories.tsx +80 -0
  149. package/src/components/native-select/native-select.tsx +54 -0
  150. package/src/components/navigation-menu/navigation-menu.meta.md +108 -0
  151. package/src/components/navigation-menu/navigation-menu.stories.tsx +112 -0
  152. package/src/components/navigation-menu/navigation-menu.tsx +125 -0
  153. package/src/components/notification/notification.meta.md +91 -0
  154. package/src/components/notification/notification.stories.tsx +96 -0
  155. package/src/components/notification/notification.tsx +84 -0
  156. package/src/components/pagination/pagination.meta.md +127 -0
  157. package/src/components/pagination/pagination.stories.tsx +62 -0
  158. package/src/components/pagination/pagination.tsx +285 -0
  159. package/src/components/popconfirm/popconfirm.meta.md +109 -0
  160. package/src/components/popconfirm/popconfirm.stories.tsx +76 -0
  161. package/src/components/popconfirm/popconfirm.tsx +134 -0
  162. package/src/components/popover/popover.meta.md +97 -0
  163. package/src/components/popover/popover.stories.tsx +82 -0
  164. package/src/components/popover/popover.tsx +55 -0
  165. package/src/components/progress/progress.meta.md +86 -0
  166. package/src/components/progress/progress.stories.tsx +75 -0
  167. package/src/components/progress/progress.tsx +195 -0
  168. package/src/components/radio-group/radio-group.meta.md +103 -0
  169. package/src/components/radio-group/radio-group.stories.tsx +77 -0
  170. package/src/components/radio-group/radio-group.tsx +78 -0
  171. package/src/components/rate/rate.meta.md +87 -0
  172. package/src/components/rate/rate.stories.tsx +81 -0
  173. package/src/components/rate/rate.tsx +153 -0
  174. package/src/components/resizable/resizable.meta.md +92 -0
  175. package/src/components/resizable/resizable.stories.tsx +104 -0
  176. package/src/components/resizable/resizable.tsx +56 -0
  177. package/src/components/result/result.meta.md +90 -0
  178. package/src/components/result/result.stories.tsx +71 -0
  179. package/src/components/result/result.tsx +91 -0
  180. package/src/components/scroll-area/scroll-area.meta.md +84 -0
  181. package/src/components/scroll-area/scroll-area.stories.tsx +41 -0
  182. package/src/components/scroll-area/scroll-area.tsx +51 -0
  183. package/src/components/segmented/segmented.meta.md +103 -0
  184. package/src/components/segmented/segmented.stories.tsx +101 -0
  185. package/src/components/segmented/segmented.tsx +138 -0
  186. package/src/components/select/select.meta.md +110 -0
  187. package/src/components/select/select.stories.tsx +100 -0
  188. package/src/components/select/select.tsx +188 -0
  189. package/src/components/separator/separator.meta.md +74 -0
  190. package/src/components/separator/separator.stories.tsx +71 -0
  191. package/src/components/separator/separator.tsx +104 -0
  192. package/src/components/sheet/sheet.meta.md +97 -0
  193. package/src/components/sheet/sheet.stories.tsx +82 -0
  194. package/src/components/sheet/sheet.tsx +139 -0
  195. package/src/components/sidebar/sidebar.meta.md +131 -0
  196. package/src/components/sidebar/sidebar.stories.tsx +82 -0
  197. package/src/components/sidebar/sidebar.tsx +351 -0
  198. package/src/components/skeleton/skeleton.meta.md +95 -0
  199. package/src/components/skeleton/skeleton.stories.tsx +79 -0
  200. package/src/components/skeleton/skeleton.tsx +144 -0
  201. package/src/components/slider/slider.meta.md +94 -0
  202. package/src/components/slider/slider.stories.tsx +69 -0
  203. package/src/components/slider/slider.tsx +86 -0
  204. package/src/components/sonner/sonner.meta.md +96 -0
  205. package/src/components/sonner/sonner.stories.tsx +91 -0
  206. package/src/components/sonner/sonner.tsx +40 -0
  207. package/src/components/space/space.meta.md +94 -0
  208. package/src/components/space/space.stories.tsx +94 -0
  209. package/src/components/space/space.tsx +106 -0
  210. package/src/components/spinner/spinner.meta.md +76 -0
  211. package/src/components/spinner/spinner.stories.tsx +71 -0
  212. package/src/components/spinner/spinner.tsx +64 -0
  213. package/src/components/statistic/statistic.meta.md +99 -0
  214. package/src/components/statistic/statistic.stories.tsx +71 -0
  215. package/src/components/statistic/statistic.tsx +197 -0
  216. package/src/components/steps/steps.meta.md +102 -0
  217. package/src/components/steps/steps.stories.tsx +75 -0
  218. package/src/components/steps/steps.tsx +170 -0
  219. package/src/components/switch/switch.meta.md +92 -0
  220. package/src/components/switch/switch.stories.tsx +75 -0
  221. package/src/components/switch/switch.tsx +101 -0
  222. package/src/components/table/table.meta.md +95 -0
  223. package/src/components/table/table.stories.tsx +75 -0
  224. package/src/components/table/table.tsx +122 -0
  225. package/src/components/tabs/tabs.meta.md +98 -0
  226. package/src/components/tabs/tabs.stories.tsx +70 -0
  227. package/src/components/tabs/tabs.tsx +119 -0
  228. package/src/components/tag/tag.meta.md +94 -0
  229. package/src/components/tag/tag.stories.tsx +77 -0
  230. package/src/components/tag/tag.tsx +185 -0
  231. package/src/components/textarea/textarea.meta.md +83 -0
  232. package/src/components/textarea/textarea.stories.tsx +63 -0
  233. package/src/components/textarea/textarea.tsx +113 -0
  234. package/src/components/time-picker/time-picker.meta.md +83 -0
  235. package/src/components/time-picker/time-picker.stories.tsx +59 -0
  236. package/src/components/time-picker/time-picker.tsx +94 -0
  237. package/src/components/timeline/timeline.meta.md +102 -0
  238. package/src/components/timeline/timeline.stories.tsx +104 -0
  239. package/src/components/timeline/timeline.tsx +147 -0
  240. package/src/components/toggle/toggle.meta.md +88 -0
  241. package/src/components/toggle/toggle.stories.tsx +66 -0
  242. package/src/components/toggle/toggle.tsx +53 -0
  243. package/src/components/toggle-group/toggle-group.meta.md +90 -0
  244. package/src/components/toggle-group/toggle-group.stories.tsx +83 -0
  245. package/src/components/toggle-group/toggle-group.tsx +78 -0
  246. package/src/components/tooltip/tooltip.meta.md +99 -0
  247. package/src/components/tooltip/tooltip.stories.tsx +71 -0
  248. package/src/components/tooltip/tooltip.tsx +93 -0
  249. package/src/components/tour/tour.meta.md +116 -0
  250. package/src/components/tour/tour.stories.tsx +66 -0
  251. package/src/components/tour/tour.tsx +242 -0
  252. package/src/components/transfer/transfer.meta.md +90 -0
  253. package/src/components/transfer/transfer.stories.tsx +68 -0
  254. package/src/components/transfer/transfer.tsx +251 -0
  255. package/src/components/tree/tree.meta.md +111 -0
  256. package/src/components/tree/tree.stories.tsx +109 -0
  257. package/src/components/tree/tree.tsx +367 -0
  258. package/src/components/tree-select/tree-select.meta.md +100 -0
  259. package/src/components/tree-select/tree-select.stories.tsx +80 -0
  260. package/src/components/tree-select/tree-select.tsx +171 -0
  261. package/src/components/typography/typography.meta.md +102 -0
  262. package/src/components/typography/typography.stories.tsx +115 -0
  263. package/src/components/typography/typography.tsx +245 -0
  264. package/src/components/upload/upload.meta.md +111 -0
  265. package/src/components/upload/upload.stories.tsx +75 -0
  266. package/src/components/upload/upload.tsx +265 -0
  267. package/src/components/watermark/watermark.meta.md +95 -0
  268. package/src/components/watermark/watermark.stories.tsx +78 -0
  269. package/src/components/watermark/watermark.tsx +165 -0
  270. package/src/utils/cn.ts +6 -0
@@ -0,0 +1,84 @@
1
+ ---
2
+ id: scroll-area
3
+ name: ScrollArea
4
+ type: component
5
+ category: layout
6
+ since: 0.1.0
7
+ package: "@teamix-evo/ui"
8
+ ---
9
+
10
+ # ScrollArea
11
+
12
+ 自定义滚动容器 — Radix ScrollArea。**shadcn-only**(antd 用原生滚动)。
13
+ **核心价值**:跨浏览器一致的滚动条样式 + 触屏友好 + 自动隐藏 thumb。
14
+
15
+ ## When to use
16
+
17
+ - 需要一致滚动条样式的容器(列表 / 侧栏 / 抽屉内长内容)
18
+ - 在 Radix Dialog / Sheet 内的可滚动子区域
19
+ - 需要隐藏原生滚动条但保留滚动能力
20
+
21
+ ## When NOT to use
22
+
23
+ - 整页滚动 → 用 `<body>` 默认
24
+ - 短内容(< 视口)→ 不需要包装
25
+
26
+ ## Props
27
+
28
+ > 以下表格由 `pnpm --filter @teamix-evo/ui gen:meta` 自动生成。
29
+
30
+ <!-- auto:props:begin -->
31
+ _(no props)_
32
+ <!-- auto:props:end -->
33
+
34
+ ## 依赖
35
+
36
+ > 以下表格由 `pnpm --filter @teamix-evo/ui gen:meta` 自动生成,数据源是 [`manifest.json`](../../../manifest.json)。**手工编辑 marker 之间的内容会在下次生成时被覆盖**。
37
+
38
+ <!-- auto:deps:begin -->
39
+ ### 同库依赖
40
+
41
+ > `teamix-evo ui add scroll-area` 时,以下 entry 会被自动连带安装(无需手动 add)。
42
+
43
+ | Entry | 类型 | 描述 |
44
+ | --- | --- | --- |
45
+ | `cn` | util | Tailwind className 合并工具(clsx + tailwind-merge) |
46
+
47
+ ### npm 依赖
48
+
49
+ > 业务侧需要先 `pnpm add` / `npm install` 这些包。CLI 在 `ui add` 完成后会列出此提示。
50
+
51
+ ```bash
52
+ pnpm add @radix-ui/react-scroll-area@^1.2.0
53
+ ```
54
+ <!-- auto:deps:end -->
55
+
56
+ > 子组件:`ScrollArea`(主容器,内置 Viewport + ScrollBar 组合)/ `ScrollBar`(独立 ScrollBar,需要自定义双向滚动时使用)。
57
+
58
+ ## AI 生成纪律
59
+
60
+ - **必给容器固定高度**:ScrollArea 不会自动撑满父级,要么 `h-*` / `max-h-*`,要么父级 flex 撑开
61
+ - **`type` 控制行为**:`auto`(默认,内容溢出才显示)/ `always`(常驻)/ `scroll`(交互后显示)/ `hover`(hover 时显示)
62
+ - **横向滚动需独立 ScrollBar**:`<ScrollBar orientation="horizontal" />` 加在 `<ScrollArea>` 内
63
+ - **不嵌套 ScrollArea**:嵌套滚动会让 thumb 行为混乱
64
+
65
+ ## Examples
66
+
67
+ ```tsx
68
+ import { ScrollArea, ScrollBar } from '@/components/ui/scroll-area';
69
+
70
+ // 纵向(默认)
71
+ <ScrollArea className="h-72 w-48 rounded-md border p-4">
72
+ {Array.from({ length: 50 }).map((_, i) => (
73
+ <div key={i} className="text-sm">第 {i + 1} 行</div>
74
+ ))}
75
+ </ScrollArea>
76
+
77
+ // 横向 + 纵向
78
+ <ScrollArea className="h-72 w-96 whitespace-nowrap rounded-md border">
79
+ <div className="flex w-max gap-4 p-4">
80
+ {items.map((it) => <div key={it.id} className="size-32 shrink-0">...</div>)}
81
+ </div>
82
+ <ScrollBar orientation="horizontal" />
83
+ </ScrollArea>
84
+ ```
@@ -0,0 +1,41 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { ScrollArea, ScrollBar } from './scroll-area';
3
+
4
+ const meta: Meta<typeof ScrollArea> = {
5
+ title: '布局与容器 · Layout/ScrollArea',
6
+ component: ScrollArea,
7
+ tags: ['autodocs'],
8
+ };
9
+
10
+ export default meta;
11
+ type Story = StoryObj<typeof ScrollArea>;
12
+
13
+ export const Vertical: Story = {
14
+ render: () => (
15
+ <ScrollArea className="h-72 w-48 rounded-md border p-4">
16
+ {Array.from({ length: 50 }).map((_, i) => (
17
+ <div key={i} className="py-1 text-sm">
18
+ 第 {i + 1} 行
19
+ </div>
20
+ ))}
21
+ </ScrollArea>
22
+ ),
23
+ };
24
+
25
+ export const Horizontal: Story = {
26
+ render: () => (
27
+ <ScrollArea className="w-96 whitespace-nowrap rounded-md border">
28
+ <div className="flex w-max gap-4 p-4">
29
+ {Array.from({ length: 12 }).map((_, i) => (
30
+ <div
31
+ key={i}
32
+ className="flex size-32 shrink-0 items-center justify-center rounded-md bg-muted text-sm text-muted-foreground"
33
+ >
34
+ #{i + 1}
35
+ </div>
36
+ ))}
37
+ </div>
38
+ <ScrollBar orientation="horizontal" />
39
+ </ScrollArea>
40
+ ),
41
+ };
@@ -0,0 +1,51 @@
1
+ import * as React from 'react';
2
+ import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
3
+
4
+ import { cn } from '@/utils/cn';
5
+
6
+ export interface ScrollAreaProps
7
+ extends React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root> {}
8
+
9
+ const ScrollArea = React.forwardRef<
10
+ React.ElementRef<typeof ScrollAreaPrimitive.Root>,
11
+ ScrollAreaProps
12
+ >(({ className, children, ...props }, ref) => (
13
+ <ScrollAreaPrimitive.Root
14
+ ref={ref}
15
+ className={cn('relative overflow-hidden', className)}
16
+ {...props}
17
+ >
18
+ <ScrollAreaPrimitive.Viewport className="size-full rounded-[inherit]">
19
+ {children}
20
+ </ScrollAreaPrimitive.Viewport>
21
+ <ScrollBar />
22
+ <ScrollAreaPrimitive.Corner />
23
+ </ScrollAreaPrimitive.Root>
24
+ ));
25
+ ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
26
+
27
+ const ScrollBar = React.forwardRef<
28
+ React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
29
+ React.ComponentPropsWithoutRef<
30
+ typeof ScrollAreaPrimitive.ScrollAreaScrollbar
31
+ >
32
+ >(({ className, orientation = 'vertical', ...props }, ref) => (
33
+ <ScrollAreaPrimitive.ScrollAreaScrollbar
34
+ ref={ref}
35
+ orientation={orientation}
36
+ className={cn(
37
+ 'flex touch-none select-none transition-colors',
38
+ orientation === 'vertical' &&
39
+ 'h-full w-2.5 border-l border-l-transparent p-[1px]',
40
+ orientation === 'horizontal' &&
41
+ 'h-2.5 flex-col border-t border-t-transparent p-[1px]',
42
+ className,
43
+ )}
44
+ {...props}
45
+ >
46
+ <ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
47
+ </ScrollAreaPrimitive.ScrollAreaScrollbar>
48
+ ));
49
+ ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
50
+
51
+ export { ScrollArea, ScrollBar };
@@ -0,0 +1,103 @@
1
+ ---
2
+ id: segmented
3
+ name: Segmented
4
+ type: component
5
+ category: form
6
+ since: 0.1.0
7
+ package: "@teamix-evo/ui"
8
+ ---
9
+
10
+ # Segmented
11
+
12
+ 分段控制器 — antd 独有补足。**等价 antd `Segmented`**(v5.0+)。视觉类似 iOS/macOS 系统 Segmented Control,强调**互斥单选 + 紧凑紧贴**(日 / 周 / 月、列表 / 网格、明 / 暗主题切换)。
13
+
14
+ ## When to use
15
+
16
+ - 视图切换(列表 / 网格 / 时间轴)
17
+ - 时间区间切换(日 / 周 / 月 / 年)
18
+ - 主题 / 设置项的 2~4 档枚举(配 icon 视觉更直观)
19
+
20
+ ## When NOT to use
21
+
22
+ - 工具栏按钮组(多选 / 不互斥)→ `ToggleGroup`
23
+ - 数据流单选过滤 → `RadioGroup`
24
+ - 选项超过 6 个 → 改用 `Select` / `Tabs`
25
+
26
+ <!-- auto:props:begin -->
27
+ | 名称 | 类型 | 默认值 | 必填 | 说明 |
28
+ | --- | --- | --- | --- | --- |
29
+ | `options` | `SegmentedOption[]` | – | ✓ | 候选项数组。 |
30
+ | `value` | `string` | – | – | 受控 value。 |
31
+ | `defaultValue` | `string` | – | – | uncontrolled 初值。 |
32
+ | `onChange` | `(value: string) => void` | – | – | value 变化回调。 |
33
+ | `size` | `'sm' \| 'default' \| 'lg'` | `"default"` | – | 尺寸。 |
34
+ | `block` | `boolean` | `false` | – | 是否撑满父容器宽度(antd `block` 并集)。 |
35
+ | `disabled` | `boolean` | – | – | 整组禁用。 |
36
+ <!-- auto:props:end -->
37
+
38
+ <!-- auto:deps:begin -->
39
+ ### 同库依赖
40
+
41
+ > `teamix-evo ui add segmented` 时,以下 entry 会被自动连带安装(无需手动 add)。
42
+
43
+ | Entry | 类型 | 描述 |
44
+ | --- | --- | --- |
45
+ | `cn` | util | Tailwind className 合并工具(clsx + tailwind-merge) |
46
+
47
+ ### npm 依赖
48
+
49
+ > 业务侧需要先 `pnpm add` / `npm install` 这些包。CLI 在 `ui add` 完成后会列出此提示。
50
+
51
+ ```bash
52
+ pnpm add class-variance-authority@^0.7.0
53
+ ```
54
+ <!-- auto:deps:end -->
55
+
56
+ ## AI 生成纪律
57
+
58
+ - **选项数控制在 2~5 个**:Segmented 是紧凑控件,超过 5 个会拥挤
59
+ - **同语义同长度文本**:`日 / 周 / 月` 比 `今天的视图 / 本周 / 月` 视觉对称
60
+ - **配 icon 时图标尺寸 ≤ 16px**:Segmented 高度只有 32~40px,icon 大了挤压文字
61
+ - **`block=true`** 用于在容器内撑满(如卡片头切换视图);默认 fit-content 紧凑
62
+ - **不要用 Segmented 触发危险操作**:它是单选切换语义,不是按钮组
63
+ - **disabled 单项**:配 `disabled` 的选项依然显示,但点击 / 键盘均不可达
64
+
65
+ ## Examples
66
+
67
+ ```tsx
68
+ import { Segmented } from '@/components/ui/segmented';
69
+ import { LayoutGrid, List, Calendar } from 'lucide-react';
70
+ import * as React from 'react';
71
+
72
+ // 基础
73
+ <Segmented
74
+ options={[
75
+ { value: 'day', label: '日' },
76
+ { value: 'week', label: '周' },
77
+ { value: 'month', label: '月' },
78
+ ]}
79
+ defaultValue="week"
80
+ />
81
+
82
+ // 配 icon
83
+ const [view, setView] = React.useState('grid');
84
+ <Segmented
85
+ value={view}
86
+ onChange={setView}
87
+ options={[
88
+ { value: 'grid', label: '网格', icon: <LayoutGrid className="size-4" /> },
89
+ { value: 'list', label: '列表', icon: <List className="size-4" /> },
90
+ { value: 'cal', label: '日历', icon: <Calendar className="size-4" /> },
91
+ ]}
92
+ />
93
+
94
+ // block 撑满
95
+ <Segmented
96
+ block
97
+ options={[
98
+ { value: 'light', label: '浅色' },
99
+ { value: 'dark', label: '深色' },
100
+ { value: 'system', label: '跟随系统' },
101
+ ]}
102
+ />
103
+ ```
@@ -0,0 +1,101 @@
1
+ import * as React from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+ import { Calendar, LayoutGrid, List } from 'lucide-react';
4
+ import { Segmented } from './segmented';
5
+
6
+ const meta: Meta<typeof Segmented> = {
7
+ title: '表单与输入 · Form/Segmented',
8
+ component: Segmented,
9
+ tags: ['autodocs'],
10
+ parameters: {
11
+ docs: {
12
+ description: {
13
+ component:
14
+ '分段控制器 — iOS / macOS 风格的紧凑互斥单选(视图切换、时间区间、主题档)。等价 antd `Segmented`(v5.0+),与 ToggleGroup 互补(后者偏工具栏多选)。视觉走 OpenTrek tokens,所有样式来自 `@teamix-evo/design`,无 mock。',
15
+ },
16
+ },
17
+ },
18
+ argTypes: {
19
+ size: { control: 'inline-radio', options: ['sm', 'default', 'lg'] },
20
+ block: { control: 'boolean' },
21
+ disabled: { control: 'boolean' },
22
+ },
23
+ args: { size: 'default', block: false, disabled: false },
24
+ };
25
+
26
+ export default meta;
27
+ type Story = StoryObj<typeof Segmented>;
28
+
29
+ export const Playground: Story = {
30
+ render: (args) => (
31
+ <Segmented
32
+ {...args}
33
+ defaultValue="week"
34
+ options={[
35
+ { value: 'day', label: '日' },
36
+ { value: 'week', label: '周' },
37
+ { value: 'month', label: '月' },
38
+ ]}
39
+ />
40
+ ),
41
+ };
42
+
43
+ export const WithIcons: Story = {
44
+ parameters: { controls: { disable: true } },
45
+ render: () => {
46
+ const [v, setV] = React.useState('grid');
47
+ return (
48
+ <Segmented
49
+ value={v}
50
+ onChange={setV}
51
+ options={[
52
+ { value: 'grid', label: '网格', icon: <LayoutGrid className="size-4" /> },
53
+ { value: 'list', label: '列表', icon: <List className="size-4" /> },
54
+ { value: 'cal', label: '日历', icon: <Calendar className="size-4" /> },
55
+ ]}
56
+ />
57
+ );
58
+ },
59
+ };
60
+
61
+ export const Block: Story = {
62
+ parameters: { controls: { disable: true } },
63
+ render: () => (
64
+ <div className="w-80">
65
+ <Segmented
66
+ block
67
+ defaultValue="light"
68
+ options={[
69
+ { value: 'light', label: '浅色' },
70
+ { value: 'dark', label: '深色' },
71
+ { value: 'system', label: '跟随系统' },
72
+ ]}
73
+ />
74
+ </div>
75
+ ),
76
+ };
77
+
78
+ export const Sizes: Story = {
79
+ parameters: { controls: { disable: true } },
80
+ render: () => (
81
+ <div className="flex flex-col items-start gap-3">
82
+ <Segmented size="sm" defaultValue="a" options={[{ value: 'a', label: 'A' }, { value: 'b', label: 'B' }]} />
83
+ <Segmented size="default" defaultValue="a" options={[{ value: 'a', label: 'A' }, { value: 'b', label: 'B' }]} />
84
+ <Segmented size="lg" defaultValue="a" options={[{ value: 'a', label: 'A' }, { value: 'b', label: 'B' }]} />
85
+ </div>
86
+ ),
87
+ };
88
+
89
+ export const WithDisabledItem: Story = {
90
+ parameters: { controls: { disable: true } },
91
+ render: () => (
92
+ <Segmented
93
+ defaultValue="open"
94
+ options={[
95
+ { value: 'open', label: '进行中' },
96
+ { value: 'closed', label: '已关闭' },
97
+ { value: 'archived', label: '已归档', disabled: true },
98
+ ]}
99
+ />
100
+ ),
101
+ };
@@ -0,0 +1,138 @@
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 segmentedVariants = cva(
7
+ 'inline-flex w-fit items-center rounded-md bg-muted p-1',
8
+ {
9
+ variants: {
10
+ size: {
11
+ sm: 'h-8 text-xs',
12
+ default: 'h-9 text-sm',
13
+ lg: 'h-10 text-base',
14
+ },
15
+ block: { true: 'w-full', false: 'w-fit' },
16
+ },
17
+ defaultVariants: { size: 'default', block: false },
18
+ },
19
+ );
20
+
21
+ const itemVariants = cva(
22
+ 'flex items-center justify-center gap-1.5 rounded-sm px-3 transition-all cursor-pointer select-none focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring',
23
+ {
24
+ variants: {
25
+ active: {
26
+ true: 'bg-background text-foreground shadow-sm',
27
+ false: 'text-muted-foreground hover:text-foreground',
28
+ },
29
+ disabled: {
30
+ true: 'cursor-not-allowed opacity-50 hover:text-muted-foreground',
31
+ false: '',
32
+ },
33
+ block: { true: 'flex-1', false: '' },
34
+ },
35
+ defaultVariants: { active: false, disabled: false, block: false },
36
+ },
37
+ );
38
+
39
+ export interface SegmentedOption {
40
+ /** 真实 value(受控比对依据)。 */
41
+ value: string;
42
+ /** 显示文本。 */
43
+ label: React.ReactNode;
44
+ /** 左侧图标(可选)。 */
45
+ icon?: React.ReactNode;
46
+ /** 禁用此项。 */
47
+ disabled?: boolean;
48
+ }
49
+
50
+ export interface SegmentedProps
51
+ extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange' | 'defaultValue'>,
52
+ VariantProps<typeof segmentedVariants> {
53
+ /** 候选项数组。 */
54
+ options: SegmentedOption[];
55
+ /** 受控 value。 */
56
+ value?: string;
57
+ /** uncontrolled 初值。 */
58
+ defaultValue?: string;
59
+ /** value 变化回调。 */
60
+ onChange?: (value: string) => void;
61
+ /**
62
+ * 尺寸。
63
+ * @default "default"
64
+ */
65
+ size?: 'sm' | 'default' | 'lg';
66
+ /**
67
+ * 是否撑满父容器宽度(antd `block` 并集)。
68
+ * @default false
69
+ */
70
+ block?: boolean;
71
+ /** 整组禁用。 */
72
+ disabled?: boolean;
73
+ }
74
+
75
+ /**
76
+ * 分段控制器 — antd 独有补足。**等价 antd `Segmented`**(v5.0+)。
77
+ * 与 `ToggleGroup` 区别:Segmented 视觉**类似 iOS / macOS 系统的 Segmented Control**,
78
+ * 强调"互斥单选 + 紧凑紧贴"(同语义的视图切换:日/周/月、列表/网格);
79
+ * ToggleGroup 偏向工具栏按钮(可选 multiple、视觉更"按钮化")。
80
+ */
81
+ const Segmented = React.forwardRef<HTMLDivElement, SegmentedProps>(
82
+ (
83
+ {
84
+ options,
85
+ value,
86
+ defaultValue,
87
+ onChange,
88
+ size,
89
+ block,
90
+ disabled = false,
91
+ className,
92
+ ...props
93
+ },
94
+ ref,
95
+ ) => {
96
+ const isControlled = value !== undefined;
97
+ const [internal, setInternal] = React.useState<string>(
98
+ defaultValue ?? options[0]?.value ?? '',
99
+ );
100
+ const current = isControlled ? value! : internal;
101
+
102
+ const select = (next: string) => {
103
+ if (!isControlled) setInternal(next);
104
+ onChange?.(next);
105
+ };
106
+
107
+ return (
108
+ <div
109
+ ref={ref}
110
+ role="radiogroup"
111
+ className={cn(segmentedVariants({ size, block }), className)}
112
+ {...props}
113
+ >
114
+ {options.map((opt) => {
115
+ const itemDisabled = disabled || opt.disabled;
116
+ const active = opt.value === current;
117
+ return (
118
+ <button
119
+ key={opt.value}
120
+ type="button"
121
+ role="radio"
122
+ aria-checked={active}
123
+ disabled={itemDisabled}
124
+ onClick={() => !itemDisabled && select(opt.value)}
125
+ className={cn(itemVariants({ active, disabled: itemDisabled, block }))}
126
+ >
127
+ {opt.icon ? <span className="inline-flex">{opt.icon}</span> : null}
128
+ {opt.label}
129
+ </button>
130
+ );
131
+ })}
132
+ </div>
133
+ );
134
+ },
135
+ );
136
+ Segmented.displayName = 'Segmented';
137
+
138
+ export { Segmented };
@@ -0,0 +1,110 @@
1
+ ---
2
+ id: select
3
+ name: Select
4
+ type: component
5
+ category: form
6
+ since: 0.1.0
7
+ package: "@teamix-evo/ui"
8
+ ---
9
+
10
+ # Select
11
+
12
+ 下拉选择 — Radix Select 标准实现。**仅单选**(Radix Select 设计如此),多选 / 搜索请用 `Combobox`(基于 Command + Popover,在 v0.x)。
13
+ 对应 antd `Select` 的最常见单选场景;antd 的 `mode="multiple"` / `showSearch` / `tags` 等高级形态由 Combobox 接力。
14
+
15
+ ## When to use
16
+
17
+ - 单选枚举(状态 / 类型 / 区域 / 时区等)
18
+ - 选项 ≥ 5 时替代 RadioGroup
19
+ - 选项较长 / 需要分组 / 需要 Label 分隔
20
+
21
+ ## When NOT to use
22
+
23
+ - 多选 / 可搜索 → `Combobox`(v0.x)
24
+ - 选项 ≤ 4 → `RadioGroup`(更直观)
25
+ - 自由输入 → `Input`
26
+ - 需要异步加载选项 → `Combobox` + 自定义数据源
27
+
28
+ ## Props
29
+
30
+ > 以下表格由 `pnpm --filter @teamix-evo/ui gen:meta` 自动生成。下表是 `SelectTrigger` 的 props;`Select`(Root)透传 Radix `value / defaultValue / onValueChange / open / disabled / required / name`。
31
+
32
+ <!-- auto:props:begin -->
33
+ | 名称 | 类型 | 默认值 | 必填 | 说明 |
34
+ | --- | --- | --- | --- | --- |
35
+ | `position` | `React.ComponentPropsWithoutRef< typeof SelectPrimitive.Content >['position']` | `"popper"` | – | 浮层定位策略。`popper` 跟随 trigger 并自动避让边界;`item-aligned` 把当前选中项与 trigger 对齐(类似原生 `<select>`)。默认 `popper` 与 antd Select 行为一致。 |
36
+ <!-- auto:props:end -->
37
+
38
+ ## 依赖
39
+
40
+ > 以下表格由 `pnpm --filter @teamix-evo/ui gen:meta` 自动生成,数据源是 [`manifest.json`](../../../manifest.json)。**手工编辑 marker 之间的内容会在下次生成时被覆盖**。
41
+
42
+ <!-- auto:deps:begin -->
43
+ ### 同库依赖
44
+
45
+ > `teamix-evo ui add select` 时,以下 entry 会被自动连带安装(无需手动 add)。
46
+
47
+ | Entry | 类型 | 描述 |
48
+ | --- | --- | --- |
49
+ | `cn` | util | Tailwind className 合并工具(clsx + tailwind-merge) |
50
+
51
+ ### npm 依赖
52
+
53
+ > 业务侧需要先 `pnpm add` / `npm install` 这些包。CLI 在 `ui add` 完成后会列出此提示。
54
+
55
+ ```bash
56
+ pnpm add @radix-ui/react-select@^2.1.0 lucide-react@^0.460.0
57
+ ```
58
+ <!-- auto:deps:end -->
59
+
60
+ > 完整子组件:`Select / SelectGroup / SelectValue / SelectTrigger / SelectContent / SelectLabel / SelectItem / SelectSeparator / SelectScrollUpButton / SelectScrollDownButton`。
61
+
62
+ ## AI 生成纪律
63
+
64
+ - **`SelectValue` 配 placeholder**:`<SelectValue placeholder="..." />` 提供未选时的占位文字
65
+ - **每个 SelectItem 必有 value**:不能空字符串(Radix 拒绝);用稳定 ID
66
+ - **大量选项必分组**:超过 8 个用 `SelectGroup` + `SelectLabel`,加 `SelectSeparator` 分段
67
+ - **不要嵌套 Select**:嵌套下拉是反模式
68
+ - **键盘可用性自带**:Radix 自动支持 Type-ahead(首字母搜索),不需要再加 search
69
+
70
+ ## Examples
71
+
72
+ ```tsx
73
+ import {
74
+ Select, SelectTrigger, SelectValue, SelectContent,
75
+ SelectGroup, SelectLabel, SelectItem, SelectSeparator,
76
+ } from '@/components/ui/select';
77
+
78
+ // 基础
79
+ <Select>
80
+ <SelectTrigger className="w-48">
81
+ <SelectValue placeholder="选择城市" />
82
+ </SelectTrigger>
83
+ <SelectContent>
84
+ <SelectItem value="bj">北京</SelectItem>
85
+ <SelectItem value="sh">上海</SelectItem>
86
+ <SelectItem value="hz">杭州</SelectItem>
87
+ </SelectContent>
88
+ </Select>
89
+
90
+ // 分组
91
+ <Select>
92
+ <SelectTrigger className="w-48"><SelectValue placeholder="选择时区" /></SelectTrigger>
93
+ <SelectContent>
94
+ <SelectGroup>
95
+ <SelectLabel>亚洲</SelectLabel>
96
+ <SelectItem value="cn">北京时间 (UTC+8)</SelectItem>
97
+ <SelectItem value="jp">东京时间 (UTC+9)</SelectItem>
98
+ </SelectGroup>
99
+ <SelectSeparator />
100
+ <SelectGroup>
101
+ <SelectLabel>欧洲</SelectLabel>
102
+ <SelectItem value="uk">伦敦时间 (UTC+0)</SelectItem>
103
+ </SelectGroup>
104
+ </SelectContent>
105
+ </Select>
106
+
107
+ // 受控
108
+ const [v, setV] = React.useState('bj');
109
+ <Select value={v} onValueChange={setV}>...</Select>
110
+ ```