@purpurds/table 8.3.1 → 8.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/LICENSE.txt +205 -35
- package/dist/drag-indicator-circle.d.ts +13 -0
- package/dist/drag-indicator-circle.d.ts.map +1 -0
- package/dist/draggable-table.d.ts +23 -0
- package/dist/draggable-table.d.ts.map +1 -0
- package/dist/empty-table.d.ts +14 -0
- package/dist/empty-table.d.ts.map +1 -0
- package/dist/loading-table-rows.d.ts +13 -0
- package/dist/loading-table-rows.d.ts.map +1 -0
- package/dist/styles.css +1 -1
- package/dist/table-body.d.ts +2 -2
- package/dist/table-body.d.ts.map +1 -1
- package/dist/table-column-header-cell.d.ts +15 -2
- package/dist/table-column-header-cell.d.ts.map +1 -1
- package/dist/table-content.d.ts +42 -0
- package/dist/table-content.d.ts.map +1 -0
- package/dist/table-headers.d.ts +28 -0
- package/dist/table-headers.d.ts.map +1 -0
- package/dist/table-row-cell-skeleton.d.ts +1 -1
- package/dist/table-row-cell-skeleton.d.ts.map +1 -1
- package/dist/table-row-cell.d.ts +5 -2
- package/dist/table-row-cell.d.ts.map +1 -1
- package/dist/table-row.d.ts +2 -2
- package/dist/table-row.d.ts.map +1 -1
- package/dist/table-settings-drawer.d.ts +44 -11
- package/dist/table-settings-drawer.d.ts.map +1 -1
- package/dist/table.cjs.js +89 -85
- package/dist/table.cjs.js.map +1 -1
- package/dist/table.d.ts +3 -3
- package/dist/table.d.ts.map +1 -1
- package/dist/table.es.js +14397 -10195
- package/dist/table.es.js.map +1 -1
- package/dist/test-utils/helpers.d.ts +1 -0
- package/dist/test-utils/helpers.d.ts.map +1 -1
- package/dist/types.d.ts +23 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/use-drag-handle.hook.d.ts +15 -0
- package/dist/use-drag-handle.hook.d.ts.map +1 -0
- package/dist/use-drag-indicator-position.hook.d.ts +19 -0
- package/dist/use-drag-indicator-position.hook.d.ts.map +1 -0
- package/dist/use-drop-indicator.hook.d.ts +15 -0
- package/dist/use-drop-indicator.hook.d.ts.map +1 -0
- package/dist/use-element-visibility.hook.d.ts +4 -0
- package/dist/use-element-visibility.hook.d.ts.map +1 -0
- package/dist/use-table-scroll.hook.d.ts +6 -0
- package/dist/use-table-scroll.hook.d.ts.map +1 -0
- package/dist/utils/custom-keyboard-coordinates.d.ts +8 -0
- package/dist/utils/custom-keyboard-coordinates.d.ts.map +1 -0
- package/package.json +27 -23
- package/src/drag-indicator-circle.tsx +36 -0
- package/src/draggable-table.test.tsx +381 -0
- package/src/draggable-table.tsx +191 -0
- package/src/empty-table.tsx +54 -0
- package/src/loading-table-rows.tsx +41 -0
- package/src/table-body.tsx +1 -3
- package/src/table-column-header-cell.tsx +135 -64
- package/src/table-content-drag.test.tsx +505 -0
- package/src/table-content.tsx +165 -0
- package/src/table-dnd-integration.test.tsx +425 -0
- package/src/table-drag-and-drop.test.tsx +276 -0
- package/src/table-headers.tsx +118 -0
- package/src/table-row-cell-skeleton.tsx +1 -1
- package/src/table-row-cell.test.tsx +2 -1
- package/src/table-row-cell.tsx +42 -31
- package/src/table-row.tsx +1 -3
- package/src/table-settings-drawer.module.scss +165 -2
- package/src/table-settings-drawer.test.tsx +0 -99
- package/src/table-settings-drawer.tsx +359 -53
- package/src/table.module.scss +191 -30
- package/src/table.stories.tsx +60 -4
- package/src/table.test.tsx +5 -1
- package/src/table.tsx +255 -213
- package/src/test-utils/helpers.ts +2 -0
- package/src/types.ts +25 -2
- package/src/use-drag-handle.hook.tsx +60 -0
- package/src/use-drag-handle.test.tsx +380 -0
- package/src/use-drag-indicator-position.hook.ts +74 -0
- package/src/use-drop-indicator.hook.ts +46 -0
- package/src/use-element-visibility.hook.ts +28 -0
- package/src/use-table-scroll.hook.tsx +30 -0
- package/src/utils/custom-keyboard-coordinates.ts +83 -0
- package/vitest.setup.ts +1 -1
|
@@ -0,0 +1,505 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
import { axe } from "vitest-axe";
|
|
4
|
+
|
|
5
|
+
import { EmptyTableContent, LoadingTableContent, NormalTableContent } from "./table-content";
|
|
6
|
+
|
|
7
|
+
// Mock tanstack table
|
|
8
|
+
const mockTanstackTable = {
|
|
9
|
+
getVisibleLeafColumns: vi.fn(() => [{ id: "col1" }, { id: "col2" }]),
|
|
10
|
+
} as unknown as Parameters<typeof NormalTableContent>[0]["tanstackTable"];
|
|
11
|
+
|
|
12
|
+
// Mock row data
|
|
13
|
+
const mockTableRows = [
|
|
14
|
+
{
|
|
15
|
+
id: "row1",
|
|
16
|
+
getIsSelected: () => false,
|
|
17
|
+
getVisibleCells: () => [
|
|
18
|
+
{
|
|
19
|
+
id: "cell1",
|
|
20
|
+
column: {
|
|
21
|
+
id: "col1",
|
|
22
|
+
getIsFirstColumn: () => true,
|
|
23
|
+
getIsLastColumn: () => false,
|
|
24
|
+
columnDef: { meta: { cellType: "default" } },
|
|
25
|
+
},
|
|
26
|
+
getValue: () => "Value 1",
|
|
27
|
+
getContext: () => ({ getValue: () => "Value 1" }),
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: "cell2",
|
|
31
|
+
column: {
|
|
32
|
+
id: "col2",
|
|
33
|
+
getIsFirstColumn: () => false,
|
|
34
|
+
getIsLastColumn: () => true,
|
|
35
|
+
columnDef: { meta: { cellType: "default" } },
|
|
36
|
+
},
|
|
37
|
+
getValue: () => "Value 2",
|
|
38
|
+
getContext: () => ({ getValue: () => "Value 2" }),
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: "row2",
|
|
44
|
+
getIsSelected: () => true,
|
|
45
|
+
getVisibleCells: () => [
|
|
46
|
+
{
|
|
47
|
+
id: "cell3",
|
|
48
|
+
column: {
|
|
49
|
+
id: "col1",
|
|
50
|
+
getIsFirstColumn: () => true,
|
|
51
|
+
getIsLastColumn: () => false,
|
|
52
|
+
columnDef: { meta: { cellType: "default" } },
|
|
53
|
+
},
|
|
54
|
+
getValue: () => "Value 3",
|
|
55
|
+
getContext: () => ({ getValue: () => "Value 3" }),
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: "cell4",
|
|
59
|
+
column: {
|
|
60
|
+
id: "col2",
|
|
61
|
+
getIsFirstColumn: () => false,
|
|
62
|
+
getIsLastColumn: () => true,
|
|
63
|
+
columnDef: { meta: { cellType: "default" } },
|
|
64
|
+
},
|
|
65
|
+
getValue: () => "Value 4",
|
|
66
|
+
getContext: () => ({ getValue: () => "Value 4" }),
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
},
|
|
70
|
+
] as unknown as Parameters<typeof NormalTableContent>[0]["tableRows"];
|
|
71
|
+
|
|
72
|
+
const mockRenderTableHeaders = () => (
|
|
73
|
+
<tr>
|
|
74
|
+
<th>Header 1</th>
|
|
75
|
+
<th>Header 2</th>
|
|
76
|
+
</tr>
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
describe("Table Content Components with Drag and Drop", () => {
|
|
80
|
+
describe("NormalTableContent", () => {
|
|
81
|
+
const defaultProps = {
|
|
82
|
+
tanstackTable: mockTanstackTable,
|
|
83
|
+
tableRows: mockTableRows,
|
|
84
|
+
showColumnFiltersEnabled: false,
|
|
85
|
+
fullWidth: false,
|
|
86
|
+
renderTableHeaders: mockRenderTableHeaders,
|
|
87
|
+
stickyFirstColumn: false,
|
|
88
|
+
getStickyColumn: () => false,
|
|
89
|
+
isScrolled: false,
|
|
90
|
+
showBorder: () => false,
|
|
91
|
+
enableColumnDrag: false,
|
|
92
|
+
activeId: null,
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
it("should render table with drag disabled by default", () => {
|
|
96
|
+
render(<NormalTableContent {...defaultProps} />);
|
|
97
|
+
|
|
98
|
+
const table = screen.getByRole("table");
|
|
99
|
+
expect(table).toBeInTheDocument();
|
|
100
|
+
expect(table.className).toContain("purpur-table__table");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("should render table with drag enabled", () => {
|
|
104
|
+
render(<NormalTableContent {...defaultProps} enableColumnDrag={true} />);
|
|
105
|
+
|
|
106
|
+
const table = screen.getByRole("table");
|
|
107
|
+
expect(table).toBeInTheDocument();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("should apply full width class when fullWidth is true", () => {
|
|
111
|
+
render(<NormalTableContent {...defaultProps} fullWidth={true} />);
|
|
112
|
+
|
|
113
|
+
const table = screen.getByRole("table");
|
|
114
|
+
expect(table.className).toContain("purpur-table__table--full-width");
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("should render correct number of rows", () => {
|
|
118
|
+
render(<NormalTableContent {...defaultProps} />);
|
|
119
|
+
|
|
120
|
+
const rows = screen.getAllByRole("row");
|
|
121
|
+
// Should have header row + data rows
|
|
122
|
+
expect(rows.length).toBeGreaterThan(mockTableRows.length);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("should pass enableColumnDrag prop to table cells", () => {
|
|
126
|
+
render(<NormalTableContent {...defaultProps} enableColumnDrag={true} />);
|
|
127
|
+
|
|
128
|
+
// All cells should receive the enableColumnDrag prop
|
|
129
|
+
const table = screen.getByRole("table");
|
|
130
|
+
expect(table).toBeInTheDocument();
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("should pass draggingActive prop to cells when activeId matches", () => {
|
|
134
|
+
render(<NormalTableContent {...defaultProps} enableColumnDrag={true} activeId="col1" />);
|
|
135
|
+
|
|
136
|
+
const table = screen.getByRole("table");
|
|
137
|
+
expect(table).toBeInTheDocument();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("should handle sticky columns with drag enabled", () => {
|
|
141
|
+
render(
|
|
142
|
+
<NormalTableContent
|
|
143
|
+
{...defaultProps}
|
|
144
|
+
stickyFirstColumn={true}
|
|
145
|
+
getStickyColumn={(index) => index === 0}
|
|
146
|
+
enableColumnDrag={true}
|
|
147
|
+
/>
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
const table = screen.getByRole("table");
|
|
151
|
+
expect(table).toBeInTheDocument();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("should handle scrolled state with drag enabled", () => {
|
|
155
|
+
render(<NormalTableContent {...defaultProps} isScrolled={true} enableColumnDrag={true} />);
|
|
156
|
+
|
|
157
|
+
const table = screen.getByRole("table");
|
|
158
|
+
expect(table).toBeInTheDocument();
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("should handle border showing with drag enabled", () => {
|
|
162
|
+
render(
|
|
163
|
+
<NormalTableContent
|
|
164
|
+
{...defaultProps}
|
|
165
|
+
showBorder={(index) => index === 0}
|
|
166
|
+
enableColumnDrag={true}
|
|
167
|
+
/>
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
const table = screen.getByRole("table");
|
|
171
|
+
expect(table).toBeInTheDocument();
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("should be accessible with drag enabled", async () => {
|
|
175
|
+
const { container } = render(
|
|
176
|
+
<NormalTableContent {...defaultProps} enableColumnDrag={true} />
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
const results = await axe(container);
|
|
180
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
181
|
+
// @ts-ignore
|
|
182
|
+
expect(results).toHaveNoViolations();
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("should handle empty table rows", () => {
|
|
186
|
+
render(<NormalTableContent {...defaultProps} tableRows={[]} enableColumnDrag={true} />);
|
|
187
|
+
|
|
188
|
+
const table = screen.getByRole("table");
|
|
189
|
+
expect(table).toBeInTheDocument();
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("should handle selected rows with drag enabled", () => {
|
|
193
|
+
const selectedRows = mockTableRows.map(
|
|
194
|
+
(row: Parameters<typeof NormalTableContent>[0]["tableRows"][0]) => ({
|
|
195
|
+
...row,
|
|
196
|
+
getIsSelected: () => true,
|
|
197
|
+
})
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
render(
|
|
201
|
+
<NormalTableContent {...defaultProps} tableRows={selectedRows} enableColumnDrag={true} />
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
const table = screen.getByRole("table");
|
|
205
|
+
expect(table).toBeInTheDocument();
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
describe("LoadingTableContent", () => {
|
|
210
|
+
const defaultProps = {
|
|
211
|
+
tanstackTable: mockTanstackTable,
|
|
212
|
+
tableRows: [],
|
|
213
|
+
showColumnFiltersEnabled: false,
|
|
214
|
+
fullWidth: false,
|
|
215
|
+
renderTableHeaders: mockRenderTableHeaders,
|
|
216
|
+
skeletonRows: 5,
|
|
217
|
+
getStickyColumn: () => false,
|
|
218
|
+
stickyFirstColumn: false,
|
|
219
|
+
isScrolled: false,
|
|
220
|
+
showBorder: () => false,
|
|
221
|
+
getColumnWidths: () => ["100px", "150px"],
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
it("should render loading table with drag context support", () => {
|
|
225
|
+
render(<LoadingTableContent {...defaultProps} />);
|
|
226
|
+
|
|
227
|
+
const table = screen.getByRole("table");
|
|
228
|
+
expect(table).toBeInTheDocument();
|
|
229
|
+
expect(table.className).toContain("purpur-table__table");
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it("should apply full width class when fullWidth is true", () => {
|
|
233
|
+
render(<LoadingTableContent {...defaultProps} fullWidth={true} />);
|
|
234
|
+
|
|
235
|
+
const table = screen.getByRole("table");
|
|
236
|
+
expect(table.className).toContain("purpur-table__table--full-width");
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it("should render with column filters enabled", () => {
|
|
240
|
+
render(<LoadingTableContent {...defaultProps} showColumnFiltersEnabled={true} />);
|
|
241
|
+
|
|
242
|
+
const table = screen.getByRole("table");
|
|
243
|
+
expect(table).toBeInTheDocument();
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it("should handle sticky columns in loading state", () => {
|
|
247
|
+
render(
|
|
248
|
+
<LoadingTableContent
|
|
249
|
+
{...defaultProps}
|
|
250
|
+
stickyFirstColumn={true}
|
|
251
|
+
getStickyColumn={(index) => index === 0}
|
|
252
|
+
/>
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
const table = screen.getByRole("table");
|
|
256
|
+
expect(table).toBeInTheDocument();
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it("should handle scrolled state in loading", () => {
|
|
260
|
+
render(<LoadingTableContent {...defaultProps} isScrolled={true} />);
|
|
261
|
+
|
|
262
|
+
const table = screen.getByRole("table");
|
|
263
|
+
expect(table).toBeInTheDocument();
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it("should pass correct skeleton row count", () => {
|
|
267
|
+
const skeletonRows = 3;
|
|
268
|
+
render(<LoadingTableContent {...defaultProps} skeletonRows={skeletonRows} />);
|
|
269
|
+
|
|
270
|
+
const table = screen.getByRole("table");
|
|
271
|
+
expect(table).toBeInTheDocument();
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it("should pass column widths to loading rows", () => {
|
|
275
|
+
const columnWidths = ["120px", "180px", "200px"];
|
|
276
|
+
render(<LoadingTableContent {...defaultProps} getColumnWidths={() => columnWidths} />);
|
|
277
|
+
|
|
278
|
+
const table = screen.getByRole("table");
|
|
279
|
+
expect(table).toBeInTheDocument();
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it("should be accessible in loading state", async () => {
|
|
283
|
+
const { container } = render(<LoadingTableContent {...defaultProps} />);
|
|
284
|
+
|
|
285
|
+
const results = await axe(container);
|
|
286
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
287
|
+
// @ts-ignore
|
|
288
|
+
expect(results).toHaveNoViolations();
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
describe("EmptyTableContent", () => {
|
|
293
|
+
const defaultProps = {
|
|
294
|
+
tanstackTable: mockTanstackTable,
|
|
295
|
+
tableRows: [],
|
|
296
|
+
showColumnFiltersEnabled: false,
|
|
297
|
+
fullWidth: false,
|
|
298
|
+
renderTableHeaders: mockRenderTableHeaders,
|
|
299
|
+
variant: "primary" as const,
|
|
300
|
+
emptyTableHeadingTag: "h2" as const,
|
|
301
|
+
emptyTableCopy: {
|
|
302
|
+
title: "No data available",
|
|
303
|
+
description: "There are no items to display in this table.",
|
|
304
|
+
},
|
|
305
|
+
emptyTableIcon: <div data-testid="empty-icon">Empty Icon</div>,
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
it("should render empty table with drag context support", () => {
|
|
309
|
+
render(<EmptyTableContent {...defaultProps} />);
|
|
310
|
+
|
|
311
|
+
const table = screen.getByRole("table");
|
|
312
|
+
expect(table).toBeInTheDocument();
|
|
313
|
+
expect(table.className).toContain("purpur-table__table");
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it("should apply full width class when fullWidth is true", () => {
|
|
317
|
+
render(<EmptyTableContent {...defaultProps} fullWidth={true} />);
|
|
318
|
+
|
|
319
|
+
const table = screen.getByRole("table");
|
|
320
|
+
expect(table.className).toContain("purpur-table__table--full-width");
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it("should render with column filters enabled", () => {
|
|
324
|
+
render(<EmptyTableContent {...defaultProps} showColumnFiltersEnabled={true} />);
|
|
325
|
+
|
|
326
|
+
const table = screen.getByRole("table");
|
|
327
|
+
expect(table).toBeInTheDocument();
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it("should display empty table content", () => {
|
|
331
|
+
render(<EmptyTableContent {...defaultProps} />);
|
|
332
|
+
|
|
333
|
+
expect(screen.getByText(defaultProps.emptyTableCopy.title)).toBeInTheDocument();
|
|
334
|
+
expect(screen.getByText(defaultProps.emptyTableCopy.description)).toBeInTheDocument();
|
|
335
|
+
expect(screen.getByTestId("empty-icon")).toBeInTheDocument();
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it("should handle different variants", () => {
|
|
339
|
+
const variants = ["primary", "secondary"] as const;
|
|
340
|
+
|
|
341
|
+
variants.forEach((variant) => {
|
|
342
|
+
const { unmount } = render(<EmptyTableContent {...defaultProps} variant={variant} />);
|
|
343
|
+
|
|
344
|
+
const table = screen.getByRole("table");
|
|
345
|
+
expect(table).toBeInTheDocument();
|
|
346
|
+
|
|
347
|
+
unmount();
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
it("should handle different heading tags", () => {
|
|
352
|
+
const headingTags = ["h1", "h2", "h3", "h4", "h5", "h6"] as const;
|
|
353
|
+
|
|
354
|
+
headingTags.forEach((tag) => {
|
|
355
|
+
const { unmount } = render(
|
|
356
|
+
<EmptyTableContent {...defaultProps} emptyTableHeadingTag={tag} />
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
const table = screen.getByRole("table");
|
|
360
|
+
expect(table).toBeInTheDocument();
|
|
361
|
+
|
|
362
|
+
unmount();
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
it("should handle missing icon", () => {
|
|
367
|
+
render(<EmptyTableContent {...defaultProps} emptyTableIcon={undefined} />);
|
|
368
|
+
|
|
369
|
+
const table = screen.getByRole("table");
|
|
370
|
+
expect(table).toBeInTheDocument();
|
|
371
|
+
expect(screen.queryByTestId("empty-icon")).not.toBeInTheDocument();
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
it("should calculate correct colSpan from tanstack table", () => {
|
|
375
|
+
const mockTableWithMoreColumns = {
|
|
376
|
+
getVisibleLeafColumns: vi.fn(() => [
|
|
377
|
+
{ id: "col1" },
|
|
378
|
+
{ id: "col2" },
|
|
379
|
+
{ id: "col3" },
|
|
380
|
+
{ id: "col4" },
|
|
381
|
+
]),
|
|
382
|
+
} as unknown as Parameters<typeof EmptyTableContent>[0]["tanstackTable"];
|
|
383
|
+
|
|
384
|
+
render(<EmptyTableContent {...defaultProps} tanstackTable={mockTableWithMoreColumns} />);
|
|
385
|
+
|
|
386
|
+
const table = screen.getByRole("table");
|
|
387
|
+
expect(table).toBeInTheDocument();
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
it("should be accessible in empty state", async () => {
|
|
391
|
+
const { container } = render(<EmptyTableContent {...defaultProps} />);
|
|
392
|
+
|
|
393
|
+
const results = await axe(container);
|
|
394
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
395
|
+
// @ts-ignore
|
|
396
|
+
expect(results).toHaveNoViolations();
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it("should handle empty copy strings", () => {
|
|
400
|
+
render(
|
|
401
|
+
<EmptyTableContent
|
|
402
|
+
{...defaultProps}
|
|
403
|
+
emptyTableCopy={{
|
|
404
|
+
title: "",
|
|
405
|
+
description: "",
|
|
406
|
+
}}
|
|
407
|
+
/>
|
|
408
|
+
);
|
|
409
|
+
|
|
410
|
+
const table = screen.getByRole("table");
|
|
411
|
+
expect(table).toBeInTheDocument();
|
|
412
|
+
});
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
describe("Integration between content types", () => {
|
|
416
|
+
it("should maintain consistent table structure across all content types", () => {
|
|
417
|
+
const baseProps = {
|
|
418
|
+
tanstackTable: mockTanstackTable,
|
|
419
|
+
showColumnFiltersEnabled: false,
|
|
420
|
+
fullWidth: true,
|
|
421
|
+
renderTableHeaders: mockRenderTableHeaders,
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
// Normal content
|
|
425
|
+
const { container: normalContainer, unmount: unmountNormal } = render(
|
|
426
|
+
<NormalTableContent
|
|
427
|
+
{...baseProps}
|
|
428
|
+
tableRows={mockTableRows}
|
|
429
|
+
stickyFirstColumn={false}
|
|
430
|
+
getStickyColumn={() => false}
|
|
431
|
+
isScrolled={false}
|
|
432
|
+
showBorder={() => false}
|
|
433
|
+
enableColumnDrag={false}
|
|
434
|
+
activeId={null}
|
|
435
|
+
/>
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
const normalTable = normalContainer.querySelector("table");
|
|
439
|
+
expect(normalTable?.className).toContain("purpur-table__table");
|
|
440
|
+
expect(normalTable?.className).toContain("purpur-table__table--full-width");
|
|
441
|
+
unmountNormal();
|
|
442
|
+
|
|
443
|
+
// Loading content
|
|
444
|
+
const { container: loadingContainer, unmount: unmountLoading } = render(
|
|
445
|
+
<LoadingTableContent
|
|
446
|
+
{...baseProps}
|
|
447
|
+
tableRows={[]}
|
|
448
|
+
skeletonRows={5}
|
|
449
|
+
getStickyColumn={() => false}
|
|
450
|
+
stickyFirstColumn={false}
|
|
451
|
+
isScrolled={false}
|
|
452
|
+
showBorder={() => false}
|
|
453
|
+
getColumnWidths={() => []}
|
|
454
|
+
/>
|
|
455
|
+
);
|
|
456
|
+
|
|
457
|
+
const loadingTable = loadingContainer.querySelector("table");
|
|
458
|
+
expect(loadingTable?.className).toContain("purpur-table__table");
|
|
459
|
+
expect(loadingTable?.className).toContain("purpur-table__table--full-width");
|
|
460
|
+
unmountLoading();
|
|
461
|
+
|
|
462
|
+
// Empty content
|
|
463
|
+
const { container: emptyContainer, unmount: unmountEmpty } = render(
|
|
464
|
+
<EmptyTableContent
|
|
465
|
+
{...baseProps}
|
|
466
|
+
tableRows={[]}
|
|
467
|
+
variant="primary"
|
|
468
|
+
emptyTableHeadingTag="h2"
|
|
469
|
+
emptyTableCopy={{
|
|
470
|
+
title: "Empty",
|
|
471
|
+
description: "No data",
|
|
472
|
+
}}
|
|
473
|
+
/>
|
|
474
|
+
);
|
|
475
|
+
|
|
476
|
+
const emptyTable = emptyContainer.querySelector("table");
|
|
477
|
+
expect(emptyTable?.className).toContain("purpur-table__table");
|
|
478
|
+
expect(emptyTable?.className).toContain("purpur-table__table--full-width");
|
|
479
|
+
unmountEmpty();
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
it("should handle drag and drop props consistently", () => {
|
|
483
|
+
// Test that drag-related props don't break any content type
|
|
484
|
+
expect(() => {
|
|
485
|
+
render(
|
|
486
|
+
<div>
|
|
487
|
+
<NormalTableContent
|
|
488
|
+
tanstackTable={mockTanstackTable}
|
|
489
|
+
tableRows={mockTableRows}
|
|
490
|
+
showColumnFiltersEnabled={false}
|
|
491
|
+
fullWidth={false}
|
|
492
|
+
renderTableHeaders={mockRenderTableHeaders}
|
|
493
|
+
stickyFirstColumn={false}
|
|
494
|
+
getStickyColumn={() => false}
|
|
495
|
+
isScrolled={false}
|
|
496
|
+
showBorder={() => false}
|
|
497
|
+
enableColumnDrag={true}
|
|
498
|
+
activeId="col1"
|
|
499
|
+
/>
|
|
500
|
+
</div>
|
|
501
|
+
);
|
|
502
|
+
}).not.toThrow();
|
|
503
|
+
});
|
|
504
|
+
});
|
|
505
|
+
});
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { UniqueIdentifier } from "@dnd-kit/core";
|
|
3
|
+
import { type HeadingTagType } from "@purpurds/heading";
|
|
4
|
+
import type { Row, Table } from "@tanstack/react-table";
|
|
5
|
+
import type { RowData } from "@tanstack/react-table";
|
|
6
|
+
import c from "classnames/bind";
|
|
7
|
+
|
|
8
|
+
import { EmptyTable } from "./empty-table";
|
|
9
|
+
import { LoadingTableRows } from "./loading-table-rows";
|
|
10
|
+
import styles from "./table.module.scss";
|
|
11
|
+
import { TableBody } from "./table-body";
|
|
12
|
+
import { TableHeader } from "./table-header";
|
|
13
|
+
import { TableRow } from "./table-row";
|
|
14
|
+
import { TableRowCell } from "./table-row-cell";
|
|
15
|
+
|
|
16
|
+
const cx = c.bind(styles);
|
|
17
|
+
const rootClassName = "purpur-table";
|
|
18
|
+
|
|
19
|
+
type BaseTableContentProps<TData extends RowData> = {
|
|
20
|
+
tanstackTable: Table<TData>;
|
|
21
|
+
tableRows: Row<TData>[];
|
|
22
|
+
showColumnFiltersEnabled: boolean;
|
|
23
|
+
fullWidth: boolean;
|
|
24
|
+
renderTableHeaders: () => React.ReactNode;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
type NormalTableContentProps<TData extends RowData> = BaseTableContentProps<TData> & {
|
|
28
|
+
stickyFirstColumn: boolean;
|
|
29
|
+
getStickyColumn: (index: number) => boolean;
|
|
30
|
+
isScrolled: boolean;
|
|
31
|
+
showBorder: (index: number) => boolean;
|
|
32
|
+
enableColumnDrag: boolean;
|
|
33
|
+
activeId: UniqueIdentifier | null;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export function NormalTableContent<TData extends RowData>({
|
|
37
|
+
tableRows,
|
|
38
|
+
showColumnFiltersEnabled,
|
|
39
|
+
fullWidth,
|
|
40
|
+
renderTableHeaders,
|
|
41
|
+
stickyFirstColumn,
|
|
42
|
+
getStickyColumn,
|
|
43
|
+
isScrolled,
|
|
44
|
+
showBorder,
|
|
45
|
+
enableColumnDrag,
|
|
46
|
+
activeId,
|
|
47
|
+
}: NormalTableContentProps<TData>) {
|
|
48
|
+
return (
|
|
49
|
+
<table
|
|
50
|
+
className={cx([
|
|
51
|
+
`${rootClassName}__table`,
|
|
52
|
+
{ [`${rootClassName}__table--full-width`]: fullWidth },
|
|
53
|
+
])}
|
|
54
|
+
>
|
|
55
|
+
<TableHeader columnFiltersEnabled={showColumnFiltersEnabled}>
|
|
56
|
+
{renderTableHeaders()}
|
|
57
|
+
</TableHeader>
|
|
58
|
+
<TableBody>
|
|
59
|
+
{tableRows.map((row, rowIndex) => (
|
|
60
|
+
<TableRow key={row.id} isSelected={row.getIsSelected()}>
|
|
61
|
+
{row.getVisibleCells().map((cell, cellIndex) => (
|
|
62
|
+
<TableRowCell
|
|
63
|
+
key={cell.id}
|
|
64
|
+
cell={cell}
|
|
65
|
+
isLastRow={rowIndex === tableRows.length - 1}
|
|
66
|
+
isFirstCell={cellIndex === 0}
|
|
67
|
+
isLastCell={cellIndex === row.getVisibleCells().length - 1}
|
|
68
|
+
stickyColumn={stickyFirstColumn && getStickyColumn(cellIndex)}
|
|
69
|
+
isScrolled={isScrolled}
|
|
70
|
+
showBorder={showBorder(cellIndex)}
|
|
71
|
+
enableColumnDrag={enableColumnDrag || false}
|
|
72
|
+
draggingActive={activeId === cell.column.id}
|
|
73
|
+
/>
|
|
74
|
+
))}
|
|
75
|
+
</TableRow>
|
|
76
|
+
))}
|
|
77
|
+
</TableBody>
|
|
78
|
+
</table>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
type LoadingTableContentProps<TData extends RowData> = BaseTableContentProps<TData> & {
|
|
83
|
+
skeletonRows: number;
|
|
84
|
+
getStickyColumn: (index: number) => boolean;
|
|
85
|
+
stickyFirstColumn: boolean;
|
|
86
|
+
isScrolled: boolean;
|
|
87
|
+
showBorder: (index: number) => boolean;
|
|
88
|
+
getColumnWidths: () => (string | number)[];
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export function LoadingTableContent<TData extends RowData>({
|
|
92
|
+
showColumnFiltersEnabled,
|
|
93
|
+
fullWidth,
|
|
94
|
+
renderTableHeaders,
|
|
95
|
+
skeletonRows,
|
|
96
|
+
getStickyColumn,
|
|
97
|
+
stickyFirstColumn,
|
|
98
|
+
isScrolled,
|
|
99
|
+
showBorder,
|
|
100
|
+
getColumnWidths,
|
|
101
|
+
}: LoadingTableContentProps<TData>) {
|
|
102
|
+
return (
|
|
103
|
+
<table
|
|
104
|
+
className={cx([
|
|
105
|
+
`${rootClassName}__table`,
|
|
106
|
+
{ [`${rootClassName}__table--full-width`]: fullWidth },
|
|
107
|
+
])}
|
|
108
|
+
>
|
|
109
|
+
<TableHeader columnFiltersEnabled={showColumnFiltersEnabled}>
|
|
110
|
+
{renderTableHeaders()}
|
|
111
|
+
</TableHeader>
|
|
112
|
+
<TableBody>
|
|
113
|
+
<LoadingTableRows
|
|
114
|
+
rowCount={skeletonRows}
|
|
115
|
+
getStickyColumn={getStickyColumn}
|
|
116
|
+
stickyFirstColumn={stickyFirstColumn}
|
|
117
|
+
isScrolled={isScrolled}
|
|
118
|
+
cellWidths={getColumnWidths()}
|
|
119
|
+
showBorder={showBorder}
|
|
120
|
+
/>
|
|
121
|
+
</TableBody>
|
|
122
|
+
</table>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
type EmptyTableContentProps<TData extends RowData> = BaseTableContentProps<TData> & {
|
|
127
|
+
variant: "primary" | "secondary";
|
|
128
|
+
emptyTableHeadingTag: HeadingTagType;
|
|
129
|
+
emptyTableCopy: { title: string; description: string };
|
|
130
|
+
emptyTableIcon?: React.ReactNode;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
export function EmptyTableContent<TData extends RowData>({
|
|
134
|
+
tanstackTable,
|
|
135
|
+
showColumnFiltersEnabled,
|
|
136
|
+
fullWidth,
|
|
137
|
+
renderTableHeaders,
|
|
138
|
+
variant,
|
|
139
|
+
emptyTableHeadingTag,
|
|
140
|
+
emptyTableCopy,
|
|
141
|
+
emptyTableIcon,
|
|
142
|
+
}: EmptyTableContentProps<TData>) {
|
|
143
|
+
return (
|
|
144
|
+
<table
|
|
145
|
+
className={cx([
|
|
146
|
+
`${rootClassName}__table`,
|
|
147
|
+
{ [`${rootClassName}__table--full-width`]: fullWidth },
|
|
148
|
+
])}
|
|
149
|
+
>
|
|
150
|
+
<TableHeader columnFiltersEnabled={showColumnFiltersEnabled}>
|
|
151
|
+
{renderTableHeaders()}
|
|
152
|
+
</TableHeader>
|
|
153
|
+
<TableBody>
|
|
154
|
+
<EmptyTable
|
|
155
|
+
variant={variant}
|
|
156
|
+
tag={emptyTableHeadingTag}
|
|
157
|
+
title={emptyTableCopy.title}
|
|
158
|
+
description={emptyTableCopy.description}
|
|
159
|
+
colSpan={tanstackTable.getVisibleLeafColumns().length}
|
|
160
|
+
icon={emptyTableIcon}
|
|
161
|
+
/>
|
|
162
|
+
</TableBody>
|
|
163
|
+
</table>
|
|
164
|
+
);
|
|
165
|
+
}
|