@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,92 @@
1
+ ---
2
+ id: hover-card
3
+ name: HoverCard
4
+ type: component
5
+ category: feedback
6
+ since: 0.1.0
7
+ package: "@teamix-evo/ui"
8
+ ---
9
+
10
+ # HoverCard
11
+
12
+ 悬浮信息卡 — Radix HoverCard 包装。**shadcn-only**(antd 无对标 — antd Tooltip 仅文字,Popover 是 click 触发,HoverCard 是 hover 触发的富内容)。
13
+
14
+ `Tooltip` 与 `HoverCard` 的边界:
15
+ - 1 行简短文字 → `Tooltip`
16
+ - 多行 / 含图片 / 含弱交互(链接 / Avatar)→ `HoverCard`
17
+
18
+ ## When to use
19
+
20
+ - 鼠标悬浮在 @username / 资源名 上展示卡片(GitHub / Twitter 风格)
21
+ - 列表项的 hover 预览(标题 + 摘要 + tag)
22
+ - 缩略链接预览(URL + 简介 + 域名)
23
+
24
+ ## When NOT to use
25
+
26
+ - 移动端首要功能 → 触屏不触发 hover,改 `Popover`
27
+ - 强交互 → `Popover`
28
+ - 简短文字 → `Tooltip`
29
+
30
+ ## Props
31
+
32
+ > 以下表格由 `pnpm --filter @teamix-evo/ui gen:meta` 自动生成。下表为 `HoverCardContent` 的 props;`HoverCard` Root 透传 `openDelay / closeDelay / open / defaultOpen / onOpenChange`。
33
+
34
+ <!-- auto:props:begin -->
35
+ _(no props)_
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 hover-card` 时,以下 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-hover-card@^1.1.0
57
+ ```
58
+ <!-- auto:deps:end -->
59
+
60
+ ## AI 生成纪律
61
+
62
+ - **Trigger 用 `asChild`**:wrap 已有元素而非新增 button
63
+ - **`openDelay` 别太短**:用户快速划过时不要把卡片闪一下;**默认 700ms** 通常合理
64
+ - **HoverCard 内不放表单**:表单需要 click 焦点 → 用 `Popover`
65
+ - **不要嵌套 HoverCard**:嵌套 hover 状态混乱
66
+
67
+ ## Examples
68
+
69
+ ```tsx
70
+ import {
71
+ HoverCard, HoverCardTrigger, HoverCardContent,
72
+ } from '@/components/ui/hover-card';
73
+ import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar';
74
+
75
+ <HoverCard>
76
+ <HoverCardTrigger asChild>
77
+ <a href="/u/lyca" className="font-medium text-primary hover:underline">@lyca</a>
78
+ </HoverCardTrigger>
79
+ <HoverCardContent>
80
+ <div className="flex gap-3">
81
+ <Avatar>
82
+ <AvatarImage src="/u/lyca.jpg" />
83
+ <AvatarFallback>LY</AvatarFallback>
84
+ </Avatar>
85
+ <div className="space-y-1">
86
+ <h4 className="text-sm font-semibold">@lyca</h4>
87
+ <p className="text-xs text-muted-foreground">Frontend engineer · Joined 2020</p>
88
+ </div>
89
+ </div>
90
+ </HoverCardContent>
91
+ </HoverCard>
92
+ ```
@@ -0,0 +1,68 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { HoverCard, HoverCardTrigger, HoverCardContent } from './hover-card';
3
+ import { Avatar, AvatarImage, AvatarFallback } from '@/components/avatar/avatar';
4
+
5
+ const meta: Meta<typeof HoverCardContent> = {
6
+ title: '反馈与浮层 · Feedback/HoverCard',
7
+ component: HoverCardContent,
8
+ tags: ['autodocs'],
9
+ parameters: {
10
+ docs: {
11
+ description: {
12
+ component:
13
+ '悬浮卡片 — 鼠标悬停时延迟展示更多信息(常用于用户名片、链接预览)。基于 Radix HoverCard,**仅鼠标悬停触发**(键盘聚焦不触发,交互场景与 Tooltip / Popover 互补);支持 `side` / `align` 定位与 `openDelay` / `closeDelay`。shadcn 专有,antd 可由 `Popover` 的 trigger="hover" 近似实现。视觉走 OpenTrek semantic tokens,所有样式来自 `@teamix-evo/design`,无 mock。',
14
+ },
15
+ },
16
+ },
17
+ argTypes: {
18
+ side: {
19
+ control: 'inline-radio',
20
+ options: ['top', 'right', 'bottom', 'left'],
21
+ },
22
+ align: {
23
+ control: 'inline-radio',
24
+ options: ['start', 'center', 'end'],
25
+ },
26
+ },
27
+ args: { side: 'bottom', align: 'center' },
28
+ decorators: [
29
+ (Story) => (
30
+ <div className="flex h-48 items-center justify-center">
31
+ <Story />
32
+ </div>
33
+ ),
34
+ ],
35
+ };
36
+
37
+ export default meta;
38
+ type Story = StoryObj<typeof HoverCardContent>;
39
+
40
+ export const Playground: Story = {
41
+ render: (args) => (
42
+ <HoverCard openDelay={150}>
43
+ <HoverCardTrigger asChild>
44
+ <a
45
+ href="#"
46
+ onClick={(e) => e.preventDefault()}
47
+ className="font-medium text-primary hover:underline"
48
+ >
49
+ @lyca
50
+ </a>
51
+ </HoverCardTrigger>
52
+ <HoverCardContent {...args}>
53
+ <div className="flex gap-3">
54
+ <Avatar>
55
+ <AvatarImage src="https://github.com/shadcn.png" />
56
+ <AvatarFallback>LY</AvatarFallback>
57
+ </Avatar>
58
+ <div className="space-y-1">
59
+ <h4 className="text-sm font-semibold">@lyca</h4>
60
+ <p className="text-xs text-muted-foreground">
61
+ Frontend engineer · Joined Dec 2020
62
+ </p>
63
+ </div>
64
+ </div>
65
+ </HoverCardContent>
66
+ </HoverCard>
67
+ ),
68
+ };
@@ -0,0 +1,29 @@
1
+ import * as React from 'react';
2
+ import * as HoverCardPrimitive from '@radix-ui/react-hover-card';
3
+
4
+ import { cn } from '@/utils/cn';
5
+
6
+ const HoverCard = HoverCardPrimitive.Root;
7
+ const HoverCardTrigger = HoverCardPrimitive.Trigger;
8
+
9
+ export interface HoverCardContentProps
10
+ extends React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content> {}
11
+
12
+ const HoverCardContent = React.forwardRef<
13
+ React.ElementRef<typeof HoverCardPrimitive.Content>,
14
+ HoverCardContentProps
15
+ >(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (
16
+ <HoverCardPrimitive.Content
17
+ ref={ref}
18
+ align={align}
19
+ sideOffset={sideOffset}
20
+ className={cn(
21
+ 'z-50 w-64 rounded-md border bg-popover p-4 text-sm text-popover-foreground shadow-md outline-none animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-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',
22
+ className,
23
+ )}
24
+ {...props}
25
+ />
26
+ ));
27
+ HoverCardContent.displayName = HoverCardPrimitive.Content.displayName;
28
+
29
+ export { HoverCard, HoverCardTrigger, HoverCardContent };
@@ -0,0 +1,94 @@
1
+ ---
2
+ id: image
3
+ name: Image
4
+ type: component
5
+ category: foundation
6
+ since: 0.1.0
7
+ package: "@teamix-evo/ui"
8
+ ---
9
+
10
+ # Image
11
+
12
+ 图片 — antd 独有补足。**自动处理加载态(Skeleton 占位)+ 失败态(图标)+ 点击预览**(Dialog 模态展示原图)。
13
+
14
+ ## When to use
15
+
16
+ - 用户上传 / 远程图片(尺寸不可控,需要占位避免布局抖动)
17
+ - 详情页大图 + 点击放大查看
18
+ - 图片画廊 / 缩略图列表
19
+
20
+ ## When NOT to use
21
+
22
+ - 装饰图标 → `lucide-react`
23
+ - 强需求 lazy-load + 优化 → 用框架自带(Next.js `<Image>`)
24
+ - 仅显示固定尺寸图标 → `<img>` 直接用
25
+
26
+ <!-- auto:props:begin -->
27
+ | 名称 | 类型 | 默认值 | 必填 | 说明 |
28
+ | --- | --- | --- | --- | --- |
29
+ | `src` | `string` | – | ✓ | – |
30
+ | `alt` | `string` | – | ✓ | – |
31
+ | `preview` | `boolean` | `true` | – | 是否启用点击预览(antd `preview` 并集) — 点击后弹模态展示原图。 |
32
+ | `fallback` | `React.ReactNode` | – | – | 加载失败时显示的占位元素;不传则用默认 `ImageOff` 图标。 |
33
+ | `placeholder` | `React.ReactNode` | – | – | 加载中显示的占位 — 默认 Skeleton 占位与图片同尺寸。 |
34
+ | `className` | `string` | – | – | 容器 className(图片本身用 `imgClassName`)。 |
35
+ | `imgClassName` | `string` | – | – | 真实 `<img>` 的 className(尺寸 / 圆角 / object-fit)。 |
36
+ <!-- auto:props:end -->
37
+
38
+ <!-- auto:deps:begin -->
39
+ ### 同库依赖
40
+
41
+ > `teamix-evo ui add image` 时,以下 entry 会被自动连带安装(无需手动 add)。
42
+
43
+ | Entry | 类型 | 描述 |
44
+ | --- | --- | --- |
45
+ | `cn` | util | Tailwind className 合并工具(clsx + tailwind-merge) |
46
+ | `dialog` | component | 模态对话框 — Radix Dialog + antd Modal 并集(组合式 Header/Footer/Title/Description) |
47
+ | `skeleton` | component | 骨架屏 — shadcn pulse 单元素 + antd Avatar/Image/Button/Input/Paragraph 子家族,自动 motion-reduce |
48
+
49
+ ### npm 依赖
50
+
51
+ > 业务侧需要先 `pnpm add` / `npm install` 这些包。CLI 在 `ui add` 完成后会列出此提示。
52
+
53
+ ```bash
54
+ pnpm add lucide-react@^0.460.0
55
+ ```
56
+ <!-- auto:deps:end -->
57
+
58
+ ## AI 生成纪律
59
+
60
+ - **`alt` 必填**:无障碍要求(屏幕阅读器朗读)
61
+ - **`width / height` 或容器 `className` 必有一个**:否则加载前占位高度为 0,导致布局抖动
62
+ - **`preview={false}` 用于头像 / 小缩略图**:不需要点击放大的场景
63
+ - **`fallback` 自定义占位**:品牌侧统一图(LOGO 灰)用 `<img src="/fallback.svg" />` 传入
64
+ - **不要嵌套 Image**:嵌套预览模态行为混乱
65
+
66
+ ## Examples
67
+
68
+ ```tsx
69
+ import { Image } from '@/components/ui/image';
70
+
71
+ // 基础(自带预览)
72
+ <Image
73
+ src="/cover.jpg"
74
+ alt="项目封面"
75
+ className="rounded-md"
76
+ imgClassName="size-64 object-cover"
77
+ />
78
+
79
+ // 关闭预览
80
+ <Image
81
+ src="/avatar.jpg"
82
+ alt="头像"
83
+ preview={false}
84
+ imgClassName="size-10 rounded-full object-cover"
85
+ />
86
+
87
+ // 自定义失败占位
88
+ <Image
89
+ src="/might-fail.jpg"
90
+ alt="..."
91
+ fallback={<img src="/brand-fallback.svg" alt="" />}
92
+ imgClassName="size-32 object-cover"
93
+ />
94
+ ```
@@ -0,0 +1,55 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { Image } from './image';
3
+
4
+ const meta: Meta<typeof Image> = {
5
+ title: '基础原语 · Foundation/Image',
6
+ component: Image,
7
+ tags: ['autodocs'],
8
+ parameters: {
9
+ docs: {
10
+ description: {
11
+ component:
12
+ '图片 — 自动处理加载(Skeleton 占位)+ 失败(图标占位)+ 点击预览(Dialog 模态原图)。alt 必填(无障碍)。OpenTrek tokens 适配,等价 antd Image。',
13
+ },
14
+ },
15
+ },
16
+ };
17
+
18
+ export default meta;
19
+ type Story = StoryObj<typeof Image>;
20
+
21
+ export const Default: Story = {
22
+ render: () => (
23
+ <Image
24
+ src="https://placehold.co/640x360/e5e7eb/6b7280?text=Cover"
25
+ alt="封面"
26
+ className="rounded-md"
27
+ imgClassName="w-64 object-cover"
28
+ />
29
+ ),
30
+ };
31
+
32
+ export const Avatar: Story = {
33
+ parameters: { controls: { disable: true } },
34
+ render: () => (
35
+ <Image
36
+ src="https://github.com/shadcn.png"
37
+ alt="头像"
38
+ preview={false}
39
+ className="rounded-full"
40
+ imgClassName="size-12 object-cover rounded-full"
41
+ />
42
+ ),
43
+ };
44
+
45
+ export const ErrorState: Story = {
46
+ parameters: { controls: { disable: true } },
47
+ render: () => (
48
+ <Image
49
+ src="https://this-domain-does-not-exist-xyz.example/x.jpg"
50
+ alt="加载失败示例"
51
+ className="rounded-md"
52
+ imgClassName="size-48 object-cover"
53
+ />
54
+ ),
55
+ };
@@ -0,0 +1,138 @@
1
+ import * as React from 'react';
2
+ import { ImageOff, X } from 'lucide-react';
3
+
4
+ import { cn } from '@/utils/cn';
5
+ import {
6
+ Dialog,
7
+ DialogContent,
8
+ DialogTrigger,
9
+ } from '@/components/dialog/dialog';
10
+ import { Skeleton } from '@/components/skeleton/skeleton';
11
+
12
+ export interface ImageProps
13
+ extends Omit<React.ImgHTMLAttributes<HTMLImageElement>, 'src' | 'placeholder'> {
14
+ src: string;
15
+ alt: string;
16
+ /**
17
+ * 是否启用点击预览(antd `preview` 并集) — 点击后弹模态展示原图。
18
+ * @default true
19
+ */
20
+ preview?: boolean;
21
+ /**
22
+ * 加载失败时显示的占位元素;不传则用默认 `ImageOff` 图标。
23
+ */
24
+ fallback?: React.ReactNode;
25
+ /**
26
+ * 加载中显示的占位 — 默认 Skeleton 占位与图片同尺寸。
27
+ */
28
+ placeholder?: React.ReactNode;
29
+ /** 容器 className(图片本身用 `imgClassName`)。 */
30
+ className?: string;
31
+ /** 真实 `<img>` 的 className(尺寸 / 圆角 / object-fit)。 */
32
+ imgClassName?: string;
33
+ }
34
+
35
+ const Image = React.forwardRef<HTMLImageElement, ImageProps>(
36
+ (
37
+ {
38
+ src,
39
+ alt,
40
+ preview = true,
41
+ fallback,
42
+ placeholder,
43
+ className,
44
+ imgClassName,
45
+ onLoad,
46
+ onError,
47
+ ...props
48
+ },
49
+ ref,
50
+ ) => {
51
+ const [status, setStatus] = React.useState<
52
+ 'loading' | 'loaded' | 'error'
53
+ >('loading');
54
+
55
+ const img = (
56
+ <img
57
+ ref={ref}
58
+ src={src}
59
+ alt={alt}
60
+ onLoad={(e) => {
61
+ setStatus('loaded');
62
+ onLoad?.(e);
63
+ }}
64
+ onError={(e) => {
65
+ setStatus('error');
66
+ onError?.(e);
67
+ }}
68
+ className={cn(
69
+ 'block transition-opacity',
70
+ status !== 'loaded' && 'opacity-0',
71
+ imgClassName,
72
+ )}
73
+ {...props}
74
+ />
75
+ );
76
+
77
+ const overlay = (
78
+ <>
79
+ {status === 'loading' ? (
80
+ <div className="absolute inset-0">
81
+ {placeholder ?? <Skeleton className="size-full rounded-[inherit]" />}
82
+ </div>
83
+ ) : null}
84
+ {status === 'error' ? (
85
+ <div className="absolute inset-0 flex flex-col items-center justify-center gap-1 rounded-[inherit] bg-muted text-muted-foreground">
86
+ {fallback ?? (
87
+ <>
88
+ <ImageOff className="size-6" />
89
+ <span className="text-xs">加载失败</span>
90
+ </>
91
+ )}
92
+ </div>
93
+ ) : null}
94
+ </>
95
+ );
96
+
97
+ const inner = (
98
+ <span
99
+ className={cn(
100
+ 'relative inline-block overflow-hidden',
101
+ preview &&
102
+ status === 'loaded' &&
103
+ 'cursor-zoom-in transition-opacity hover:opacity-90',
104
+ className,
105
+ )}
106
+ >
107
+ {img}
108
+ {overlay}
109
+ </span>
110
+ );
111
+
112
+ if (!preview || status !== 'loaded') return inner;
113
+
114
+ return (
115
+ <Dialog>
116
+ <DialogTrigger asChild>{inner}</DialogTrigger>
117
+ <DialogContent
118
+ className="max-w-[90vw] border-0 bg-transparent p-0 shadow-none"
119
+ showClose={false}
120
+ >
121
+ <div className="relative">
122
+ <img
123
+ src={src}
124
+ alt={alt}
125
+ className="max-h-[85vh] w-auto rounded-md object-contain"
126
+ />
127
+ <div className="pointer-events-none absolute -top-3 -right-3 flex size-8 items-center justify-center rounded-full bg-background text-foreground shadow">
128
+ <X className="size-4" />
129
+ </div>
130
+ </div>
131
+ </DialogContent>
132
+ </Dialog>
133
+ );
134
+ },
135
+ );
136
+ Image.displayName = 'Image';
137
+
138
+ export { Image };
@@ -0,0 +1,109 @@
1
+ ---
2
+ id: input
3
+ name: Input
4
+ type: component
5
+ category: form
6
+ since: 0.1.0
7
+ package: "@teamix-evo/ui"
8
+ ---
9
+
10
+ # Input
11
+
12
+ 文本输入框 — shadcn 简洁版基底 + antd 的 `prefix / suffix / clearable / showCount / addonBefore / addonAfter / size`。
13
+ 单组件覆盖大部分表单输入场景,保持 forwardRef + 原生 `<input>` 透传。
14
+
15
+ ## When to use
16
+
17
+ - 任何文本输入(text / email / password / number / search / tel / url)
18
+ - 配合 `prefix` 加搜索图标
19
+ - 配合 `addonBefore="https://"` 输入域名
20
+ - 配合 `clearable` 给搜索框一键清空
21
+ - 配合 `showCount + maxLength` 给社媒发文限制字数
22
+
23
+ ## When NOT to use
24
+
25
+ - 多行文本 → `Textarea`
26
+ - 数字微调 → `Input type="number"` 已足够,需要步进按钮考虑独立 `InputNumber`
27
+ - 选择类 → `Select` / `Combobox`
28
+ - OTP 验证码 → `InputOTP`(v0.x)
29
+
30
+ ## Props
31
+
32
+ > 以下表格由 `pnpm --filter @teamix-evo/ui gen:meta` 自动生成。
33
+
34
+ <!-- auto:props:begin -->
35
+ | 名称 | 类型 | 默认值 | 必填 | 说明 |
36
+ | --- | --- | --- | --- | --- |
37
+ | `prefix` | `React.ReactNode` | – | – | 输入框前置图标 / 文本(antd `prefix` 并集)。 |
38
+ | `suffix` | `React.ReactNode` | – | – | 输入框后置图标 / 文本(antd `suffix` 并集)。 |
39
+ | `clearable` | `boolean` | `false` | – | 显示清除按钮,有内容时点击清空(antd `allowClear` 并集)。 |
40
+ | `showCount` | `boolean` | `false` | – | 显示字符计数(antd `showCount` 并集);需配合 `maxLength` 一起使用。 |
41
+ | `addonBefore` | `React.ReactNode` | – | – | 输入框前置标签(antd `addonBefore` 并集),与外层组合形成 input-group。 |
42
+ | `addonAfter` | `React.ReactNode` | – | – | 输入框后置标签(antd `addonAfter` 并集)。 |
43
+ | `size` | `'sm' \| 'default' \| 'lg'` | `"default"` | – | 尺寸。 |
44
+ | `value` | `string` | – | – | 受控值,用于 clearable / showCount 的逻辑;未传时回退到 uncontrolled 行为。 |
45
+ | `defaultValue` | `string` | – | – | uncontrolled 初始值。 |
46
+ <!-- auto:props:end -->
47
+
48
+ ## 依赖
49
+
50
+ > 以下表格由 `pnpm --filter @teamix-evo/ui gen:meta` 自动生成,数据源是 [`manifest.json`](../../../manifest.json)。**手工编辑 marker 之间的内容会在下次生成时被覆盖**。
51
+
52
+ <!-- auto:deps:begin -->
53
+ ### 同库依赖
54
+
55
+ > `teamix-evo ui add input` 时,以下 entry 会被自动连带安装(无需手动 add)。
56
+
57
+ | Entry | 类型 | 描述 |
58
+ | --- | --- | --- |
59
+ | `cn` | util | Tailwind className 合并工具(clsx + tailwind-merge) |
60
+
61
+ ### npm 依赖
62
+
63
+ > 业务侧需要先 `pnpm add` / `npm install` 这些包。CLI 在 `ui add` 完成后会列出此提示。
64
+
65
+ ```bash
66
+ pnpm add lucide-react@^0.460.0
67
+ ```
68
+ <!-- auto:deps:end -->
69
+
70
+ > 透传所有 `<input>` 原生属性(`type` / `placeholder` / `name` / `id` / `aria-*` / `onChange` / ...)。
71
+
72
+ ## AI 生成纪律
73
+
74
+ - **`prefix/suffix` 与 `addonBefore/addonAfter` 区别**:`prefix/suffix` 在边框**内**,`addon*` 是独立按钮形态在边框**外**(常用于域名 / 单位 / 协议头)
75
+ - **`showCount` 必须配 `maxLength`**:无 maxLength 时 showCount 不显示
76
+ - **`clearable` 与 `suffix` 互斥位置**:有内容时 clear 优先,suffix 仍渲染但视觉权重需协调;**不要**同时塞太多 affix 内容
77
+ - **type="password" 的可见切换**:用 `suffix={<Eye />}` + 状态控制 type 切换(组件不内置,业务侧做)
78
+ - **`type="search"` 配合 `clearable`**:搜索框最佳搭档,提交按钮用 `addonAfter`
79
+ - **不要硬编码颜色**:`bg-background` / `border-input` / `placeholder:text-muted-foreground` 都是 design token,**不要**改成 `bg-white`
80
+
81
+ ## Examples
82
+
83
+ ```tsx
84
+ import { Input } from '@/components/ui/input';
85
+ import { Search, Mail } from 'lucide-react';
86
+
87
+ // 基础
88
+ <Input placeholder="请输入..." />
89
+
90
+ // 受控
91
+ const [v, setV] = React.useState('');
92
+ <Input value={v} onChange={(e) => setV(e.target.value)} />
93
+
94
+ // 前置图标
95
+ <Input prefix={<Search />} placeholder="搜索..." />
96
+
97
+ // 可清除 + 字数统计
98
+ <Input clearable showCount maxLength={140} placeholder="动态..." />
99
+
100
+ // 域名 input(addon-before)
101
+ <Input addonBefore="https://" addonAfter=".com" defaultValue="teamix-evo" />
102
+
103
+ // 邮箱 + suffix
104
+ <Input type="email" suffix={<Mail />} placeholder="you@example.com" />
105
+
106
+ // 尺寸
107
+ <Input size="sm" placeholder="紧凑" />
108
+ <Input size="lg" placeholder="宽松" />
109
+ ```