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