@a2v2ai/uikit 0.0.37 → 0.0.38

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 (182) hide show
  1. package/Alert/Alert.stories.tsx +121 -0
  2. package/Alert/Alert.tsx +71 -0
  3. package/AlertDialog/AlertDialog.stories.tsx +665 -0
  4. package/AlertDialog/AlertDialog.tsx +241 -0
  5. package/Avatar/Avatar.stories.tsx +128 -0
  6. package/Avatar/Avatar.tsx +71 -0
  7. package/Badge/Badge.stories.tsx +76 -0
  8. package/Badge/Badge.tsx +39 -0
  9. package/Breadcrumb/Breadcrumb.stories.tsx +231 -0
  10. package/Breadcrumb/Breadcrumb.tsx +114 -0
  11. package/Button/Button.stories.tsx +684 -0
  12. package/Button/Button.tsx +107 -0
  13. package/Calendar/Calendar.stories.tsx +291 -0
  14. package/Calendar/Calendar.tsx +246 -0
  15. package/Card/Card.stories.tsx +136 -0
  16. package/Card/Card.tsx +96 -0
  17. package/Carousel/Carousel.stories.tsx +256 -0
  18. package/Carousel/Carousel.tsx +301 -0
  19. package/ChatBubble/ChatBubble.stories.tsx +339 -0
  20. package/ChatBubble/ChatBubble.tsx +179 -0
  21. package/Checkbox/Checkbox.stories.tsx +137 -0
  22. package/Checkbox/Checkbox.tsx +53 -0
  23. package/DataTable/DataTable.stories.tsx +400 -0
  24. package/DataTable/DataTable.tsx +207 -0
  25. package/Drawer/Drawer.stories.tsx +721 -0
  26. package/Drawer/Drawer.tsx +201 -0
  27. package/DropdownMenu/DropdownMenu.stories.tsx +251 -0
  28. package/DropdownMenu/DropdownMenu.tsx +199 -0
  29. package/ErrorMessage/ErrorMessage.stories.tsx +159 -0
  30. package/ErrorMessage/ErrorMessage.tsx +55 -0
  31. package/Flex/Flex.stories.tsx +390 -0
  32. package/Flex/Flex.tsx +102 -0
  33. package/IconButton/IconButton.stories.tsx +566 -0
  34. package/IconButton/IconButton.tsx +95 -0
  35. package/Input/Input.stories.tsx +566 -0
  36. package/Input/Input.tsx +168 -0
  37. package/InputOTP/InputOTP.stories.tsx +246 -0
  38. package/InputOTP/InputOTP.tsx +127 -0
  39. package/Label/Label.stories.tsx +110 -0
  40. package/Label/Label.tsx +44 -0
  41. package/Loader/Loader.stories.tsx +170 -0
  42. package/Loader/Loader.tsx +62 -0
  43. package/Menubar/Menubar.stories.tsx +382 -0
  44. package/Menubar/Menubar.tsx +274 -0
  45. package/Menubar/index.ts +18 -0
  46. package/Pagination/Pagination.stories.tsx +196 -0
  47. package/Pagination/Pagination.tsx +122 -0
  48. package/Popover/Popover.stories.tsx +133 -0
  49. package/Popover/Popover.tsx +31 -0
  50. package/Progress/Progress.stories.tsx +146 -0
  51. package/Progress/Progress.tsx +67 -0
  52. package/RadioGroup/RadioGroup.stories.tsx +159 -0
  53. package/RadioGroup/RadioGroup.tsx +68 -0
  54. package/ScrollArea/ScrollArea.stories.tsx +136 -0
  55. package/ScrollArea/ScrollArea.tsx +46 -0
  56. package/Select/Select.stories.tsx +378 -0
  57. package/Select/Select.tsx +230 -0
  58. package/Separator/Separator.stories.tsx +110 -0
  59. package/Separator/Separator.tsx +29 -0
  60. package/Sidebar/Sidebar.stories.tsx +340 -0
  61. package/Sidebar/Sidebar.tsx +414 -0
  62. package/Sidebar/index.ts +28 -0
  63. package/Skeleton/Skeleton.stories.tsx +117 -0
  64. package/Skeleton/Skeleton.tsx +16 -0
  65. package/Slider/Slider.stories.tsx +216 -0
  66. package/Slider/Slider.tsx +29 -0
  67. package/Spinner/Spinner.stories.tsx +210 -0
  68. package/Spinner/Spinner.tsx +78 -0
  69. package/Switch/Switch.stories.tsx +146 -0
  70. package/Switch/Switch.tsx +59 -0
  71. package/Table/Table.stories.tsx +510 -0
  72. package/Table/Table.tsx +114 -0
  73. package/Tabs/Tabs.stories.tsx +197 -0
  74. package/Tabs/Tabs.tsx +74 -0
  75. package/Textarea/Textarea.stories.tsx +187 -0
  76. package/Textarea/Textarea.tsx +73 -0
  77. package/Toast/Toast.stories.tsx +285 -0
  78. package/Toast/Toast.tsx +59 -0
  79. package/Tooltip/Tooltip.stories.tsx +463 -0
  80. package/Tooltip/Tooltip.tsx +96 -0
  81. package/Typography/Typography.stories.tsx +425 -0
  82. package/Typography/Typography.tsx +106 -0
  83. package/helpers.ts +5 -0
  84. package/{icons.js → icons.ts} +1 -1
  85. package/index.ts +217 -0
  86. package/lib/typography-types.ts +223 -0
  87. package/lib/utils.ts +15 -0
  88. package/package.json +5 -2
  89. package/tsconfig.json +22 -0
  90. package/Alert/Alert.d.ts +0 -13
  91. package/Alert/Alert.js +0 -25
  92. package/AlertDialog/AlertDialog.d.ts +0 -43
  93. package/AlertDialog/AlertDialog.js +0 -71
  94. package/Avatar/Avatar.d.ts +0 -14
  95. package/Avatar/Avatar.js +0 -25
  96. package/Badge/Badge.d.ts +0 -11
  97. package/Badge/Badge.js +0 -23
  98. package/Breadcrumb/Breadcrumb.d.ts +0 -19
  99. package/Breadcrumb/Breadcrumb.js +0 -23
  100. package/Button/Button.d.ts +0 -23
  101. package/Button/Button.js +0 -52
  102. package/Calendar/Calendar.d.ts +0 -20
  103. package/Calendar/Calendar.js +0 -78
  104. package/Card/Card.d.ts +0 -16
  105. package/Card/Card.js +0 -28
  106. package/Carousel/Carousel.d.ts +0 -37
  107. package/Carousel/Carousel.js +0 -132
  108. package/ChatBubble/ChatBubble.d.ts +0 -33
  109. package/ChatBubble/ChatBubble.js +0 -107
  110. package/Checkbox/Checkbox.d.ts +0 -12
  111. package/Checkbox/Checkbox.js +0 -20
  112. package/DataTable/DataTable.d.ts +0 -35
  113. package/DataTable/DataTable.js +0 -51
  114. package/Drawer/Drawer.d.ts +0 -33
  115. package/Drawer/Drawer.js +0 -55
  116. package/DropdownMenu/DropdownMenu.d.ts +0 -27
  117. package/DropdownMenu/DropdownMenu.js +0 -35
  118. package/ErrorMessage/ErrorMessage.d.ts +0 -27
  119. package/ErrorMessage/ErrorMessage.js +0 -14
  120. package/Flex/Flex.d.ts +0 -31
  121. package/Flex/Flex.js +0 -64
  122. package/IconButton/IconButton.d.ts +0 -23
  123. package/IconButton/IconButton.js +0 -48
  124. package/Input/Input.d.ts +0 -27
  125. package/Input/Input.js +0 -42
  126. package/InputOTP/InputOTP.d.ts +0 -20
  127. package/InputOTP/InputOTP.js +0 -44
  128. package/Label/Label.d.ts +0 -13
  129. package/Label/Label.js +0 -19
  130. package/Loader/Loader.d.ts +0 -21
  131. package/Loader/Loader.js +0 -30
  132. package/Menubar/Menubar.d.ts +0 -26
  133. package/Menubar/Menubar.js +0 -54
  134. package/Menubar/index.d.ts +0 -1
  135. package/Menubar/index.js +0 -1
  136. package/Pagination/Pagination.d.ts +0 -35
  137. package/Pagination/Pagination.js +0 -37
  138. package/Popover/Popover.d.ts +0 -7
  139. package/Popover/Popover.js +0 -10
  140. package/Progress/Progress.d.ts +0 -17
  141. package/Progress/Progress.js +0 -33
  142. package/RadioGroup/RadioGroup.d.ts +0 -13
  143. package/RadioGroup/RadioGroup.js +0 -26
  144. package/ScrollArea/ScrollArea.d.ts +0 -5
  145. package/ScrollArea/ScrollArea.js +0 -11
  146. package/Select/Select.d.ts +0 -29
  147. package/Select/Select.js +0 -50
  148. package/Separator/Separator.d.ts +0 -4
  149. package/Separator/Separator.js +0 -7
  150. package/Sidebar/Sidebar.d.ts +0 -48
  151. package/Sidebar/Sidebar.js +0 -116
  152. package/Sidebar/index.d.ts +0 -2
  153. package/Sidebar/index.js +0 -1
  154. package/Skeleton/Skeleton.d.ts +0 -4
  155. package/Skeleton/Skeleton.js +0 -7
  156. package/Slider/Slider.d.ts +0 -6
  157. package/Slider/Slider.js +0 -7
  158. package/Spinner/Spinner.d.ts +0 -19
  159. package/Spinner/Spinner.js +0 -31
  160. package/Switch/Switch.d.ts +0 -12
  161. package/Switch/Switch.js +0 -30
  162. package/Table/Table.d.ts +0 -10
  163. package/Table/Table.js +0 -20
  164. package/Tabs/Tabs.d.ts +0 -15
  165. package/Tabs/Tabs.js +0 -24
  166. package/Textarea/Textarea.d.ts +0 -19
  167. package/Textarea/Textarea.js +0 -31
  168. package/Toast/Toast.d.ts +0 -12
  169. package/Toast/Toast.js +0 -25
  170. package/Tooltip/Tooltip.d.ts +0 -17
  171. package/Tooltip/Tooltip.js +0 -29
  172. package/Typography/Typography.d.ts +0 -20
  173. package/Typography/Typography.js +0 -43
  174. package/helpers.d.ts +0 -4
  175. package/helpers.js +0 -5
  176. package/icons.d.ts +0 -1
  177. package/index.d.ts +0 -42
  178. package/index.js +0 -45
  179. package/lib/typography-types.d.ts +0 -4
  180. package/lib/typography-types.js +0 -90
  181. package/lib/utils.d.ts +0 -3
  182. package/lib/utils.js +0 -14
@@ -0,0 +1,400 @@
1
+ import type { Meta, StoryObj } from "@storybook/react"
2
+ import { useState } from "react"
3
+ import { DataTable, type DataTableColumn } from "./DataTable"
4
+ import { Badge } from "../Badge/Badge"
5
+
6
+ interface Invoice {
7
+ id: string
8
+ invoice: string
9
+ paymentStatus: "Pending" | "Processing" | "Success" | "Failed"
10
+ totalAmount: string
11
+ paymentMethod: string
12
+ }
13
+
14
+ const invoices: Invoice[] = [
15
+ {
16
+ id: "1",
17
+ invoice: "INV001",
18
+ paymentStatus: "Success",
19
+ totalAmount: "$250.00",
20
+ paymentMethod: "Credit Card",
21
+ },
22
+ {
23
+ id: "2",
24
+ invoice: "INV002",
25
+ paymentStatus: "Pending",
26
+ totalAmount: "$150.00",
27
+ paymentMethod: "PayPal",
28
+ },
29
+ {
30
+ id: "3",
31
+ invoice: "INV003",
32
+ paymentStatus: "Processing",
33
+ totalAmount: "$350.00",
34
+ paymentMethod: "Bank Transfer",
35
+ },
36
+ {
37
+ id: "4",
38
+ invoice: "INV004",
39
+ paymentStatus: "Failed",
40
+ totalAmount: "$450.00",
41
+ paymentMethod: "Credit Card",
42
+ },
43
+ {
44
+ id: "5",
45
+ invoice: "INV005",
46
+ paymentStatus: "Success",
47
+ totalAmount: "$550.00",
48
+ paymentMethod: "PayPal",
49
+ },
50
+ ]
51
+
52
+ const meta: Meta<typeof DataTable> = {
53
+ title: "Components/DataTable",
54
+ component: DataTable,
55
+ parameters: {
56
+ layout: "padded",
57
+ },
58
+ tags: ["autodocs"],
59
+ }
60
+
61
+ export default meta
62
+ type Story = StoryObj<typeof DataTable<Invoice>>
63
+
64
+ const basicColumns: DataTableColumn<Invoice>[] = [
65
+ {
66
+ key: "invoice",
67
+ title: "Invoice",
68
+ dataIndex: "invoice",
69
+ },
70
+ {
71
+ key: "status",
72
+ title: "Status",
73
+ dataIndex: "paymentStatus",
74
+ },
75
+ {
76
+ key: "method",
77
+ title: "Method",
78
+ dataIndex: "paymentMethod",
79
+ },
80
+ {
81
+ key: "amount",
82
+ title: "Amount",
83
+ dataIndex: "totalAmount",
84
+ },
85
+ ]
86
+
87
+ export const Default: Story = {
88
+ render: () => (
89
+ <DataTable columns={basicColumns} dataSource={invoices} rowKey="id" />
90
+ ),
91
+ }
92
+
93
+ const columnsWithRender: DataTableColumn<Invoice>[] = [
94
+ {
95
+ key: "invoice",
96
+ title: "Invoice",
97
+ dataIndex: "invoice",
98
+ render: (value) => <span className="font-medium">{value as string}</span>,
99
+ },
100
+ {
101
+ key: "status",
102
+ title: "Status",
103
+ dataIndex: "paymentStatus",
104
+ render: (value) => {
105
+ const status = value as Invoice["paymentStatus"]
106
+ const variant =
107
+ status === "Success"
108
+ ? "success"
109
+ : status === "Failed"
110
+ ? "destructive"
111
+ : status === "Processing"
112
+ ? "warning"
113
+ : "secondary"
114
+ return <Badge variant={variant}>{status}</Badge>
115
+ },
116
+ },
117
+ {
118
+ key: "method",
119
+ title: "Method",
120
+ dataIndex: "paymentMethod",
121
+ render: (value) => (
122
+ <span className="text-grey-600">{value as string}</span>
123
+ ),
124
+ },
125
+ {
126
+ key: "amount",
127
+ title: "Amount",
128
+ dataIndex: "totalAmount",
129
+ render: (value) => (
130
+ <span className="font-semibold text-main-900">{value as string}</span>
131
+ ),
132
+ },
133
+ ]
134
+
135
+ export const WithCustomRenderers: Story = {
136
+ render: () => (
137
+ <DataTable
138
+ columns={columnsWithRender}
139
+ dataSource={invoices}
140
+ rowKey="id"
141
+ />
142
+ ),
143
+ }
144
+
145
+ const columnsWithWidth: DataTableColumn<Invoice>[] = [
146
+ {
147
+ key: "invoice",
148
+ title: "Invoice",
149
+ dataIndex: "invoice",
150
+ width: 100,
151
+ },
152
+ {
153
+ key: "status",
154
+ title: "Status",
155
+ dataIndex: "paymentStatus",
156
+ width: 120,
157
+ },
158
+ {
159
+ key: "method",
160
+ title: "Payment Method",
161
+ dataIndex: "paymentMethod",
162
+ width: 200,
163
+ },
164
+ {
165
+ key: "amount",
166
+ title: "Total Amount",
167
+ dataIndex: "totalAmount",
168
+ width: 150,
169
+ },
170
+ ]
171
+
172
+ export const WithColumnWidths: Story = {
173
+ render: () => (
174
+ <DataTable columns={columnsWithWidth} dataSource={invoices} rowKey="id" />
175
+ ),
176
+ }
177
+
178
+ const columnsWithTitleRender: DataTableColumn<Invoice>[] = [
179
+ {
180
+ key: "invoice",
181
+ title: "Invoice",
182
+ dataIndex: "invoice",
183
+ titleRender: () => (
184
+ <span className="flex items-center gap-2">
185
+ <span>📄</span>
186
+ <span>Invoice</span>
187
+ </span>
188
+ ),
189
+ },
190
+ {
191
+ key: "status",
192
+ title: "Status",
193
+ dataIndex: "paymentStatus",
194
+ titleRender: () => (
195
+ <span className="flex items-center gap-2">
196
+ <span>🔄</span>
197
+ <span>Status</span>
198
+ </span>
199
+ ),
200
+ render: (value) => {
201
+ const status = value as Invoice["paymentStatus"]
202
+ const variant =
203
+ status === "Success"
204
+ ? "success"
205
+ : status === "Failed"
206
+ ? "destructive"
207
+ : status === "Processing"
208
+ ? "warning"
209
+ : "secondary"
210
+ return <Badge variant={variant}>{status}</Badge>
211
+ },
212
+ },
213
+ {
214
+ key: "method",
215
+ title: "Method",
216
+ dataIndex: "paymentMethod",
217
+ titleRender: () => (
218
+ <span className="flex items-center gap-2">
219
+ <span>💳</span>
220
+ <span>Method</span>
221
+ </span>
222
+ ),
223
+ },
224
+ {
225
+ key: "amount",
226
+ title: "Amount",
227
+ dataIndex: "totalAmount",
228
+ titleRender: () => (
229
+ <span className="flex items-center gap-2">
230
+ <span>💰</span>
231
+ <span>Amount</span>
232
+ </span>
233
+ ),
234
+ },
235
+ ]
236
+
237
+ export const WithCustomTitleRender: Story = {
238
+ render: () => (
239
+ <DataTable
240
+ columns={columnsWithTitleRender}
241
+ dataSource={invoices}
242
+ rowKey="id"
243
+ />
244
+ ),
245
+ }
246
+
247
+ export const WithRowKeyFunction: Story = {
248
+ render: () => (
249
+ <DataTable
250
+ columns={basicColumns}
251
+ dataSource={invoices}
252
+ rowKey={(record) => `invoice-${record.id}`}
253
+ />
254
+ ),
255
+ }
256
+
257
+ export const EmptyState: Story = {
258
+ render: () => (
259
+ <DataTable columns={basicColumns} dataSource={[]} rowKey="id" />
260
+ ),
261
+ }
262
+
263
+ // Generate more invoices for pagination demo
264
+ const manyInvoices: Invoice[] = Array.from({ length: 25 }, (_, i) => ({
265
+ id: String(i + 1),
266
+ invoice: `INV${String(i + 1).padStart(3, "0")}`,
267
+ paymentStatus: (["Success", "Pending", "Processing", "Failed"] as const)[i % 4],
268
+ totalAmount: `$${(100 + i * 50).toFixed(2)}`,
269
+ paymentMethod: (["Credit Card", "PayPal", "Bank Transfer"] as const)[i % 3],
270
+ }))
271
+
272
+ const WithPaginationComponent = () => {
273
+ const [currentPage, setCurrentPage] = useState(1)
274
+ const pageSize = 5
275
+
276
+ const paginatedData = manyInvoices.slice(
277
+ (currentPage - 1) * pageSize,
278
+ currentPage * pageSize
279
+ )
280
+
281
+ return (
282
+ <DataTable
283
+ columns={columnsWithRender}
284
+ dataSource={paginatedData}
285
+ rowKey="id"
286
+ pagination={{
287
+ current: currentPage,
288
+ pageSize,
289
+ total: manyInvoices.length,
290
+ onChange: setCurrentPage,
291
+ }}
292
+ />
293
+ )
294
+ }
295
+
296
+ export const WithPagination: Story = {
297
+ render: () => <WithPaginationComponent />,
298
+ }
299
+
300
+ const WithManyPagesComponent = () => {
301
+ const [currentPage, setCurrentPage] = useState(1)
302
+ const pageSize = 2
303
+
304
+ const paginatedData = manyInvoices.slice(
305
+ (currentPage - 1) * pageSize,
306
+ currentPage * pageSize
307
+ )
308
+
309
+ return (
310
+ <DataTable
311
+ columns={basicColumns}
312
+ dataSource={paginatedData}
313
+ rowKey="id"
314
+ pagination={{
315
+ current: currentPage,
316
+ pageSize,
317
+ total: manyInvoices.length,
318
+ onChange: setCurrentPage,
319
+ }}
320
+ />
321
+ )
322
+ }
323
+
324
+ export const WithManyPages: Story = {
325
+ render: () => <WithManyPagesComponent />,
326
+ }
327
+
328
+ export const WithScroll: Story = {
329
+ render: () => (
330
+ <DataTable
331
+ columns={columnsWithRender}
332
+ dataSource={manyInvoices}
333
+ rowKey="id"
334
+ scroll={{ y: 300 }}
335
+ />
336
+ ),
337
+ }
338
+
339
+ export const WithScrollAndPagination: Story = {
340
+ render: () => {
341
+ const [currentPage, setCurrentPage] = useState(1)
342
+ const pageSize = 10
343
+
344
+ const paginatedData = manyInvoices.slice(
345
+ (currentPage - 1) * pageSize,
346
+ currentPage * pageSize
347
+ )
348
+
349
+ return (
350
+ <DataTable
351
+ columns={columnsWithRender}
352
+ dataSource={paginatedData}
353
+ rowKey="id"
354
+ scroll={{ y: 250 }}
355
+ pagination={{
356
+ current: currentPage,
357
+ pageSize,
358
+ total: manyInvoices.length,
359
+ onChange: setCurrentPage,
360
+ }}
361
+ />
362
+ )
363
+ },
364
+ }
365
+
366
+ export const Loading: Story = {
367
+ render: () => (
368
+ <DataTable
369
+ columns={basicColumns}
370
+ dataSource={[]}
371
+ rowKey="id"
372
+ loading
373
+ />
374
+ ),
375
+ }
376
+
377
+ export const LoadingWithCustomRows: Story = {
378
+ render: () => (
379
+ <DataTable
380
+ columns={columnsWithWidth}
381
+ dataSource={[]}
382
+ rowKey="id"
383
+ loading
384
+ loadingRows={10}
385
+ />
386
+ ),
387
+ }
388
+
389
+ export const LoadingWithScroll: Story = {
390
+ render: () => (
391
+ <DataTable
392
+ columns={basicColumns}
393
+ dataSource={[]}
394
+ rowKey="id"
395
+ loading
396
+ loadingRows={8}
397
+ scroll={{ y: 300 }}
398
+ />
399
+ ),
400
+ }
@@ -0,0 +1,207 @@
1
+ import type { ReactNode } from "react"
2
+ import {
3
+ Table,
4
+ TableBody,
5
+ TableCell,
6
+ TableHead,
7
+ TableHeader,
8
+ TableRow,
9
+ } from "../Table/Table"
10
+ import {
11
+ Pagination,
12
+ PaginationContent,
13
+ PaginationEllipsis,
14
+ PaginationItem,
15
+ PaginationLink,
16
+ PaginationNext,
17
+ PaginationPrevious,
18
+ } from "../Pagination/Pagination"
19
+ import { Skeleton } from "../Skeleton/Skeleton"
20
+
21
+ interface DataTableColumn<T> {
22
+ key: string
23
+ title: string
24
+ titleRender?: () => ReactNode
25
+ dataIndex?: keyof T
26
+ width?: string | number
27
+ render?: (value: unknown, record: T, index: number) => ReactNode
28
+ className?: string
29
+ }
30
+
31
+ interface DataTablePagination {
32
+ current: number
33
+ pageSize: number
34
+ total: number
35
+ onChange: (page: number) => void
36
+ }
37
+
38
+ interface DataTableScroll {
39
+ y?: number | string
40
+ }
41
+
42
+ interface DataTableProps<T extends object> {
43
+ columns: DataTableColumn<T>[]
44
+ dataSource: T[]
45
+ rowKey: keyof T | ((record: T) => string)
46
+ className?: string
47
+ pagination?: DataTablePagination
48
+ scroll?: DataTableScroll
49
+ loading?: boolean
50
+ loadingRows?: number
51
+ }
52
+
53
+ const getPageNumbers = (current: number, totalPages: number): (number | "ellipsis")[] => {
54
+ const pages: (number | "ellipsis")[] = []
55
+
56
+ if (totalPages <= 7) {
57
+ for (let i = 1; i <= totalPages; i++) {
58
+ pages.push(i)
59
+ }
60
+ return pages
61
+ }
62
+
63
+ // Always show first page
64
+ pages.push(1)
65
+
66
+ if (current <= 3) {
67
+ // Near the start
68
+ pages.push(2, 3, 4, "ellipsis", totalPages)
69
+ } else if (current >= totalPages - 2) {
70
+ // Near the end
71
+ pages.push("ellipsis", totalPages - 3, totalPages - 2, totalPages - 1, totalPages)
72
+ } else {
73
+ // In the middle
74
+ pages.push("ellipsis", current - 1, current, current + 1, "ellipsis", totalPages)
75
+ }
76
+
77
+ return pages
78
+ }
79
+
80
+ const DataTable = <T extends object>({
81
+ columns,
82
+ dataSource,
83
+ rowKey,
84
+ className,
85
+ pagination,
86
+ scroll,
87
+ loading,
88
+ loadingRows = 5,
89
+ }: DataTableProps<T>) => {
90
+ const getRowKey = (record: T): string => {
91
+ if (typeof rowKey === "function") {
92
+ return rowKey(record)
93
+ }
94
+ return String(record[rowKey])
95
+ }
96
+
97
+ const getCellValue = (
98
+ record: T,
99
+ column: DataTableColumn<T>,
100
+ index: number
101
+ ): ReactNode => {
102
+ const value = column.dataIndex ? record[column.dataIndex] : undefined
103
+ if (column.render) {
104
+ return column.render(value, record, index)
105
+ }
106
+ return value as ReactNode
107
+ }
108
+
109
+ const totalPages = pagination ? Math.ceil(pagination.total / pagination.pageSize) : 0
110
+ const pageNumbers = pagination ? getPageNumbers(pagination.current, totalPages) : []
111
+
112
+ const tableContent = (
113
+ <Table className={className}>
114
+ <TableHeader className={scroll?.y ? "sticky top-0 z-10" : undefined}>
115
+ <TableRow>
116
+ {columns.map((column) => (
117
+ <TableHead
118
+ key={column.key}
119
+ className={column.className}
120
+ style={{ width: column.width }}
121
+ >
122
+ {column.titleRender ? column.titleRender() : column.title}
123
+ </TableHead>
124
+ ))}
125
+ </TableRow>
126
+ </TableHeader>
127
+ <TableBody>
128
+ {loading
129
+ ? Array.from({ length: loadingRows }).map((_, rowIndex) => (
130
+ <TableRow key={`skeleton-${rowIndex}`}>
131
+ {columns.map((column) => (
132
+ <TableCell key={column.key}>
133
+ <Skeleton className="h-4 w-full" />
134
+ </TableCell>
135
+ ))}
136
+ </TableRow>
137
+ ))
138
+ : dataSource.map((record, index) => (
139
+ <TableRow key={getRowKey(record)}>
140
+ {columns.map((column) => (
141
+ <TableCell key={column.key}>
142
+ {getCellValue(record, column, index)}
143
+ </TableCell>
144
+ ))}
145
+ </TableRow>
146
+ ))}
147
+ </TableBody>
148
+ </Table>
149
+ )
150
+
151
+ return (
152
+ <div className="w-full">
153
+ {scroll?.y ? (
154
+ <div
155
+ className="relative w-full overflow-auto"
156
+ style={{ maxHeight: scroll.y }}
157
+ >
158
+ {tableContent}
159
+ </div>
160
+ ) : (
161
+ tableContent
162
+ )}
163
+
164
+ {pagination && totalPages > 1 && (
165
+ <div className="mt-4">
166
+ <Pagination>
167
+ <PaginationContent>
168
+ <PaginationItem>
169
+ <PaginationPrevious
170
+ disabled={pagination.current === 1}
171
+ onClick={() => pagination.onChange(pagination.current - 1)}
172
+ />
173
+ </PaginationItem>
174
+
175
+ {pageNumbers.map((page, index) => (
176
+ <PaginationItem key={index}>
177
+ {page === "ellipsis" ? (
178
+ <PaginationEllipsis />
179
+ ) : (
180
+ <PaginationLink
181
+ isActive={page === pagination.current}
182
+ onClick={() => pagination.onChange(page)}
183
+ >
184
+ {page}
185
+ </PaginationLink>
186
+ )}
187
+ </PaginationItem>
188
+ ))}
189
+
190
+ <PaginationItem>
191
+ <PaginationNext
192
+ disabled={pagination.current === totalPages}
193
+ onClick={() => pagination.onChange(pagination.current + 1)}
194
+ />
195
+ </PaginationItem>
196
+ </PaginationContent>
197
+ </Pagination>
198
+ </div>
199
+ )}
200
+ </div>
201
+ )
202
+ }
203
+
204
+ DataTable.displayName = "DataTable"
205
+
206
+ export { DataTable }
207
+ export type { DataTableColumn, DataTableProps, DataTablePagination, DataTableScroll }