@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,54 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import {
3
+ Drawer,
4
+ DrawerTrigger,
5
+ DrawerContent,
6
+ DrawerHeader,
7
+ DrawerTitle,
8
+ DrawerDescription,
9
+ DrawerFooter,
10
+ DrawerClose,
11
+ } from './drawer';
12
+ import { Button } from '@/components/button/button';
13
+
14
+ const meta: Meta<typeof DrawerContent> = {
15
+ title: '反馈与浮层 · Feedback/Drawer',
16
+ component: DrawerContent,
17
+ tags: ['autodocs'],
18
+ parameters: {
19
+ docs: {
20
+ description: {
21
+ component:
22
+ '抽屉(移动优先) — 从屏幕边缘滑入的浮层容器,常用于移动端表单 / 详情 / 导航等中等复杂度的二级界面。基于 [Vaul](https://vaul.emilkowal.ski/) 实现,提供原生手势 + 拖拽关闭 + 顶部把手;桌面端可视为 `Sheet` 的移动友好版本(能力对齐 antd Drawer)。视觉走 OpenTrek semantic tokens,所有样式来自 `@teamix-evo/design`,无 mock。',
23
+ },
24
+ },
25
+ },
26
+ };
27
+
28
+ export default meta;
29
+ type Story = StoryObj<typeof DrawerContent>;
30
+
31
+ export const Playground: Story = {
32
+ render: () => (
33
+ <Drawer>
34
+ <DrawerTrigger asChild>
35
+ <Button variant="outline">打开 Drawer</Button>
36
+ </DrawerTrigger>
37
+ <DrawerContent>
38
+ <DrawerHeader>
39
+ <DrawerTitle>发起讨论</DrawerTitle>
40
+ <DrawerDescription>讨论将通知到该项目所有成员。</DrawerDescription>
41
+ </DrawerHeader>
42
+ <div className="px-4 pb-4 text-sm text-muted-foreground">
43
+ 表单字段放在这里... 试试向下拖拽抽屉关闭。
44
+ </div>
45
+ <DrawerFooter>
46
+ <Button>提交</Button>
47
+ <DrawerClose asChild>
48
+ <Button variant="outline">取消</Button>
49
+ </DrawerClose>
50
+ </DrawerFooter>
51
+ </DrawerContent>
52
+ </Drawer>
53
+ ),
54
+ };
@@ -0,0 +1,114 @@
1
+ import * as React from 'react';
2
+ import { Drawer as DrawerPrimitive } from 'vaul';
3
+
4
+ import { cn } from '@/utils/cn';
5
+
6
+ const Drawer = ({
7
+ shouldScaleBackground = true,
8
+ ...props
9
+ }: React.ComponentProps<typeof DrawerPrimitive.Root>) => (
10
+ <DrawerPrimitive.Root
11
+ shouldScaleBackground={shouldScaleBackground}
12
+ {...props}
13
+ />
14
+ );
15
+ Drawer.displayName = 'Drawer';
16
+
17
+ const DrawerTrigger = DrawerPrimitive.Trigger;
18
+ const DrawerPortal = DrawerPrimitive.Portal;
19
+ const DrawerClose = DrawerPrimitive.Close;
20
+
21
+ const DrawerOverlay = React.forwardRef<
22
+ React.ElementRef<typeof DrawerPrimitive.Overlay>,
23
+ React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay>
24
+ >(({ className, ...props }, ref) => (
25
+ <DrawerPrimitive.Overlay
26
+ ref={ref}
27
+ className={cn('fixed inset-0 z-50 bg-black/80', className)}
28
+ {...props}
29
+ />
30
+ ));
31
+ DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName;
32
+
33
+ export interface DrawerContentProps
34
+ extends React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content> {}
35
+
36
+ const DrawerContent = React.forwardRef<
37
+ React.ElementRef<typeof DrawerPrimitive.Content>,
38
+ DrawerContentProps
39
+ >(({ className, children, ...props }, ref) => (
40
+ <DrawerPortal>
41
+ <DrawerOverlay />
42
+ <DrawerPrimitive.Content
43
+ ref={ref}
44
+ className={cn(
45
+ 'fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background',
46
+ className,
47
+ )}
48
+ {...props}
49
+ >
50
+ <div className="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
51
+ {children}
52
+ </DrawerPrimitive.Content>
53
+ </DrawerPortal>
54
+ ));
55
+ DrawerContent.displayName = 'DrawerContent';
56
+
57
+ const DrawerHeader = ({
58
+ className,
59
+ ...props
60
+ }: React.HTMLAttributes<HTMLDivElement>) => (
61
+ <div
62
+ className={cn('grid gap-1.5 p-4 text-center sm:text-left', className)}
63
+ {...props}
64
+ />
65
+ );
66
+ DrawerHeader.displayName = 'DrawerHeader';
67
+
68
+ const DrawerFooter = ({
69
+ className,
70
+ ...props
71
+ }: React.HTMLAttributes<HTMLDivElement>) => (
72
+ <div className={cn('mt-auto flex flex-col gap-2 p-4', className)} {...props} />
73
+ );
74
+ DrawerFooter.displayName = 'DrawerFooter';
75
+
76
+ const DrawerTitle = React.forwardRef<
77
+ React.ElementRef<typeof DrawerPrimitive.Title>,
78
+ React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title>
79
+ >(({ className, ...props }, ref) => (
80
+ <DrawerPrimitive.Title
81
+ ref={ref}
82
+ className={cn(
83
+ 'text-lg font-semibold leading-none tracking-tight',
84
+ className,
85
+ )}
86
+ {...props}
87
+ />
88
+ ));
89
+ DrawerTitle.displayName = DrawerPrimitive.Title.displayName;
90
+
91
+ const DrawerDescription = React.forwardRef<
92
+ React.ElementRef<typeof DrawerPrimitive.Description>,
93
+ React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description>
94
+ >(({ className, ...props }, ref) => (
95
+ <DrawerPrimitive.Description
96
+ ref={ref}
97
+ className={cn('text-sm text-muted-foreground', className)}
98
+ {...props}
99
+ />
100
+ ));
101
+ DrawerDescription.displayName = DrawerPrimitive.Description.displayName;
102
+
103
+ export {
104
+ Drawer,
105
+ DrawerPortal,
106
+ DrawerOverlay,
107
+ DrawerTrigger,
108
+ DrawerClose,
109
+ DrawerContent,
110
+ DrawerHeader,
111
+ DrawerFooter,
112
+ DrawerTitle,
113
+ DrawerDescription,
114
+ };
@@ -0,0 +1,103 @@
1
+ ---
2
+ id: dropdown-menu
3
+ name: DropdownMenu
4
+ type: component
5
+ category: navigation
6
+ since: 0.1.0
7
+ package: "@teamix-evo/ui"
8
+ ---
9
+
10
+ # DropdownMenu
11
+
12
+ 下拉菜单 — Radix DropdownMenu 完整实现:Item / CheckboxItem / RadioItem / Label / Separator / Shortcut / Sub(子菜单)。
13
+ 对应 antd `Dropdown`,但本组件是**完整 menu primitives**(antd Dropdown 主要走 children 自由排版,本组件提供语义化每一种 item)。
14
+
15
+ ## When to use
16
+
17
+ - 行内动作菜单(列表项的"更多")
18
+ - 用户头像下拉(账户 / 设置 / 退出)
19
+ - 工具栏溢出菜单
20
+ - 多选 / 单选下拉(用 CheckboxItem / RadioItem)
21
+
22
+ ## When NOT to use
23
+
24
+ - 页面顶部主导航 → `NavigationMenu`
25
+ - 工具栏多个一级菜单 → `Menubar`
26
+ - 右键菜单 → `ContextMenu`
27
+ - 选择类表单 → `Select` / `Combobox`
28
+
29
+ ## Props
30
+
31
+ > 以下表格由 `pnpm --filter @teamix-evo/ui gen:meta` 自动生成。下表是 `DropdownMenuContent` 的 props;`DropdownMenu`(Root)透传 Radix `open / defaultOpen / onOpenChange / modal`。
32
+
33
+ <!-- auto:props:begin -->
34
+ _(组件无 `<Name>Props` interface — props 详见 [`dropdown-menu.tsx`](./dropdown-menu.tsx))_
35
+ <!-- auto:props:end -->
36
+
37
+ ## 依赖
38
+
39
+ > 以下表格由 `pnpm --filter @teamix-evo/ui gen:meta` 自动生成,数据源是 [`manifest.json`](../../../manifest.json)。**手工编辑 marker 之间的内容会在下次生成时被覆盖**。
40
+
41
+ <!-- auto:deps:begin -->
42
+ ### 同库依赖
43
+
44
+ > `teamix-evo ui add dropdown-menu` 时,以下 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 @radix-ui/react-dropdown-menu@^2.1.0 lucide-react@^0.460.0
56
+ ```
57
+ <!-- auto:deps:end -->
58
+
59
+ > 完整子组件:`DropdownMenu` / `DropdownMenuTrigger` / `DropdownMenuContent` / `DropdownMenuItem`(支持 `inset`)/ `DropdownMenuCheckboxItem` / `DropdownMenuRadioItem` / `DropdownMenuLabel` / `DropdownMenuSeparator` / `DropdownMenuShortcut` / `DropdownMenuGroup` / `DropdownMenuRadioGroup` / `DropdownMenuSub`(嵌套)/ `DropdownMenuSubTrigger` / `DropdownMenuSubContent`。
60
+
61
+ ## AI 生成纪律
62
+
63
+ - **Trigger 用 `asChild`**:wrap 已有 Button,而非新增 button 层
64
+ - **每个 Item 必有 `onClick` 或 `onSelect`**:无操作的 Item 是反模式 — 移到 Description 区
65
+ - **危险动作单独配色**:`<DropdownMenuItem className="text-destructive focus:text-destructive">删除</DropdownMenuItem>`,**不要**塞 destructive Button 进 Item
66
+ - **Shortcut 用 `<DropdownMenuShortcut>`**:右对齐快捷键文案,**不要**手写 `<span className="ml-auto">`
67
+ - **inset 对齐有 icon 的 item**:同一菜单内有 icon item 也有无 icon item 时,无 icon 的加 `inset` 让文字对齐
68
+
69
+ ## Examples
70
+
71
+ ```tsx
72
+ import {
73
+ DropdownMenu, DropdownMenuTrigger, DropdownMenuContent,
74
+ DropdownMenuItem, DropdownMenuCheckboxItem, DropdownMenuRadioItem,
75
+ DropdownMenuRadioGroup, DropdownMenuLabel, DropdownMenuSeparator,
76
+ DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubTrigger,
77
+ DropdownMenuSubContent, DropdownMenuPortal,
78
+ } from '@/components/ui/dropdown-menu';
79
+ import { Button } from '@/components/ui/button';
80
+ import { User, Settings, LogOut } from 'lucide-react';
81
+
82
+ <DropdownMenu>
83
+ <DropdownMenuTrigger asChild>
84
+ <Button variant="outline">菜单</Button>
85
+ </DropdownMenuTrigger>
86
+ <DropdownMenuContent className="w-56">
87
+ <DropdownMenuLabel>我的账户</DropdownMenuLabel>
88
+ <DropdownMenuSeparator />
89
+ <DropdownMenuItem>
90
+ <User /> 个人资料
91
+ <DropdownMenuShortcut>⌘P</DropdownMenuShortcut>
92
+ </DropdownMenuItem>
93
+ <DropdownMenuItem>
94
+ <Settings /> 设置
95
+ <DropdownMenuShortcut>⌘,</DropdownMenuShortcut>
96
+ </DropdownMenuItem>
97
+ <DropdownMenuSeparator />
98
+ <DropdownMenuItem className="text-destructive focus:text-destructive">
99
+ <LogOut /> 退出
100
+ </DropdownMenuItem>
101
+ </DropdownMenuContent>
102
+ </DropdownMenu>
103
+ ```
@@ -0,0 +1,112 @@
1
+ import * as React from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+ import { LogOut, Settings, User, Cloud, Github } from 'lucide-react';
4
+ import {
5
+ DropdownMenu,
6
+ DropdownMenuTrigger,
7
+ DropdownMenuContent,
8
+ DropdownMenuItem,
9
+ DropdownMenuCheckboxItem,
10
+ DropdownMenuRadioGroup,
11
+ DropdownMenuRadioItem,
12
+ DropdownMenuLabel,
13
+ DropdownMenuSeparator,
14
+ DropdownMenuShortcut,
15
+ } from './dropdown-menu';
16
+ import { Button } from '@/components/button/button';
17
+
18
+ const meta: Meta<typeof DropdownMenuContent> = {
19
+ title: '导航 · Navigation/DropdownMenu',
20
+ component: DropdownMenuContent,
21
+ tags: ['autodocs'],
22
+ };
23
+
24
+ export default meta;
25
+ type Story = StoryObj<typeof DropdownMenuContent>;
26
+
27
+ export const Default: Story = {
28
+ render: () => (
29
+ <DropdownMenu>
30
+ <DropdownMenuTrigger asChild>
31
+ <Button variant="outline">打开菜单</Button>
32
+ </DropdownMenuTrigger>
33
+ <DropdownMenuContent className="w-56">
34
+ <DropdownMenuLabel>我的账户</DropdownMenuLabel>
35
+ <DropdownMenuSeparator />
36
+ <DropdownMenuItem>
37
+ <User /> 个人资料
38
+ <DropdownMenuShortcut>⌘P</DropdownMenuShortcut>
39
+ </DropdownMenuItem>
40
+ <DropdownMenuItem>
41
+ <Settings /> 设置
42
+ <DropdownMenuShortcut>⌘,</DropdownMenuShortcut>
43
+ </DropdownMenuItem>
44
+ <DropdownMenuItem>
45
+ <Cloud /> API
46
+ </DropdownMenuItem>
47
+ <DropdownMenuItem>
48
+ <Github /> GitHub
49
+ </DropdownMenuItem>
50
+ <DropdownMenuSeparator />
51
+ <DropdownMenuItem className="text-destructive focus:text-destructive">
52
+ <LogOut /> 退出
53
+ <DropdownMenuShortcut>⇧⌘Q</DropdownMenuShortcut>
54
+ </DropdownMenuItem>
55
+ </DropdownMenuContent>
56
+ </DropdownMenu>
57
+ ),
58
+ };
59
+
60
+ export const CheckboxItems: Story = {
61
+ parameters: { controls: { disable: true } },
62
+ render: () => {
63
+ const [bookmarks, setBookmarks] = React.useState(true);
64
+ const [fullURLs, setFullURLs] = React.useState(false);
65
+ return (
66
+ <DropdownMenu>
67
+ <DropdownMenuTrigger asChild>
68
+ <Button variant="outline">视图设置</Button>
69
+ </DropdownMenuTrigger>
70
+ <DropdownMenuContent className="w-56">
71
+ <DropdownMenuLabel>外观</DropdownMenuLabel>
72
+ <DropdownMenuSeparator />
73
+ <DropdownMenuCheckboxItem
74
+ checked={bookmarks}
75
+ onCheckedChange={setBookmarks}
76
+ >
77
+ 显示书签栏
78
+ </DropdownMenuCheckboxItem>
79
+ <DropdownMenuCheckboxItem
80
+ checked={fullURLs}
81
+ onCheckedChange={setFullURLs}
82
+ >
83
+ 显示完整 URL
84
+ </DropdownMenuCheckboxItem>
85
+ </DropdownMenuContent>
86
+ </DropdownMenu>
87
+ );
88
+ },
89
+ };
90
+
91
+ export const RadioItems: Story = {
92
+ parameters: { controls: { disable: true } },
93
+ render: () => {
94
+ const [pos, setPos] = React.useState('bottom');
95
+ return (
96
+ <DropdownMenu>
97
+ <DropdownMenuTrigger asChild>
98
+ <Button variant="outline">面板位置</Button>
99
+ </DropdownMenuTrigger>
100
+ <DropdownMenuContent className="w-56">
101
+ <DropdownMenuLabel>面板位置</DropdownMenuLabel>
102
+ <DropdownMenuSeparator />
103
+ <DropdownMenuRadioGroup value={pos} onValueChange={setPos}>
104
+ <DropdownMenuRadioItem value="top">顶部</DropdownMenuRadioItem>
105
+ <DropdownMenuRadioItem value="bottom">底部</DropdownMenuRadioItem>
106
+ <DropdownMenuRadioItem value="right">右侧</DropdownMenuRadioItem>
107
+ </DropdownMenuRadioGroup>
108
+ </DropdownMenuContent>
109
+ </DropdownMenu>
110
+ );
111
+ },
112
+ };
@@ -0,0 +1,195 @@
1
+ import * as React from 'react';
2
+ import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
3
+ import { Check, ChevronRight, Circle } from 'lucide-react';
4
+
5
+ import { cn } from '@/utils/cn';
6
+
7
+ const DropdownMenu = DropdownMenuPrimitive.Root;
8
+ const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
9
+ const DropdownMenuGroup = DropdownMenuPrimitive.Group;
10
+ const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
11
+ const DropdownMenuSub = DropdownMenuPrimitive.Sub;
12
+ const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
13
+
14
+ const DropdownMenuSubTrigger = React.forwardRef<
15
+ React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
16
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
17
+ inset?: boolean;
18
+ }
19
+ >(({ className, inset, children, ...props }, ref) => (
20
+ <DropdownMenuPrimitive.SubTrigger
21
+ ref={ref}
22
+ className={cn(
23
+ 'flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
24
+ inset && 'pl-8',
25
+ className,
26
+ )}
27
+ {...props}
28
+ >
29
+ {children}
30
+ <ChevronRight className="ml-auto" />
31
+ </DropdownMenuPrimitive.SubTrigger>
32
+ ));
33
+ DropdownMenuSubTrigger.displayName =
34
+ DropdownMenuPrimitive.SubTrigger.displayName;
35
+
36
+ const DropdownMenuSubContent = React.forwardRef<
37
+ React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
38
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
39
+ >(({ className, ...props }, ref) => (
40
+ <DropdownMenuPrimitive.SubContent
41
+ ref={ref}
42
+ className={cn(
43
+ 'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
44
+ className,
45
+ )}
46
+ {...props}
47
+ />
48
+ ));
49
+ DropdownMenuSubContent.displayName =
50
+ DropdownMenuPrimitive.SubContent.displayName;
51
+
52
+ const DropdownMenuContent = React.forwardRef<
53
+ React.ElementRef<typeof DropdownMenuPrimitive.Content>,
54
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
55
+ >(({ className, sideOffset = 4, ...props }, ref) => (
56
+ <DropdownMenuPrimitive.Portal>
57
+ <DropdownMenuPrimitive.Content
58
+ ref={ref}
59
+ sideOffset={sideOffset}
60
+ className={cn(
61
+ 'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
62
+ className,
63
+ )}
64
+ {...props}
65
+ />
66
+ </DropdownMenuPrimitive.Portal>
67
+ ));
68
+ DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
69
+
70
+ const DropdownMenuItem = React.forwardRef<
71
+ React.ElementRef<typeof DropdownMenuPrimitive.Item>,
72
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
73
+ inset?: boolean;
74
+ }
75
+ >(({ className, inset, ...props }, ref) => (
76
+ <DropdownMenuPrimitive.Item
77
+ ref={ref}
78
+ className={cn(
79
+ 'relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
80
+ inset && 'pl-8',
81
+ className,
82
+ )}
83
+ {...props}
84
+ />
85
+ ));
86
+ DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
87
+
88
+ const DropdownMenuCheckboxItem = React.forwardRef<
89
+ React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
90
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
91
+ >(({ className, children, checked, ...props }, ref) => (
92
+ <DropdownMenuPrimitive.CheckboxItem
93
+ ref={ref}
94
+ className={cn(
95
+ 'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
96
+ className,
97
+ )}
98
+ checked={checked}
99
+ {...props}
100
+ >
101
+ <span className="absolute left-2 flex size-3.5 items-center justify-center">
102
+ <DropdownMenuPrimitive.ItemIndicator>
103
+ <Check className="size-4" />
104
+ </DropdownMenuPrimitive.ItemIndicator>
105
+ </span>
106
+ {children}
107
+ </DropdownMenuPrimitive.CheckboxItem>
108
+ ));
109
+ DropdownMenuCheckboxItem.displayName =
110
+ DropdownMenuPrimitive.CheckboxItem.displayName;
111
+
112
+ const DropdownMenuRadioItem = React.forwardRef<
113
+ React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
114
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
115
+ >(({ className, children, ...props }, ref) => (
116
+ <DropdownMenuPrimitive.RadioItem
117
+ ref={ref}
118
+ className={cn(
119
+ 'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
120
+ className,
121
+ )}
122
+ {...props}
123
+ >
124
+ <span className="absolute left-2 flex size-3.5 items-center justify-center">
125
+ <DropdownMenuPrimitive.ItemIndicator>
126
+ <Circle className="size-2 fill-current" />
127
+ </DropdownMenuPrimitive.ItemIndicator>
128
+ </span>
129
+ {children}
130
+ </DropdownMenuPrimitive.RadioItem>
131
+ ));
132
+ DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
133
+
134
+ const DropdownMenuLabel = React.forwardRef<
135
+ React.ElementRef<typeof DropdownMenuPrimitive.Label>,
136
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
137
+ inset?: boolean;
138
+ }
139
+ >(({ className, inset, ...props }, ref) => (
140
+ <DropdownMenuPrimitive.Label
141
+ ref={ref}
142
+ className={cn(
143
+ 'px-2 py-1.5 text-sm font-semibold',
144
+ inset && 'pl-8',
145
+ className,
146
+ )}
147
+ {...props}
148
+ />
149
+ ));
150
+ DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
151
+
152
+ const DropdownMenuSeparator = React.forwardRef<
153
+ React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
154
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
155
+ >(({ className, ...props }, ref) => (
156
+ <DropdownMenuPrimitive.Separator
157
+ ref={ref}
158
+ className={cn('-mx-1 my-1 h-px bg-muted', className)}
159
+ {...props}
160
+ />
161
+ ));
162
+ DropdownMenuSeparator.displayName =
163
+ DropdownMenuPrimitive.Separator.displayName;
164
+
165
+ const DropdownMenuShortcut = ({
166
+ className,
167
+ ...props
168
+ }: React.HTMLAttributes<HTMLSpanElement>) => (
169
+ <span
170
+ className={cn(
171
+ 'ml-auto text-xs tracking-widest text-muted-foreground',
172
+ className,
173
+ )}
174
+ {...props}
175
+ />
176
+ );
177
+ DropdownMenuShortcut.displayName = 'DropdownMenuShortcut';
178
+
179
+ export {
180
+ DropdownMenu,
181
+ DropdownMenuTrigger,
182
+ DropdownMenuContent,
183
+ DropdownMenuItem,
184
+ DropdownMenuCheckboxItem,
185
+ DropdownMenuRadioItem,
186
+ DropdownMenuLabel,
187
+ DropdownMenuSeparator,
188
+ DropdownMenuShortcut,
189
+ DropdownMenuGroup,
190
+ DropdownMenuPortal,
191
+ DropdownMenuSub,
192
+ DropdownMenuSubContent,
193
+ DropdownMenuSubTrigger,
194
+ DropdownMenuRadioGroup,
195
+ };
@@ -0,0 +1,81 @@
1
+ ---
2
+ id: empty
3
+ name: Empty
4
+ type: component
5
+ category: feedback
6
+ since: 0.1.0
7
+ package: "@teamix-evo/ui"
8
+ ---
9
+
10
+ # Empty
11
+
12
+ 空状态 — antd 独有补足。**列表 / 表格 / 搜索结果为空**时的占位提示,提供引导动作鼓励用户创建首条数据。
13
+
14
+ ## When to use
15
+
16
+ - 列表 / 表格无数据
17
+ - 搜索结果为空
18
+ - 收件箱 / 通知 / 任务首次访问无内容
19
+
20
+ ## When NOT to use
21
+
22
+ - 加载中 → `Skeleton`
23
+ - 错误 / 失败 → `Result` 或 `Alert`
24
+ - 表单字段空 → 直接 placeholder
25
+
26
+ <!-- auto:props:begin -->
27
+ | 名称 | 类型 | 默认值 | 必填 | 说明 |
28
+ | --- | --- | --- | --- | --- |
29
+ | `image` | `React.ReactNode` | – | – | 自定义图标(覆盖默认 Inbox)。 |
30
+ | `description` | `React.ReactNode` | `"暂无数据"` | – | 描述文本。 |
31
+ | `extra` | `React.ReactNode` | – | – | 操作区(放 Button 引导用户创建首条数据)。 |
32
+ | `size` | `'sm' \| 'default'` | `"default"` | – | 尺寸 — `sm` 用于卡片内 / 表格空态;`default` 用于整区。 |
33
+ <!-- auto:props:end -->
34
+
35
+ <!-- auto:deps:begin -->
36
+ ### 同库依赖
37
+
38
+ > `teamix-evo ui add empty` 时,以下 entry 会被自动连带安装(无需手动 add)。
39
+
40
+ | Entry | 类型 | 描述 |
41
+ | --- | --- | --- |
42
+ | `cn` | util | Tailwind className 合并工具(clsx + tailwind-merge) |
43
+
44
+ ### npm 依赖
45
+
46
+ > 业务侧需要先 `pnpm add` / `npm install` 这些包。CLI 在 `ui add` 完成后会列出此提示。
47
+
48
+ ```bash
49
+ pnpm add lucide-react@^0.460.0
50
+ ```
51
+ <!-- auto:deps:end -->
52
+
53
+ ## AI 生成纪律
54
+
55
+ - **`description` 引导而非陈述**:"暂无数据" 是最朴素的;有创建动作时改成"还没有项目,点击下方新建"
56
+ - **`extra` 必有时配主操作**:Empty 不只是装饰,要给"接下来做什么"
57
+ - **`size="sm"` 在卡片 / 表格内**:不要让 Empty 撑爆容器
58
+ - **不嵌套 Empty**:嵌套占位是反模式
59
+
60
+ ## Examples
61
+
62
+ ```tsx
63
+ import { Empty } from '@/components/ui/empty';
64
+ import { Button } from '@/components/ui/button';
65
+ import { Plus } from 'lucide-react';
66
+
67
+ // 基础
68
+ <Empty />
69
+
70
+ // 带描述 + 操作
71
+ <Empty
72
+ description="还没有项目"
73
+ extra={<Button icon={<Plus />}>创建项目</Button>}
74
+ />
75
+
76
+ // 表格内空态
77
+ <Empty size="sm" description="无匹配的搜索结果" />
78
+
79
+ // 自定义图标
80
+ <Empty image={<Mailbox className="size-16" />} description="收件箱空空" />
81
+ ```