@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,112 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { Heart, MessageSquare, MoreHorizontal, Share } from 'lucide-react';
3
+ import {
4
+ Card,
5
+ CardHeader,
6
+ CardTitle,
7
+ CardDescription,
8
+ CardContent,
9
+ CardFooter,
10
+ CardCover,
11
+ CardActions,
12
+ CardMeta,
13
+ } from './card';
14
+ import { Button } from '@/components/button/button';
15
+ import {
16
+ Avatar,
17
+ AvatarImage,
18
+ AvatarFallback,
19
+ } from '@/components/avatar/avatar';
20
+
21
+ const meta: Meta<typeof Card> = {
22
+ title: '布局与容器 · Layout/Card',
23
+ component: Card,
24
+ tags: ['autodocs'],
25
+ argTypes: {
26
+ hoverable: { control: 'boolean' },
27
+ loading: { control: 'boolean' },
28
+ },
29
+ args: { hoverable: false, loading: false },
30
+ };
31
+
32
+ export default meta;
33
+ type Story = StoryObj<typeof Card>;
34
+
35
+ export const Basic: Story = {
36
+ render: (args) => (
37
+ <Card {...args} className="w-80">
38
+ <CardHeader
39
+ extra={
40
+ <Button size="icon" variant="ghost" aria-label="更多">
41
+ <MoreHorizontal />
42
+ </Button>
43
+ }
44
+ >
45
+ <CardTitle>项目名称</CardTitle>
46
+ <CardDescription>项目简介一行...</CardDescription>
47
+ </CardHeader>
48
+ <CardContent>
49
+ <p className="text-sm text-muted-foreground">主体内容区域。</p>
50
+ </CardContent>
51
+ <CardFooter className="justify-end gap-2">
52
+ <Button variant="outline">取消</Button>
53
+ <Button>确认</Button>
54
+ </CardFooter>
55
+ </Card>
56
+ ),
57
+ };
58
+
59
+ export const Hoverable: Story = {
60
+ args: { hoverable: true },
61
+ render: (args) => (
62
+ <Card {...args} className="w-72">
63
+ <CardContent className="pt-6">
64
+ <p className="text-sm">悬浮我看阴影变化</p>
65
+ </CardContent>
66
+ </Card>
67
+ ),
68
+ };
69
+
70
+ export const Loading: Story = {
71
+ args: { loading: true },
72
+ render: (args) => <Card {...args} className="w-80" />,
73
+ };
74
+
75
+ export const Media: Story = {
76
+ parameters: { controls: { disable: true } },
77
+ render: () => (
78
+ <Card hoverable className="w-80">
79
+ <CardCover>
80
+ <img
81
+ src="https://placehold.co/600x320/e5e7eb/6b7280?text=Cover"
82
+ alt="cover"
83
+ />
84
+ </CardCover>
85
+ <CardContent className="pt-4">
86
+ <CardMeta
87
+ avatar={
88
+ <Avatar>
89
+ <AvatarImage src="https://github.com/shadcn.png" />
90
+ <AvatarFallback>U</AvatarFallback>
91
+ </Avatar>
92
+ }
93
+ title="发布者"
94
+ description="2 小时前 · 北京"
95
+ />
96
+ </CardContent>
97
+ <CardActions
98
+ items={[
99
+ <span key="like" className="inline-flex items-center gap-1">
100
+ <Heart className="size-4" /> 12
101
+ </span>,
102
+ <span key="share" className="inline-flex items-center gap-1">
103
+ <Share className="size-4" /> 4
104
+ </span>,
105
+ <span key="comment" className="inline-flex items-center gap-1">
106
+ <MessageSquare className="size-4" /> 8
107
+ </span>,
108
+ ]}
109
+ />
110
+ </Card>
111
+ ),
112
+ };
@@ -0,0 +1,222 @@
1
+ import * as React from 'react';
2
+
3
+ import { cn } from '@/utils/cn';
4
+ import { Skeleton } from '@/components/skeleton/skeleton';
5
+
6
+ export interface CardProps extends React.HTMLAttributes<HTMLDivElement> {
7
+ /**
8
+ * 鼠标 hover 时的视觉反馈(antd `hoverable` 并集) — 阴影升级 + 微交互。
9
+ * @default false
10
+ */
11
+ hoverable?: boolean;
12
+ /**
13
+ * 加载态(antd `loading` 并集) — 整卡渲染为骨架屏占位,不展示 children。
14
+ * @default false
15
+ */
16
+ loading?: boolean;
17
+ }
18
+
19
+ const Card = React.forwardRef<HTMLDivElement, CardProps>(
20
+ ({ className, hoverable = false, loading = false, children, ...props }, ref) => (
21
+ <div
22
+ ref={ref}
23
+ className={cn(
24
+ 'rounded-lg border bg-card text-card-foreground shadow-sm transition-shadow',
25
+ hoverable && 'cursor-pointer hover:shadow-md',
26
+ className,
27
+ )}
28
+ {...props}
29
+ >
30
+ {loading ? (
31
+ <div className="space-y-3 p-6">
32
+ <Skeleton shape="line" className="w-2/5" />
33
+ <Skeleton shape="line" />
34
+ <Skeleton shape="line" className="w-4/5" />
35
+ </div>
36
+ ) : (
37
+ children
38
+ )}
39
+ </div>
40
+ ),
41
+ );
42
+ Card.displayName = 'Card';
43
+
44
+ export interface CardHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
45
+ /**
46
+ * 右侧附加内容(antd `extra` 并集) — 用 flex 撑开,与 title/description 同行。
47
+ * 已等价 shadcn 2026-04 新增的 `CardAction` 子组件,推荐传 `<CardAction>...</CardAction>` 节点。
48
+ */
49
+ extra?: React.ReactNode;
50
+ }
51
+
52
+ const CardHeader = React.forwardRef<HTMLDivElement, CardHeaderProps>(
53
+ ({ className, extra, children, ...props }, ref) => (
54
+ <div
55
+ ref={ref}
56
+ className={cn('flex flex-col space-y-1.5 p-6', className)}
57
+ {...props}
58
+ >
59
+ {extra ? (
60
+ <div className="flex items-start justify-between gap-3">
61
+ <div className="flex flex-1 flex-col space-y-1.5">{children}</div>
62
+ <div className="shrink-0">{extra}</div>
63
+ </div>
64
+ ) : (
65
+ children
66
+ )}
67
+ </div>
68
+ ),
69
+ );
70
+ CardHeader.displayName = 'CardHeader';
71
+
72
+ const CardAction = React.forwardRef<
73
+ HTMLDivElement,
74
+ React.HTMLAttributes<HTMLDivElement>
75
+ >(({ className, ...props }, ref) => (
76
+ <div
77
+ ref={ref}
78
+ className={cn(
79
+ 'col-start-2 row-span-2 row-start-1 self-start justify-self-end',
80
+ className,
81
+ )}
82
+ {...props}
83
+ />
84
+ ));
85
+ CardAction.displayName = 'CardAction';
86
+
87
+ const CardTitle = React.forwardRef<
88
+ HTMLHeadingElement,
89
+ React.HTMLAttributes<HTMLHeadingElement>
90
+ >(({ className, ...props }, ref) => (
91
+ <h3
92
+ ref={ref}
93
+ className={cn(
94
+ 'text-base font-semibold leading-none tracking-tight',
95
+ className,
96
+ )}
97
+ {...props}
98
+ />
99
+ ));
100
+ CardTitle.displayName = 'CardTitle';
101
+
102
+ const CardDescription = React.forwardRef<
103
+ HTMLParagraphElement,
104
+ React.HTMLAttributes<HTMLParagraphElement>
105
+ >(({ className, ...props }, ref) => (
106
+ <p
107
+ ref={ref}
108
+ className={cn('text-sm text-muted-foreground', className)}
109
+ {...props}
110
+ />
111
+ ));
112
+ CardDescription.displayName = 'CardDescription';
113
+
114
+ const CardContent = React.forwardRef<
115
+ HTMLDivElement,
116
+ React.HTMLAttributes<HTMLDivElement>
117
+ >(({ className, ...props }, ref) => (
118
+ <div ref={ref} className={cn('p-6 pt-0', className)} {...props} />
119
+ ));
120
+ CardContent.displayName = 'CardContent';
121
+
122
+ const CardFooter = React.forwardRef<
123
+ HTMLDivElement,
124
+ React.HTMLAttributes<HTMLDivElement>
125
+ >(({ className, ...props }, ref) => (
126
+ <div
127
+ ref={ref}
128
+ className={cn('flex items-center p-6 pt-0', className)}
129
+ {...props}
130
+ />
131
+ ));
132
+ CardFooter.displayName = 'CardFooter';
133
+
134
+ // ─── antd 风格 sub-components(并集)──────────────────────────────────────
135
+
136
+ export interface CardCoverProps extends React.HTMLAttributes<HTMLDivElement> {}
137
+ const CardCover = React.forwardRef<HTMLDivElement, CardCoverProps>(
138
+ ({ className, children, ...props }, ref) => (
139
+ <div
140
+ ref={ref}
141
+ className={cn(
142
+ 'overflow-hidden rounded-t-lg border-b [&>img]:w-full [&>img]:object-cover',
143
+ className,
144
+ )}
145
+ {...props}
146
+ >
147
+ {children}
148
+ </div>
149
+ ),
150
+ );
151
+ CardCover.displayName = 'CardCover';
152
+
153
+ export interface CardActionsProps
154
+ extends React.HTMLAttributes<HTMLDivElement> {
155
+ /** 操作项数组(antd `actions` 并集) — 自动均分等宽,中间分隔线。 */
156
+ items: React.ReactNode[];
157
+ }
158
+ const CardActions = React.forwardRef<HTMLDivElement, CardActionsProps>(
159
+ ({ className, items, ...props }, ref) => (
160
+ <div
161
+ ref={ref}
162
+ className={cn(
163
+ 'flex items-stretch divide-x divide-border border-t',
164
+ className,
165
+ )}
166
+ {...props}
167
+ >
168
+ {items.map((item, i) => (
169
+ <div
170
+ key={i}
171
+ className="flex flex-1 items-center justify-center py-3 text-sm text-muted-foreground transition-colors hover:bg-accent"
172
+ >
173
+ {item}
174
+ </div>
175
+ ))}
176
+ </div>
177
+ ),
178
+ );
179
+ CardActions.displayName = 'CardActions';
180
+
181
+ export interface CardMetaProps
182
+ extends Omit<React.HTMLAttributes<HTMLDivElement>, 'title'> {
183
+ /** 头像槽(antd `Card.Meta` `avatar` 并集)。 */
184
+ avatar?: React.ReactNode;
185
+ /** 标题(粗体)。 */
186
+ title?: React.ReactNode;
187
+ /** 描述。 */
188
+ description?: React.ReactNode;
189
+ }
190
+ const CardMeta = React.forwardRef<HTMLDivElement, CardMetaProps>(
191
+ ({ className, avatar, title, description, ...props }, ref) => (
192
+ <div
193
+ ref={ref}
194
+ className={cn('flex items-start gap-3', className)}
195
+ {...props}
196
+ >
197
+ {avatar ? <div className="shrink-0">{avatar}</div> : null}
198
+ <div className="flex min-w-0 flex-1 flex-col gap-1">
199
+ {title ? (
200
+ <div className="text-sm font-semibold leading-none">{title}</div>
201
+ ) : null}
202
+ {description ? (
203
+ <div className="text-sm text-muted-foreground">{description}</div>
204
+ ) : null}
205
+ </div>
206
+ </div>
207
+ ),
208
+ );
209
+ CardMeta.displayName = 'CardMeta';
210
+
211
+ export {
212
+ Card,
213
+ CardHeader,
214
+ CardTitle,
215
+ CardDescription,
216
+ CardContent,
217
+ CardFooter,
218
+ CardCover,
219
+ CardActions,
220
+ CardAction,
221
+ CardMeta,
222
+ };
@@ -0,0 +1,117 @@
1
+ ---
2
+ id: carousel
3
+ name: Carousel
4
+ type: component
5
+ category: data-display
6
+ since: 0.1.0
7
+ package: "@teamix-evo/ui"
8
+ ---
9
+
10
+ # Carousel
11
+
12
+ 走马灯 — 基于 [`embla-carousel-react`](https://www.embla-carousel.com/),性能与可访问性出色。
13
+ 对应 antd `Carousel` 的核心场景。
14
+
15
+ ## When to use
16
+
17
+ - 首屏 banner / 营销轮播
18
+ - 卡片横向滚动(配 `basis-1/3` 等让一屏多卡可见)
19
+ - 图片画廊
20
+ - 商品 / 服务的横向展示
21
+
22
+ ## When NOT to use
23
+
24
+ - 密集 tab 切换 → `Tabs`
25
+ - 数据表格 → `Table` / `DataTable`
26
+ - 全屏画廊(含缩放 / 全屏)→ 专用图库组件
27
+
28
+ ## Props
29
+
30
+ > 以下表格由 `pnpm --filter @teamix-evo/ui gen:meta` 自动生成。
31
+
32
+ <!-- auto:props:begin -->
33
+ | 名称 | 类型 | 默认值 | 必填 | 说明 |
34
+ | --- | --- | --- | --- | --- |
35
+ | `opts` | `CarouselOptions` | – | – | embla 选项透传(`loop / align / startIndex / dragFree` 等)。 |
36
+ | `plugins` | `CarouselPlugin` | – | – | embla 插件数组(`autoplay`、`fade` 等)。 |
37
+ | `orientation` | `'horizontal' \| 'vertical'` | `"horizontal"` | – | 滚动方向。 |
38
+ | `setApi` | `(api: CarouselApi) => void` | – | – | 提供 embla API 的回调,用于业务侧自行控制(scrollNext / scrollTo 等)。 |
39
+ <!-- auto:props:end -->
40
+
41
+ ## 依赖
42
+
43
+ > 以下表格由 `pnpm --filter @teamix-evo/ui gen:meta` 自动生成,数据源是 [`manifest.json`](../../../manifest.json)。**手工编辑 marker 之间的内容会在下次生成时被覆盖**。
44
+
45
+ <!-- auto:deps:begin -->
46
+ ### 同库依赖
47
+
48
+ > `teamix-evo ui add carousel` 时,以下 entry 会被自动连带安装(无需手动 add)。
49
+
50
+ | Entry | 类型 | 描述 |
51
+ | --- | --- | --- |
52
+ | `cn` | util | Tailwind className 合并工具(clsx + tailwind-merge) |
53
+ | `button` | component | 通用按钮 — shadcn 实现 + antd 功能扩展(loading / icon / shape / block / dashed variant) |
54
+
55
+ ### npm 依赖
56
+
57
+ > 业务侧需要先 `pnpm add` / `npm install` 这些包。CLI 在 `ui add` 完成后会列出此提示。
58
+
59
+ ```bash
60
+ pnpm add embla-carousel-react@^8.0.0 lucide-react@^0.460.0
61
+ ```
62
+ <!-- auto:deps:end -->
63
+
64
+ > 子组件:`Carousel`(主容器,内置 embla)/ `CarouselContent`(可滚动 wrapper)/ `CarouselItem`(单页)/ `CarouselPrevious` + `CarouselNext`(导航按钮)+ `useCarousel()` 内部 hook。
65
+
66
+ ## AI 生成纪律
67
+
68
+ - **`opts.loop` 默认 false**:必须显式开启 loop 才能循环
69
+ - **配 `setApi` 拿 embla API**:业务侧需要 `scrollTo / on('select', ...)` 时,通过 `setApi` 回调收口
70
+ - **`basis-*` 控制每屏几张**:`<CarouselItem className="basis-1/3">` 一屏 3 张
71
+ - **`orientation="vertical"` 父容器需固定高度**:embla 不会自动撑开
72
+ - **autoplay 用 `embla-carousel-autoplay` 插件**:`plugins={[Autoplay({ delay: 4000 })]}`
73
+ - **触屏拖动自带**:不需要额外配置
74
+
75
+ ## Examples
76
+
77
+ ```tsx
78
+ import {
79
+ Carousel, CarouselContent, CarouselItem,
80
+ CarouselPrevious, CarouselNext,
81
+ } from '@/components/ui/carousel';
82
+
83
+ // 基础(单页)
84
+ <Carousel className="w-full max-w-md">
85
+ <CarouselContent>
86
+ {Array.from({ length: 5 }).map((_, i) => (
87
+ <CarouselItem key={i}>
88
+ <Card>{i + 1}</Card>
89
+ </CarouselItem>
90
+ ))}
91
+ </CarouselContent>
92
+ <CarouselPrevious />
93
+ <CarouselNext />
94
+ </Carousel>
95
+
96
+ // 一屏 3 张
97
+ <Carousel opts={{ align: 'start' }} className="w-full max-w-2xl">
98
+ <CarouselContent>
99
+ {items.map((it, i) => (
100
+ <CarouselItem key={i} className="md:basis-1/3">
101
+ <Card>{it.title}</Card>
102
+ </CarouselItem>
103
+ ))}
104
+ </CarouselContent>
105
+ <CarouselPrevious />
106
+ <CarouselNext />
107
+ </Carousel>
108
+
109
+ // 受控 / 拿 API
110
+ const [api, setApi] = React.useState<CarouselApi>();
111
+ React.useEffect(() => {
112
+ if (!api) return;
113
+ api.on('select', () => console.log('current:', api.selectedScrollSnap()));
114
+ }, [api]);
115
+
116
+ <Carousel setApi={setApi}>...</Carousel>
117
+ ```
@@ -0,0 +1,84 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import {
3
+ Carousel,
4
+ CarouselContent,
5
+ CarouselItem,
6
+ CarouselPrevious,
7
+ CarouselNext,
8
+ } from './carousel';
9
+ import { Card, CardContent } from '@/components/card/card';
10
+
11
+ const meta: Meta<typeof Carousel> = {
12
+ title: '数据展示 · Data Display/Carousel',
13
+ component: Carousel,
14
+ tags: ['autodocs'],
15
+ parameters: { layout: 'centered' },
16
+ };
17
+
18
+ export default meta;
19
+ type Story = StoryObj<typeof Carousel>;
20
+
21
+ export const Single: Story = {
22
+ render: () => (
23
+ <Carousel className="w-full max-w-md">
24
+ <CarouselContent>
25
+ {Array.from({ length: 5 }).map((_, i) => (
26
+ <CarouselItem key={i}>
27
+ <Card>
28
+ <CardContent className="flex aspect-video items-center justify-center p-6">
29
+ <span className="text-4xl font-semibold">{i + 1}</span>
30
+ </CardContent>
31
+ </Card>
32
+ </CarouselItem>
33
+ ))}
34
+ </CarouselContent>
35
+ <CarouselPrevious />
36
+ <CarouselNext />
37
+ </Carousel>
38
+ ),
39
+ };
40
+
41
+ export const ThreePerView: Story = {
42
+ parameters: { controls: { disable: true } },
43
+ render: () => (
44
+ <Carousel
45
+ opts={{ align: 'start' }}
46
+ className="w-full max-w-2xl"
47
+ >
48
+ <CarouselContent>
49
+ {Array.from({ length: 8 }).map((_, i) => (
50
+ <CarouselItem key={i} className="md:basis-1/3">
51
+ <Card>
52
+ <CardContent className="flex aspect-square items-center justify-center p-6">
53
+ <span className="text-3xl font-semibold">{i + 1}</span>
54
+ </CardContent>
55
+ </Card>
56
+ </CarouselItem>
57
+ ))}
58
+ </CarouselContent>
59
+ <CarouselPrevious />
60
+ <CarouselNext />
61
+ </Carousel>
62
+ ),
63
+ };
64
+
65
+ export const Looping: Story = {
66
+ parameters: { controls: { disable: true } },
67
+ render: () => (
68
+ <Carousel className="w-full max-w-md" opts={{ loop: true }}>
69
+ <CarouselContent>
70
+ {Array.from({ length: 4 }).map((_, i) => (
71
+ <CarouselItem key={i}>
72
+ <Card>
73
+ <CardContent className="flex aspect-video items-center justify-center p-6">
74
+ <span className="text-2xl">循环 #{i + 1}</span>
75
+ </CardContent>
76
+ </Card>
77
+ </CarouselItem>
78
+ ))}
79
+ </CarouselContent>
80
+ <CarouselPrevious />
81
+ <CarouselNext />
82
+ </Carousel>
83
+ ),
84
+ };