@teamix-evo/ui 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (295) hide show
  1. package/README.md +184 -184
  2. package/manifest.json +680 -492
  3. package/package.json +20 -10
  4. package/src/components/accordion/accordion.meta.md +5 -4
  5. package/src/components/accordion/accordion.stories.tsx +14 -9
  6. package/src/components/accordion/accordion.tsx +104 -8
  7. package/src/components/affix/affix.meta.md +20 -2
  8. package/src/components/affix/affix.stories.tsx +102 -25
  9. package/src/components/affix/affix.tsx +79 -9
  10. package/src/components/alert/alert.meta.md +44 -13
  11. package/src/components/alert/alert.stories.tsx +66 -21
  12. package/src/components/alert/alert.tsx +81 -34
  13. package/src/components/alert-dialog/alert-dialog.meta.md +61 -16
  14. package/src/components/alert-dialog/alert-dialog.stories.tsx +145 -3
  15. package/src/components/alert-dialog/alert-dialog.tsx +60 -13
  16. package/src/components/anchor/anchor.meta.md +8 -3
  17. package/src/components/anchor/anchor.stories.tsx +3 -3
  18. package/src/components/anchor/anchor.tsx +2 -2
  19. package/src/components/app/app.meta.md +9 -4
  20. package/src/components/app/app.stories.tsx +9 -7
  21. package/src/components/aspect-ratio/aspect-ratio.meta.md +4 -3
  22. package/src/components/aspect-ratio/aspect-ratio.stories.tsx +3 -3
  23. package/src/components/auto-complete/auto-complete.meta.md +14 -6
  24. package/src/components/auto-complete/auto-complete.stories.tsx +47 -4
  25. package/src/components/auto-complete/auto-complete.tsx +119 -71
  26. package/src/components/avatar/avatar.meta.md +6 -7
  27. package/src/components/avatar/avatar.stories.tsx +21 -3
  28. package/src/components/avatar/avatar.tsx +24 -23
  29. package/src/components/badge/badge.meta.md +10 -9
  30. package/src/components/badge/badge.stories.tsx +2 -2
  31. package/src/components/badge/badge.tsx +9 -15
  32. package/src/components/breadcrumb/breadcrumb.meta.md +27 -7
  33. package/src/components/breadcrumb/breadcrumb.stories.tsx +127 -4
  34. package/src/components/breadcrumb/breadcrumb.tsx +22 -8
  35. package/src/components/button/button.meta.md +258 -21
  36. package/src/components/button/button.stories.tsx +549 -41
  37. package/src/components/button/button.tsx +335 -33
  38. package/src/components/button/demo/as-child.tsx +24 -0
  39. package/src/components/button/demo/basic.tsx +8 -0
  40. package/src/components/button/demo/block.tsx +16 -0
  41. package/src/components/button/demo/loading.tsx +19 -0
  42. package/src/components/button/demo/shapes.tsx +18 -0
  43. package/src/components/button/demo/sizes.tsx +19 -0
  44. package/src/components/button/demo/variants.tsx +19 -0
  45. package/src/components/button/demo/with-icon.tsx +20 -0
  46. package/src/components/calendar/calendar.meta.md +13 -3
  47. package/src/components/calendar/calendar.stories.tsx +6 -6
  48. package/src/components/calendar/calendar.tsx +73 -8
  49. package/src/components/card/card.meta.md +27 -5
  50. package/src/components/card/card.stories.tsx +42 -3
  51. package/src/components/card/card.tsx +146 -63
  52. package/src/components/carousel/carousel.meta.md +4 -3
  53. package/src/components/carousel/carousel.stories.tsx +11 -6
  54. package/src/components/cascader/cascader.meta.md +47 -17
  55. package/src/components/cascader/cascader.stories.tsx +22 -10
  56. package/src/components/cascader/cascader.tsx +428 -85
  57. package/src/components/checkbox/checkbox.meta.md +75 -7
  58. package/src/components/checkbox/checkbox.stories.tsx +161 -3
  59. package/src/components/checkbox/checkbox.tsx +77 -9
  60. package/src/components/collapsible/collapsible.meta.md +14 -6
  61. package/src/components/collapsible/collapsible.stories.tsx +10 -2
  62. package/src/components/collapsible/collapsible.tsx +93 -6
  63. package/src/components/color-picker/color-picker.meta.md +12 -7
  64. package/src/components/color-picker/color-picker.stories.tsx +86 -7
  65. package/src/components/color-picker/color-picker.tsx +20 -9
  66. package/src/components/command/command.meta.md +29 -13
  67. package/src/components/command/command.stories.tsx +4 -4
  68. package/src/components/command/command.tsx +19 -8
  69. package/src/components/context-menu/context-menu.meta.md +11 -8
  70. package/src/components/context-menu/context-menu.stories.tsx +11 -3
  71. package/src/components/context-menu/context-menu.tsx +21 -8
  72. package/src/components/data-table/data-table.meta.md +6 -5
  73. package/src/components/data-table/data-table.stories.tsx +13 -6
  74. package/src/components/data-table/data-table.tsx +2 -2
  75. package/src/components/date-picker/date-picker.meta.md +88 -19
  76. package/src/components/date-picker/date-picker.stories.tsx +55 -5
  77. package/src/components/date-picker/date-picker.tsx +1489 -91
  78. package/src/components/descriptions/descriptions.meta.md +10 -5
  79. package/src/components/descriptions/descriptions.stories.tsx +3 -3
  80. package/src/components/descriptions/descriptions.tsx +22 -14
  81. package/src/components/dialog/dialog.meta.md +76 -13
  82. package/src/components/dialog/dialog.stories.tsx +182 -20
  83. package/src/components/dialog/dialog.tsx +67 -15
  84. package/src/components/dialog/imperative.tsx +252 -0
  85. package/src/components/drawer/drawer.meta.md +33 -34
  86. package/src/components/drawer/drawer.stories.tsx +29 -12
  87. package/src/components/drawer/drawer.tsx +22 -113
  88. package/src/components/dropdown-menu/dropdown-menu.meta.md +78 -10
  89. package/src/components/dropdown-menu/dropdown-menu.stories.tsx +88 -2
  90. package/src/components/dropdown-menu/dropdown-menu.tsx +24 -10
  91. package/src/components/ellipsis/ellipsis.meta.md +87 -0
  92. package/src/components/ellipsis/ellipsis.stories.tsx +72 -0
  93. package/src/components/ellipsis/ellipsis.tsx +153 -0
  94. package/src/components/empty/empty.meta.md +9 -4
  95. package/src/components/empty/empty.stories.tsx +4 -4
  96. package/src/components/empty/empty.tsx +10 -3
  97. package/src/components/field/field.meta.md +47 -9
  98. package/src/components/field/field.stories.tsx +385 -5
  99. package/src/components/field/field.tsx +263 -35
  100. package/src/components/filter-bar/filter-bar.meta.md +92 -0
  101. package/src/components/filter-bar/filter-bar.stories.tsx +1083 -0
  102. package/src/components/filter-bar/filter-bar.tsx +568 -0
  103. package/src/components/flex/flex.meta.md +54 -6
  104. package/src/components/flex/flex.stories.tsx +107 -20
  105. package/src/components/flex/flex.tsx +27 -4
  106. package/src/components/float-button/float-button.meta.md +8 -3
  107. package/src/components/float-button/float-button.stories.tsx +9 -7
  108. package/src/components/float-button/float-button.tsx +1 -1
  109. package/src/components/form/form.meta.md +39 -17
  110. package/src/components/form/form.stories.tsx +350 -3
  111. package/src/components/form/form.tsx +101 -35
  112. package/src/components/grid/grid.meta.md +7 -2
  113. package/src/components/grid/grid.stories.tsx +6 -4
  114. package/src/components/hover-card/hover-card.meta.md +20 -9
  115. package/src/components/hover-card/hover-card.stories.tsx +34 -5
  116. package/src/components/hover-card/hover-card.tsx +51 -13
  117. package/src/components/icon/DEVELOPMENT.md +809 -0
  118. package/src/components/icon/icon.meta.md +170 -0
  119. package/src/components/icon/icon.stories.tsx +344 -0
  120. package/src/components/icon/icon.tsx +248 -0
  121. package/src/components/image/image.meta.md +9 -4
  122. package/src/components/image/image.stories.tsx +3 -3
  123. package/src/components/image/image.tsx +6 -4
  124. package/src/components/input/demo/basic.tsx +12 -0
  125. package/src/components/input/demo/clearable.tsx +21 -0
  126. package/src/components/input/demo/show-count.tsx +18 -0
  127. package/src/components/input/demo/sizes.tsx +15 -0
  128. package/src/components/input/input.meta.md +39 -33
  129. package/src/components/input/input.stories.tsx +62 -35
  130. package/src/components/input/input.tsx +97 -98
  131. package/src/components/input-group/input-group.meta.md +54 -22
  132. package/src/components/input-group/input-group.stories.tsx +49 -16
  133. package/src/components/input-group/input-group.tsx +44 -8
  134. package/src/components/input-number/input-number.meta.md +64 -7
  135. package/src/components/input-number/input-number.stories.tsx +46 -8
  136. package/src/components/input-number/input-number.tsx +99 -26
  137. package/src/components/input-otp/input-otp.meta.md +4 -3
  138. package/src/components/input-otp/input-otp.stories.tsx +3 -3
  139. package/src/components/input-otp/input-otp.tsx +1 -1
  140. package/src/components/item/item.meta.md +8 -3
  141. package/src/components/item/item.stories.tsx +8 -5
  142. package/src/components/item/item.tsx +7 -6
  143. package/src/components/kbd/kbd.meta.md +13 -4
  144. package/src/components/kbd/kbd.stories.tsx +4 -4
  145. package/src/components/kbd/kbd.tsx +10 -5
  146. package/src/components/label/label.meta.md +18 -10
  147. package/src/components/label/label.stories.tsx +64 -6
  148. package/src/components/label/label.tsx +91 -19
  149. package/src/components/masonry/masonry.meta.md +8 -3
  150. package/src/components/masonry/masonry.stories.tsx +7 -5
  151. package/src/components/masonry/masonry.tsx +1 -0
  152. package/src/components/mentions/mentions.meta.md +36 -6
  153. package/src/components/mentions/mentions.stories.tsx +120 -6
  154. package/src/components/mentions/mentions.tsx +11 -5
  155. package/src/components/menubar/menubar.meta.md +30 -12
  156. package/src/components/menubar/menubar.stories.tsx +62 -2
  157. package/src/components/menubar/menubar.tsx +9 -9
  158. package/src/components/native-select/native-select.meta.md +8 -3
  159. package/src/components/native-select/native-select.stories.tsx +8 -5
  160. package/src/components/native-select/native-select.tsx +1 -1
  161. package/src/components/navigation-menu/navigation-menu.meta.md +19 -9
  162. package/src/components/navigation-menu/navigation-menu.stories.tsx +112 -9
  163. package/src/components/navigation-menu/navigation-menu.tsx +8 -4
  164. package/src/components/notification/notification.meta.md +52 -10
  165. package/src/components/notification/notification.stories.tsx +11 -9
  166. package/src/components/notification/notification.tsx +36 -21
  167. package/src/components/page-header/DEVELOPMENT.md +842 -0
  168. package/src/components/page-header/page-header.meta.md +208 -0
  169. package/src/components/page-header/page-header.stories.tsx +421 -0
  170. package/src/components/page-header/page-header.tsx +281 -0
  171. package/src/components/pagination/pagination.meta.md +140 -37
  172. package/src/components/pagination/pagination.stories.tsx +232 -10
  173. package/src/components/pagination/pagination.tsx +355 -63
  174. package/src/components/popconfirm/popconfirm.meta.md +9 -4
  175. package/src/components/popconfirm/popconfirm.stories.tsx +3 -4
  176. package/src/components/popconfirm/popconfirm.tsx +2 -2
  177. package/src/components/popover/popover.meta.md +62 -5
  178. package/src/components/popover/popover.stories.tsx +83 -7
  179. package/src/components/popover/popover.tsx +77 -28
  180. package/src/components/progress/progress.meta.md +38 -6
  181. package/src/components/progress/progress.stories.tsx +3 -3
  182. package/src/components/progress/progress.tsx +24 -16
  183. package/src/components/radio-group/radio-group.meta.md +79 -7
  184. package/src/components/radio-group/radio-group.stories.tsx +39 -3
  185. package/src/components/radio-group/radio-group.tsx +149 -18
  186. package/src/components/rate/rate.meta.md +35 -4
  187. package/src/components/rate/rate.stories.tsx +13 -5
  188. package/src/components/rate/rate.tsx +37 -10
  189. package/src/components/resizable/resizable.meta.md +7 -4
  190. package/src/components/resizable/resizable.stories.tsx +6 -6
  191. package/src/components/resizable/resizable.tsx +1 -1
  192. package/src/components/result/result.meta.md +7 -2
  193. package/src/components/result/result.stories.tsx +4 -8
  194. package/src/components/result/result.tsx +24 -15
  195. package/src/components/scroll-area/scroll-area.meta.md +4 -3
  196. package/src/components/scroll-area/scroll-area.stories.tsx +12 -4
  197. package/src/components/scroll-area/scroll-area.tsx +3 -3
  198. package/src/components/segmented/segmented.meta.md +7 -4
  199. package/src/components/segmented/segmented.stories.tsx +37 -8
  200. package/src/components/segmented/segmented.tsx +15 -7
  201. package/src/components/select/select.meta.md +197 -52
  202. package/src/components/select/select.stories.tsx +238 -63
  203. package/src/components/select/select.tsx +718 -171
  204. package/src/components/separator/separator.meta.md +4 -3
  205. package/src/components/separator/separator.stories.tsx +3 -3
  206. package/src/components/separator/separator.tsx +3 -7
  207. package/src/components/sheet/sheet.meta.md +32 -16
  208. package/src/components/sheet/sheet.stories.tsx +116 -10
  209. package/src/components/sheet/sheet.tsx +116 -29
  210. package/src/components/sidebar/sidebar.meta.md +37 -18
  211. package/src/components/sidebar/sidebar.stories.tsx +701 -29
  212. package/src/components/sidebar/sidebar.tsx +615 -142
  213. package/src/components/skeleton/skeleton.meta.md +4 -5
  214. package/src/components/skeleton/skeleton.stories.tsx +4 -4
  215. package/src/components/skeleton/skeleton.tsx +7 -7
  216. package/src/components/slider/slider.meta.md +57 -5
  217. package/src/components/slider/slider.stories.tsx +58 -6
  218. package/src/components/slider/slider.tsx +154 -13
  219. package/src/components/sonner/sonner.meta.md +58 -7
  220. package/src/components/sonner/sonner.stories.tsx +78 -5
  221. package/src/components/sonner/sonner.tsx +137 -8
  222. package/src/components/spinner/spinner.meta.md +62 -13
  223. package/src/components/spinner/spinner.stories.tsx +66 -14
  224. package/src/components/spinner/spinner.tsx +111 -9
  225. package/src/components/statistic/statistic.meta.md +7 -2
  226. package/src/components/statistic/statistic.stories.tsx +3 -7
  227. package/src/components/statistic/statistic.tsx +5 -6
  228. package/src/components/steps/steps.meta.md +18 -4
  229. package/src/components/steps/steps.stories.tsx +43 -3
  230. package/src/components/steps/steps.tsx +15 -12
  231. package/src/components/switch/switch.meta.md +51 -5
  232. package/src/components/switch/switch.stories.tsx +6 -6
  233. package/src/components/switch/switch.tsx +109 -41
  234. package/src/components/table/table.meta.md +17 -6
  235. package/src/components/table/table.stories.tsx +10 -5
  236. package/src/components/table/table.tsx +4 -4
  237. package/src/components/tabs/tabs.meta.md +38 -25
  238. package/src/components/tabs/tabs.stories.tsx +111 -25
  239. package/src/components/tabs/tabs.tsx +125 -54
  240. package/src/components/tag/tag.meta.md +105 -40
  241. package/src/components/tag/tag.stories.tsx +189 -16
  242. package/src/components/tag/tag.tsx +222 -21
  243. package/src/components/textarea/textarea.meta.md +35 -19
  244. package/src/components/textarea/textarea.stories.tsx +32 -6
  245. package/src/components/textarea/textarea.tsx +33 -9
  246. package/src/components/time-picker/time-picker.meta.md +124 -32
  247. package/src/components/time-picker/time-picker.stories.tsx +85 -15
  248. package/src/components/time-picker/time-picker.tsx +913 -61
  249. package/src/components/timeline/timeline.meta.md +14 -6
  250. package/src/components/timeline/timeline.stories.tsx +37 -7
  251. package/src/components/timeline/timeline.tsx +35 -14
  252. package/src/components/toggle/toggle.meta.md +5 -4
  253. package/src/components/toggle/toggle.stories.tsx +4 -4
  254. package/src/components/toggle/toggle.tsx +4 -3
  255. package/src/components/toggle-group/toggle-group.meta.md +5 -4
  256. package/src/components/toggle-group/toggle-group.stories.tsx +3 -3
  257. package/src/components/toggle-group/toggle-group.tsx +2 -2
  258. package/src/components/tooltip/tooltip.meta.md +55 -5
  259. package/src/components/tooltip/tooltip.stories.tsx +42 -5
  260. package/src/components/tooltip/tooltip.tsx +81 -21
  261. package/src/components/tour/tour.meta.md +9 -4
  262. package/src/components/tour/tour.stories.tsx +3 -3
  263. package/src/components/tour/tour.tsx +4 -4
  264. package/src/components/transfer/transfer.meta.md +11 -6
  265. package/src/components/transfer/transfer.stories.tsx +4 -8
  266. package/src/components/transfer/transfer.tsx +28 -21
  267. package/src/components/tree/tree.meta.md +63 -5
  268. package/src/components/tree/tree.stories.tsx +31 -12
  269. package/src/components/tree/tree.tsx +9 -8
  270. package/src/components/tree-select/tree-select.meta.md +59 -8
  271. package/src/components/tree-select/tree-select.stories.tsx +3 -3
  272. package/src/components/tree-select/tree-select.tsx +42 -7
  273. package/src/components/typography/typography.meta.md +61 -14
  274. package/src/components/typography/typography.stories.tsx +12 -11
  275. package/src/components/typography/typography.tsx +43 -28
  276. package/src/components/upload/upload.meta.md +49 -4
  277. package/src/components/upload/upload.stories.tsx +72 -12
  278. package/src/components/upload/upload.tsx +170 -37
  279. package/src/components/watermark/watermark.meta.md +7 -2
  280. package/src/components/watermark/watermark.stories.tsx +101 -9
  281. package/src/components/watermark/watermark.tsx +1 -0
  282. package/src/hooks/use-breakpoint.ts +117 -0
  283. package/src/hooks/use-debounce-callback.ts +52 -0
  284. package/src/hooks/use-mobile.ts +23 -0
  285. package/src/stories/theme-tokens.stories.tsx +747 -0
  286. package/src/utils/trigger-input.ts +53 -0
  287. package/src/components/button-group/button-group.meta.md +0 -92
  288. package/src/components/button-group/button-group.stories.tsx +0 -90
  289. package/src/components/button-group/button-group.tsx +0 -75
  290. package/src/components/combobox/combobox.meta.md +0 -93
  291. package/src/components/combobox/combobox.stories.tsx +0 -55
  292. package/src/components/combobox/combobox.tsx +0 -130
  293. package/src/components/space/space.meta.md +0 -94
  294. package/src/components/space/space.stories.tsx +0 -94
  295. package/src/components/space/space.tsx +0 -106
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import type { Meta, StoryObj } from '@storybook/react';
2
+ import type { Meta, StoryObj } from '@storybook/react-vite';
3
3
  import { useForm } from 'react-hook-form';
4
4
  import { zodResolver } from '@hookform/resolvers/zod';
5
5
  import { z } from 'zod';
@@ -11,19 +11,21 @@ import {
11
11
  FormControl,
12
12
  FormDescription,
13
13
  FormMessage,
14
+ FormSection,
15
+ FormActions,
14
16
  } from './form';
15
17
  import { Input } from '@/components/input/input';
16
18
  import { Button } from '@/components/button/button';
17
19
 
18
20
  const meta: Meta<typeof Form> = {
19
- title: '表单与输入 · Form/Form',
21
+ title: '数据录入 · Data Entry/Form',
20
22
  component: Form,
21
23
  tags: ['autodocs'],
22
24
  parameters: {
23
25
  docs: {
24
26
  description: {
25
27
  component:
26
- '表单 — `react-hook-form` + `zod` 校验 + 语义化字段组件(`FormItem` / `FormLabel` / `FormControl` / `FormDescription` / `FormMessage`)。对齐 shadcn Form 的可组合范式;对比 antd Form 用 `Field` 命令式注册的方式,本组件采用 RHF 的声明式 controller,类型安全且性能更好。视觉走 OpenTrek semantic tokens,所有样式来自 `@teamix-evo/design`,无 mock。',
28
+ '表单 — `react-hook-form` + `zod` 校验 + 语义化字段组件(`FormItem` / `FormLabel` / `FormControl` / `FormDescription` / `FormMessage`)。对齐 shadcn Form 的可组合范式;对比 antd Form 用 `Field` 命令式注册的方式,本组件采用 RHF 的声明式 controller,类型安全且性能更好。`<FormLabel required>` 红色 `*` **后置**在文字和 tooltip icon 右侧(顺序为 `文字 ⓘ *`),对齐设计规范。',
27
29
  },
28
30
  },
29
31
  },
@@ -120,3 +122,348 @@ export const ZodValidated: Story = {
120
122
  );
121
123
  },
122
124
  };
125
+
126
+ // ─── WithSections ─────────────────────────────────────────────────────────────
127
+
128
+ const sectionsSchema = z.object({
129
+ name: z.string().min(2, '至少 2 个字符'),
130
+ email: z.string().email('请输入有效邮箱'),
131
+ phone: z.string().min(6, '至少 6 位'),
132
+ company: z.string().optional(),
133
+ role: z.string().optional(),
134
+ });
135
+
136
+ export const WithSections: Story = {
137
+ parameters: { layout: 'centered' },
138
+ render: () => {
139
+ const form = useForm<z.infer<typeof sectionsSchema>>({
140
+ resolver: zodResolver(sectionsSchema),
141
+ defaultValues: { name: '', email: '', phone: '', company: '', role: '' },
142
+ });
143
+ const [submitted, setSubmitted] = React.useState<unknown>(null);
144
+
145
+ return (
146
+ <div className="w-[480px]">
147
+ <Form {...form}>
148
+ <form
149
+ onSubmit={form.handleSubmit((v) => setSubmitted(v))}
150
+ className="flex flex-col gap-8"
151
+ >
152
+ <FormSection title="基本信息">
153
+ <FormField
154
+ control={form.control}
155
+ name="name"
156
+ render={({ field }) => (
157
+ <FormItem>
158
+ <FormLabel required>姓名</FormLabel>
159
+ <FormControl>
160
+ <Input placeholder="张三" {...field} />
161
+ </FormControl>
162
+ <FormMessage />
163
+ </FormItem>
164
+ )}
165
+ />
166
+ <FormField
167
+ control={form.control}
168
+ name="email"
169
+ render={({ field }) => (
170
+ <FormItem>
171
+ <FormLabel required>邮箱</FormLabel>
172
+ <FormControl>
173
+ <Input type="email" placeholder="you@example.com" {...field} />
174
+ </FormControl>
175
+ <FormDescription>用于接收通知。</FormDescription>
176
+ <FormMessage />
177
+ </FormItem>
178
+ )}
179
+ />
180
+ <FormField
181
+ control={form.control}
182
+ name="phone"
183
+ render={({ field }) => (
184
+ <FormItem>
185
+ <FormLabel required>手机</FormLabel>
186
+ <FormControl>
187
+ <Input placeholder="13800000000" {...field} />
188
+ </FormControl>
189
+ <FormMessage />
190
+ </FormItem>
191
+ )}
192
+ />
193
+ </FormSection>
194
+
195
+ <FormSection title="工作信息">
196
+ <FormField
197
+ control={form.control}
198
+ name="company"
199
+ render={({ field }) => (
200
+ <FormItem>
201
+ <FormLabel>公司</FormLabel>
202
+ <FormControl>
203
+ <Input placeholder="Acme Inc." {...field} />
204
+ </FormControl>
205
+ <FormMessage />
206
+ </FormItem>
207
+ )}
208
+ />
209
+ <FormField
210
+ control={form.control}
211
+ name="role"
212
+ render={({ field }) => (
213
+ <FormItem>
214
+ <FormLabel>职位</FormLabel>
215
+ <FormControl>
216
+ <Input placeholder="Engineer" {...field} />
217
+ </FormControl>
218
+ <FormMessage />
219
+ </FormItem>
220
+ )}
221
+ />
222
+ </FormSection>
223
+
224
+ <FormActions>
225
+ <Button
226
+ type="button"
227
+ variant="outline"
228
+ onClick={() => form.reset()}
229
+ >
230
+ 重置
231
+ </Button>
232
+ <Button type="submit">提交</Button>
233
+ </FormActions>
234
+ </form>
235
+ </Form>
236
+ {submitted ? (
237
+ <pre className="mt-4 rounded-md bg-muted p-3 text-xs">
238
+ {JSON.stringify(submitted, null, 2)}
239
+ </pre>
240
+ ) : null}
241
+ </div>
242
+ );
243
+ },
244
+ };
245
+
246
+ // ─── MultiColumn ─────────────────────────────────────────────────────────────
247
+
248
+ const multiColumnSchema = z.object({
249
+ firstName: z.string().min(1, '必填'),
250
+ lastName: z.string().min(1, '必填'),
251
+ province: z.string().min(1, '必填'),
252
+ city: z.string().min(1, '必填'),
253
+ district: z.string().min(1, '必填'),
254
+ });
255
+
256
+ export const MultiColumn: Story = {
257
+ parameters: { layout: 'centered' },
258
+ render: () => {
259
+ const form = useForm<z.infer<typeof multiColumnSchema>>({
260
+ resolver: zodResolver(multiColumnSchema),
261
+ defaultValues: {
262
+ firstName: '',
263
+ lastName: '',
264
+ province: '',
265
+ city: '',
266
+ district: '',
267
+ },
268
+ });
269
+ const [submitted, setSubmitted] = React.useState<unknown>(null);
270
+
271
+ return (
272
+ <div className="w-[640px]">
273
+ <Form {...form}>
274
+ <form
275
+ onSubmit={form.handleSubmit((v) => setSubmitted(v))}
276
+ className="flex flex-col gap-8"
277
+ >
278
+ <FormSection title="姓名(两栏)" columns={2}>
279
+ <FormField
280
+ control={form.control}
281
+ name="firstName"
282
+ render={({ field }) => (
283
+ <FormItem>
284
+ <FormLabel required>名</FormLabel>
285
+ <FormControl>
286
+ <Input placeholder="三" {...field} />
287
+ </FormControl>
288
+ <FormMessage />
289
+ </FormItem>
290
+ )}
291
+ />
292
+ <FormField
293
+ control={form.control}
294
+ name="lastName"
295
+ render={({ field }) => (
296
+ <FormItem>
297
+ <FormLabel required>姓</FormLabel>
298
+ <FormControl>
299
+ <Input placeholder="张" {...field} />
300
+ </FormControl>
301
+ <FormMessage />
302
+ </FormItem>
303
+ )}
304
+ />
305
+ </FormSection>
306
+
307
+ <FormSection title="地址(三栏)" columns={3}>
308
+ <FormField
309
+ control={form.control}
310
+ name="province"
311
+ render={({ field }) => (
312
+ <FormItem>
313
+ <FormLabel required>省份</FormLabel>
314
+ <FormControl>
315
+ <Input placeholder="浙江" {...field} />
316
+ </FormControl>
317
+ <FormMessage />
318
+ </FormItem>
319
+ )}
320
+ />
321
+ <FormField
322
+ control={form.control}
323
+ name="city"
324
+ render={({ field }) => (
325
+ <FormItem>
326
+ <FormLabel required>城市</FormLabel>
327
+ <FormControl>
328
+ <Input placeholder="杭州" {...field} />
329
+ </FormControl>
330
+ <FormMessage />
331
+ </FormItem>
332
+ )}
333
+ />
334
+ <FormField
335
+ control={form.control}
336
+ name="district"
337
+ render={({ field }) => (
338
+ <FormItem>
339
+ <FormLabel required>区县</FormLabel>
340
+ <FormControl>
341
+ <Input placeholder="西湖区" {...field} />
342
+ </FormControl>
343
+ <FormMessage />
344
+ </FormItem>
345
+ )}
346
+ />
347
+ </FormSection>
348
+
349
+ <FormActions>
350
+ <Button type="submit">提交</Button>
351
+ </FormActions>
352
+ </form>
353
+ </Form>
354
+ {submitted ? (
355
+ <pre className="mt-4 rounded-md bg-muted p-3 text-xs">
356
+ {JSON.stringify(submitted, null, 2)}
357
+ </pre>
358
+ ) : null}
359
+ </div>
360
+ );
361
+ },
362
+ };
363
+
364
+ // ─── StickyActions ───────────────────────────────────────────────────────────
365
+ const stickySchema = z.object({
366
+ title: z.string().min(1, '必填'),
367
+ description: z.string().optional(),
368
+ remark1: z.string().optional(),
369
+ remark2: z.string().optional(),
370
+ remark3: z.string().optional(),
371
+ remark4: z.string().optional(),
372
+ remark5: z.string().optional(),
373
+ remark6: z.string().optional(),
374
+ });
375
+
376
+ export const StickyActions: Story = {
377
+ parameters: { layout: 'centered' },
378
+ render: () => {
379
+ const form = useForm<z.infer<typeof stickySchema>>({
380
+ resolver: zodResolver(stickySchema),
381
+ defaultValues: {
382
+ title: '',
383
+ description: '',
384
+ remark1: '',
385
+ remark2: '',
386
+ remark3: '',
387
+ remark4: '',
388
+ remark5: '',
389
+ remark6: '',
390
+ },
391
+ });
392
+ const [submitted, setSubmitted] = React.useState<unknown>(null);
393
+
394
+ return (
395
+ <div className="flex h-[480px] w-[420px] flex-col overflow-hidden rounded-md border border-border">
396
+ <Form {...form}>
397
+ <form
398
+ onSubmit={form.handleSubmit((v) => setSubmitted(v))}
399
+ className="flex h-full flex-col"
400
+ >
401
+ <div className="flex-1 overflow-y-auto p-4">
402
+ <FormSection title="表单内容">
403
+ <FormField
404
+ control={form.control}
405
+ name="title"
406
+ render={({ field }) => (
407
+ <FormItem>
408
+ <FormLabel required>标题</FormLabel>
409
+ <FormControl>
410
+ <Input placeholder="请输入标题" {...field} />
411
+ </FormControl>
412
+ <FormMessage />
413
+ </FormItem>
414
+ )}
415
+ />
416
+ <FormField
417
+ control={form.control}
418
+ name="description"
419
+ render={({ field }) => (
420
+ <FormItem>
421
+ <FormLabel>描述</FormLabel>
422
+ <FormControl>
423
+ <Input placeholder="请输入描述" {...field} />
424
+ </FormControl>
425
+ <FormMessage />
426
+ </FormItem>
427
+ )}
428
+ />
429
+ {(['remark1', 'remark2', 'remark3', 'remark4', 'remark5', 'remark6'] as const).map(
430
+ (n, i) => (
431
+ <FormField
432
+ key={n}
433
+ control={form.control}
434
+ name={n}
435
+ render={({ field }) => (
436
+ <FormItem>
437
+ <FormLabel>备注 {i + 1}</FormLabel>
438
+ <FormControl>
439
+ <Input placeholder={`备注 ${i + 1}`} {...field} />
440
+ </FormControl>
441
+ <FormMessage />
442
+ </FormItem>
443
+ )}
444
+ />
445
+ ),
446
+ )}
447
+ </FormSection>
448
+ </div>
449
+ <FormActions position="sticky" className="px-4">
450
+ <Button
451
+ type="button"
452
+ variant="outline"
453
+ onClick={() => form.reset()}
454
+ >
455
+ 重置
456
+ </Button>
457
+ <Button type="submit">提交</Button>
458
+ </FormActions>
459
+ </form>
460
+ </Form>
461
+ {submitted ? (
462
+ <pre className="m-4 rounded-md bg-muted p-3 text-xs">
463
+ {JSON.stringify(submitted, null, 2)}
464
+ </pre>
465
+ ) : null}
466
+ </div>
467
+ );
468
+ },
469
+ };
@@ -1,5 +1,4 @@
1
1
  import * as React from 'react';
2
- import * as LabelPrimitive from '@radix-ui/react-label';
3
2
  import { Slot } from '@radix-ui/react-slot';
4
3
  import {
5
4
  Controller,
@@ -12,7 +11,17 @@ import {
12
11
  } from 'react-hook-form';
13
12
 
14
13
  import { cn } from '@/utils/cn';
15
- import { Label } from '@/components/label/label';
14
+ import {
15
+ Field,
16
+ FieldLabel,
17
+ FieldDescription,
18
+ FieldSection,
19
+ FieldActions,
20
+ type FieldProps,
21
+ type FieldLabelProps,
22
+ type FieldSectionProps,
23
+ type FieldActionsProps,
24
+ } from '@/components/field/field';
16
25
 
17
26
  /**
18
27
  * `Form` props — 即 `useForm()` 返回值(`UseFormReturn<TFieldValues>`) 的全部方法,
@@ -71,22 +80,6 @@ const FormItemContext = React.createContext<FormItemContextValue>(
71
80
  {} as FormItemContextValue,
72
81
  );
73
82
 
74
- /**
75
- * `FormItem` 渲染单个表单字段容器,生成稳定 id 给 Label/Description/Message 关联。
76
- */
77
- const FormItem = React.forwardRef<
78
- HTMLDivElement,
79
- React.HTMLAttributes<HTMLDivElement>
80
- >(({ className, ...props }, ref) => {
81
- const id = React.useId();
82
- return (
83
- <FormItemContext.Provider value={{ id }}>
84
- <div ref={ref} className={cn('space-y-2', className)} {...props} />
85
- </FormItemContext.Provider>
86
- );
87
- });
88
- FormItem.displayName = 'FormItem';
89
-
90
83
  /**
91
84
  * 组合 FormField + FormItem 的字段元数据,用于 Label / Control / Description / Message。
92
85
  */
@@ -110,22 +103,74 @@ const useFormField = () => {
110
103
  };
111
104
  };
112
105
 
113
- const FormLabel = React.forwardRef<
114
- React.ElementRef<typeof LabelPrimitive.Root>,
115
- React.ComponentPropsWithoutRef<typeof Label>
116
- >(({ className, ...props }, ref) => {
117
- const { error, formItemId } = useFormField();
118
- return (
119
- <Label
120
- ref={ref}
121
- className={cn(error && 'text-destructive', className)}
122
- htmlFor={formItemId}
123
- {...props}
124
- />
125
- );
126
- });
106
+ // ─── FormItem(下沉到 Field atom)─────────────────────────────────────────────
107
+
108
+ /**
109
+ * `FormItemProps` 透传 Field 所有 props(orientation 响应式 / inputWidth / 等),
110
+ * **但不接受 `invalid`** RHF 流程下 invalid 由 fieldState.error 自动决定。
111
+ */
112
+ export type FormItemProps = Omit<FieldProps, 'invalid'>;
113
+
114
+ /**
115
+ * `FormItem` 渲染单个表单字段容器 — 内部用 `Field` 作为视觉骨架,
116
+ * 自动从 RHF `fieldState.error` 注入 `invalid`,Label/Description/Message 关联用稳定 id。
117
+ *
118
+ * 等于"RHF 接入 + 自动 invalid 注入" 的 Field —— 完整继承 Field 的:
119
+ * - orientation(标量 / 响应式)
120
+ * - inputWidth(5 档宽度)
121
+ * - className / style / data-* 透传
122
+ */
123
+ const FormItem = React.forwardRef<HTMLDivElement, FormItemProps>(
124
+ ({ children, ...fieldProps }, ref) => {
125
+ const id = React.useId();
126
+ return (
127
+ <FormItemContext.Provider value={{ id }}>
128
+ <FormItemBody ref={ref} {...fieldProps}>
129
+ {children}
130
+ </FormItemBody>
131
+ </FormItemContext.Provider>
132
+ );
133
+ },
134
+ );
135
+ FormItem.displayName = 'FormItem';
136
+
137
+ const FormItemBody = React.forwardRef<HTMLDivElement, FormItemProps>(
138
+ ({ children, ...fieldProps }, ref) => {
139
+ const { error } = useFormField();
140
+ return (
141
+ <Field ref={ref} invalid={!!error} {...fieldProps}>
142
+ {children}
143
+ </Field>
144
+ );
145
+ },
146
+ );
147
+ FormItemBody.displayName = 'FormItemBody';
148
+
149
+ // ─── FormLabel / FormControl / FormDescription / FormMessage ─────────────────
150
+
151
+ /**
152
+ * `FormLabel` = `FieldLabel` 包装 + 自动 `htmlFor=formItemId`。
153
+ * 着色(invalid → destructive)/ 必填 `*` / horizontal max ⅓ 等所有视觉行为继承自 FieldLabel。
154
+ */
155
+ const FormLabel = React.forwardRef<HTMLLabelElement, FieldLabelProps>(
156
+ ({ className, ...props }, ref) => {
157
+ const { formItemId } = useFormField();
158
+ return (
159
+ <FieldLabel
160
+ ref={ref}
161
+ htmlFor={formItemId}
162
+ className={className}
163
+ {...props}
164
+ />
165
+ );
166
+ },
167
+ );
127
168
  FormLabel.displayName = 'FormLabel';
128
169
 
170
+ /**
171
+ * `FormControl` 用 Radix Slot 注入 `id` / `aria-describedby` / `aria-invalid` 到子控件。
172
+ * 内部仅放真正的输入控件(Input / Select / Checkbox / RadioGroup 等)。
173
+ */
129
174
  const FormControl = React.forwardRef<
130
175
  React.ElementRef<typeof Slot>,
131
176
  React.ComponentPropsWithoutRef<typeof Slot>
@@ -146,22 +191,30 @@ const FormControl = React.forwardRef<
146
191
  });
147
192
  FormControl.displayName = 'FormControl';
148
193
 
194
+ /**
195
+ * `FormDescription` = `FieldDescription` 包装 + 自动 `id=formDescriptionId`(给 aria-describedby 用)。
196
+ */
149
197
  const FormDescription = React.forwardRef<
150
198
  HTMLParagraphElement,
151
199
  React.HTMLAttributes<HTMLParagraphElement>
152
200
  >(({ className, ...props }, ref) => {
153
201
  const { formDescriptionId } = useFormField();
154
202
  return (
155
- <p
203
+ <FieldDescription
156
204
  ref={ref}
157
205
  id={formDescriptionId}
158
- className={cn('text-xs text-muted-foreground', className)}
206
+ className={className}
159
207
  {...props}
160
208
  />
161
209
  );
162
210
  });
163
211
  FormDescription.displayName = 'FormDescription';
164
212
 
213
+ /**
214
+ * `FormMessage` 渲染错误信息 — 有 RHF `error.message` 时取其文本,否则取 children;都没就不渲染。
215
+ * 视觉等同 FieldError(`-mt-1 text-xs font-medium text-destructive`),
216
+ * 但显示条件由 `error || children` 决定(非 Field.invalid),保留 shadcn 原语义。
217
+ */
165
218
  const FormMessage = React.forwardRef<
166
219
  HTMLParagraphElement,
167
220
  React.HTMLAttributes<HTMLParagraphElement>
@@ -173,7 +226,8 @@ const FormMessage = React.forwardRef<
173
226
  <p
174
227
  ref={ref}
175
228
  id={formMessageId}
176
- className={cn('text-xs font-medium text-destructive', className)}
229
+ role="alert"
230
+ className={cn('-mt-1 text-xs font-medium text-destructive', className)}
177
231
  {...props}
178
232
  >
179
233
  {body}
@@ -182,6 +236,16 @@ const FormMessage = React.forwardRef<
182
236
  });
183
237
  FormMessage.displayName = 'FormMessage';
184
238
 
239
+ // ─── FormSection / FormActions ────────────────────────────────────────────────
240
+ //
241
+ // 共享 Field 包的同款实现 — 同一职责一处实现(避免规范变更时双写)。
242
+ // - FormSection: title + columns(1|2|3|4 多栏 grid)
243
+ // - FormActions: container 容器感知 + position + align + alignWithInput(基线对齐)
244
+ // 见 `packages/ui/src/components/field/field.tsx`。
245
+
246
+ export type FormSectionProps = FieldSectionProps;
247
+ export type FormActionsProps = FieldActionsProps;
248
+
185
249
  export {
186
250
  useFormField,
187
251
  Form,
@@ -191,4 +255,6 @@ export {
191
255
  FormDescription,
192
256
  FormMessage,
193
257
  FormField,
258
+ FieldSection as FormSection,
259
+ FieldActions as FormActions,
194
260
  };
@@ -1,13 +1,14 @@
1
1
  ---
2
2
  id: grid
3
3
  name: Grid
4
+ displayName: 栅格
4
5
  type: component
5
6
  category: layout
6
7
  since: 0.1.0
7
- package: "@teamix-evo/ui"
8
+ package: '@teamix-evo/ui'
8
9
  ---
9
10
 
10
- # Grid (Row / Col)
11
+ # Grid (Row / Col) 栅格
11
12
 
12
13
  24 栅格系统 — antd 独有补足。**等价 antd `Row` + `Col`**:基于 CSS Grid 的 24 列容器,支持 `span` / `offset` / `order` / `flex` / `gutter` / `justify` / `align` 等核心 API,中后台表单 / 看板布局基石。
13
14
 
@@ -23,10 +24,14 @@ package: "@teamix-evo/ui"
23
24
  - inline 间距小集合 → `Space`
24
25
  - 不固定列数的卡片墙 → 直接用 Tailwind `grid-cols-N` 或 `Item` 配 `ItemGroup`
25
26
 
27
+ ## Props
28
+
26
29
  <!-- auto:props:begin -->
27
30
  _(组件无 `<Name>Props` interface — props 详见 [`grid.tsx`](./grid.tsx))_
28
31
  <!-- auto:props:end -->
29
32
 
33
+ ## 依赖
34
+
30
35
  <!-- auto:deps:begin -->
31
36
  ### 同库依赖
32
37
 
@@ -1,15 +1,15 @@
1
- import type { Meta, StoryObj } from '@storybook/react';
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
2
  import { Row, Col } from './grid';
3
3
 
4
4
  const meta: Meta<typeof Row> = {
5
- title: '布局与容器 · Layout/Grid',
5
+ title: '布局 · Layout/Grid',
6
6
  component: Row,
7
7
  tags: ['autodocs'],
8
8
  parameters: {
9
9
  docs: {
10
10
  description: {
11
11
  component:
12
- '24 栅格 — 基于 CSS Grid 的 24 列容器(antd Row + Col)。中后台表单 / 看板布局基石,支持 span / offset / order / flex / gutter / justify / align。响应式建议直接用 Tailwind 断点 className(`md:col-span-12`),更原生可控。视觉走 OpenTrek tokens,所有样式来自 `@teamix-evo/design`,无 mock。',
12
+ '24 栅格 — 基于 CSS Grid 的 24 列容器(antd Row + Col)。中后台表单 / 看板布局基石,支持 span / offset / order / flex / gutter / justify / align。响应式建议直接用 Tailwind 断点 className。',
13
13
  },
14
14
  },
15
15
  },
@@ -19,7 +19,9 @@ export default meta;
19
19
  type Story = StoryObj<typeof Row>;
20
20
 
21
21
  const Box = ({ children }: { children: React.ReactNode }) => (
22
- <div className="rounded-md bg-muted px-3 py-4 text-center text-sm">{children}</div>
22
+ <div className="rounded-md bg-muted px-3 py-4 text-center text-sm">
23
+ {children}
24
+ </div>
23
25
  );
24
26
 
25
27
  export const Playground: Story = {