@teamix-evo/ui 0.2.0 → 0.4.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 (296) hide show
  1. package/README.md +184 -184
  2. package/manifest.json +695 -487
  3. package/package.json +16 -10
  4. package/src/components/accordion/accordion.meta.md +5 -9
  5. package/src/components/accordion/accordion.stories.tsx +3 -3
  6. package/src/components/accordion/accordion.tsx +104 -8
  7. package/src/components/affix/affix.meta.md +21 -12
  8. package/src/components/affix/affix.stories.tsx +101 -26
  9. package/src/components/affix/affix.tsx +79 -9
  10. package/src/components/alert/alert.meta.md +52 -26
  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 +48 -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 +10 -14
  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 +10 -14
  20. package/src/components/app/app.stories.tsx +6 -6
  21. package/src/components/aspect-ratio/aspect-ratio.meta.md +4 -8
  22. package/src/components/aspect-ratio/aspect-ratio.stories.tsx +3 -3
  23. package/src/components/auto-complete/auto-complete.meta.md +19 -20
  24. package/src/components/auto-complete/auto-complete.stories.tsx +44 -3
  25. package/src/components/auto-complete/auto-complete.tsx +119 -71
  26. package/src/components/avatar/avatar.meta.md +9 -22
  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 +14 -18
  30. package/src/components/badge/badge.stories.tsx +2 -2
  31. package/src/components/badge/badge.tsx +2 -2
  32. package/src/components/breadcrumb/breadcrumb.meta.md +29 -20
  33. package/src/components/breadcrumb/breadcrumb.stories.tsx +120 -5
  34. package/src/components/breadcrumb/breadcrumb.tsx +22 -8
  35. package/src/components/button/button.meta.md +261 -29
  36. package/src/components/button/button.stories.tsx +549 -41
  37. package/src/components/button/button.tsx +341 -35
  38. package/src/components/calendar/calendar.meta.md +19 -14
  39. package/src/components/calendar/calendar.stories.tsx +5 -5
  40. package/src/components/calendar/calendar.tsx +73 -8
  41. package/src/components/card/card.meta.md +31 -34
  42. package/src/components/card/card.stories.tsx +34 -3
  43. package/src/components/card/card.tsx +147 -63
  44. package/src/components/carousel/carousel.meta.md +10 -14
  45. package/src/components/carousel/carousel.stories.tsx +1 -1
  46. package/src/components/cascader/cascader.meta.md +43 -22
  47. package/src/components/cascader/cascader.stories.tsx +13 -2
  48. package/src/components/cascader/cascader.tsx +439 -89
  49. package/src/components/checkbox/checkbox.meta.md +74 -24
  50. package/src/components/checkbox/checkbox.stories.tsx +160 -2
  51. package/src/components/checkbox/checkbox.tsx +79 -9
  52. package/src/components/collapsible/collapsible.meta.md +7 -6
  53. package/src/components/collapsible/collapsible.stories.tsx +2 -2
  54. package/src/components/collapsible/collapsible.tsx +93 -6
  55. package/src/components/color-picker/color-picker.meta.md +16 -20
  56. package/src/components/color-picker/color-picker.stories.tsx +86 -7
  57. package/src/components/color-picker/color-picker.tsx +19 -9
  58. package/src/components/command/command.meta.md +7 -11
  59. package/src/components/command/command.stories.tsx +4 -4
  60. package/src/components/command/command.tsx +18 -7
  61. package/src/components/context-menu/context-menu.meta.md +5 -25
  62. package/src/components/context-menu/context-menu.stories.tsx +4 -4
  63. package/src/components/context-menu/context-menu.tsx +21 -8
  64. package/src/components/data-table/data-table.meta.md +14 -18
  65. package/src/components/data-table/data-table.stories.tsx +1 -1
  66. package/src/components/data-table/data-table.tsx +2 -2
  67. package/src/components/date-picker/date-picker.meta.md +90 -41
  68. package/src/components/date-picker/date-picker.stories.tsx +55 -5
  69. package/src/components/date-picker/date-picker.tsx +1489 -91
  70. package/src/components/descriptions/descriptions.meta.md +12 -16
  71. package/src/components/descriptions/descriptions.stories.tsx +2 -2
  72. package/src/components/descriptions/descriptions.tsx +22 -14
  73. package/src/components/dialog/dialog.meta.md +67 -17
  74. package/src/components/dialog/dialog.stories.tsx +182 -20
  75. package/src/components/dialog/dialog.tsx +67 -15
  76. package/src/components/dialog/imperative.tsx +252 -0
  77. package/src/components/drawer/drawer.meta.md +27 -39
  78. package/src/components/drawer/drawer.stories.tsx +29 -12
  79. package/src/components/drawer/drawer.tsx +22 -114
  80. package/src/components/dropdown-menu/dropdown-menu.meta.md +64 -24
  81. package/src/components/dropdown-menu/dropdown-menu.stories.tsx +81 -3
  82. package/src/components/dropdown-menu/dropdown-menu.tsx +24 -10
  83. package/src/components/ellipsis/ellipsis.meta.md +87 -0
  84. package/src/components/ellipsis/ellipsis.stories.tsx +72 -0
  85. package/src/components/ellipsis/ellipsis.tsx +153 -0
  86. package/src/components/empty/empty.meta.md +10 -14
  87. package/src/components/empty/empty.stories.tsx +3 -3
  88. package/src/components/empty/empty.tsx +10 -3
  89. package/src/components/field/field.meta.md +46 -25
  90. package/src/components/field/field.stories.tsx +380 -3
  91. package/src/components/field/field.tsx +263 -35
  92. package/src/components/filter-bar/filter-bar.meta.md +92 -0
  93. package/src/components/filter-bar/filter-bar.stories.tsx +1086 -0
  94. package/src/components/filter-bar/filter-bar.tsx +568 -0
  95. package/src/components/flex/flex.meta.md +59 -20
  96. package/src/components/flex/flex.stories.tsx +65 -10
  97. package/src/components/flex/flex.tsx +27 -4
  98. package/src/components/float-button/float-button.meta.md +10 -29
  99. package/src/components/float-button/float-button.stories.tsx +6 -6
  100. package/src/components/form/form.meta.md +31 -52
  101. package/src/components/form/form.stories.tsx +354 -4
  102. package/src/components/form/form.tsx +101 -35
  103. package/src/components/grid/grid.meta.md +4 -24
  104. package/src/components/grid/grid.stories.tsx +2 -2
  105. package/src/components/hover-card/hover-card.meta.md +9 -10
  106. package/src/components/hover-card/hover-card.stories.tsx +29 -4
  107. package/src/components/hover-card/hover-card.tsx +51 -13
  108. package/src/components/icon/DEVELOPMENT.md +809 -0
  109. package/src/components/icon/icon.meta.md +170 -0
  110. package/src/components/icon/icon.stories.tsx +344 -0
  111. package/src/components/icon/icon.tsx +248 -0
  112. package/src/components/image/image.meta.md +14 -18
  113. package/src/components/image/image.stories.tsx +3 -3
  114. package/src/components/image/image.tsx +2 -0
  115. package/src/components/input/input.meta.md +44 -43
  116. package/src/components/input/input.stories.tsx +62 -35
  117. package/src/components/input/input.tsx +96 -101
  118. package/src/components/input-group/input-group.meta.md +53 -39
  119. package/src/components/input-group/input-group.stories.tsx +49 -16
  120. package/src/components/input-group/input-group.tsx +45 -10
  121. package/src/components/input-number/input-number.meta.md +68 -20
  122. package/src/components/input-number/input-number.stories.tsx +33 -6
  123. package/src/components/input-number/input-number.tsx +81 -22
  124. package/src/components/input-otp/input-otp.meta.md +8 -20
  125. package/src/components/input-otp/input-otp.stories.tsx +3 -3
  126. package/src/components/input-otp/input-otp.tsx +1 -1
  127. package/src/components/item/item.meta.md +8 -26
  128. package/src/components/item/item.stories.tsx +3 -3
  129. package/src/components/item/item.tsx +7 -6
  130. package/src/components/kbd/kbd.meta.md +7 -19
  131. package/src/components/kbd/kbd.stories.tsx +4 -4
  132. package/src/components/kbd/kbd.tsx +8 -4
  133. package/src/components/label/label.meta.md +21 -18
  134. package/src/components/label/label.stories.tsx +64 -6
  135. package/src/components/label/label.tsx +91 -19
  136. package/src/components/masonry/masonry.meta.md +8 -12
  137. package/src/components/masonry/masonry.stories.tsx +4 -4
  138. package/src/components/mentions/mentions.meta.md +42 -21
  139. package/src/components/mentions/mentions.stories.tsx +120 -6
  140. package/src/components/mentions/mentions.tsx +10 -5
  141. package/src/components/menubar/menubar.meta.md +4 -8
  142. package/src/components/menubar/menubar.stories.tsx +55 -3
  143. package/src/components/menubar/menubar.tsx +9 -9
  144. package/src/components/native-select/native-select.meta.md +7 -11
  145. package/src/components/native-select/native-select.stories.tsx +4 -4
  146. package/src/components/native-select/native-select.tsx +2 -2
  147. package/src/components/navigation-menu/navigation-menu.meta.md +4 -8
  148. package/src/components/navigation-menu/navigation-menu.stories.tsx +106 -3
  149. package/src/components/navigation-menu/navigation-menu.tsx +6 -3
  150. package/src/components/notification/notification.meta.md +41 -8
  151. package/src/components/notification/notification.stories.tsx +9 -9
  152. package/src/components/notification/notification.tsx +34 -19
  153. package/src/components/page-header/DEVELOPMENT.md +842 -0
  154. package/src/components/page-header/page-header.meta.md +210 -0
  155. package/src/components/page-header/page-header.stories.tsx +428 -0
  156. package/src/components/page-header/page-header.tsx +284 -0
  157. package/src/components/page-shell/page-shell.meta.md +116 -0
  158. package/src/components/page-shell/page-shell.stories.tsx +149 -0
  159. package/src/components/page-shell/page-shell.tsx +115 -0
  160. package/src/components/pagination/pagination.meta.md +122 -50
  161. package/src/components/pagination/pagination.stories.tsx +227 -11
  162. package/src/components/pagination/pagination.tsx +345 -63
  163. package/src/components/popconfirm/popconfirm.meta.md +19 -23
  164. package/src/components/popconfirm/popconfirm.stories.tsx +2 -2
  165. package/src/components/popconfirm/popconfirm.tsx +1 -1
  166. package/src/components/popover/popover.meta.md +64 -12
  167. package/src/components/popover/popover.stories.tsx +83 -7
  168. package/src/components/popover/popover.tsx +77 -28
  169. package/src/components/progress/progress.meta.md +43 -26
  170. package/src/components/progress/progress.stories.tsx +2 -2
  171. package/src/components/progress/progress.tsx +19 -11
  172. package/src/components/radio-group/radio-group.meta.md +78 -11
  173. package/src/components/radio-group/radio-group.stories.tsx +38 -2
  174. package/src/components/radio-group/radio-group.tsx +149 -18
  175. package/src/components/rate/rate.meta.md +41 -19
  176. package/src/components/rate/rate.stories.tsx +2 -2
  177. package/src/components/rate/rate.tsx +37 -10
  178. package/src/components/resizable/resizable.meta.md +4 -12
  179. package/src/components/resizable/resizable.stories.tsx +5 -5
  180. package/src/components/resizable/resizable.tsx +1 -1
  181. package/src/components/result/result.meta.md +10 -14
  182. package/src/components/result/result.stories.tsx +2 -2
  183. package/src/components/result/result.tsx +21 -12
  184. package/src/components/scroll-area/scroll-area.meta.md +4 -8
  185. package/src/components/scroll-area/scroll-area.stories.tsx +5 -5
  186. package/src/components/segmented/segmented.meta.md +15 -17
  187. package/src/components/segmented/segmented.stories.tsx +3 -3
  188. package/src/components/segmented/segmented.tsx +16 -8
  189. package/src/components/select/select.meta.md +199 -67
  190. package/src/components/select/select.stories.tsx +238 -63
  191. package/src/components/select/select.tsx +718 -171
  192. package/src/components/separator/separator.meta.md +10 -14
  193. package/src/components/separator/separator.stories.tsx +2 -2
  194. package/src/components/separator/separator.tsx +3 -7
  195. package/src/components/sheet/sheet.meta.md +26 -21
  196. package/src/components/sheet/sheet.stories.tsx +116 -10
  197. package/src/components/sheet/sheet.tsx +116 -29
  198. package/src/components/sidebar/sidebar.meta.md +29 -38
  199. package/src/components/sidebar/sidebar.stories.tsx +696 -29
  200. package/src/components/sidebar/sidebar.tsx +643 -141
  201. package/src/components/skeleton/skeleton.meta.md +7 -31
  202. package/src/components/skeleton/skeleton.stories.tsx +3 -3
  203. package/src/components/skeleton/skeleton.tsx +7 -7
  204. package/src/components/slider/slider.meta.md +60 -13
  205. package/src/components/slider/slider.stories.tsx +58 -6
  206. package/src/components/slider/slider.tsx +154 -13
  207. package/src/components/sonner/sonner.meta.md +54 -8
  208. package/src/components/sonner/sonner.stories.tsx +79 -11
  209. package/src/components/sonner/sonner.tsx +137 -8
  210. package/src/components/spinner/spinner.meta.md +57 -21
  211. package/src/components/spinner/spinner.stories.tsx +66 -14
  212. package/src/components/spinner/spinner.tsx +111 -9
  213. package/src/components/statistic/statistic.meta.md +14 -30
  214. package/src/components/statistic/statistic.stories.tsx +1 -1
  215. package/src/components/statistic/statistic.tsx +4 -5
  216. package/src/components/steps/steps.meta.md +20 -15
  217. package/src/components/steps/steps.stories.tsx +37 -2
  218. package/src/components/steps/steps.tsx +15 -12
  219. package/src/components/switch/switch.meta.md +56 -15
  220. package/src/components/switch/switch.stories.tsx +5 -5
  221. package/src/components/switch/switch.tsx +59 -13
  222. package/src/components/table/table.meta.md +3 -7
  223. package/src/components/table/table.stories.tsx +1 -1
  224. package/src/components/table/table.tsx +8 -6
  225. package/src/components/tabs/tabs.meta.md +40 -32
  226. package/src/components/tabs/tabs.stories.tsx +104 -26
  227. package/src/components/tabs/tabs.tsx +125 -54
  228. package/src/components/tag/tag.meta.md +104 -68
  229. package/src/components/tag/tag.stories.tsx +183 -15
  230. package/src/components/tag/tag.tsx +222 -21
  231. package/src/components/textarea/textarea.meta.md +42 -31
  232. package/src/components/textarea/textarea.stories.tsx +32 -6
  233. package/src/components/textarea/textarea.tsx +32 -8
  234. package/src/components/time-picker/time-picker.meta.md +119 -50
  235. package/src/components/time-picker/time-picker.stories.tsx +65 -33
  236. package/src/components/time-picker/time-picker.tsx +889 -101
  237. package/src/components/timeline/timeline.meta.md +16 -17
  238. package/src/components/timeline/timeline.stories.tsx +24 -4
  239. package/src/components/timeline/timeline.tsx +32 -12
  240. package/src/components/toggle/toggle.meta.md +8 -12
  241. package/src/components/toggle/toggle.stories.tsx +4 -4
  242. package/src/components/toggle/toggle.tsx +4 -3
  243. package/src/components/toggle-group/toggle-group.meta.md +10 -14
  244. package/src/components/toggle-group/toggle-group.stories.tsx +3 -3
  245. package/src/components/toggle-group/toggle-group.tsx +2 -2
  246. package/src/components/tooltip/tooltip.meta.md +63 -18
  247. package/src/components/tooltip/tooltip.stories.tsx +42 -5
  248. package/src/components/tooltip/tooltip.tsx +81 -21
  249. package/src/components/tour/tour.meta.md +16 -20
  250. package/src/components/tour/tour.stories.tsx +3 -3
  251. package/src/components/tour/tour.tsx +3 -3
  252. package/src/components/transfer/transfer.meta.md +18 -22
  253. package/src/components/transfer/transfer.stories.tsx +2 -2
  254. package/src/components/transfer/transfer.tsx +28 -21
  255. package/src/components/tree/tree.meta.md +67 -22
  256. package/src/components/tree/tree.stories.tsx +1 -1
  257. package/src/components/tree/tree.tsx +9 -8
  258. package/src/components/tree-select/tree-select.meta.md +59 -23
  259. package/src/components/tree-select/tree-select.stories.tsx +2 -2
  260. package/src/components/tree-select/tree-select.tsx +42 -7
  261. package/src/components/typography/typography.meta.md +61 -39
  262. package/src/components/typography/typography.stories.tsx +14 -9
  263. package/src/components/typography/typography.tsx +38 -25
  264. package/src/components/upload/upload.meta.md +61 -25
  265. package/src/components/upload/upload.stories.tsx +69 -3
  266. package/src/components/upload/upload.tsx +170 -37
  267. package/src/components/watermark/watermark.meta.md +15 -19
  268. package/src/components/watermark/watermark.stories.tsx +98 -8
  269. package/src/hooks/use-breakpoint.ts +117 -0
  270. package/src/hooks/use-debounce-callback.ts +52 -0
  271. package/src/hooks/use-mobile.ts +23 -0
  272. package/src/stories/theme-tokens.stories.tsx +747 -0
  273. package/src/utils/trigger-input.ts +57 -0
  274. package/src/components/button/demo/as-child.tsx +0 -24
  275. package/src/components/button/demo/basic.tsx +0 -8
  276. package/src/components/button/demo/block.tsx +0 -16
  277. package/src/components/button/demo/loading.tsx +0 -19
  278. package/src/components/button/demo/shapes.tsx +0 -18
  279. package/src/components/button/demo/sizes.tsx +0 -19
  280. package/src/components/button/demo/variants.tsx +0 -19
  281. package/src/components/button/demo/with-icon.tsx +0 -20
  282. package/src/components/button-group/button-group.meta.md +0 -101
  283. package/src/components/button-group/button-group.stories.tsx +0 -93
  284. package/src/components/button-group/button-group.tsx +0 -75
  285. package/src/components/combobox/combobox.meta.md +0 -102
  286. package/src/components/combobox/combobox.stories.tsx +0 -55
  287. package/src/components/combobox/combobox.tsx +0 -130
  288. package/src/components/input/demo/addon.tsx +0 -15
  289. package/src/components/input/demo/basic.tsx +0 -12
  290. package/src/components/input/demo/clearable.tsx +0 -21
  291. package/src/components/input/demo/show-count.tsx +0 -18
  292. package/src/components/input/demo/sizes.tsx +0 -15
  293. package/src/components/input/demo/with-prefix-suffix.tsx +0 -19
  294. package/src/components/space/space.meta.md +0 -103
  295. package/src/components/space/space.stories.tsx +0 -108
  296. package/src/components/space/space.tsx +0 -106
@@ -0,0 +1,1086 @@
1
+ import * as React from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react-vite';
3
+ import { useForm, FormProvider } from 'react-hook-form';
4
+ // `as never` cast on zodResolver(...) below: zod 3.25.x ships zod v4
5
+ // internals while @hookform/resolvers@3.x still types Resolver against
6
+ // zod v3 Schema. Remove when upgrading to @hookform/resolvers v4.
7
+ import { zodResolver } from '@hookform/resolvers/zod';
8
+ import { z } from 'zod';
9
+ import {
10
+ FilterBar,
11
+ FilterBarSearch,
12
+ FilterBarTrigger,
13
+ FilterBarPanel,
14
+ FilterBarActions,
15
+ FilterBarPreset,
16
+ } from './filter-bar';
17
+ import {
18
+ FormField,
19
+ FormItem,
20
+ FormLabel,
21
+ FormControl,
22
+ FormMessage,
23
+ } from '@/components/form/form';
24
+ import { Input } from '@/components/input/input';
25
+ import { Select } from '@/components/select/select';
26
+ import {
27
+ DatePicker,
28
+ DateRangePicker,
29
+ } from '@/components/date-picker/date-picker';
30
+ import type { DateRange } from 'react-day-picker';
31
+ import { Switch } from '@/components/switch/switch';
32
+ import { Checkbox } from '@/components/checkbox/checkbox';
33
+ import { CheckboxGroup } from '@/components/checkbox/checkbox';
34
+ import { RadioGroup } from '@/components/radio-group/radio-group';
35
+ import { Button } from '@/components/button/button';
36
+
37
+ // ─── Meta ────────────────────────────────────────────────────────────────────
38
+
39
+ const meta: Meta<typeof FilterBar> = {
40
+ title: '数据录入 · Data Entry/FilterBar',
41
+ component: FilterBar,
42
+ tags: ['autodocs'],
43
+ parameters: {
44
+ layout: 'fullscreen',
45
+ docs: {
46
+ description: {
47
+ component:
48
+ '筛选栏 —— 面向 Table/List 的筛选表单变体,行内实时筛选 + 可折叠高级面板栅格布局。Radix Collapsible 实现面板展开收起 + react-hook-form 表单状态管理 + Grid 栅格面板布局;对齐 SAP Fiori FilterBar 的 basic/advanced 双模式,补齐 antd-pro QueryFilter 的 inline/panel 实时触发与防抖能力:`form` / `defaultExpanded` / `onFilter` / `onReset` / `filterDebounce` / `columns`。视觉走 OpenTrek semantic tokens,所有样式来自 `@teamix-evo/tokens`,无 mock。',
49
+ },
50
+ },
51
+ },
52
+ };
53
+
54
+ export default meta;
55
+ type Story = StoryObj<typeof FilterBar>;
56
+
57
+ // ─── Story: InlineFilter ─────────────────────────────────────────────────────
58
+
59
+ export const InlineFilter: Story = {
60
+ name: '行内筛选(实时触发)',
61
+ render: () => {
62
+ const form = useForm({
63
+ defaultValues: { keyword: '', status: '', enabled: false },
64
+ });
65
+ const [result, setResult] = React.useState<Record<string, any>>({});
66
+
67
+ return (
68
+ <div className="w-full space-y-4 p-6">
69
+ <FormProvider {...form}>
70
+ <FilterBar form={form} onFilter={setResult}>
71
+ <FilterBarSearch>
72
+ <FormField
73
+ control={form.control}
74
+ name="keyword"
75
+ render={({ field }) => (
76
+ <FormItem className="flex-row items-center gap-2">
77
+ <FormControl>
78
+ <Input
79
+ placeholder="搜索关键词..."
80
+ className="w-48"
81
+ {...field}
82
+ />
83
+ </FormControl>
84
+ </FormItem>
85
+ )}
86
+ />
87
+ <FormField
88
+ control={form.control}
89
+ name="status"
90
+ render={({ field }) => (
91
+ <FormItem className="flex-row items-center gap-2">
92
+ <FormControl>
93
+ <Select
94
+ options={[
95
+ { value: 'running', label: '运行中' },
96
+ { value: 'stopped', label: '已停止' },
97
+ { value: 'error', label: '异常' },
98
+ ]}
99
+ placeholder="状态"
100
+ value={field.value}
101
+ onChange={field.onChange}
102
+ className="w-36"
103
+ />
104
+ </FormControl>
105
+ </FormItem>
106
+ )}
107
+ />
108
+ <FormField
109
+ control={form.control}
110
+ name="enabled"
111
+ render={({ field }) => (
112
+ <FormItem className="flex-row items-center gap-2">
113
+ <FormLabel className="text-xs">仅显示启用</FormLabel>
114
+ <FormControl>
115
+ <Switch
116
+ checked={field.value}
117
+ onCheckedChange={field.onChange}
118
+ size="sm"
119
+ />
120
+ </FormControl>
121
+ </FormItem>
122
+ )}
123
+ />
124
+ </FilterBarSearch>
125
+ </FilterBar>
126
+ </FormProvider>
127
+ {Object.keys(result).length > 0 && (
128
+ <pre className="rounded-md bg-muted p-3 text-xs">
129
+ onFilter: {JSON.stringify(result, null, 2)}
130
+ </pre>
131
+ )}
132
+ </div>
133
+ );
134
+ },
135
+ };
136
+
137
+ // ─── Story: WithPanel ────────────────────────────────────────────────────────
138
+
139
+ export const WithPanel: Story = {
140
+ name: '行内 + 可展开面板',
141
+ render: () => {
142
+ const form = useForm({
143
+ defaultValues: {
144
+ keyword: '',
145
+ region: '',
146
+ org: '',
147
+ dateRange: undefined as DateRange | undefined,
148
+ tagGroup: '',
149
+ instanceId: '',
150
+ cpu: '',
151
+ },
152
+ });
153
+ const [result, setResult] = React.useState<Record<string, any>>({});
154
+
155
+ return (
156
+ <div className="w-full space-y-4 p-6">
157
+ <FormProvider {...form}>
158
+ <FilterBar
159
+ form={form}
160
+ onFilter={setResult}
161
+ onReset={() => setResult({})}
162
+ >
163
+ <FilterBarSearch autoFilter={false}>
164
+ <FormField
165
+ control={form.control}
166
+ name="keyword"
167
+ render={({ field }) => (
168
+ <FormItem className="flex-row items-center gap-2">
169
+ <FormControl>
170
+ <Input
171
+ placeholder="实例 ID / 名称"
172
+ className="w-56"
173
+ {...field}
174
+ />
175
+ </FormControl>
176
+ </FormItem>
177
+ )}
178
+ />
179
+ <FilterBarTrigger />
180
+ </FilterBarSearch>
181
+ <FilterBarPanel columns={3}>
182
+ <FormField
183
+ control={form.control}
184
+ name="dateRange"
185
+ render={({ field }) => (
186
+ <FormItem>
187
+ <FormLabel>起止时间</FormLabel>
188
+ <FormControl>
189
+ <DateRangePicker
190
+ value={field.value}
191
+ onChange={field.onChange}
192
+ placeholders={['起始日期', '结束日期']}
193
+ />
194
+ </FormControl>
195
+ </FormItem>
196
+ )}
197
+ />
198
+ <FormField
199
+ control={form.control}
200
+ name="org"
201
+ render={({ field }) => (
202
+ <FormItem>
203
+ <FormLabel>组织</FormLabel>
204
+ <FormControl>
205
+ <Select
206
+ options={[
207
+ { value: 'dev', label: '研发部' },
208
+ { value: 'ops', label: '运维部' },
209
+ { value: 'test', label: '测试部' },
210
+ ]}
211
+ placeholder="请选择"
212
+ value={field.value}
213
+ onChange={field.onChange}
214
+ />
215
+ </FormControl>
216
+ </FormItem>
217
+ )}
218
+ />
219
+ <FormField
220
+ control={form.control}
221
+ name="tagGroup"
222
+ render={({ field }) => (
223
+ <FormItem>
224
+ <FormLabel>标签选择组</FormLabel>
225
+ <FormControl>
226
+ <Select
227
+ options={[
228
+ { value: 'web', label: 'Web 应用' },
229
+ { value: 'api', label: 'API 服务' },
230
+ { value: 'db', label: '数据库' },
231
+ { value: 'cache', label: '缓存' },
232
+ ]}
233
+ placeholder="请选择或输入标签键或标签值"
234
+ searchable
235
+ value={field.value}
236
+ onChange={field.onChange}
237
+ />
238
+ </FormControl>
239
+ </FormItem>
240
+ )}
241
+ />
242
+ <FormField
243
+ control={form.control}
244
+ name="region"
245
+ render={({ field }) => (
246
+ <FormItem>
247
+ <FormLabel>区域</FormLabel>
248
+ <FormControl>
249
+ <Select
250
+ options={[
251
+ { value: 'cn-hangzhou', label: '华东1(杭州)' },
252
+ { value: 'cn-shanghai', label: '华东2(上海)' },
253
+ { value: 'cn-beijing', label: '华北2(北京)' },
254
+ { value: 'cn-shenzhen', label: '华南1(深圳)' },
255
+ ]}
256
+ placeholder="请选择"
257
+ value={field.value}
258
+ onChange={field.onChange}
259
+ />
260
+ </FormControl>
261
+ </FormItem>
262
+ )}
263
+ />
264
+ <FormField
265
+ control={form.control}
266
+ name="instanceId"
267
+ render={({ field }) => (
268
+ <FormItem>
269
+ <FormLabel>实例 ID</FormLabel>
270
+ <FormControl>
271
+ <Input placeholder="输入实例 ID" {...field} />
272
+ </FormControl>
273
+ </FormItem>
274
+ )}
275
+ />
276
+ <FormField
277
+ control={form.control}
278
+ name="cpu"
279
+ render={({ field }) => (
280
+ <FormItem>
281
+ <FormLabel>CPU</FormLabel>
282
+ <FormControl>
283
+ <Input placeholder="输入 CPU 核数" {...field} />
284
+ </FormControl>
285
+ </FormItem>
286
+ )}
287
+ />
288
+ <FilterBarActions />
289
+ </FilterBarPanel>
290
+ </FilterBar>
291
+ </FormProvider>
292
+ {Object.keys(result).length > 0 && (
293
+ <pre className="rounded-md bg-muted p-3 text-xs">
294
+ onFilter: {JSON.stringify(result, null, 2)}
295
+ </pre>
296
+ )}
297
+ </div>
298
+ );
299
+ },
300
+ };
301
+
302
+ // ─── Story: PurePanel ────────────────────────────────────────────────────────
303
+
304
+ export const PurePanel: Story = {
305
+ name: '纯面板模式(默认展开)',
306
+ render: () => {
307
+ const form = useForm({
308
+ defaultValues: {
309
+ startDate: undefined as Date | undefined,
310
+ org: '',
311
+ resourceGroup: [] as string[],
312
+ region: '',
313
+ instanceId: '',
314
+ enableMonitor: false,
315
+ },
316
+ });
317
+ const [result, setResult] = React.useState<Record<string, any>>({});
318
+
319
+ return (
320
+ <div className="w-full space-y-4 p-6">
321
+ <FormProvider {...form}>
322
+ <FilterBar
323
+ form={form}
324
+ defaultExpanded
325
+ onFilter={setResult}
326
+ onReset={() => setResult({})}
327
+ >
328
+ <FilterBarPanel columns={3}>
329
+ <FormField
330
+ control={form.control}
331
+ name="startDate"
332
+ render={({ field }) => (
333
+ <FormItem>
334
+ <FormLabel required>起始日期</FormLabel>
335
+ <FormControl>
336
+ <DatePicker
337
+ value={field.value}
338
+ onChange={field.onChange}
339
+ placeholder="选择日期"
340
+ />
341
+ </FormControl>
342
+ </FormItem>
343
+ )}
344
+ />
345
+ <FormField
346
+ control={form.control}
347
+ name="org"
348
+ render={({ field }) => (
349
+ <FormItem>
350
+ <FormLabel required>组织</FormLabel>
351
+ <FormControl>
352
+ <Select
353
+ options={[
354
+ { value: 'dev', label: '研发部' },
355
+ { value: 'ops', label: '运维部' },
356
+ { value: 'test', label: '测试部' },
357
+ { value: 'product', label: '产品部' },
358
+ ]}
359
+ placeholder="请选择"
360
+ value={field.value}
361
+ onChange={field.onChange}
362
+ />
363
+ </FormControl>
364
+ </FormItem>
365
+ )}
366
+ />
367
+ <FormField
368
+ control={form.control}
369
+ name="resourceGroup"
370
+ render={({ field }) => (
371
+ <FormItem>
372
+ <FormLabel>资源集</FormLabel>
373
+ <FormControl>
374
+ <CheckboxGroup
375
+ options={[
376
+ { value: 'ecs', label: 'ECS' },
377
+ { value: 'rds', label: 'RDS' },
378
+ { value: 'oss', label: 'OSS' },
379
+ { value: 'slb', label: 'SLB' },
380
+ ]}
381
+ value={field.value}
382
+ onChange={field.onChange}
383
+ />
384
+ </FormControl>
385
+ </FormItem>
386
+ )}
387
+ />
388
+ <FormField
389
+ control={form.control}
390
+ name="region"
391
+ render={({ field }) => (
392
+ <FormItem>
393
+ <FormLabel>区域</FormLabel>
394
+ <FormControl>
395
+ <RadioGroup
396
+ orientation="horizontal"
397
+ className="flex flex-row gap-4"
398
+ options={[
399
+ { value: 'cn-hangzhou', label: '杭州' },
400
+ { value: 'cn-shanghai', label: '上海' },
401
+ { value: 'cn-beijing', label: '北京' },
402
+ ]}
403
+ value={field.value}
404
+ onValueChange={field.onChange}
405
+ />
406
+ </FormControl>
407
+ </FormItem>
408
+ )}
409
+ />
410
+ <FormField
411
+ control={form.control}
412
+ name="instanceId"
413
+ render={({ field }) => (
414
+ <FormItem>
415
+ <FormLabel>实例 ID</FormLabel>
416
+ <FormControl>
417
+ <Input placeholder="输入实例 ID" {...field} />
418
+ </FormControl>
419
+ </FormItem>
420
+ )}
421
+ />
422
+ <FormField
423
+ control={form.control}
424
+ name="enableMonitor"
425
+ render={({ field }) => (
426
+ <FormItem>
427
+ <FormLabel>启用监控</FormLabel>
428
+ <FormControl>
429
+ <Switch
430
+ checked={field.value}
431
+ onCheckedChange={field.onChange}
432
+ />
433
+ </FormControl>
434
+ </FormItem>
435
+ )}
436
+ />
437
+ <FilterBarActions />
438
+ </FilterBarPanel>
439
+ </FilterBar>
440
+ </FormProvider>
441
+ {Object.keys(result).length > 0 && (
442
+ <pre className="rounded-md bg-muted p-3 text-xs">
443
+ onFilter: {JSON.stringify(result, null, 2)}
444
+ </pre>
445
+ )}
446
+ </div>
447
+ );
448
+ },
449
+ };
450
+
451
+ // ─── Story: PresetUsage ──────────────────────────────────────────────────────
452
+
453
+ export const PresetUsage: Story = {
454
+ name: 'FilterBarPreset 快捷用法',
455
+ render: () => {
456
+ const form = useForm({
457
+ defaultValues: {
458
+ keyword: '',
459
+ status: '',
460
+ dateRange: undefined as DateRange | undefined,
461
+ org: '',
462
+ region: '',
463
+ enableHA: false,
464
+ },
465
+ });
466
+ const [result, setResult] = React.useState<Record<string, any>>({});
467
+
468
+ const inlineFields = (
469
+ <>
470
+ <FormField
471
+ control={form.control}
472
+ name="keyword"
473
+ render={({ field }) => (
474
+ <FormItem className="flex-row items-center gap-2">
475
+ <FormControl>
476
+ <Input placeholder="搜索..." className="w-48" {...field} />
477
+ </FormControl>
478
+ </FormItem>
479
+ )}
480
+ />
481
+ <FormField
482
+ control={form.control}
483
+ name="status"
484
+ render={({ field }) => (
485
+ <FormItem className="flex-row items-center gap-2">
486
+ <FormControl>
487
+ <Select
488
+ options={[
489
+ { value: 'active', label: '活跃' },
490
+ { value: 'inactive', label: '非活跃' },
491
+ { value: 'archived', label: '已归档' },
492
+ ]}
493
+ placeholder="全部状态"
494
+ value={field.value}
495
+ onChange={field.onChange}
496
+ className="w-36"
497
+ />
498
+ </FormControl>
499
+ </FormItem>
500
+ )}
501
+ />
502
+ </>
503
+ );
504
+
505
+ const panelFields = (
506
+ <>
507
+ <FormField
508
+ control={form.control}
509
+ name="dateRange"
510
+ render={({ field }) => (
511
+ <FormItem>
512
+ <FormLabel>起止时间</FormLabel>
513
+ <FormControl>
514
+ <DateRangePicker
515
+ value={field.value}
516
+ onChange={field.onChange}
517
+ placeholders={['起始日期', '结束日期']}
518
+ />
519
+ </FormControl>
520
+ </FormItem>
521
+ )}
522
+ />
523
+ <FormField
524
+ control={form.control}
525
+ name="org"
526
+ render={({ field }) => (
527
+ <FormItem>
528
+ <FormLabel>组织</FormLabel>
529
+ <FormControl>
530
+ <Select
531
+ options={[
532
+ { value: 'dev', label: '研发部' },
533
+ { value: 'ops', label: '运维部' },
534
+ { value: 'test', label: '测试部' },
535
+ ]}
536
+ placeholder="请选择"
537
+ value={field.value}
538
+ onChange={field.onChange}
539
+ />
540
+ </FormControl>
541
+ </FormItem>
542
+ )}
543
+ />
544
+ <FormField
545
+ control={form.control}
546
+ name="region"
547
+ render={({ field }) => (
548
+ <FormItem>
549
+ <FormLabel>区域</FormLabel>
550
+ <FormControl>
551
+ <Select
552
+ options={[
553
+ { value: 'cn-hangzhou', label: '华东1(杭州)' },
554
+ { value: 'cn-shanghai', label: '华东2(上海)' },
555
+ { value: 'cn-beijing', label: '华北2(北京)' },
556
+ ]}
557
+ placeholder="请选择"
558
+ value={field.value}
559
+ onChange={field.onChange}
560
+ />
561
+ </FormControl>
562
+ </FormItem>
563
+ )}
564
+ />
565
+ <FormField
566
+ control={form.control}
567
+ name="enableHA"
568
+ render={({ field }) => (
569
+ <FormItem>
570
+ <FormLabel>高可用</FormLabel>
571
+ <FormControl>
572
+ <Switch
573
+ checked={field.value}
574
+ onCheckedChange={field.onChange}
575
+ checkedChildren="开"
576
+ unCheckedChildren="关"
577
+ />
578
+ </FormControl>
579
+ </FormItem>
580
+ )}
581
+ />
582
+ </>
583
+ );
584
+
585
+ return (
586
+ <div className="w-full space-y-4 p-6">
587
+ <FormProvider {...form}>
588
+ <FilterBarPreset
589
+ form={form}
590
+ inlineFields={inlineFields}
591
+ panelFields={panelFields}
592
+ columns={3}
593
+ onFilter={setResult}
594
+ onReset={() => setResult({})}
595
+ />
596
+ </FormProvider>
597
+ {Object.keys(result).length > 0 && (
598
+ <pre className="rounded-md bg-muted p-3 text-xs">
599
+ onFilter: {JSON.stringify(result, null, 2)}
600
+ </pre>
601
+ )}
602
+ </div>
603
+ );
604
+ },
605
+ };
606
+
607
+ // ─── Story: WithValidation ───────────────────────────────────────────────────
608
+
609
+ const filterSchema = z.object({
610
+ keyword: z.string().optional(),
611
+ org: z.string().min(1, '请选择组织'),
612
+ region: z.string().optional(),
613
+ agreeTerms: z.literal(true, {
614
+ errorMap: () => ({ message: '请勾选同意条款' }),
615
+ }),
616
+ });
617
+
618
+ export const WithValidation: Story = {
619
+ name: '带 Zod 校验',
620
+ render: () => {
621
+ const form = useForm<z.infer<typeof filterSchema>>({
622
+ resolver: zodResolver(filterSchema as never),
623
+ defaultValues: {
624
+ keyword: '',
625
+ org: '',
626
+ region: '',
627
+ agreeTerms: false as unknown as true,
628
+ },
629
+ });
630
+ const [result, setResult] = React.useState<Record<string, any>>({});
631
+
632
+ return (
633
+ <div className="w-full space-y-4 p-6">
634
+ <FormProvider {...form}>
635
+ <FilterBar
636
+ form={form}
637
+ defaultExpanded
638
+ onFilter={setResult}
639
+ onReset={() => setResult({})}
640
+ >
641
+ <FilterBarPanel columns={3}>
642
+ <FormField
643
+ control={form.control}
644
+ name="keyword"
645
+ render={({ field }) => (
646
+ <FormItem>
647
+ <FormLabel>关键词</FormLabel>
648
+ <FormControl>
649
+ <Input placeholder="搜索..." {...field} />
650
+ </FormControl>
651
+ </FormItem>
652
+ )}
653
+ />
654
+ <FormField
655
+ control={form.control}
656
+ name="org"
657
+ render={({ field }) => (
658
+ <FormItem>
659
+ <FormLabel required>组织</FormLabel>
660
+ <FormControl>
661
+ <Select
662
+ options={[
663
+ { value: 'dev', label: '研发部' },
664
+ { value: 'ops', label: '运维部' },
665
+ { value: 'test', label: '测试部' },
666
+ ]}
667
+ placeholder="必填"
668
+ value={field.value}
669
+ onChange={field.onChange}
670
+ error={!!form.formState.errors.org}
671
+ />
672
+ </FormControl>
673
+ <FormMessage />
674
+ </FormItem>
675
+ )}
676
+ />
677
+ <FormField
678
+ control={form.control}
679
+ name="region"
680
+ render={({ field }) => (
681
+ <FormItem>
682
+ <FormLabel>区域</FormLabel>
683
+ <FormControl>
684
+ <Select
685
+ options={[
686
+ { value: 'cn-hangzhou', label: '华东1(杭州)' },
687
+ { value: 'cn-shanghai', label: '华东2(上海)' },
688
+ { value: 'cn-beijing', label: '华北2(北京)' },
689
+ ]}
690
+ placeholder="可选"
691
+ value={field.value}
692
+ onChange={field.onChange}
693
+ />
694
+ </FormControl>
695
+ </FormItem>
696
+ )}
697
+ />
698
+ <FormField
699
+ control={form.control}
700
+ name="agreeTerms"
701
+ render={({ field }) => (
702
+ <FormItem>
703
+ <div className="flex items-center gap-2">
704
+ <FormControl>
705
+ <Checkbox
706
+ checked={field.value}
707
+ onCheckedChange={field.onChange}
708
+ />
709
+ </FormControl>
710
+ <FormLabel>同意数据使用条款</FormLabel>
711
+ </div>
712
+ <FormMessage />
713
+ </FormItem>
714
+ )}
715
+ />
716
+ <FilterBarActions />
717
+ </FilterBarPanel>
718
+ </FilterBar>
719
+ </FormProvider>
720
+ {Object.keys(result).length > 0 && (
721
+ <pre className="rounded-md bg-muted p-3 text-xs">
722
+ onFilter: {JSON.stringify(result, null, 2)}
723
+ </pre>
724
+ )}
725
+ </div>
726
+ );
727
+ },
728
+ };
729
+
730
+ // ─── Story: Responsive ──────────────────────────────────────────────────────
731
+
732
+ export const Responsive: Story = {
733
+ name: '响应式多栏(responsiveColumns)',
734
+ parameters: {
735
+ docs: {
736
+ description: {
737
+ story:
738
+ '传入 `responsiveColumns` 后面板切换为 CSS Grid 响应式布局。5 档断点对齐设计规范 §4.1: `base` <432 / `xs` ≥432 / `s` ≥672 / `m` ≥944 / `l` ≥1200 / `xl` ≥1600。拖动 viewport 观察 1 → 2 → 4 列切换。',
739
+ },
740
+ },
741
+ },
742
+ render: () => {
743
+ const form = useForm({
744
+ defaultValues: {
745
+ keyword: '',
746
+ org: '',
747
+ region: '',
748
+ status: '',
749
+ instanceId: '',
750
+ cpu: '',
751
+ },
752
+ });
753
+ const [result, setResult] = React.useState<Record<string, any>>({});
754
+
755
+ return (
756
+ <div className="w-full space-y-4 p-6">
757
+ <FormProvider {...form}>
758
+ <FilterBar
759
+ form={form}
760
+ defaultExpanded
761
+ onFilter={setResult}
762
+ onReset={() => setResult({})}
763
+ >
764
+ <FilterBarPanel
765
+ responsiveColumns={{ base: 1, xs: 2, m: 3, l: 4 }}
766
+ >
767
+ <FormField
768
+ control={form.control}
769
+ name="keyword"
770
+ render={({ field }) => (
771
+ <FormItem>
772
+ <FormLabel>关键词</FormLabel>
773
+ <FormControl>
774
+ <Input placeholder="输入关键词" {...field} />
775
+ </FormControl>
776
+ </FormItem>
777
+ )}
778
+ />
779
+ <FormField
780
+ control={form.control}
781
+ name="org"
782
+ render={({ field }) => (
783
+ <FormItem>
784
+ <FormLabel>组织</FormLabel>
785
+ <FormControl>
786
+ <Select
787
+ options={[
788
+ { value: 'dev', label: '研发部' },
789
+ { value: 'ops', label: '运维部' },
790
+ { value: 'test', label: '测试部' },
791
+ ]}
792
+ placeholder="请选择"
793
+ value={field.value}
794
+ onChange={field.onChange}
795
+ />
796
+ </FormControl>
797
+ </FormItem>
798
+ )}
799
+ />
800
+ <FormField
801
+ control={form.control}
802
+ name="region"
803
+ render={({ field }) => (
804
+ <FormItem>
805
+ <FormLabel>区域</FormLabel>
806
+ <FormControl>
807
+ <Select
808
+ options={[
809
+ { value: 'cn-hangzhou', label: '华东1(杭州)' },
810
+ { value: 'cn-shanghai', label: '华东2(上海)' },
811
+ { value: 'cn-beijing', label: '华北2(北京)' },
812
+ ]}
813
+ placeholder="请选择"
814
+ value={field.value}
815
+ onChange={field.onChange}
816
+ />
817
+ </FormControl>
818
+ </FormItem>
819
+ )}
820
+ />
821
+ <FormField
822
+ control={form.control}
823
+ name="status"
824
+ render={({ field }) => (
825
+ <FormItem>
826
+ <FormLabel>状态</FormLabel>
827
+ <FormControl>
828
+ <Select
829
+ options={[
830
+ { value: 'running', label: '运行中' },
831
+ { value: 'stopped', label: '已停止' },
832
+ { value: 'error', label: '异常' },
833
+ ]}
834
+ placeholder="请选择"
835
+ value={field.value}
836
+ onChange={field.onChange}
837
+ />
838
+ </FormControl>
839
+ </FormItem>
840
+ )}
841
+ />
842
+ <FormField
843
+ control={form.control}
844
+ name="instanceId"
845
+ render={({ field }) => (
846
+ <FormItem>
847
+ <FormLabel>实例 ID</FormLabel>
848
+ <FormControl>
849
+ <Input placeholder="输入实例 ID" {...field} />
850
+ </FormControl>
851
+ </FormItem>
852
+ )}
853
+ />
854
+ <FormField
855
+ control={form.control}
856
+ name="cpu"
857
+ render={({ field }) => (
858
+ <FormItem>
859
+ <FormLabel>CPU</FormLabel>
860
+ <FormControl>
861
+ <Input placeholder="输入 CPU 核数" {...field} />
862
+ </FormControl>
863
+ </FormItem>
864
+ )}
865
+ />
866
+ <FilterBarActions />
867
+ </FilterBarPanel>
868
+ </FilterBar>
869
+ </FormProvider>
870
+ {Object.keys(result).length > 0 && (
871
+ <pre className="rounded-md bg-muted p-3 text-xs">
872
+ onFilter: {JSON.stringify(result, null, 2)}
873
+ </pre>
874
+ )}
875
+ </div>
876
+ );
877
+ },
878
+ };
879
+
880
+ // ─── Story: RightAligned ─────────────────────────────────────────────────
881
+ export const RightAligned: Story = {
882
+ name: 'Label 右对齐(labelAlign="right")',
883
+ parameters: {
884
+ docs: {
885
+ description: {
886
+ story:
887
+ '`labelAlign="right"` 将面板内所有 `<label>` 右对齐,适用于表单式筛选区。结合 `inputMaxWidth` 可得到经典右对齐表单视觉。',
888
+ },
889
+ },
890
+ },
891
+ render: () => {
892
+ const form = useForm({
893
+ defaultValues: { keyword: '', org: '', region: '', status: '' },
894
+ });
895
+ const [result, setResult] = React.useState<Record<string, any>>({});
896
+
897
+ return (
898
+ <div className="w-full space-y-4 p-6">
899
+ <FormProvider {...form}>
900
+ <FilterBar
901
+ form={form}
902
+ defaultExpanded
903
+ labelAlign="right"
904
+ onFilter={setResult}
905
+ onReset={() => setResult({})}
906
+ >
907
+ <FilterBarPanel columns={2}>
908
+ <FormField
909
+ control={form.control}
910
+ name="keyword"
911
+ render={({ field }) => (
912
+ <FormItem>
913
+ <FormLabel>关键词</FormLabel>
914
+ <FormControl>
915
+ <Input placeholder="输入关键词" {...field} />
916
+ </FormControl>
917
+ </FormItem>
918
+ )}
919
+ />
920
+ <FormField
921
+ control={form.control}
922
+ name="org"
923
+ render={({ field }) => (
924
+ <FormItem>
925
+ <FormLabel>组织</FormLabel>
926
+ <FormControl>
927
+ <Select
928
+ options={[
929
+ { value: 'dev', label: '研发部' },
930
+ { value: 'ops', label: '运维部' },
931
+ ]}
932
+ placeholder="请选择"
933
+ value={field.value}
934
+ onChange={field.onChange}
935
+ />
936
+ </FormControl>
937
+ </FormItem>
938
+ )}
939
+ />
940
+ <FormField
941
+ control={form.control}
942
+ name="region"
943
+ render={({ field }) => (
944
+ <FormItem>
945
+ <FormLabel>区域</FormLabel>
946
+ <FormControl>
947
+ <Select
948
+ options={[
949
+ { value: 'cn-hangzhou', label: '华东1(杭州)' },
950
+ { value: 'cn-beijing', label: '华北2(北京)' },
951
+ ]}
952
+ placeholder="请选择"
953
+ value={field.value}
954
+ onChange={field.onChange}
955
+ />
956
+ </FormControl>
957
+ </FormItem>
958
+ )}
959
+ />
960
+ <FormField
961
+ control={form.control}
962
+ name="status"
963
+ render={({ field }) => (
964
+ <FormItem>
965
+ <FormLabel>运行状态</FormLabel>
966
+ <FormControl>
967
+ <Select
968
+ options={[
969
+ { value: 'running', label: '运行中' },
970
+ { value: 'stopped', label: '已停止' },
971
+ ]}
972
+ placeholder="请选择"
973
+ value={field.value}
974
+ onChange={field.onChange}
975
+ />
976
+ </FormControl>
977
+ </FormItem>
978
+ )}
979
+ />
980
+ <FilterBarActions />
981
+ </FilterBarPanel>
982
+ </FilterBar>
983
+ </FormProvider>
984
+ {Object.keys(result).length > 0 && (
985
+ <pre className="rounded-md bg-muted p-3 text-xs">
986
+ onFilter: {JSON.stringify(result, null, 2)}
987
+ </pre>
988
+ )}
989
+ </div>
990
+ );
991
+ },
992
+ };
993
+
994
+ // ─── Story: WithMaxWidth ─────────────────────────────────────────────────
995
+ export const WithMaxWidth: Story = {
996
+ name: '输入域最大宽度(inputMaxWidth)',
997
+ parameters: {
998
+ docs: {
999
+ description: {
1000
+ story:
1001
+ '`inputMaxWidth` 为面板内每个输入域设置最大宽度(默认多栏 400px / 双栏 600px)。本例显式约束为 320px,防止输入域过宽。',
1002
+ },
1003
+ },
1004
+ },
1005
+ render: () => {
1006
+ const form = useForm({
1007
+ defaultValues: { keyword: '', org: '', region: '' },
1008
+ });
1009
+ const [result, setResult] = React.useState<Record<string, any>>({});
1010
+
1011
+ return (
1012
+ <div className="w-full space-y-4 p-6">
1013
+ <FormProvider {...form}>
1014
+ <FilterBar
1015
+ form={form}
1016
+ defaultExpanded
1017
+ inputMaxWidth={320}
1018
+ onFilter={setResult}
1019
+ onReset={() => setResult({})}
1020
+ >
1021
+ <FilterBarPanel columns={3}>
1022
+ <FormField
1023
+ control={form.control}
1024
+ name="keyword"
1025
+ render={({ field }) => (
1026
+ <FormItem>
1027
+ <FormLabel>关键词</FormLabel>
1028
+ <FormControl>
1029
+ <Input placeholder="输入关键词" {...field} />
1030
+ </FormControl>
1031
+ </FormItem>
1032
+ )}
1033
+ />
1034
+ <FormField
1035
+ control={form.control}
1036
+ name="org"
1037
+ render={({ field }) => (
1038
+ <FormItem>
1039
+ <FormLabel>组织</FormLabel>
1040
+ <FormControl>
1041
+ <Select
1042
+ options={[
1043
+ { value: 'dev', label: '研发部' },
1044
+ { value: 'ops', label: '运维部' },
1045
+ ]}
1046
+ placeholder="请选择"
1047
+ value={field.value}
1048
+ onChange={field.onChange}
1049
+ />
1050
+ </FormControl>
1051
+ </FormItem>
1052
+ )}
1053
+ />
1054
+ <FormField
1055
+ control={form.control}
1056
+ name="region"
1057
+ render={({ field }) => (
1058
+ <FormItem>
1059
+ <FormLabel>区域</FormLabel>
1060
+ <FormControl>
1061
+ <Select
1062
+ options={[
1063
+ { value: 'cn-hangzhou', label: '华东1(杭州)' },
1064
+ { value: 'cn-shanghai', label: '华东2(上海)' },
1065
+ ]}
1066
+ placeholder="请选择"
1067
+ value={field.value}
1068
+ onChange={field.onChange}
1069
+ />
1070
+ </FormControl>
1071
+ </FormItem>
1072
+ )}
1073
+ />
1074
+ <FilterBarActions />
1075
+ </FilterBarPanel>
1076
+ </FilterBar>
1077
+ </FormProvider>
1078
+ {Object.keys(result).length > 0 && (
1079
+ <pre className="rounded-md bg-muted p-3 text-xs">
1080
+ onFilter: {JSON.stringify(result, null, 2)}
1081
+ </pre>
1082
+ )}
1083
+ </div>
1084
+ );
1085
+ },
1086
+ };