@boxcustodia/library 2.0.0-alpha.12 → 2.0.0-alpha.14
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.
- package/dist/index.cjs.js +1 -138
- package/dist/index.d.ts +1087 -720
- package/dist/index.es.js +7011 -56097
- package/dist/theme.css +1 -1
- package/package.json +34 -26
- package/src/__doc__/Examples.tsx +1 -1
- package/src/__doc__/Intro.mdx +3 -3
- package/src/__doc__/Tabs.mdx +112 -0
- package/src/__doc__/V2.mdx +1246 -0
- package/src/components/accordion/accordion.stories.tsx +143 -0
- package/src/components/accordion/accordion.tsx +135 -0
- package/src/components/accordion/index.ts +1 -0
- package/src/components/alert/alert.stories.tsx +24 -4
- package/src/components/alert/alert.tsx +17 -9
- package/src/components/alert-dialog/alert-dialog.stories.tsx +24 -0
- package/src/components/alert-dialog/alert-dialog.test.tsx +1 -1
- package/src/components/alert-dialog/alert-dialog.tsx +58 -10
- package/src/components/auto-complete/auto-complete.stories.tsx +616 -200
- package/src/components/auto-complete/auto-complete.tsx +420 -68
- package/src/components/auto-complete/index.ts +0 -1
- package/src/components/avatar/avatar.stories.tsx +162 -21
- package/src/components/avatar/avatar.tsx +79 -20
- package/src/components/button/button.stories.tsx +219 -294
- package/src/components/button/button.test.tsx +10 -17
- package/src/components/button/button.tsx +78 -19
- package/src/components/button/components/base-button.tsx +30 -53
- package/src/components/button/index.ts +0 -1
- package/src/components/calendar/calendar.stories.tsx +1 -1
- package/src/components/calendar/calendar.tsx +4 -4
- package/src/components/card/card.stories.tsx +141 -69
- package/src/components/card/card.tsx +155 -54
- package/src/components/center/center.stories.tsx +22 -39
- package/src/components/checkbox/checkbox.stories.tsx +25 -5
- package/src/components/checkbox/checkbox.tsx +76 -15
- package/src/components/checkbox-group/checkbox-group.stories.tsx +116 -28
- package/src/components/checkbox-group/checkbox-group.tsx +84 -3
- package/src/components/combobox/combobox.stories.tsx +33 -23
- package/src/components/combobox/combobox.tsx +99 -77
- package/src/components/date-picker/date-input.stories.tsx +14 -6
- package/src/components/date-picker/date-input.tsx +2 -2
- package/src/components/date-picker/date-picker.model.ts +13 -4
- package/src/components/date-picker/date-picker.stories.tsx +38 -12
- package/src/components/date-picker/date-picker.tsx +28 -14
- package/src/components/dialog/dialog.stories.tsx +18 -0
- package/src/components/dialog/dialog.test.tsx +1 -1
- package/src/components/dialog/dialog.tsx +51 -20
- package/src/components/divider/divider.stories.tsx +126 -51
- package/src/components/divider/divider.tsx +16 -16
- package/src/components/dropzone/dropzone.stories.tsx +71 -90
- package/src/components/dropzone/dropzone.tsx +383 -105
- package/src/components/dropzone/index.ts +0 -1
- package/src/components/empty/empty.stories.tsx +165 -0
- package/src/components/empty/empty.tsx +156 -0
- package/src/components/empty/index.ts +1 -0
- package/src/components/field/field.stories.tsx +227 -4
- package/src/components/field/field.tsx +77 -42
- package/src/components/form/form.stories.tsx +320 -197
- package/src/components/form/form.tsx +3 -23
- package/src/components/index.ts +2 -6
- package/src/components/input/input.stories.tsx +5 -5
- package/src/components/input/input.tsx +4 -4
- package/src/components/kbd/kbd.stories.tsx +1 -0
- package/src/components/label/label.stories.tsx +16 -0
- package/src/components/label/label.tsx +13 -2
- package/src/components/loader/loader.stories.tsx +7 -5
- package/src/components/loader/loader.tsx +8 -3
- package/src/components/menu/menu-primitives.tsx +207 -196
- package/src/components/menu/menu.stories.tsx +276 -146
- package/src/components/menu/menu.tsx +146 -54
- package/src/components/number-input/number-input.stories.tsx +27 -4
- package/src/components/number-input/number-input.test.tsx +2 -2
- package/src/components/number-input/number-input.tsx +31 -33
- package/src/components/otp/index.ts +1 -0
- package/src/components/otp/otp.stories.tsx +209 -0
- package/src/components/otp/otp.tsx +100 -0
- package/src/components/pagination/index.ts +1 -0
- package/src/components/pagination/pagination.model.ts +2 -0
- package/src/components/pagination/pagination.stories.tsx +154 -59
- package/src/components/pagination/pagination.test.tsx +122 -57
- package/src/components/pagination/pagination.tsx +575 -77
- package/src/components/password/password.stories.tsx +18 -3
- package/src/components/password/password.tsx +29 -9
- package/src/components/popover/popover.stories.tsx +26 -5
- package/src/components/popover/popover.tsx +15 -23
- package/src/components/progress/progress.stories.tsx +1 -0
- package/src/components/radio-group/index.ts +1 -0
- package/src/components/radio-group/radio-group.stories.tsx +251 -0
- package/src/components/radio-group/radio-group.tsx +212 -0
- package/src/components/scroll-area/scroll-area.stories.tsx +1 -0
- package/src/components/select/select.stories.tsx +118 -19
- package/src/components/select/select.tsx +67 -62
- package/src/components/skeleton/skeleton.stories.tsx +1 -0
- package/src/components/stack/stack.stories.tsx +179 -89
- package/src/components/stack/stack.tsx +2 -2
- package/src/components/stepper/index.ts +1 -1
- package/src/components/stepper/stepper.stories.tsx +767 -83
- package/src/components/stepper/stepper.test.tsx +18 -18
- package/src/components/stepper/stepper.tsx +554 -0
- package/src/components/switch/switch.stories.tsx +15 -1
- package/src/components/switch/switch.tsx +17 -4
- package/src/components/table/index.ts +0 -2
- package/src/components/table/table.stories.tsx +131 -18
- package/src/components/table/table.test.tsx +1 -1
- package/src/components/table/table.tsx +183 -77
- package/src/components/tabs/tabs.stories.tsx +373 -155
- package/src/components/tabs/tabs.test.tsx +12 -12
- package/src/components/tabs/tabs.tsx +72 -149
- package/src/components/tag/index.ts +0 -1
- package/src/components/tag/tag.stories.tsx +155 -120
- package/src/components/tag/tag.tsx +47 -95
- package/src/components/textarea/textarea.stories.tsx +8 -22
- package/src/components/textarea/textarea.tsx +17 -79
- package/src/components/timeline/timeline.stories.tsx +323 -42
- package/src/components/timeline/timeline.tsx +359 -132
- package/src/components/toast/toast.stories.tsx +1 -0
- package/src/components/tooltip/tooltip.tsx +11 -9
- package/src/components/tree/index.ts +0 -1
- package/src/components/tree/tree.stories.tsx +365 -408
- package/src/components/tree/tree.test.tsx +163 -0
- package/src/components/tree/tree.tsx +212 -36
- package/src/hooks/useAsync/__doc__/useAsync.stories.tsx +5 -5
- package/src/hooks/useClipboard/__doc__/useClipboard.stories.tsx +1 -3
- package/src/hooks/useDebounceCallback/__doc__/useDebouncedCallback.stories.tsx +6 -6
- package/src/hooks/useDocumentTitle/__doc__/useDocumentTitle.stories.tsx +1 -1
- package/src/hooks/useEventListener/__test__/useEventListener.test.tsx +1 -1
- package/src/hooks/useLocalStorage/__doc__/useLocalStorage.stories.tsx +1 -1
- package/src/hooks/usePagination/usePagination.tsx +36 -24
- package/src/styles/theme.css +1 -1
- package/src/utils/form.tsx +67 -37
- package/src/utils/index.ts +1 -1
- package/src/__doc__/Migration.mdx +0 -475
- package/src/components/auto-complete/auto-complete-primitives.tsx +0 -155
- package/src/components/background-image/background-image.stories.tsx +0 -21
- package/src/components/background-image/background-image.test.tsx +0 -29
- package/src/components/background-image/background-image.tsx +0 -23
- package/src/components/background-image/index.ts +0 -1
- package/src/components/button/button.variants.ts +0 -44
- package/src/components/button/components/loader-overlay.tsx +0 -21
- package/src/components/button/components/loading-icon.tsx +0 -47
- package/src/components/dropzone/upload-primitives.tsx +0 -310
- package/src/components/dropzone/use-dropzone.ts +0 -122
- package/src/components/empty-state/empty-state.stories.tsx +0 -56
- package/src/components/empty-state/empty-state.tsx +0 -39
- package/src/components/empty-state/index.ts +0 -1
- package/src/components/heading/heading.stories.tsx +0 -74
- package/src/components/heading/heading.tsx +0 -28
- package/src/components/heading/heading.variants.ts +0 -27
- package/src/components/heading/index.ts +0 -1
- package/src/components/kbd/kbd.variants.ts +0 -26
- package/src/components/menu/util/render-menu-item.tsx +0 -54
- package/src/components/multi-select/hooks/use-multi-select.ts +0 -66
- package/src/components/multi-select/index.ts +0 -1
- package/src/components/multi-select/multi-select.stories.tsx +0 -294
- package/src/components/multi-select/multi-select.tsx +0 -300
- package/src/components/multi-select/multi-select.variants.ts +0 -22
- package/src/components/pagination/components/pagination-option.tsx +0 -27
- package/src/components/show/index.ts +0 -1
- package/src/components/show/show.stories.tsx +0 -197
- package/src/components/show/show.test.tsx +0 -41
- package/src/components/show/show.tsx +0 -16
- package/src/components/stepper/Stepper.tsx +0 -190
- package/src/components/stepper/context/stepper-context.tsx +0 -11
- package/src/components/table/table-primitives.tsx +0 -122
- package/src/components/table/table.model.ts +0 -20
- package/src/components/table-pagination/index.ts +0 -2
- package/src/components/table-pagination/table-pagination.model.ts +0 -2
- package/src/components/table-pagination/table-pagination.stories.tsx +0 -23
- package/src/components/table-pagination/table-pagination.test.tsx +0 -32
- package/src/components/table-pagination/table-pagination.tsx +0 -108
- package/src/components/tabs/context/tabs-context.tsx +0 -14
- package/src/components/tag/tag.variants.ts +0 -31
- package/src/components/timeline/timeline-status.ts +0 -5
- package/src/components/tree/hooks/use-controllable-tree-state.ts +0 -80
- package/src/components/tree/tree-primitives.tsx +0 -126
|
@@ -1,11 +1,26 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
2
|
import { ImageIcon } from "lucide-react";
|
|
3
3
|
import { useMemo } from "react";
|
|
4
|
-
import { Avatar, Checkbox,
|
|
4
|
+
import { Avatar, Checkbox, Tag } from "../../components";
|
|
5
5
|
import { useSelection } from "../../hooks";
|
|
6
|
+
import {
|
|
7
|
+
ColumnDef,
|
|
8
|
+
Table,
|
|
9
|
+
TableBody,
|
|
10
|
+
TableCell,
|
|
11
|
+
TableHead,
|
|
12
|
+
TableHeader,
|
|
13
|
+
TableRoot,
|
|
14
|
+
TableRow,
|
|
15
|
+
} from "./table";
|
|
6
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Data table with typed column definitions. `onRowClick` and `onRowDoubleClick`
|
|
19
|
+
* fire at the row level — not per cell. For per-cell events or full markup control,
|
|
20
|
+
* compose with the exported primitives directly.
|
|
21
|
+
*/
|
|
7
22
|
const meta: Meta<typeof Table> = {
|
|
8
|
-
title: "
|
|
23
|
+
title: "Components/Table",
|
|
9
24
|
component: Table,
|
|
10
25
|
};
|
|
11
26
|
|
|
@@ -44,7 +59,7 @@ const data: User[] = [
|
|
|
44
59
|
},
|
|
45
60
|
{
|
|
46
61
|
id: 4,
|
|
47
|
-
name: "
|
|
62
|
+
name: "Smith",
|
|
48
63
|
photo: "https://i.pravatar.cc/300?un=1234589",
|
|
49
64
|
age: 15,
|
|
50
65
|
gender: "female",
|
|
@@ -71,7 +86,6 @@ const columns: ColumnDef<User>[] = [
|
|
|
71
86
|
key: "age",
|
|
72
87
|
cell: (row) => {
|
|
73
88
|
const isAdult = row.age > 18;
|
|
74
|
-
|
|
75
89
|
return (
|
|
76
90
|
<Tag variant={isAdult ? "secondary" : "error"}>
|
|
77
91
|
{isAdult ? "Adult" : "Minor"}
|
|
@@ -87,31 +101,34 @@ const columns: ColumnDef<User>[] = [
|
|
|
87
101
|
|
|
88
102
|
export const Default: Story<User> = {
|
|
89
103
|
args: {
|
|
90
|
-
columns
|
|
91
|
-
data
|
|
104
|
+
columns,
|
|
105
|
+
data,
|
|
92
106
|
className: "table-fixed",
|
|
93
107
|
},
|
|
94
108
|
};
|
|
95
109
|
|
|
96
110
|
/**
|
|
97
|
-
*
|
|
111
|
+
* Set a max height on `classNames.container` to clip the body and enable vertical
|
|
112
|
+
* scroll. The header stays sticky via `position: sticky` on `<th>`.
|
|
98
113
|
*
|
|
99
114
|
* ```tsx
|
|
100
|
-
* <Table
|
|
101
|
-
* ....
|
|
102
|
-
* containerClassName="max-h-[500px]"
|
|
103
|
-
* />
|
|
115
|
+
* <Table classNames={{ container: "max-h-[500px]" }} ... />
|
|
104
116
|
* ```
|
|
105
117
|
*/
|
|
106
118
|
export const Scrollable: Story<User> = {
|
|
107
119
|
args: {
|
|
108
|
-
columns
|
|
120
|
+
columns,
|
|
109
121
|
data: [...data, ...data, ...data],
|
|
110
122
|
className: "table-fixed",
|
|
111
|
-
|
|
123
|
+
classNames: { container: "max-h-[500px]" },
|
|
112
124
|
},
|
|
113
125
|
};
|
|
114
126
|
|
|
127
|
+
/**
|
|
128
|
+
* `onRowClick` and `selected` wire directly to `useSelection`. `getRowKey` is
|
|
129
|
+
* required here because `columnsAdapter` rebuilds the columns array on each render —
|
|
130
|
+
* without stable keys React reconciles by index and checkbox state drifts.
|
|
131
|
+
*/
|
|
115
132
|
export const Selection: Story<User> = {
|
|
116
133
|
render: () => {
|
|
117
134
|
const {
|
|
@@ -126,7 +143,7 @@ export const Selection: Story<User> = {
|
|
|
126
143
|
const columnsAdapter = (): ColumnDef<User>[] => [
|
|
127
144
|
{
|
|
128
145
|
key: "id",
|
|
129
|
-
width:
|
|
146
|
+
width: 40,
|
|
130
147
|
header: () => (
|
|
131
148
|
<Checkbox
|
|
132
149
|
name="selectAll"
|
|
@@ -155,15 +172,111 @@ export const Selection: Story<User> = {
|
|
|
155
172
|
className="table-fixed"
|
|
156
173
|
onRowClick={toggle}
|
|
157
174
|
selected={isSelected}
|
|
175
|
+
getRowKey={(row) => row.id}
|
|
158
176
|
/>
|
|
159
177
|
);
|
|
160
178
|
},
|
|
161
179
|
};
|
|
162
180
|
|
|
163
|
-
export const
|
|
181
|
+
export const DoubleClick: Story<User> = {
|
|
164
182
|
args: {
|
|
165
|
-
columns
|
|
166
|
-
data
|
|
167
|
-
|
|
183
|
+
columns,
|
|
184
|
+
data,
|
|
185
|
+
onRowDoubleClick: (row: User) => alert(`Double-clicked: ${row.name}`),
|
|
168
186
|
},
|
|
169
187
|
};
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* `classNames` expone los slots `container`, `thead`, `tbody`, `tr`, `th` y `td`.
|
|
191
|
+
* `tr` aplica sólo a las filas del body — la fila del header queda intacta.
|
|
192
|
+
*/
|
|
193
|
+
export const WithClassNames: Story<User> = {
|
|
194
|
+
args: {
|
|
195
|
+
columns,
|
|
196
|
+
data,
|
|
197
|
+
classNames: {
|
|
198
|
+
thead: "bg-muted/50",
|
|
199
|
+
tr: "odd:bg-muted/20",
|
|
200
|
+
th: "uppercase text-xs tracking-wider",
|
|
201
|
+
td: "text-muted-foreground",
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Use the exported primitives when the composite `Table` is not enough: per-cell
|
|
208
|
+
* events, custom `tfoot`, or arbitrary markup between sections.
|
|
209
|
+
*
|
|
210
|
+
* ```tsx
|
|
211
|
+
* import { TableRoot, TableHeader, TableBody, TableRow, TableHead, TableCell } from "./table";
|
|
212
|
+
* ```
|
|
213
|
+
*/
|
|
214
|
+
export const Primitive: Story<User> = {
|
|
215
|
+
render: () => (
|
|
216
|
+
<TableRoot>
|
|
217
|
+
<TableHeader>
|
|
218
|
+
<TableRow>
|
|
219
|
+
<TableHead>Name</TableHead>
|
|
220
|
+
<TableHead>Age</TableHead>
|
|
221
|
+
<TableHead>Gender</TableHead>
|
|
222
|
+
</TableRow>
|
|
223
|
+
</TableHeader>
|
|
224
|
+
<TableBody>
|
|
225
|
+
{data.map((row) => (
|
|
226
|
+
<TableRow key={row.id} onClick={() => alert(row.name)}>
|
|
227
|
+
<TableCell>{row.name}</TableCell>
|
|
228
|
+
<TableCell>{row.age}</TableCell>
|
|
229
|
+
<TableCell>{row.gender}</TableCell>
|
|
230
|
+
</TableRow>
|
|
231
|
+
))}
|
|
232
|
+
</TableBody>
|
|
233
|
+
</TableRoot>
|
|
234
|
+
),
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
// ─── Changelog ───────────────────────────────────────────────────────────────
|
|
238
|
+
//
|
|
239
|
+
// v2 — Breaking changes
|
|
240
|
+
//
|
|
241
|
+
// REMOVED props (*Props escape hatches — use primitives for full prop control):
|
|
242
|
+
// - theadProps
|
|
243
|
+
// - tbodyProps
|
|
244
|
+
// - trProps → was also incorrectly applied to the header row
|
|
245
|
+
// - thProps
|
|
246
|
+
// - tdProps
|
|
247
|
+
//
|
|
248
|
+
// KEPT as className-only escape hatches:
|
|
249
|
+
// - theadClassName, tbodyClassName, trClassName, thClassName, tdClassName
|
|
250
|
+
// Note: trClassName now applies to body rows only, NOT the header row.
|
|
251
|
+
//
|
|
252
|
+
// CHANGED behavior:
|
|
253
|
+
// - onRowClick fires on <tr>, not <td>. Event no longer bubbles per-cell.
|
|
254
|
+
// - onDoubleClick renamed to onRowDoubleClick for consistency with onRowClick.
|
|
255
|
+
// Native onDoubleClick is omitted from the table spread — linter will catch misuse.
|
|
256
|
+
// - cell fallback uses explicit check (`column.cell ? ... : row[key]`)
|
|
257
|
+
// instead of truthiness (`||`). cell() returning null/0/"" no longer
|
|
258
|
+
// falls back to raw row value.
|
|
259
|
+
//
|
|
260
|
+
// ADDED props:
|
|
261
|
+
// - getRowKey: (row, index) => string | number — stable React keys
|
|
262
|
+
//
|
|
263
|
+
// CONSOLIDATED files:
|
|
264
|
+
// - table-primitives.tsx deleted — primitives now live in table.tsx
|
|
265
|
+
// - table.model.ts deleted — ColumnDef now exported from table.tsx
|
|
266
|
+
// - index.ts now only re-exports from "./table"
|
|
267
|
+
//
|
|
268
|
+
// Migration example for removed *Props escape hatches:
|
|
269
|
+
//
|
|
270
|
+
// Before:
|
|
271
|
+
// <Table tdProps={{ onClick: handleCellClick }} ... />
|
|
272
|
+
//
|
|
273
|
+
// After (use primitives):
|
|
274
|
+
// <TableRoot>
|
|
275
|
+
// <TableBody>
|
|
276
|
+
// {data.map(row => (
|
|
277
|
+
// <TableRow key={row.id}>
|
|
278
|
+
// <TableCell onClick={handleCellClick}>{row.name}</TableCell>
|
|
279
|
+
// </TableRow>
|
|
280
|
+
// ))}
|
|
281
|
+
// </TableBody>
|
|
282
|
+
// </TableRoot>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { render, screen } from "@testing-library/react";
|
|
2
2
|
import { describe, expect, it, vi } from "vitest";
|
|
3
3
|
import { ColumnDef, Table } from "../../components";
|
|
4
|
-
import { click } from "../../utils";
|
|
4
|
+
import { click } from "../../utils/tests";
|
|
5
5
|
|
|
6
6
|
describe("Table", () => {
|
|
7
7
|
interface Person {
|
|
@@ -1,104 +1,210 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import { ClassName } from "@/models";
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { type ReactNode } from "react";
|
|
4
3
|
import { cn } from "../../lib";
|
|
5
|
-
import { ColumnDef } from "./table.model";
|
|
6
|
-
import {
|
|
7
|
-
TableBody,
|
|
8
|
-
TableCell,
|
|
9
|
-
TableHead,
|
|
10
|
-
TableHeader,
|
|
11
|
-
TableRoot,
|
|
12
|
-
TableRow,
|
|
13
|
-
} from "./table-primitives";
|
|
14
4
|
|
|
15
|
-
|
|
5
|
+
// ─── Types ───────────────────────────────────────────────────────────────────
|
|
6
|
+
|
|
7
|
+
export type ColumnDef<T> = {
|
|
8
|
+
header: string | (() => ReactNode);
|
|
9
|
+
key: keyof T;
|
|
10
|
+
cell?: (row: T, index: number) => ReactNode;
|
|
11
|
+
width?: string | number;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// ─── Primitives ──────────────────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
type TableRootProps = React.ComponentProps<"table"> & {
|
|
17
|
+
/** Styles applied to each internal slot. */
|
|
18
|
+
classNames?: {
|
|
19
|
+
/** Outer wrapper div with overflow handling. */
|
|
20
|
+
container?: string;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export function TableRoot({ className, classNames, ...props }: TableRootProps) {
|
|
25
|
+
return (
|
|
26
|
+
<div
|
|
27
|
+
data-slot="table-container"
|
|
28
|
+
className={cn(
|
|
29
|
+
"relative w-full overflow-auto rounded-md border",
|
|
30
|
+
classNames?.container,
|
|
31
|
+
)}
|
|
32
|
+
>
|
|
33
|
+
<table
|
|
34
|
+
data-slot="table"
|
|
35
|
+
className={cn("w-full caption-bottom text-sm", className)}
|
|
36
|
+
{...props}
|
|
37
|
+
/>
|
|
38
|
+
</div>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function TableHeader({
|
|
43
|
+
className,
|
|
44
|
+
...props
|
|
45
|
+
}: React.ComponentProps<"thead">) {
|
|
46
|
+
return (
|
|
47
|
+
<thead
|
|
48
|
+
data-slot="table-header"
|
|
49
|
+
className={cn("[&_tr]:border-b", className)}
|
|
50
|
+
{...props}
|
|
51
|
+
/>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function TableBody({
|
|
56
|
+
className,
|
|
57
|
+
...props
|
|
58
|
+
}: React.ComponentProps<"tbody">) {
|
|
59
|
+
return (
|
|
60
|
+
<tbody
|
|
61
|
+
data-slot="table-body"
|
|
62
|
+
className={cn("[&_tr:last-child]:border-0", className)}
|
|
63
|
+
{...props}
|
|
64
|
+
/>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function TableFooter({
|
|
69
|
+
className,
|
|
70
|
+
...props
|
|
71
|
+
}: React.ComponentProps<"tfoot">) {
|
|
72
|
+
return (
|
|
73
|
+
<tfoot
|
|
74
|
+
data-slot="table-footer"
|
|
75
|
+
className={cn(
|
|
76
|
+
"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
|
|
77
|
+
className,
|
|
78
|
+
)}
|
|
79
|
+
{...props}
|
|
80
|
+
/>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
|
|
85
|
+
return (
|
|
86
|
+
<tr
|
|
87
|
+
data-slot="table-row"
|
|
88
|
+
className={cn(
|
|
89
|
+
"border-b transition-colors hover:bg-muted data-[selected=true]:bg-muted",
|
|
90
|
+
className,
|
|
91
|
+
)}
|
|
92
|
+
{...props}
|
|
93
|
+
/>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function TableHead({ className, ...props }: React.ComponentProps<"th">) {
|
|
98
|
+
return (
|
|
99
|
+
<th
|
|
100
|
+
data-slot="table-head"
|
|
101
|
+
className={cn(
|
|
102
|
+
"h-12 px-4 text-left align-middle font-medium text-muted-foreground sticky top-0 bg-background [&:has([role=checkbox])]:pr-0",
|
|
103
|
+
className,
|
|
104
|
+
)}
|
|
105
|
+
{...props}
|
|
106
|
+
/>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function TableCell({ className, ...props }: React.ComponentProps<"td">) {
|
|
111
|
+
return (
|
|
112
|
+
<td
|
|
113
|
+
data-slot="table-cell"
|
|
114
|
+
className={cn(
|
|
115
|
+
"p-4 align-middle [&:has([role=checkbox])]:pr-0",
|
|
116
|
+
className,
|
|
117
|
+
)}
|
|
118
|
+
{...props}
|
|
119
|
+
/>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function TableCaption({
|
|
124
|
+
className,
|
|
125
|
+
...props
|
|
126
|
+
}: React.ComponentProps<"caption">) {
|
|
127
|
+
return (
|
|
128
|
+
<caption
|
|
129
|
+
data-slot="table-caption"
|
|
130
|
+
className={cn("mt-4 text-sm text-muted-foreground", className)}
|
|
131
|
+
{...props}
|
|
132
|
+
/>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ─── Composite ───────────────────────────────────────────────────────────────
|
|
137
|
+
|
|
138
|
+
type TableProps<T> = {
|
|
16
139
|
data: T[];
|
|
17
140
|
columns: ColumnDef<T>[];
|
|
141
|
+
getRowKey?: (row: T, index: number) => string | number;
|
|
18
142
|
onRowClick?: (row: T) => void;
|
|
19
|
-
|
|
143
|
+
onRowDoubleClick?: (row: T) => void;
|
|
20
144
|
selected?: (row: T) => boolean;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
145
|
+
/** Styles applied to each internal slot. */
|
|
146
|
+
classNames?: {
|
|
147
|
+
/** Outer wrapper div with overflow handling. */
|
|
148
|
+
container?: string;
|
|
149
|
+
/** `<thead>` element. */
|
|
150
|
+
thead?: string;
|
|
151
|
+
/** `<tbody>` element. */
|
|
152
|
+
tbody?: string;
|
|
153
|
+
/** `<tr>` inside `<thead>` (the single header row). */
|
|
154
|
+
headerRow?: string;
|
|
155
|
+
/** Each `<tr>` inside `<tbody>` (the repeating body rows). */
|
|
156
|
+
tr?: string;
|
|
157
|
+
/** Header cell `<th>`. */
|
|
158
|
+
th?: string;
|
|
159
|
+
/** Body cell `<td>`. */
|
|
160
|
+
td?: string;
|
|
161
|
+
};
|
|
162
|
+
} & Omit<React.ComponentProps<"table">, "onDoubleClick">;
|
|
39
163
|
|
|
40
|
-
export
|
|
164
|
+
export function Table<T>({
|
|
41
165
|
data = [],
|
|
42
166
|
columns = [],
|
|
167
|
+
getRowKey,
|
|
43
168
|
onRowClick,
|
|
44
|
-
|
|
169
|
+
onRowDoubleClick,
|
|
45
170
|
selected,
|
|
46
|
-
|
|
47
|
-
theadProps,
|
|
48
|
-
tbodyClassName,
|
|
49
|
-
tbodyProps,
|
|
50
|
-
trClassName,
|
|
51
|
-
trProps,
|
|
52
|
-
thClassName,
|
|
53
|
-
thProps,
|
|
54
|
-
tdClassName,
|
|
55
|
-
tdProps,
|
|
171
|
+
classNames,
|
|
56
172
|
...tableProps
|
|
57
|
-
}:
|
|
173
|
+
}: TableProps<T>) {
|
|
58
174
|
return (
|
|
59
|
-
<TableRoot
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
>
|
|
64
|
-
<TableRow
|
|
175
|
+
<TableRoot
|
|
176
|
+
{...tableProps}
|
|
177
|
+
classNames={{ container: classNames?.container }}
|
|
178
|
+
>
|
|
179
|
+
<TableHeader className={classNames?.thead}>
|
|
180
|
+
<TableRow className={classNames?.headerRow}>
|
|
65
181
|
{columns.map((column, i) => (
|
|
66
182
|
<TableHead
|
|
67
183
|
key={i}
|
|
68
|
-
{
|
|
69
|
-
|
|
70
|
-
style={{
|
|
71
|
-
width: column.width,
|
|
72
|
-
}}
|
|
184
|
+
className={classNames?.th}
|
|
185
|
+
style={{ width: column.width }}
|
|
73
186
|
>
|
|
74
|
-
{
|
|
187
|
+
{typeof column.header === "function"
|
|
188
|
+
? column.header()
|
|
189
|
+
: column.header}
|
|
75
190
|
</TableHead>
|
|
76
191
|
))}
|
|
77
192
|
</TableRow>
|
|
78
193
|
</TableHeader>
|
|
79
|
-
<TableBody
|
|
80
|
-
{...tbodyProps}
|
|
81
|
-
className={cn(tbodyProps?.className, tbodyClassName)}
|
|
82
|
-
>
|
|
194
|
+
<TableBody className={classNames?.tbody}>
|
|
83
195
|
{data.map((row, i) => (
|
|
84
196
|
<TableRow
|
|
85
|
-
key={i}
|
|
86
|
-
{
|
|
197
|
+
key={getRowKey ? getRowKey(row, i) : i}
|
|
198
|
+
className={classNames?.tr}
|
|
87
199
|
data-selected={selected?.(row)}
|
|
88
|
-
|
|
200
|
+
onClick={() => onRowClick?.(row)}
|
|
201
|
+
onDoubleClick={() => onRowDoubleClick?.(row)}
|
|
89
202
|
>
|
|
90
|
-
{columns.map((column,
|
|
91
|
-
<TableCell
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
tdProps?.onClick?.(e);
|
|
96
|
-
onRowClick?.(row);
|
|
97
|
-
}}
|
|
98
|
-
onDoubleClick={() => onDoubleClick?.(row)}
|
|
99
|
-
className={cn(tdProps?.className, tdClassName)}
|
|
100
|
-
>
|
|
101
|
-
{(column.cell?.(row, i) as JSX.Element) || row[column.key]}
|
|
203
|
+
{columns.map((column, j) => (
|
|
204
|
+
<TableCell key={j} className={classNames?.td}>
|
|
205
|
+
{column.cell
|
|
206
|
+
? column.cell(row, i)
|
|
207
|
+
: (row[column.key] as ReactNode)}
|
|
102
208
|
</TableCell>
|
|
103
209
|
))}
|
|
104
210
|
</TableRow>
|
|
@@ -106,4 +212,4 @@ export const Table = <T,>({
|
|
|
106
212
|
</TableBody>
|
|
107
213
|
</TableRoot>
|
|
108
214
|
);
|
|
109
|
-
}
|
|
215
|
+
}
|