@teamix-evo/ui 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (270) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +336 -0
  3. package/_data.json +12 -0
  4. package/manifest.json +1688 -0
  5. package/package.json +90 -0
  6. package/src/components/accordion/accordion.meta.md +87 -0
  7. package/src/components/accordion/accordion.stories.tsx +67 -0
  8. package/src/components/accordion/accordion.tsx +58 -0
  9. package/src/components/affix/affix.meta.md +80 -0
  10. package/src/components/affix/affix.stories.tsx +57 -0
  11. package/src/components/affix/affix.tsx +97 -0
  12. package/src/components/alert/alert.meta.md +101 -0
  13. package/src/components/alert/alert.stories.tsx +93 -0
  14. package/src/components/alert/alert.tsx +132 -0
  15. package/src/components/alert-dialog/alert-dialog.meta.md +107 -0
  16. package/src/components/alert-dialog/alert-dialog.stories.tsx +81 -0
  17. package/src/components/alert-dialog/alert-dialog.tsx +136 -0
  18. package/src/components/anchor/anchor.meta.md +87 -0
  19. package/src/components/anchor/anchor.stories.tsx +74 -0
  20. package/src/components/anchor/anchor.tsx +130 -0
  21. package/src/components/app/app.meta.md +86 -0
  22. package/src/components/app/app.stories.tsx +62 -0
  23. package/src/components/app/app.tsx +58 -0
  24. package/src/components/aspect-ratio/aspect-ratio.meta.md +81 -0
  25. package/src/components/aspect-ratio/aspect-ratio.stories.tsx +59 -0
  26. package/src/components/aspect-ratio/aspect-ratio.tsx +22 -0
  27. package/src/components/auto-complete/auto-complete.meta.md +102 -0
  28. package/src/components/auto-complete/auto-complete.stories.tsx +93 -0
  29. package/src/components/auto-complete/auto-complete.tsx +205 -0
  30. package/src/components/avatar/avatar.meta.md +94 -0
  31. package/src/components/avatar/avatar.stories.tsx +80 -0
  32. package/src/components/avatar/avatar.tsx +126 -0
  33. package/src/components/badge/badge.meta.md +119 -0
  34. package/src/components/badge/badge.stories.tsx +153 -0
  35. package/src/components/badge/badge.tsx +210 -0
  36. package/src/components/breadcrumb/breadcrumb.meta.md +107 -0
  37. package/src/components/breadcrumb/breadcrumb.stories.tsx +84 -0
  38. package/src/components/breadcrumb/breadcrumb.tsx +122 -0
  39. package/src/components/button/button.meta.md +98 -0
  40. package/src/components/button/button.stories.tsx +235 -0
  41. package/src/components/button/button.tsx +160 -0
  42. package/src/components/button-group/button-group.meta.md +92 -0
  43. package/src/components/button-group/button-group.stories.tsx +90 -0
  44. package/src/components/button-group/button-group.tsx +75 -0
  45. package/src/components/calendar/calendar.meta.md +118 -0
  46. package/src/components/calendar/calendar.stories.tsx +68 -0
  47. package/src/components/calendar/calendar.tsx +107 -0
  48. package/src/components/card/card.meta.md +117 -0
  49. package/src/components/card/card.stories.tsx +112 -0
  50. package/src/components/card/card.tsx +222 -0
  51. package/src/components/carousel/carousel.meta.md +117 -0
  52. package/src/components/carousel/carousel.stories.tsx +84 -0
  53. package/src/components/carousel/carousel.tsx +224 -0
  54. package/src/components/cascader/cascader.meta.md +110 -0
  55. package/src/components/cascader/cascader.stories.tsx +108 -0
  56. package/src/components/cascader/cascader.tsx +198 -0
  57. package/src/components/checkbox/checkbox.meta.md +99 -0
  58. package/src/components/checkbox/checkbox.stories.tsx +130 -0
  59. package/src/components/checkbox/checkbox.tsx +125 -0
  60. package/src/components/collapsible/collapsible.meta.md +80 -0
  61. package/src/components/collapsible/collapsible.stories.tsx +35 -0
  62. package/src/components/collapsible/collapsible.tsx +18 -0
  63. package/src/components/color-picker/color-picker.meta.md +84 -0
  64. package/src/components/color-picker/color-picker.stories.tsx +80 -0
  65. package/src/components/color-picker/color-picker.tsx +160 -0
  66. package/src/components/combobox/combobox.meta.md +93 -0
  67. package/src/components/combobox/combobox.stories.tsx +55 -0
  68. package/src/components/combobox/combobox.tsx +130 -0
  69. package/src/components/command/command.meta.md +104 -0
  70. package/src/components/command/command.stories.tsx +59 -0
  71. package/src/components/command/command.tsx +147 -0
  72. package/src/components/context-menu/context-menu.meta.md +90 -0
  73. package/src/components/context-menu/context-menu.stories.tsx +46 -0
  74. package/src/components/context-menu/context-menu.tsx +191 -0
  75. package/src/components/data-table/data-table.meta.md +149 -0
  76. package/src/components/data-table/data-table.stories.tsx +125 -0
  77. package/src/components/data-table/data-table.tsx +185 -0
  78. package/src/components/date-picker/date-picker.meta.md +106 -0
  79. package/src/components/date-picker/date-picker.stories.tsx +58 -0
  80. package/src/components/date-picker/date-picker.tsx +156 -0
  81. package/src/components/descriptions/descriptions.meta.md +78 -0
  82. package/src/components/descriptions/descriptions.stories.tsx +60 -0
  83. package/src/components/descriptions/descriptions.tsx +129 -0
  84. package/src/components/dialog/dialog.meta.md +105 -0
  85. package/src/components/dialog/dialog.stories.tsx +93 -0
  86. package/src/components/dialog/dialog.tsx +128 -0
  87. package/src/components/drawer/drawer.meta.md +96 -0
  88. package/src/components/drawer/drawer.stories.tsx +54 -0
  89. package/src/components/drawer/drawer.tsx +114 -0
  90. package/src/components/dropdown-menu/dropdown-menu.meta.md +103 -0
  91. package/src/components/dropdown-menu/dropdown-menu.stories.tsx +112 -0
  92. package/src/components/dropdown-menu/dropdown-menu.tsx +195 -0
  93. package/src/components/empty/empty.meta.md +81 -0
  94. package/src/components/empty/empty.stories.tsx +46 -0
  95. package/src/components/empty/empty.tsx +47 -0
  96. package/src/components/field/field.meta.md +116 -0
  97. package/src/components/field/field.stories.tsx +117 -0
  98. package/src/components/field/field.tsx +164 -0
  99. package/src/components/flex/flex.meta.md +94 -0
  100. package/src/components/flex/flex.stories.tsx +112 -0
  101. package/src/components/flex/flex.tsx +122 -0
  102. package/src/components/float-button/float-button.meta.md +87 -0
  103. package/src/components/float-button/float-button.stories.tsx +78 -0
  104. package/src/components/float-button/float-button.tsx +143 -0
  105. package/src/components/form/form.meta.md +131 -0
  106. package/src/components/form/form.stories.tsx +122 -0
  107. package/src/components/form/form.tsx +194 -0
  108. package/src/components/grid/grid.meta.md +87 -0
  109. package/src/components/grid/grid.stories.tsx +99 -0
  110. package/src/components/grid/grid.tsx +130 -0
  111. package/src/components/hover-card/hover-card.meta.md +92 -0
  112. package/src/components/hover-card/hover-card.stories.tsx +68 -0
  113. package/src/components/hover-card/hover-card.tsx +29 -0
  114. package/src/components/image/image.meta.md +94 -0
  115. package/src/components/image/image.stories.tsx +55 -0
  116. package/src/components/image/image.tsx +138 -0
  117. package/src/components/input/input.meta.md +109 -0
  118. package/src/components/input/input.stories.tsx +117 -0
  119. package/src/components/input/input.tsx +213 -0
  120. package/src/components/input-group/input-group.meta.md +92 -0
  121. package/src/components/input-group/input-group.stories.tsx +88 -0
  122. package/src/components/input-group/input-group.tsx +107 -0
  123. package/src/components/input-number/input-number.meta.md +91 -0
  124. package/src/components/input-number/input-number.stories.tsx +87 -0
  125. package/src/components/input-number/input-number.tsx +210 -0
  126. package/src/components/input-otp/input-otp.meta.md +105 -0
  127. package/src/components/input-otp/input-otp.stories.tsx +65 -0
  128. package/src/components/input-otp/input-otp.tsx +97 -0
  129. package/src/components/item/item.meta.md +116 -0
  130. package/src/components/item/item.stories.tsx +113 -0
  131. package/src/components/item/item.tsx +171 -0
  132. package/src/components/kbd/kbd.meta.md +85 -0
  133. package/src/components/kbd/kbd.stories.tsx +70 -0
  134. package/src/components/kbd/kbd.tsx +81 -0
  135. package/src/components/label/label.meta.md +91 -0
  136. package/src/components/label/label.stories.tsx +87 -0
  137. package/src/components/label/label.tsx +66 -0
  138. package/src/components/masonry/masonry.meta.md +85 -0
  139. package/src/components/masonry/masonry.stories.tsx +66 -0
  140. package/src/components/masonry/masonry.tsx +59 -0
  141. package/src/components/mentions/mentions.meta.md +89 -0
  142. package/src/components/mentions/mentions.stories.tsx +75 -0
  143. package/src/components/mentions/mentions.tsx +237 -0
  144. package/src/components/menubar/menubar.meta.md +100 -0
  145. package/src/components/menubar/menubar.stories.tsx +81 -0
  146. package/src/components/menubar/menubar.tsx +232 -0
  147. package/src/components/native-select/native-select.meta.md +88 -0
  148. package/src/components/native-select/native-select.stories.tsx +80 -0
  149. package/src/components/native-select/native-select.tsx +54 -0
  150. package/src/components/navigation-menu/navigation-menu.meta.md +108 -0
  151. package/src/components/navigation-menu/navigation-menu.stories.tsx +112 -0
  152. package/src/components/navigation-menu/navigation-menu.tsx +125 -0
  153. package/src/components/notification/notification.meta.md +91 -0
  154. package/src/components/notification/notification.stories.tsx +96 -0
  155. package/src/components/notification/notification.tsx +84 -0
  156. package/src/components/pagination/pagination.meta.md +127 -0
  157. package/src/components/pagination/pagination.stories.tsx +62 -0
  158. package/src/components/pagination/pagination.tsx +285 -0
  159. package/src/components/popconfirm/popconfirm.meta.md +109 -0
  160. package/src/components/popconfirm/popconfirm.stories.tsx +76 -0
  161. package/src/components/popconfirm/popconfirm.tsx +134 -0
  162. package/src/components/popover/popover.meta.md +97 -0
  163. package/src/components/popover/popover.stories.tsx +82 -0
  164. package/src/components/popover/popover.tsx +55 -0
  165. package/src/components/progress/progress.meta.md +86 -0
  166. package/src/components/progress/progress.stories.tsx +75 -0
  167. package/src/components/progress/progress.tsx +195 -0
  168. package/src/components/radio-group/radio-group.meta.md +103 -0
  169. package/src/components/radio-group/radio-group.stories.tsx +77 -0
  170. package/src/components/radio-group/radio-group.tsx +78 -0
  171. package/src/components/rate/rate.meta.md +87 -0
  172. package/src/components/rate/rate.stories.tsx +81 -0
  173. package/src/components/rate/rate.tsx +153 -0
  174. package/src/components/resizable/resizable.meta.md +92 -0
  175. package/src/components/resizable/resizable.stories.tsx +104 -0
  176. package/src/components/resizable/resizable.tsx +56 -0
  177. package/src/components/result/result.meta.md +90 -0
  178. package/src/components/result/result.stories.tsx +71 -0
  179. package/src/components/result/result.tsx +91 -0
  180. package/src/components/scroll-area/scroll-area.meta.md +84 -0
  181. package/src/components/scroll-area/scroll-area.stories.tsx +41 -0
  182. package/src/components/scroll-area/scroll-area.tsx +51 -0
  183. package/src/components/segmented/segmented.meta.md +103 -0
  184. package/src/components/segmented/segmented.stories.tsx +101 -0
  185. package/src/components/segmented/segmented.tsx +138 -0
  186. package/src/components/select/select.meta.md +110 -0
  187. package/src/components/select/select.stories.tsx +100 -0
  188. package/src/components/select/select.tsx +188 -0
  189. package/src/components/separator/separator.meta.md +74 -0
  190. package/src/components/separator/separator.stories.tsx +71 -0
  191. package/src/components/separator/separator.tsx +104 -0
  192. package/src/components/sheet/sheet.meta.md +97 -0
  193. package/src/components/sheet/sheet.stories.tsx +82 -0
  194. package/src/components/sheet/sheet.tsx +139 -0
  195. package/src/components/sidebar/sidebar.meta.md +131 -0
  196. package/src/components/sidebar/sidebar.stories.tsx +82 -0
  197. package/src/components/sidebar/sidebar.tsx +351 -0
  198. package/src/components/skeleton/skeleton.meta.md +95 -0
  199. package/src/components/skeleton/skeleton.stories.tsx +79 -0
  200. package/src/components/skeleton/skeleton.tsx +144 -0
  201. package/src/components/slider/slider.meta.md +94 -0
  202. package/src/components/slider/slider.stories.tsx +69 -0
  203. package/src/components/slider/slider.tsx +86 -0
  204. package/src/components/sonner/sonner.meta.md +96 -0
  205. package/src/components/sonner/sonner.stories.tsx +91 -0
  206. package/src/components/sonner/sonner.tsx +40 -0
  207. package/src/components/space/space.meta.md +94 -0
  208. package/src/components/space/space.stories.tsx +94 -0
  209. package/src/components/space/space.tsx +106 -0
  210. package/src/components/spinner/spinner.meta.md +76 -0
  211. package/src/components/spinner/spinner.stories.tsx +71 -0
  212. package/src/components/spinner/spinner.tsx +64 -0
  213. package/src/components/statistic/statistic.meta.md +99 -0
  214. package/src/components/statistic/statistic.stories.tsx +71 -0
  215. package/src/components/statistic/statistic.tsx +197 -0
  216. package/src/components/steps/steps.meta.md +102 -0
  217. package/src/components/steps/steps.stories.tsx +75 -0
  218. package/src/components/steps/steps.tsx +170 -0
  219. package/src/components/switch/switch.meta.md +92 -0
  220. package/src/components/switch/switch.stories.tsx +75 -0
  221. package/src/components/switch/switch.tsx +101 -0
  222. package/src/components/table/table.meta.md +95 -0
  223. package/src/components/table/table.stories.tsx +75 -0
  224. package/src/components/table/table.tsx +122 -0
  225. package/src/components/tabs/tabs.meta.md +98 -0
  226. package/src/components/tabs/tabs.stories.tsx +70 -0
  227. package/src/components/tabs/tabs.tsx +119 -0
  228. package/src/components/tag/tag.meta.md +94 -0
  229. package/src/components/tag/tag.stories.tsx +77 -0
  230. package/src/components/tag/tag.tsx +185 -0
  231. package/src/components/textarea/textarea.meta.md +83 -0
  232. package/src/components/textarea/textarea.stories.tsx +63 -0
  233. package/src/components/textarea/textarea.tsx +113 -0
  234. package/src/components/time-picker/time-picker.meta.md +83 -0
  235. package/src/components/time-picker/time-picker.stories.tsx +59 -0
  236. package/src/components/time-picker/time-picker.tsx +94 -0
  237. package/src/components/timeline/timeline.meta.md +102 -0
  238. package/src/components/timeline/timeline.stories.tsx +104 -0
  239. package/src/components/timeline/timeline.tsx +147 -0
  240. package/src/components/toggle/toggle.meta.md +88 -0
  241. package/src/components/toggle/toggle.stories.tsx +66 -0
  242. package/src/components/toggle/toggle.tsx +53 -0
  243. package/src/components/toggle-group/toggle-group.meta.md +90 -0
  244. package/src/components/toggle-group/toggle-group.stories.tsx +83 -0
  245. package/src/components/toggle-group/toggle-group.tsx +78 -0
  246. package/src/components/tooltip/tooltip.meta.md +99 -0
  247. package/src/components/tooltip/tooltip.stories.tsx +71 -0
  248. package/src/components/tooltip/tooltip.tsx +93 -0
  249. package/src/components/tour/tour.meta.md +116 -0
  250. package/src/components/tour/tour.stories.tsx +66 -0
  251. package/src/components/tour/tour.tsx +242 -0
  252. package/src/components/transfer/transfer.meta.md +90 -0
  253. package/src/components/transfer/transfer.stories.tsx +68 -0
  254. package/src/components/transfer/transfer.tsx +251 -0
  255. package/src/components/tree/tree.meta.md +111 -0
  256. package/src/components/tree/tree.stories.tsx +109 -0
  257. package/src/components/tree/tree.tsx +367 -0
  258. package/src/components/tree-select/tree-select.meta.md +100 -0
  259. package/src/components/tree-select/tree-select.stories.tsx +80 -0
  260. package/src/components/tree-select/tree-select.tsx +171 -0
  261. package/src/components/typography/typography.meta.md +102 -0
  262. package/src/components/typography/typography.stories.tsx +115 -0
  263. package/src/components/typography/typography.tsx +245 -0
  264. package/src/components/upload/upload.meta.md +111 -0
  265. package/src/components/upload/upload.stories.tsx +75 -0
  266. package/src/components/upload/upload.tsx +265 -0
  267. package/src/components/watermark/watermark.meta.md +95 -0
  268. package/src/components/watermark/watermark.stories.tsx +78 -0
  269. package/src/components/watermark/watermark.tsx +165 -0
  270. package/src/utils/cn.ts +6 -0
@@ -0,0 +1,125 @@
1
+ import * as React from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+ import { ArrowUpDown } from 'lucide-react';
4
+ import { DataTable, type ColumnDef } from './data-table';
5
+ import { Button } from '@/components/button/button';
6
+ import { Checkbox } from '@/components/checkbox/checkbox';
7
+ import { Badge } from '@/components/badge/badge';
8
+
9
+ interface User {
10
+ id: string;
11
+ name: string;
12
+ email: string;
13
+ status: 'active' | 'invited' | 'disabled';
14
+ amount: number;
15
+ }
16
+
17
+ const data: User[] = Array.from({ length: 35 }).map((_, i) => ({
18
+ id: `u-${i + 1}`,
19
+ name: ['Alice', 'Bob', 'Carol', 'David', 'Eve'][i % 5] + ' ' + (i + 1),
20
+ email: `user${i + 1}@example.com`,
21
+ status:
22
+ (['active', 'invited', 'disabled'] as const)[i % 3] ??
23
+ ('active' as const),
24
+ amount: Math.round(Math.random() * 1000),
25
+ }));
26
+
27
+ const columns: ColumnDef<User>[] = [
28
+ {
29
+ id: 'select',
30
+ header: ({ table }) => (
31
+ <Checkbox
32
+ checked={
33
+ table.getIsAllPageRowsSelected()
34
+ ? true
35
+ : table.getIsSomePageRowsSelected()
36
+ ? 'indeterminate'
37
+ : false
38
+ }
39
+ onCheckedChange={(v) => table.toggleAllPageRowsSelected(!!v)}
40
+ aria-label="全选"
41
+ />
42
+ ),
43
+ cell: ({ row }) => (
44
+ <Checkbox
45
+ checked={row.getIsSelected()}
46
+ onCheckedChange={(v) => row.toggleSelected(!!v)}
47
+ aria-label="选中行"
48
+ />
49
+ ),
50
+ },
51
+ { accessorKey: 'name', header: '姓名' },
52
+ {
53
+ accessorKey: 'email',
54
+ header: ({ column }) => (
55
+ <Button
56
+ variant="ghost"
57
+ size="sm"
58
+ onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
59
+ >
60
+ 邮箱
61
+ <ArrowUpDown className="ml-1" />
62
+ </Button>
63
+ ),
64
+ },
65
+ {
66
+ accessorKey: 'status',
67
+ header: '状态',
68
+ cell: ({ row }) => {
69
+ const s = row.original.status;
70
+ return s === 'active' ? (
71
+ <Badge variant="success">活跃</Badge>
72
+ ) : s === 'invited' ? (
73
+ <Badge>已邀请</Badge>
74
+ ) : (
75
+ <Badge variant="destructive">已禁用</Badge>
76
+ );
77
+ },
78
+ },
79
+ {
80
+ accessorKey: 'amount',
81
+ header: ({ column }) => (
82
+ <Button
83
+ variant="ghost"
84
+ size="sm"
85
+ onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
86
+ className="float-right"
87
+ >
88
+ 金额
89
+ <ArrowUpDown className="ml-1" />
90
+ </Button>
91
+ ),
92
+ cell: ({ row }) => (
93
+ <div className="text-right tabular-nums">¥{row.original.amount}</div>
94
+ ),
95
+ },
96
+ ];
97
+
98
+ const meta: Meta<typeof DataTable<User, unknown>> = {
99
+ title: '数据展示 · Data Display/DataTable',
100
+ component: DataTable,
101
+ tags: ['autodocs'],
102
+ parameters: { layout: 'fullscreen' },
103
+ };
104
+
105
+ export default meta;
106
+ type Story = StoryObj<typeof DataTable<User, unknown>>;
107
+
108
+ export const Default: Story = {
109
+ render: () => {
110
+ const [selected, setSelected] = React.useState<User[]>([]);
111
+ return (
112
+ <div className="p-6">
113
+ <DataTable
114
+ columns={columns}
115
+ data={data}
116
+ pagination={{ pageSize: 8 }}
117
+ onRowSelectionChange={setSelected}
118
+ />
119
+ <p className="mt-3 text-sm text-muted-foreground">
120
+ 已选 {selected.length} 行
121
+ </p>
122
+ </div>
123
+ );
124
+ },
125
+ };
@@ -0,0 +1,185 @@
1
+ import * as React from 'react';
2
+ import {
3
+ flexRender,
4
+ getCoreRowModel,
5
+ getFilteredRowModel,
6
+ getPaginationRowModel,
7
+ getSortedRowModel,
8
+ useReactTable,
9
+ type ColumnDef,
10
+ type ColumnFiltersState,
11
+ type RowSelectionState,
12
+ type SortingState,
13
+ type Table as ReactTable,
14
+ type Updater,
15
+ type VisibilityState,
16
+ } from '@tanstack/react-table';
17
+
18
+ import { cn } from '@/utils/cn';
19
+ import {
20
+ Table,
21
+ TableBody,
22
+ TableCell,
23
+ TableHead,
24
+ TableHeader,
25
+ TableRow,
26
+ } from '@/components/table/table';
27
+ import { SimplePagination } from '@/components/pagination/pagination';
28
+
29
+ export interface DataTableProps<TData, TValue> {
30
+ /** 列定义(TanStack ColumnDef)。 */
31
+ columns: ColumnDef<TData, TValue>[];
32
+ /** 行数据。 */
33
+ data: TData[];
34
+ /**
35
+ * 是否启用分页(antd `pagination` 并集)。
36
+ * 传 `true` 用默认配置;传对象自定义 pageSize / siblingCount。
37
+ * @default true
38
+ */
39
+ pagination?: boolean | { pageSize?: number; siblingCount?: number };
40
+ /**
41
+ * 是否启用排序(列定义里的 `enableSorting` 也要打开;默认列已开)。
42
+ * @default true
43
+ */
44
+ sorting?: boolean;
45
+ /**
46
+ * 是否启用列过滤(每列 `header` 配 input 单独使用;此 flag 控制 columnFilters 状态)。
47
+ * @default true
48
+ */
49
+ filtering?: boolean;
50
+ /**
51
+ * 行选择回调(antd `rowSelection.onChange` 并集)。传入即启用 selection。
52
+ * Selected rows 的原始 data 通过 `getRowId(row)` 推断,默认是 row.index。
53
+ */
54
+ onRowSelectionChange?: (selectedRows: TData[]) => void;
55
+ /** 空数据时显示的内容。 */
56
+ emptyText?: React.ReactNode;
57
+ /** 容器 className。 */
58
+ className?: string;
59
+ }
60
+
61
+ /**
62
+ * 高阶数据表 — 在基础 Table 之上叠加 TanStack 的 sorting / filtering / pagination /
63
+ * rowSelection 能力,等价 antd `Table` 的核心交互合集。
64
+ *
65
+ * 业务方需要更细粒度时(可控 state、virtualizer、column resizing 等),直接用
66
+ * `useReactTable` + 基础 Table primitives 自行拼装。
67
+ */
68
+ function DataTable<TData, TValue>({
69
+ columns,
70
+ data,
71
+ pagination = true,
72
+ sorting: enableSorting = true,
73
+ filtering: enableFiltering = true,
74
+ onRowSelectionChange,
75
+ emptyText = '暂无数据',
76
+ className,
77
+ }: DataTableProps<TData, TValue>) {
78
+ const [sorting, setSorting] = React.useState<SortingState>([]);
79
+ const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
80
+ [],
81
+ );
82
+ const [rowSelection, setRowSelection] = React.useState<RowSelectionState>({});
83
+ const [columnVisibility] = React.useState<VisibilityState>({});
84
+
85
+ const handleSelectionChange = (updater: Updater<RowSelectionState>) => {
86
+ setRowSelection(updater);
87
+ };
88
+
89
+ const pageCfg =
90
+ typeof pagination === 'object' ? pagination : { pageSize: 10 };
91
+ const pageSize = pageCfg.pageSize ?? 10;
92
+
93
+ const table = useReactTable({
94
+ data,
95
+ columns,
96
+ state: { sorting, columnFilters, columnVisibility, rowSelection },
97
+ onSortingChange: enableSorting ? setSorting : undefined,
98
+ onColumnFiltersChange: enableFiltering ? setColumnFilters : undefined,
99
+ onRowSelectionChange: onRowSelectionChange
100
+ ? handleSelectionChange
101
+ : undefined,
102
+ enableRowSelection: !!onRowSelectionChange,
103
+ getCoreRowModel: getCoreRowModel(),
104
+ getSortedRowModel: enableSorting ? getSortedRowModel() : undefined,
105
+ getFilteredRowModel: enableFiltering ? getFilteredRowModel() : undefined,
106
+ getPaginationRowModel: pagination ? getPaginationRowModel() : undefined,
107
+ initialState: pagination ? { pagination: { pageSize } } : undefined,
108
+ });
109
+
110
+ // Surface selected raw rows whenever rowSelection changes
111
+ React.useEffect(() => {
112
+ if (!onRowSelectionChange) return;
113
+ const selectedRows = table
114
+ .getSelectedRowModel()
115
+ .rows.map((r) => r.original);
116
+ onRowSelectionChange(selectedRows);
117
+ }, [rowSelection]);
118
+
119
+ const total = table.getFilteredRowModel().rows.length;
120
+
121
+ return (
122
+ <div className={cn('space-y-3', className)}>
123
+ <div className="rounded-md border">
124
+ <Table>
125
+ <TableHeader>
126
+ {table.getHeaderGroups().map((headerGroup) => (
127
+ <TableRow key={headerGroup.id}>
128
+ {headerGroup.headers.map((header) => (
129
+ <TableHead key={header.id}>
130
+ {header.isPlaceholder
131
+ ? null
132
+ : flexRender(
133
+ header.column.columnDef.header,
134
+ header.getContext(),
135
+ )}
136
+ </TableHead>
137
+ ))}
138
+ </TableRow>
139
+ ))}
140
+ </TableHeader>
141
+ <TableBody>
142
+ {table.getRowModel().rows.length > 0 ? (
143
+ table.getRowModel().rows.map((row) => (
144
+ <TableRow
145
+ key={row.id}
146
+ data-state={row.getIsSelected() && 'selected'}
147
+ >
148
+ {row.getVisibleCells().map((cell) => (
149
+ <TableCell key={cell.id}>
150
+ {flexRender(
151
+ cell.column.columnDef.cell,
152
+ cell.getContext(),
153
+ )}
154
+ </TableCell>
155
+ ))}
156
+ </TableRow>
157
+ ))
158
+ ) : (
159
+ <TableRow>
160
+ <TableCell
161
+ colSpan={columns.length}
162
+ className="h-24 text-center text-muted-foreground"
163
+ >
164
+ {emptyText}
165
+ </TableCell>
166
+ </TableRow>
167
+ )}
168
+ </TableBody>
169
+ </Table>
170
+ </div>
171
+ {pagination ? (
172
+ <SimplePagination
173
+ total={total}
174
+ pageSize={pageSize}
175
+ siblingCount={pageCfg.siblingCount}
176
+ current={table.getState().pagination.pageIndex + 1}
177
+ onChange={(p) => table.setPageIndex(p - 1)}
178
+ />
179
+ ) : null}
180
+ </div>
181
+ );
182
+ }
183
+
184
+ export { DataTable };
185
+ export type { ColumnDef, ReactTable };
@@ -0,0 +1,106 @@
1
+ ---
2
+ id: date-picker
3
+ name: DatePicker
4
+ type: component
5
+ category: form
6
+ since: 0.1.0
7
+ package: "@teamix-evo/ui"
8
+ ---
9
+
10
+ # DatePicker
11
+
12
+ 日期选择器 — `Calendar + Popover` 复合,提供两种形态:
13
+ - `<DatePicker />` — 单日(对应 antd `DatePicker`)
14
+ - `<DateRangePicker />` — 范围(对应 antd `DatePicker.RangePicker`)
15
+
16
+ 格式化用 `date-fns`(轻量、模块化、tree-shake 友好)。
17
+
18
+ ## When to use
19
+
20
+ - 表单中的单日 / 范围选择
21
+ - 报表 / 数据筛选的时间维度
22
+ - 预约 / 计划 / 日程
23
+
24
+ ## When NOT to use
25
+
26
+ - 仅显示日期(只读)→ `<time>{format(d, '...')}</time>`
27
+ - 内联日历 → 直接用 `Calendar`
28
+ - 时间选择(含时分秒)→ 配 `Input type="time"` 或 v0.x TimePicker
29
+
30
+ ## Props
31
+
32
+ > 以下表格由 `pnpm --filter @teamix-evo/ui gen:meta` 自动生成。
33
+
34
+ <!-- auto:props:begin -->
35
+ | 名称 | 类型 | 默认值 | 必填 | 说明 |
36
+ | --- | --- | --- | --- | --- |
37
+ | `value` | `Date` | – | – | 受控值 — 当前选中日期。 |
38
+ | `onChange` | `(value: Date \| undefined) => void` | – | – | 选中日期变化回调。 |
39
+ | `placeholder` | `string` | `"选择日期"` | – | 占位文本(未选时)。 |
40
+ | `format` | `string` | `"yyyy-MM-dd"` | – | date-fns format 字符串。 |
41
+ | `size` | `'sm' \| 'default' \| 'lg'` | – | – | 触发器尺寸。 |
42
+ | `className` | `string` | – | – | 触发器 className(传给底层 Button)。 |
43
+ | `disabled` | `boolean` | – | – | 触发器是否禁用。 |
44
+ | `disabledDates` | `(date: Date) => boolean` | – | – | 不可选日期 predicate(透传到 Calendar `disabled`)。 |
45
+ <!-- auto:props:end -->
46
+
47
+ ## 依赖
48
+
49
+ > 以下表格由 `pnpm --filter @teamix-evo/ui gen:meta` 自动生成,数据源是 [`manifest.json`](../../../manifest.json)。**手工编辑 marker 之间的内容会在下次生成时被覆盖**。
50
+
51
+ <!-- auto:deps:begin -->
52
+ ### 同库依赖
53
+
54
+ > `teamix-evo ui add date-picker` 时,以下 entry 会被自动连带安装(无需手动 add)。
55
+
56
+ | Entry | 类型 | 描述 |
57
+ | --- | --- | --- |
58
+ | `cn` | util | Tailwind className 合并工具(clsx + tailwind-merge) |
59
+ | `button` | component | 通用按钮 — shadcn 实现 + antd 功能扩展(loading / icon / shape / block / dashed variant) |
60
+ | `calendar` | component | 日期选择面板 — react-day-picker,mode=single|multiple|range,与 design tokens 对齐 |
61
+ | `popover` | component | 可交互浮层 — Radix Popover + antd arrow 并集 |
62
+
63
+ ### npm 依赖
64
+
65
+ > 业务侧需要先 `pnpm add` / `npm install` 这些包。CLI 在 `ui add` 完成后会列出此提示。
66
+
67
+ ```bash
68
+ pnpm add date-fns@^3.0.0 react-day-picker@^9.0.0 lucide-react@^0.460.0
69
+ ```
70
+ <!-- auto:deps:end -->
71
+
72
+ > `DateRangePicker` 的 props 详见 [`date-picker.tsx`](./date-picker.tsx) 的 `DateRangePickerProps` interface。
73
+
74
+ ## AI 生成纪律
75
+
76
+ - **`format` 用 date-fns 字符串**:`yyyy-MM-dd` / `yyyy 年 M 月 d 日` 等;**不要**用旧 moment 写法(`YYYY-MM-DD` 大写在 date-fns 是年的"week year",非"calendar year")
77
+ - **`disabledDates` 而非外部过滤**:把禁用判断 push 给 Calendar,避免选完再被刷掉的尴尬
78
+ - **范围选择默认 2 个月**:`numberOfMonths={2}` 是行业惯例(antd / shadcn / Material 都是)
79
+ - **小尺寸的范围选择需要更宽容器**:`<DateRangePicker />` 默认 w-72,不够的话 className 加宽
80
+ - **空态文案**:`placeholder` 简短,**不要**写 "请选择 / Please select" 之类无信息词;直接 "选择日期" / "下单时间"
81
+
82
+ ## Examples
83
+
84
+ ```tsx
85
+ import { DatePicker, DateRangePicker } from '@/components/ui/date-picker';
86
+ import * as React from 'react';
87
+
88
+ // 单日
89
+ const [d, setD] = React.useState<Date>();
90
+ <DatePicker value={d} onChange={setD} placeholder="选择日期" />
91
+
92
+ // 自定义格式
93
+ <DatePicker value={d} onChange={setD} format="yyyy 年 M 月 d 日" />
94
+
95
+ // 禁用过去
96
+ import { startOfDay } from 'date-fns';
97
+ <DatePicker
98
+ value={d}
99
+ onChange={setD}
100
+ disabledDates={(date) => date < startOfDay(new Date())}
101
+ />
102
+
103
+ // 范围
104
+ const [r, setR] = React.useState<{ from?: Date; to?: Date }>();
105
+ <DateRangePicker value={r} onChange={setR} placeholder="选择范围" />
106
+ ```
@@ -0,0 +1,58 @@
1
+ import * as React from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+ import type { DateRange } from 'react-day-picker';
4
+ import { DatePicker, DateRangePicker } from './date-picker';
5
+
6
+ const meta: Meta<typeof DatePicker> = {
7
+ title: '表单与输入 · Form/DatePicker',
8
+ component: DatePicker,
9
+ tags: ['autodocs'],
10
+ parameters: {
11
+ docs: {
12
+ description: {
13
+ component:
14
+ '日期选择器 — 输入框式触发器 + 弹层日历的组合,支持单日(`DatePicker`)和范围(`DateRangePicker`)两种模式。Popover + Calendar(react-day-picker v9)封装,日期格式由 `format` 控制(date-fns 语法),`size` 三档对齐 Input。能力对齐 antd DatePicker,视觉走 OpenTrek semantic tokens,所有样式来自 `@teamix-evo/design`,无 mock。',
15
+ },
16
+ },
17
+ },
18
+ argTypes: {
19
+ placeholder: { control: 'text' },
20
+ format: { control: 'text' },
21
+ size: { control: 'inline-radio', options: ['sm', 'default', 'lg'] },
22
+ disabled: { control: 'boolean' },
23
+ },
24
+ args: { placeholder: '选择日期', format: 'yyyy-MM-dd', size: 'default' },
25
+ };
26
+
27
+ export default meta;
28
+ type Story = StoryObj<typeof DatePicker>;
29
+
30
+ export const Single: Story = {
31
+ render: (args) => {
32
+ const [d, setD] = React.useState<Date | undefined>();
33
+ return <DatePicker {...args} value={d} onChange={setD} />;
34
+ },
35
+ };
36
+
37
+ export const ChineseFormat: Story = {
38
+ parameters: { controls: { disable: true } },
39
+ render: () => {
40
+ const [d, setD] = React.useState<Date | undefined>(new Date());
41
+ return (
42
+ <DatePicker
43
+ value={d}
44
+ onChange={setD}
45
+ format="yyyy 年 M 月 d 日"
46
+ placeholder="预约日期"
47
+ />
48
+ );
49
+ },
50
+ };
51
+
52
+ export const Range: StoryObj<typeof DateRangePicker> = {
53
+ parameters: { controls: { disable: true } },
54
+ render: () => {
55
+ const [r, setR] = React.useState<DateRange | undefined>();
56
+ return <DateRangePicker value={r} onChange={setR} />;
57
+ },
58
+ };
@@ -0,0 +1,156 @@
1
+ import * as React from 'react';
2
+ import { format } from 'date-fns';
3
+ import { Calendar as CalendarIcon } from 'lucide-react';
4
+ import type { DateRange } from 'react-day-picker';
5
+
6
+ import { cn } from '@/utils/cn';
7
+ import { Button } from '@/components/button/button';
8
+ import { Calendar } from '@/components/calendar/calendar';
9
+ import {
10
+ Popover,
11
+ PopoverContent,
12
+ PopoverTrigger,
13
+ } from '@/components/popover/popover';
14
+
15
+ export interface DatePickerProps {
16
+ /** 受控值 — 当前选中日期。 */
17
+ value?: Date;
18
+ /** 选中日期变化回调。 */
19
+ onChange?: (value: Date | undefined) => void;
20
+ /** 占位文本(未选时)。 @default "选择日期" */
21
+ placeholder?: string;
22
+ /** date-fns format 字符串。 @default "yyyy-MM-dd" */
23
+ format?: string;
24
+ /** 触发器尺寸。 */
25
+ size?: 'sm' | 'default' | 'lg';
26
+ /** 触发器 className(传给底层 Button)。 */
27
+ className?: string;
28
+ /** 触发器是否禁用。 */
29
+ disabled?: boolean;
30
+ /** 不可选日期 predicate(透传到 Calendar `disabled`)。 */
31
+ disabledDates?: (date: Date) => boolean;
32
+ }
33
+
34
+ const DatePicker = React.forwardRef<HTMLButtonElement, DatePickerProps>(
35
+ (
36
+ {
37
+ value,
38
+ onChange,
39
+ placeholder = '选择日期',
40
+ format: fmt = 'yyyy-MM-dd',
41
+ size = 'default',
42
+ className,
43
+ disabled,
44
+ disabledDates,
45
+ },
46
+ ref,
47
+ ) => (
48
+ <Popover>
49
+ <PopoverTrigger asChild>
50
+ <Button
51
+ ref={ref}
52
+ variant="outline"
53
+ size={size}
54
+ disabled={disabled}
55
+ className={cn(
56
+ 'w-60 justify-start text-left font-normal',
57
+ !value && 'text-muted-foreground',
58
+ className,
59
+ )}
60
+ icon={<CalendarIcon />}
61
+ >
62
+ {value ? format(value, fmt) : placeholder}
63
+ </Button>
64
+ </PopoverTrigger>
65
+ <PopoverContent className="w-auto p-0">
66
+ <Calendar
67
+ mode="single"
68
+ selected={value}
69
+ onSelect={onChange}
70
+ disabled={disabledDates}
71
+ initialFocus
72
+ />
73
+ </PopoverContent>
74
+ </Popover>
75
+ ),
76
+ );
77
+ DatePicker.displayName = 'DatePicker';
78
+
79
+ // ─── DateRangePicker(antd RangePicker 并集)───────────────────────────────
80
+
81
+ export interface DateRangePickerProps {
82
+ /** 受控值 — 当前选中范围。 */
83
+ value?: DateRange;
84
+ /** 范围变化回调。 */
85
+ onChange?: (value: DateRange | undefined) => void;
86
+ /** 占位文本。 @default "选择日期范围" */
87
+ placeholder?: string;
88
+ /** date-fns format 字符串。 @default "yyyy-MM-dd" */
89
+ format?: string;
90
+ /** 触发器尺寸。 */
91
+ size?: 'sm' | 'default' | 'lg';
92
+ /** 触发器 className。 */
93
+ className?: string;
94
+ /** 是否禁用。 */
95
+ disabled?: boolean;
96
+ /** 不可选日期 predicate。 */
97
+ disabledDates?: (date: Date) => boolean;
98
+ /** 同时显示几个月历。 @default 2 */
99
+ numberOfMonths?: number;
100
+ }
101
+
102
+ const DateRangePicker = React.forwardRef<
103
+ HTMLButtonElement,
104
+ DateRangePickerProps
105
+ >(
106
+ (
107
+ {
108
+ value,
109
+ onChange,
110
+ placeholder = '选择日期范围',
111
+ format: fmt = 'yyyy-MM-dd',
112
+ size = 'default',
113
+ className,
114
+ disabled,
115
+ disabledDates,
116
+ numberOfMonths = 2,
117
+ },
118
+ ref,
119
+ ) => (
120
+ <Popover>
121
+ <PopoverTrigger asChild>
122
+ <Button
123
+ ref={ref}
124
+ variant="outline"
125
+ size={size}
126
+ disabled={disabled}
127
+ className={cn(
128
+ 'w-72 justify-start text-left font-normal',
129
+ !value?.from && 'text-muted-foreground',
130
+ className,
131
+ )}
132
+ icon={<CalendarIcon />}
133
+ >
134
+ {value?.from
135
+ ? value.to
136
+ ? `${format(value.from, fmt)} ~ ${format(value.to, fmt)}`
137
+ : format(value.from, fmt)
138
+ : placeholder}
139
+ </Button>
140
+ </PopoverTrigger>
141
+ <PopoverContent className="w-auto p-0">
142
+ <Calendar
143
+ mode="range"
144
+ selected={value}
145
+ onSelect={onChange}
146
+ disabled={disabledDates}
147
+ numberOfMonths={numberOfMonths}
148
+ initialFocus
149
+ />
150
+ </PopoverContent>
151
+ </Popover>
152
+ ),
153
+ );
154
+ DateRangePicker.displayName = 'DateRangePicker';
155
+
156
+ export { DatePicker, DateRangePicker };