@purpurds/table 0.0.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 (142) hide show
  1. package/dist/LICENSE.txt +213 -0
  2. package/dist/cell-types/badge-cell.d.ts +8 -0
  3. package/dist/cell-types/badge-cell.d.ts.map +1 -0
  4. package/dist/cell-types/body-text-cell.d.ts +8 -0
  5. package/dist/cell-types/body-text-cell.d.ts.map +1 -0
  6. package/dist/cell-types/button-cell.d.ts +8 -0
  7. package/dist/cell-types/button-cell.d.ts.map +1 -0
  8. package/dist/cell-types/button-group-cell.d.ts +16 -0
  9. package/dist/cell-types/button-group-cell.d.ts.map +1 -0
  10. package/dist/cell-types/cta-link-cell.d.ts +8 -0
  11. package/dist/cell-types/cta-link-cell.d.ts.map +1 -0
  12. package/dist/cell-types/date-cell.d.ts +8 -0
  13. package/dist/cell-types/date-cell.d.ts.map +1 -0
  14. package/dist/cell-types/empty-cell.d.ts +4 -0
  15. package/dist/cell-types/empty-cell.d.ts.map +1 -0
  16. package/dist/cell-types/error-message-cell.d.ts +8 -0
  17. package/dist/cell-types/error-message-cell.d.ts.map +1 -0
  18. package/dist/cell-types/icon-text-cell.d.ts +8 -0
  19. package/dist/cell-types/icon-text-cell.d.ts.map +1 -0
  20. package/dist/cell-types/lead-text-cell.d.ts +8 -0
  21. package/dist/cell-types/lead-text-cell.d.ts.map +1 -0
  22. package/dist/cell-types/link-cell.d.ts +8 -0
  23. package/dist/cell-types/link-cell.d.ts.map +1 -0
  24. package/dist/cell-types/number-cell.d.ts +8 -0
  25. package/dist/cell-types/number-cell.d.ts.map +1 -0
  26. package/dist/cell-types/row-selection-cell.d.ts +8 -0
  27. package/dist/cell-types/row-selection-cell.d.ts.map +1 -0
  28. package/dist/cell-types/row-toggle-cell.d.ts +8 -0
  29. package/dist/cell-types/row-toggle-cell.d.ts.map +1 -0
  30. package/dist/cell-types/toggle-cell.d.ts +8 -0
  31. package/dist/cell-types/toggle-cell.d.ts.map +1 -0
  32. package/dist/cell-types/warning-message-cell.d.ts +8 -0
  33. package/dist/cell-types/warning-message-cell.d.ts.map +1 -0
  34. package/dist/metadata.js +17 -0
  35. package/dist/story-utils/column-def.d.ts +5 -0
  36. package/dist/story-utils/column-def.d.ts.map +1 -0
  37. package/dist/story-utils/table-data.d.ts +35 -0
  38. package/dist/story-utils/table-data.d.ts.map +1 -0
  39. package/dist/story-utils/use-fetch-table-data-hook.d.ts +11 -0
  40. package/dist/story-utils/use-fetch-table-data-hook.d.ts.map +1 -0
  41. package/dist/styles.css +1 -0
  42. package/dist/table-action-bar.d.ts +26 -0
  43. package/dist/table-action-bar.d.ts.map +1 -0
  44. package/dist/table-body.d.ts +10 -0
  45. package/dist/table-body.d.ts.map +1 -0
  46. package/dist/table-column-header-cell.d.ts +28 -0
  47. package/dist/table-column-header-cell.d.ts.map +1 -0
  48. package/dist/table-export-drawer.d.ts +17 -0
  49. package/dist/table-export-drawer.d.ts.map +1 -0
  50. package/dist/table-header.d.ts +11 -0
  51. package/dist/table-header.d.ts.map +1 -0
  52. package/dist/table-row-cell-skeleton.d.ts +14 -0
  53. package/dist/table-row-cell-skeleton.d.ts.map +1 -0
  54. package/dist/table-row-cell.d.ts +25 -0
  55. package/dist/table-row-cell.d.ts.map +1 -0
  56. package/dist/table-row.d.ts +11 -0
  57. package/dist/table-row.d.ts.map +1 -0
  58. package/dist/table-settings-drawer.d.ts +41 -0
  59. package/dist/table-settings-drawer.d.ts.map +1 -0
  60. package/dist/table-toolbar.d.ts +37 -0
  61. package/dist/table-toolbar.d.ts.map +1 -0
  62. package/dist/table.cjs.js +259 -0
  63. package/dist/table.cjs.js.map +1 -0
  64. package/dist/table.d.ts +20 -0
  65. package/dist/table.d.ts.map +1 -0
  66. package/dist/table.es.js +13585 -0
  67. package/dist/table.es.js.map +1 -0
  68. package/dist/test-utils/column-def.d.ts +6 -0
  69. package/dist/test-utils/column-def.d.ts.map +1 -0
  70. package/dist/test-utils/helpers.d.ts +138 -0
  71. package/dist/test-utils/helpers.d.ts.map +1 -0
  72. package/dist/test-utils/table-data.d.ts +33 -0
  73. package/dist/test-utils/table-data.d.ts.map +1 -0
  74. package/dist/types.d.ts +420 -0
  75. package/dist/types.d.ts.map +1 -0
  76. package/dist/use-screen-size.hook.d.ts +7 -0
  77. package/dist/use-screen-size.hook.d.ts.map +1 -0
  78. package/dist/use-truncated-hook.d.ts +10 -0
  79. package/dist/use-truncated-hook.d.ts.map +1 -0
  80. package/dist/utils/custom-functions.d.ts +9 -0
  81. package/dist/utils/custom-functions.d.ts.map +1 -0
  82. package/dist/utils/unit-conversions.d.ts +19 -0
  83. package/dist/utils/unit-conversions.d.ts.map +1 -0
  84. package/dist/utils/unit-conversions.spec.d.ts +2 -0
  85. package/dist/utils/unit-conversions.spec.d.ts.map +1 -0
  86. package/eslint.config.mjs +2 -0
  87. package/package.json +82 -0
  88. package/src/cell-types/badge-cell.tsx +25 -0
  89. package/src/cell-types/body-text-cell.tsx +54 -0
  90. package/src/cell-types/button-cell.tsx +26 -0
  91. package/src/cell-types/button-group-cell.tsx +54 -0
  92. package/src/cell-types/cta-link-cell.tsx +25 -0
  93. package/src/cell-types/date-cell.tsx +33 -0
  94. package/src/cell-types/empty-cell.tsx +6 -0
  95. package/src/cell-types/error-message-cell.tsx +30 -0
  96. package/src/cell-types/icon-text-cell.tsx +30 -0
  97. package/src/cell-types/lead-text-cell.tsx +19 -0
  98. package/src/cell-types/link-cell.tsx +58 -0
  99. package/src/cell-types/number-cell.tsx +27 -0
  100. package/src/cell-types/row-selection-cell.tsx +22 -0
  101. package/src/cell-types/row-toggle-cell.tsx +23 -0
  102. package/src/cell-types/toggle-cell.tsx +19 -0
  103. package/src/cell-types/warning-message-cell.tsx +30 -0
  104. package/src/global.d.ts +4 -0
  105. package/src/story-utils/column-def.ts +148 -0
  106. package/src/story-utils/table-data.tsx +262 -0
  107. package/src/story-utils/use-fetch-table-data-hook.tsx +30 -0
  108. package/src/table-action-bar.module.scss +106 -0
  109. package/src/table-action-bar.test.tsx +111 -0
  110. package/src/table-action-bar.tsx +104 -0
  111. package/src/table-body.tsx +25 -0
  112. package/src/table-column-header-cell.tsx +305 -0
  113. package/src/table-export-drawer.module.scss +9 -0
  114. package/src/table-export-drawer.test.tsx +75 -0
  115. package/src/table-export-drawer.tsx +59 -0
  116. package/src/table-header.tsx +35 -0
  117. package/src/table-kitchen-sink.test.tsx +1196 -0
  118. package/src/table-row-cell-skeleton.tsx +61 -0
  119. package/src/table-row-cell.test.tsx +360 -0
  120. package/src/table-row-cell.tsx +188 -0
  121. package/src/table-row.tsx +30 -0
  122. package/src/table-settings-drawer.module.scss +25 -0
  123. package/src/table-settings-drawer.test.tsx +350 -0
  124. package/src/table-settings-drawer.tsx +254 -0
  125. package/src/table-toolbar.module.scss +17 -0
  126. package/src/table-toolbar.test.tsx +95 -0
  127. package/src/table-toolbar.tsx +136 -0
  128. package/src/table.module.scss +367 -0
  129. package/src/table.stories.tsx +1246 -0
  130. package/src/table.story.css +11 -0
  131. package/src/table.test.tsx +318 -0
  132. package/src/table.tsx +501 -0
  133. package/src/test-utils/column-def.ts +152 -0
  134. package/src/test-utils/helpers.ts +234 -0
  135. package/src/test-utils/table-data.tsx +318 -0
  136. package/src/types.ts +496 -0
  137. package/src/use-screen-size.hook.ts +23 -0
  138. package/src/use-truncated-hook.tsx +74 -0
  139. package/src/utils/custom-functions.ts +52 -0
  140. package/src/utils/unit-conversions.spec.ts +92 -0
  141. package/src/utils/unit-conversions.ts +30 -0
  142. package/vitest.setup.ts +60 -0
@@ -0,0 +1,61 @@
1
+ import React from "react";
2
+ import { Skeleton } from "@purpurds/skeleton";
3
+ import c from "classnames/bind";
4
+
5
+ import styles from "./table.module.scss";
6
+ import { pxToRemString } from "./utils/unit-conversions";
7
+
8
+ const cx = c.bind(styles);
9
+
10
+ type TableRowCellSkeletonProps = {
11
+ isLastRow: boolean;
12
+ isFirstCell: boolean;
13
+ isLastCell: boolean;
14
+ stickyColumn?: boolean;
15
+ isScrolled: boolean;
16
+ showBorder: boolean;
17
+ cellWidth: number | string;
18
+ };
19
+
20
+ const rootClassName = "purpur-table-row-cell";
21
+
22
+ const TableRowCellSkeleton = ({
23
+ isLastRow,
24
+ isFirstCell,
25
+ isLastCell,
26
+ stickyColumn,
27
+ isScrolled,
28
+ showBorder,
29
+ cellWidth,
30
+ }: TableRowCellSkeletonProps) => {
31
+ const classes = cx([
32
+ rootClassName,
33
+ {
34
+ [`${rootClassName}__sticky-column`]: stickyColumn,
35
+ [`${rootClassName}__sticky-column__with-sticky-border`]: isScrolled && showBorder,
36
+ [`${rootClassName}__border-radius-first-cell`]: isFirstCell && isLastRow,
37
+ [`${rootClassName}__border-radius-last-cell`]: isLastCell && isLastRow,
38
+ [`${rootClassName}__last-row-first-cell-visible`]: isFirstCell && isLastRow,
39
+ [`${rootClassName}__last-row-last-cell-visible`]: isLastCell && isLastRow,
40
+ },
41
+ ]);
42
+
43
+ const cellWidthString = typeof cellWidth === "string" ? cellWidth : pxToRemString(cellWidth);
44
+
45
+ return (
46
+ <td
47
+ className={classes}
48
+ style={{
49
+ minWidth: cellWidthString,
50
+ maxWidth: cellWidthString,
51
+ }}
52
+ >
53
+ <Skeleton
54
+ data-testid="purpur-table-cell-skeleton"
55
+ className={cx(`${rootClassName}__skeleton`)}
56
+ />
57
+ </td>
58
+ );
59
+ };
60
+
61
+ export default TableRowCellSkeleton;
@@ -0,0 +1,360 @@
1
+ import React from "react";
2
+ import { IconCheckmark } from "@purpurds/icon/checkmark";
3
+ import { IconRemove } from "@purpurds/icon/remove";
4
+ import { Cell } from "@tanstack/react-table";
5
+ import { render, screen } from "@testing-library/react";
6
+ import { describe, expect, it, vi } from "vitest";
7
+
8
+ import TableRowCell from "./table-row-cell";
9
+ import { CellType } from "./types";
10
+
11
+ describe("Table row cell - Cell types", () => {
12
+ describe("Badge", () => {
13
+ it("should render badge", () => {
14
+ renderTableRowCell("badge", { children: "Badge label", variant: "primary" });
15
+
16
+ expect(screen.getByText("Badge label")).toBeInTheDocument();
17
+ });
18
+
19
+ it("should render empty cell when value is undefined", () => {
20
+ renderTableRowCell("badge", undefined);
21
+
22
+ expect(screen.getByText("-")).toBeInTheDocument();
23
+ });
24
+ });
25
+
26
+ describe("Body text", () => {
27
+ it("should render body text", () => {
28
+ renderTableRowCell("bodyText", "Body text");
29
+
30
+ expect(screen.getByText("Body text")).toBeInTheDocument();
31
+ });
32
+
33
+ it("should render empty cell when value is undefined", () => {
34
+ renderTableRowCell("bodyText", undefined);
35
+
36
+ expect(screen.getByText("-")).toBeInTheDocument();
37
+ });
38
+ });
39
+
40
+ describe("Button", () => {
41
+ it("should render button", () => {
42
+ renderTableRowCell(
43
+ "button",
44
+ { label: "Click Me", onClick: vi.fn() },
45
+ {
46
+ original: {
47
+ button: {
48
+ variant: "primary",
49
+ children: "Click Me",
50
+ },
51
+ },
52
+ },
53
+ "button"
54
+ );
55
+
56
+ expect(screen.getByText("Click Me")).toBeInTheDocument();
57
+ });
58
+
59
+ it("should render empty cell when value is undefined", () => {
60
+ renderTableRowCell(
61
+ "button",
62
+ undefined,
63
+ {
64
+ original: {
65
+ button: undefined,
66
+ },
67
+ },
68
+ "button"
69
+ );
70
+
71
+ expect(screen.getByText("-")).toBeInTheDocument();
72
+ });
73
+ });
74
+
75
+ describe("Button group", () => {
76
+ it("should render button group", () => {
77
+ renderTableRowCell(
78
+ "buttonGroup",
79
+ [],
80
+ {
81
+ original: {
82
+ buttonGroup: [
83
+ {
84
+ label: "Button 1",
85
+ icon: <IconCheckmark data-testid="checkmark-icon" />,
86
+ variant: "primary",
87
+ },
88
+ { label: "Button 2", icon: <IconRemove data-testid="remove-icon" /> },
89
+ ],
90
+ },
91
+ },
92
+ "buttonGroup"
93
+ );
94
+
95
+ expect(screen.getByTestId("checkmark-icon")).toBeInTheDocument();
96
+ expect(screen.getByTestId("remove-icon")).toBeInTheDocument();
97
+ });
98
+
99
+ it("should render empty cell when value is undefined", () => {
100
+ renderTableRowCell(
101
+ "buttonGroup",
102
+ [],
103
+ {
104
+ original: {
105
+ buttonGroup: undefined,
106
+ },
107
+ },
108
+ "buttonGroup"
109
+ );
110
+
111
+ expect(screen.getByText("-")).toBeInTheDocument();
112
+ });
113
+ });
114
+
115
+ describe("CTA link", () => {
116
+ it("should render CTA link", () => {
117
+ renderTableRowCell("ctaLink", { href: "#", children: "CTA link text" });
118
+
119
+ const link = screen.getByRole("link");
120
+
121
+ expect(link).toHaveTextContent("CTA link text");
122
+ expect(link).toHaveAttribute("href", "#");
123
+ });
124
+
125
+ it("should render empty cell when value is undefined", () => {
126
+ renderTableRowCell("ctaLink", undefined);
127
+
128
+ expect(screen.getByText("-")).toBeInTheDocument();
129
+ });
130
+ });
131
+
132
+ describe("Date", () => {
133
+ it("should render date", () => {
134
+ renderTableRowCell("date", "2023-03-15");
135
+
136
+ expect(screen.getByText("2023-03-15")).toBeInTheDocument();
137
+ });
138
+
139
+ it("should render empty cell when value is undefined", () => {
140
+ renderTableRowCell("date", undefined);
141
+
142
+ expect(screen.getByText("-")).toBeInTheDocument();
143
+ });
144
+ });
145
+
146
+ describe("Error alert message", () => {
147
+ it("should render error alert message", () => {
148
+ renderTableRowCell("errorAlertMessage", "Error message");
149
+
150
+ expect(screen.getByText("Error message")).toBeInTheDocument();
151
+ expect(screen.getByTestId("error-icon")).toBeInTheDocument();
152
+ });
153
+
154
+ it("should render empty cell when value is undefined", () => {
155
+ renderTableRowCell("errorAlertMessage", undefined);
156
+
157
+ expect(screen.getByText("-")).toBeInTheDocument();
158
+ });
159
+ });
160
+
161
+ describe("Icon text", () => {
162
+ it("should render icon text", () => {
163
+ renderTableRowCell("iconText", {
164
+ icon: <IconCheckmark data-testid="checkmark-icon" />,
165
+ text: "Icon text",
166
+ });
167
+
168
+ expect(screen.getByText("Icon text")).toBeInTheDocument();
169
+ expect(screen.getByTestId("checkmark-icon")).toBeInTheDocument();
170
+ });
171
+
172
+ it("should render empty cell when value is undefined", () => {
173
+ renderTableRowCell("iconText", undefined);
174
+
175
+ expect(screen.getByText("-")).toBeInTheDocument();
176
+ });
177
+ });
178
+
179
+ describe("Lead text", () => {
180
+ it("should render lead text", () => {
181
+ renderTableRowCell("leadText", "Lead text content");
182
+
183
+ expect(screen.getByText("Lead text content")).toBeInTheDocument();
184
+ });
185
+
186
+ it("should render empty cell when value is undefined", () => {
187
+ renderTableRowCell("leadText", undefined);
188
+
189
+ expect(screen.getByText("-")).toBeInTheDocument();
190
+ });
191
+ });
192
+
193
+ describe("Link", () => {
194
+ it("should render link", () => {
195
+ renderTableRowCell("link", { href: "https://example.com", children: "Example Link" });
196
+
197
+ const link = screen.getByRole("link");
198
+
199
+ expect(link).toHaveTextContent("Example Link");
200
+ expect(link).toHaveAttribute("href", "https://example.com");
201
+ });
202
+
203
+ it("should render empty cell when value is undefined", () => {
204
+ renderTableRowCell("link", undefined);
205
+
206
+ expect(screen.getByText("-")).toBeInTheDocument();
207
+ });
208
+ });
209
+
210
+ describe("Number left aligned", () => {
211
+ it("should render number left aligned", () => {
212
+ renderTableRowCell("number", 12345);
213
+
214
+ const element = screen.getByText("12345");
215
+ expect(element).toBeInTheDocument();
216
+
217
+ const hasRightAlignClass = element.className
218
+ .split(" ")
219
+ .some((c) => c.includes("_right-align"));
220
+ expect(hasRightAlignClass).toBe(false);
221
+ });
222
+
223
+ it("should render empty cell when value is undefined", () => {
224
+ renderTableRowCell("number", undefined);
225
+
226
+ expect(screen.getByText("-")).toBeInTheDocument();
227
+ });
228
+ });
229
+
230
+ describe("Number right aligned", () => {
231
+ it("should render number right aligned", () => {
232
+ renderTableRowCell("number", 12345, undefined, "id", "right");
233
+
234
+ const element = screen.getByText("12345");
235
+ expect(element).toBeInTheDocument();
236
+
237
+ const hasRightAlignClass = element.className
238
+ .split(" ")
239
+ .some((c) => c.includes("_right-align"));
240
+ expect(hasRightAlignClass).toBe(true);
241
+ });
242
+
243
+ it("should render empty cell when value is undefined", () => {
244
+ renderTableRowCell("number", undefined, undefined, "id", "right");
245
+
246
+ expect(screen.getByText("-")).toBeInTheDocument();
247
+ });
248
+ });
249
+
250
+ describe("Row selection", () => {
251
+ it("should render a checkbox", () => {
252
+ renderTableRowCell(
253
+ "rowSelection",
254
+ { checked: true, id: "row-1" },
255
+ {
256
+ getToggleSelectedHandler: vi.fn().mockImplementation(() => vi.fn()),
257
+ getIsSelected: vi.fn().mockReturnValue(true),
258
+ getCanSelect: vi.fn().mockReturnValue(true),
259
+ }
260
+ );
261
+
262
+ const checkbox = screen.getByRole("checkbox");
263
+ expect(checkbox).toBeInTheDocument();
264
+ expect(checkbox).toBeChecked();
265
+ });
266
+ });
267
+
268
+ describe("Row toggle", () => {
269
+ it("should render a radio toggle", () => {
270
+ renderTableRowCell(
271
+ "rowToggle",
272
+ { checked: true, id: "row-1" },
273
+ {
274
+ getToggleSelectedHandler: vi.fn().mockImplementation(() => vi.fn()),
275
+ getIsSelected: vi.fn().mockReturnValue(true),
276
+ getCanSelect: vi.fn().mockReturnValue(true),
277
+ }
278
+ );
279
+
280
+ const radioInput = screen.getByRole("radio");
281
+ expect(radioInput).toBeInTheDocument();
282
+ expect(radioInput).toBeChecked();
283
+ });
284
+ });
285
+
286
+ describe("Toggle", () => {
287
+ it("should render toggle", () => {
288
+ renderTableRowCell(
289
+ "toggle",
290
+ {
291
+ id: "toggle-id",
292
+ label: "Toggle label",
293
+ checked: true,
294
+ },
295
+ {
296
+ getToggleSelectedHandler: vi.fn().mockImplementation(() => vi.fn()),
297
+ }
298
+ );
299
+
300
+ expect(screen.getByText("Toggle label")).toBeInTheDocument();
301
+ });
302
+
303
+ it("should render empty cell when value is undefined", () => {
304
+ renderTableRowCell("toggle", undefined);
305
+
306
+ expect(screen.getByText("-")).toBeInTheDocument();
307
+ });
308
+ });
309
+
310
+ describe("Warning alert message", () => {
311
+ it("should render warning alert message", () => {
312
+ renderTableRowCell("warningAlertMessage", "Warning message");
313
+
314
+ expect(screen.getByText("Warning message")).toBeInTheDocument();
315
+ expect(screen.getByTestId("alert-icon")).toBeInTheDocument();
316
+ });
317
+
318
+ it("should render empty cell when value is undefined", () => {
319
+ renderTableRowCell("warningAlertMessage", undefined);
320
+
321
+ expect(screen.getByText("-")).toBeInTheDocument();
322
+ });
323
+ });
324
+ });
325
+
326
+ const renderTableRowCell = (
327
+ cellType: CellType,
328
+ value: unknown,
329
+ row?: unknown,
330
+ id = "id",
331
+ numberCellAlignment?: "left" | "right" | undefined
332
+ ) => {
333
+ render(
334
+ <table>
335
+ <tbody>
336
+ <tr>
337
+ <TableRowCell
338
+ cell={
339
+ {
340
+ column: {
341
+ columnDef: { id, meta: { cellType, numberCellAlignment } },
342
+ getSize: vi.fn().mockReturnValue(100),
343
+ columns: [],
344
+ id,
345
+ getIsFirstColumn: vi.fn().mockReturnValue(false),
346
+ getIsLastColumn: vi.fn().mockReturnValue(false),
347
+ },
348
+ getValue: vi.fn().mockReturnValue(value),
349
+ row,
350
+ } as unknown as Cell<unknown, unknown> // Typecasting to avoid setting up the entire Cell type
351
+ }
352
+ isLastRow={true}
353
+ isFirstCell={false}
354
+ isLastCell={false}
355
+ />
356
+ </tr>
357
+ </tbody>
358
+ </table>
359
+ );
360
+ };
@@ -0,0 +1,188 @@
1
+ import React, { useEffect, useRef, useState } from "react";
2
+ import { type Cell, flexRender, type RowData } from "@tanstack/react-table";
3
+ import c from "classnames/bind";
4
+
5
+ import { BadgeCell } from "./cell-types/badge-cell";
6
+ import { BodyTextCell } from "./cell-types/body-text-cell";
7
+ import { ButtonCell } from "./cell-types/button-cell";
8
+ import { ButtonGroupCell } from "./cell-types/button-group-cell";
9
+ import { CtaLinkCell } from "./cell-types/cta-link-cell";
10
+ import { DateCell } from "./cell-types/date-cell";
11
+ import { ErrorAlertMessageCell } from "./cell-types/error-message-cell";
12
+ import { IconTextCell } from "./cell-types/icon-text-cell";
13
+ import { LeadTextCell } from "./cell-types/lead-text-cell";
14
+ import { LinkCell } from "./cell-types/link-cell";
15
+ import { NumberCell } from "./cell-types/number-cell";
16
+ import { RowSelectionCell } from "./cell-types/row-selection-cell";
17
+ import { RowToggleCell } from "./cell-types/row-toggle-cell";
18
+ import { ToggleCell } from "./cell-types/toggle-cell";
19
+ import { WarningAlertMessageCell } from "./cell-types/warning-message-cell";
20
+ import styles from "./table.module.scss";
21
+ import { pxToRemString } from "./utils/unit-conversions";
22
+
23
+ const cx = c.bind(styles);
24
+
25
+ type EmptyTableCell = {
26
+ cell?: never;
27
+ children: React.ReactNode;
28
+ };
29
+
30
+ type TableRowCell<TData extends RowData> = {
31
+ cell: Cell<TData, unknown>;
32
+ children?: never;
33
+ };
34
+
35
+ type TableRowCellProps<TData extends RowData> = {
36
+ ["data-testid"]?: string;
37
+ className?: string;
38
+ stickyColumn?: boolean;
39
+ colSpan?: number;
40
+ isScrolled?: boolean;
41
+ showBorder?: boolean;
42
+ isLastRow: boolean;
43
+ isFirstCell: boolean;
44
+ isLastCell: boolean;
45
+ } & (EmptyTableCell | TableRowCell<TData>);
46
+
47
+ const rootClassName = "purpur-table-row-cell";
48
+
49
+ const TableRowCell = <TData extends RowData>({
50
+ className,
51
+ ["data-testid"]: dataTestId,
52
+ cell,
53
+ stickyColumn,
54
+ children,
55
+ colSpan = 1,
56
+ isScrolled,
57
+ showBorder,
58
+ isLastRow,
59
+ isFirstCell,
60
+ isLastCell,
61
+ }: TableRowCellProps<TData>) => {
62
+ const [isVisible, setIsVisible] = useState(false);
63
+ const elementRef = useRef<HTMLTableCellElement>(null);
64
+
65
+ useEffect(() => {
66
+ const currentElement = elementRef.current;
67
+ if (!currentElement) {
68
+ return;
69
+ }
70
+
71
+ const observer = new IntersectionObserver(
72
+ ([entry]) => {
73
+ setIsVisible(entry.isIntersecting);
74
+ },
75
+ { threshold: 1 }
76
+ );
77
+
78
+ observer.observe(currentElement);
79
+
80
+ return () => {
81
+ if (currentElement) {
82
+ observer.unobserve(currentElement);
83
+ }
84
+ };
85
+ }, []);
86
+
87
+ const classes = cx([
88
+ className,
89
+ rootClassName,
90
+ {
91
+ [`${rootClassName}__sticky-column`]: stickyColumn,
92
+ [`${rootClassName}__sticky-column__with-sticky-border`]: isScrolled && showBorder,
93
+ [`${rootClassName}__border-radius-first-cell`]: isFirstCell && isLastRow,
94
+ [`${rootClassName}__border-radius-last-cell`]: isLastCell && isLastRow,
95
+ [`${rootClassName}__last-row-first-cell-visible`]:
96
+ (cell?.column.getIsFirstColumn() || isFirstCell) && isVisible && isLastRow,
97
+ [`${rootClassName}__last-row-last-cell-visible`]:
98
+ (cell?.column.getIsLastColumn() || isLastCell) && isVisible && isLastRow,
99
+ },
100
+ ]);
101
+
102
+ const styles = cell
103
+ ? {
104
+ width: pxToRemString(cell.column.getSize()),
105
+ }
106
+ : {};
107
+
108
+ const cellContent = cell ? getCellContent(cell) : children;
109
+
110
+ return (
111
+ <td
112
+ ref={elementRef}
113
+ data-testid={dataTestId}
114
+ className={classes}
115
+ key={cell?.column.columnDef?.id}
116
+ style={styles}
117
+ colSpan={colSpan}
118
+ >
119
+ {cellContent}
120
+ </td>
121
+ );
122
+ };
123
+
124
+ export default TableRowCell;
125
+
126
+ const getCellContent = <TData extends RowData>(cell: Cell<TData, unknown>) => {
127
+ const { cellType } = cell.column.columnDef.meta ?? {};
128
+
129
+ // Define which cell types should have guaranteed truncation
130
+ const needsTruncation = ["bodyText", "iconText", "leadText", "link"].includes(cellType || "");
131
+
132
+ const content = (() => {
133
+ switch (cellType) {
134
+ case "badge":
135
+ return <BadgeCell cell={cell} />;
136
+ case "bodyText":
137
+ return <BodyTextCell cell={cell} />;
138
+ case "button":
139
+ return <ButtonCell cell={cell} />;
140
+ case "buttonGroup":
141
+ return <ButtonGroupCell cell={cell} />;
142
+ case "ctaLink":
143
+ return <CtaLinkCell cell={cell} />;
144
+ case "date":
145
+ return <DateCell cell={cell} />;
146
+ case "errorAlertMessage":
147
+ return <ErrorAlertMessageCell cell={cell} />;
148
+ case "iconText":
149
+ return <IconTextCell cell={cell} />;
150
+ case "leadText":
151
+ return <LeadTextCell cell={cell} />;
152
+ case "link":
153
+ return <LinkCell cell={cell} />;
154
+ case "number":
155
+ return <NumberCell cell={cell} />;
156
+ case "rowSelection":
157
+ return <RowSelectionCell cell={cell} />;
158
+ case "rowToggle":
159
+ return <RowToggleCell cell={cell} />;
160
+ case "toggle":
161
+ return <ToggleCell cell={cell} />;
162
+ case "warningAlertMessage":
163
+ return <WarningAlertMessageCell cell={cell} />;
164
+ default:
165
+ return flexRender(cell.column.columnDef.cell, cell.getContext());
166
+ }
167
+ })();
168
+
169
+ // If the cell type needs guaranteed truncation, wrap it with a truncation container
170
+ if (needsTruncation && cell) {
171
+ const widthInRemString = pxToRemString(cell.column.getSize());
172
+
173
+ return (
174
+ <div
175
+ className={cx(`${rootClassName}__truncate`)}
176
+ style={{
177
+ width: "100%",
178
+ maxWidth: widthInRemString,
179
+ minWidth: widthInRemString,
180
+ }}
181
+ >
182
+ {content}
183
+ </div>
184
+ );
185
+ }
186
+
187
+ return content;
188
+ };
@@ -0,0 +1,30 @@
1
+ import React, { ReactNode } from "react";
2
+ import c from "classnames/bind";
3
+
4
+ import styles from "./table.module.scss";
5
+ const cx = c.bind(styles);
6
+
7
+ type TableRowProps = {
8
+ ["data-testid"]?: string;
9
+ children: ReactNode;
10
+ className?: string;
11
+ isSelected?: boolean;
12
+ };
13
+
14
+ const rootClassName = "purpur-table-row";
15
+
16
+ const TableRow = ({
17
+ children,
18
+ className,
19
+ ["data-testid"]: dataTestId,
20
+ isSelected,
21
+ }: TableRowProps) => (
22
+ <tr
23
+ data-testid={dataTestId}
24
+ className={cx([className, rootClassName, { [`${rootClassName}--selected`]: isSelected }])}
25
+ >
26
+ {children}
27
+ </tr>
28
+ );
29
+
30
+ export default TableRow;
@@ -0,0 +1,25 @@
1
+ .purpur-table-settings-drawer {
2
+ display: flex;
3
+ flex-direction: column;
4
+ align-items: flex-start;
5
+ gap: var(--purpur-spacing-600);
6
+ align-self: stretch;
7
+
8
+ &__general-settings,
9
+ &__visible-columns {
10
+ display: flex;
11
+ flex-direction: column;
12
+ align-items: flex-start;
13
+ gap: var(--purpur-spacing-200);
14
+ align-self: stretch;
15
+ }
16
+
17
+ &__button-group {
18
+ text-align: center;
19
+ }
20
+
21
+ &__toggle {
22
+ justify-content: space-between;
23
+ width: 100%;
24
+ }
25
+ }