@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,148 @@
1
+ import { createColumnHelper } from "../table";
2
+ import { SimpleTableData, TableData } from "./table-data";
3
+
4
+ const columnHelper = createColumnHelper<TableData>();
5
+ const simpleColumnHelper = createColumnHelper<SimpleTableData>();
6
+
7
+ export const columnDef = [
8
+ columnHelper.accessor("id", {
9
+ header: "ID",
10
+ size: 100,
11
+ filterFn: "includesString",
12
+ meta: {
13
+ cellType: "leadText",
14
+ filterVariant: "string",
15
+ filterPlaceholder: "Search by id",
16
+ },
17
+ }),
18
+ columnHelper.accessor("name", {
19
+ header: "Name",
20
+ size: 220,
21
+ meta: {
22
+ cellType: "bodyText",
23
+ filterVariant: "string",
24
+ filterPlaceholder: "Search by name",
25
+ },
26
+ }),
27
+ columnHelper.accessor("badge", {
28
+ header: "Badge",
29
+ size: 150,
30
+ filterFn: "filterOnFilterKey",
31
+ sortingFn: "sortOnBadgeValue",
32
+ meta: {
33
+ cellType: "badge",
34
+ filterKey: "variant",
35
+ filterVariant: "select",
36
+ selectProps: {
37
+ value: "",
38
+ options: [
39
+ {
40
+ value: "",
41
+ label: "Default",
42
+ },
43
+ {
44
+ value: "information",
45
+ label: "Information",
46
+ },
47
+ {
48
+ value: "success",
49
+ label: "Success",
50
+ },
51
+ {
52
+ value: "warning",
53
+ label: "Warning",
54
+ },
55
+ ],
56
+ ["aria-label"]: "Filter by badge variant",
57
+ },
58
+ },
59
+ }),
60
+ columnHelper.accessor("date", {
61
+ header: "Date",
62
+ enableColumnFilter: false,
63
+ size: 150,
64
+ meta: {
65
+ cellType: "date",
66
+ },
67
+ }),
68
+ columnHelper.accessor("link", {
69
+ header: "Link",
70
+ size: 150,
71
+ enableSorting: false,
72
+ enableColumnFilter: false,
73
+ meta: {
74
+ cellType: "link",
75
+ },
76
+ }),
77
+ columnHelper.accessor("longString", {
78
+ header: "Truncate",
79
+ size: 100,
80
+ enableSorting: false,
81
+ enableColumnFilter: false,
82
+ meta: {
83
+ cellType: "bodyText",
84
+ },
85
+ }),
86
+ columnHelper.accessor("position", {
87
+ header: "Position",
88
+ size: 100,
89
+ invertSorting: true,
90
+ meta: {
91
+ cellType: "number",
92
+ numberCellAlignment: "right",
93
+ },
94
+ }),
95
+ columnHelper.display({
96
+ id: "button",
97
+ header: "Action",
98
+ size: 100,
99
+ meta: {
100
+ cellType: "button",
101
+ onClick: (cell) => {
102
+ console.log("cell: ", cell);
103
+ },
104
+ },
105
+ }),
106
+ columnHelper.display({
107
+ id: "buttonGroup",
108
+ header: "Button group",
109
+ meta: {
110
+ cellType: "buttonGroup",
111
+ onClick: (cell) => {
112
+ console.log("cell: ", cell);
113
+ },
114
+ },
115
+ }),
116
+ ];
117
+
118
+ // Simple column definition for basic examples
119
+ export const simpleColumnDef = [
120
+ simpleColumnHelper.accessor("name", {
121
+ header: "Name",
122
+ size: 120,
123
+ meta: {
124
+ cellType: "leadText",
125
+ },
126
+ }),
127
+ simpleColumnHelper.accessor("id", {
128
+ header: "ID",
129
+ size: 50,
130
+ meta: {
131
+ cellType: "bodyText",
132
+ },
133
+ }),
134
+ simpleColumnHelper.accessor("longString", {
135
+ header: "Long String",
136
+ size: 300,
137
+ meta: {
138
+ cellType: "bodyText",
139
+ },
140
+ }),
141
+ simpleColumnHelper.accessor("link", {
142
+ header: "Link",
143
+ size: 100,
144
+ meta: {
145
+ cellType: "link",
146
+ },
147
+ }),
148
+ ];
@@ -0,0 +1,262 @@
1
+ import React from "react";
2
+ import { BadgeVariant } from "@purpurds/badge";
3
+ import { ButtonProps } from "@purpurds/button";
4
+ import { IconDownload } from "@purpurds/icon/download";
5
+ import { IconMoreVertical } from "@purpurds/icon/more-vertical";
6
+ import { IconRemove } from "@purpurds/icon/remove";
7
+
8
+ import { ButtonGroupButtonProp } from "../cell-types/button-group-cell";
9
+
10
+ // Table data structure for the data table component
11
+ export type TableData = {
12
+ id: number;
13
+ name: string;
14
+ badge: {
15
+ variant: BadgeVariant;
16
+ children: React.ReactNode;
17
+ };
18
+ date: string;
19
+ link?: {
20
+ href: string;
21
+ children: React.ReactNode;
22
+ };
23
+ longString: string;
24
+ position: number;
25
+ button?: ButtonProps | undefined;
26
+ buttonGroup?: ButtonGroupButtonProp[] | undefined;
27
+ };
28
+
29
+ // Simple table data structure for basic examples
30
+ export type SimpleTableData = {
31
+ id: number;
32
+ name: string;
33
+ link: {
34
+ href: string;
35
+ children: React.ReactNode;
36
+ };
37
+ longString: string;
38
+ };
39
+
40
+ export const tableData: TableData[] = [
41
+ {
42
+ id: 12345,
43
+ name: "Name 1",
44
+ link: {
45
+ href: "#",
46
+ children: "Link 2",
47
+ },
48
+ badge: {
49
+ variant: "information",
50
+ children: "Information",
51
+ },
52
+ date: new Date("2024-05-21T15:10:00+02:00").toISOString(),
53
+ longString: "Some really long string",
54
+ position: 1,
55
+ button: {
56
+ type: "button",
57
+ children: "ClickMe",
58
+ variant: "primary",
59
+ },
60
+ },
61
+ {
62
+ id: 67890,
63
+ name: "Name 17",
64
+ link: { href: "#", children: "Link 9" },
65
+ badge: {
66
+ variant: "success",
67
+ children: "Success",
68
+ },
69
+ date: new Date("2024-08-24T15:10:00+02:00").toISOString(),
70
+ longString: "Lorem, ipsum dolor sit amet consectetur adipisicing elit.",
71
+ position: 2,
72
+ button: undefined,
73
+ },
74
+ {
75
+ id: 12346,
76
+ name: "Name 5",
77
+ link: { href: "#", children: "Link 1" },
78
+ badge: {
79
+ variant: "warning",
80
+ children: "Warning",
81
+ },
82
+ date: new Date("2025-01-14T00:00:00+01:00").toISOString(),
83
+ longString:
84
+ "Incidunt ipsa obcaecati maiores dicta aliquid, earum atque saepe, vero perspiciatis libero rem harum debitis ullam cum doloremque voluptatibus facilis temporibus aliquam!",
85
+ position: 3,
86
+ button: {
87
+ type: "button",
88
+ children: "ClickMe",
89
+ variant: "primary",
90
+ },
91
+ },
92
+ ];
93
+
94
+ export const simpleTableData: SimpleTableData[] = [
95
+ {
96
+ id: 12345,
97
+ name: "Name 1",
98
+ link: { href: "#", children: "Link 2" },
99
+ longString: "Some really long string",
100
+ },
101
+ {
102
+ id: 67890,
103
+ name: "Name 7",
104
+ link: { href: "#", children: "Link 9" },
105
+ longString: "Lorem, ipsum dolor sit amet consectetur adipisicing elit.",
106
+ },
107
+ {
108
+ id: 12346,
109
+ name: "Name 5",
110
+ link: { href: "#", children: "Link 1" },
111
+ longString: "Some really long string",
112
+ },
113
+ {
114
+ id: 98463,
115
+ name: "Name 9",
116
+ link: { href: "#", children: "Link 9" },
117
+ longString: "Some really long string",
118
+ },
119
+ ];
120
+
121
+ const firstNames = [
122
+ "John",
123
+ "Jane",
124
+ "Alex",
125
+ "Emily",
126
+ "Chris",
127
+ "Katie",
128
+ "Michael",
129
+ "Sarah",
130
+ "David",
131
+ "Laura",
132
+ ];
133
+ const lastNames = [
134
+ "Smith",
135
+ "Johnson",
136
+ "Williams",
137
+ "Brown",
138
+ "Jones",
139
+ "Garcia",
140
+ "Miller",
141
+ "Davis",
142
+ "Martinez",
143
+ "Hernandez",
144
+ ];
145
+
146
+ function generateRandomString(minLength: number, maxLength: number): string {
147
+ const length = Math.floor(Math.random() * (maxLength - minLength + 1)) + minLength;
148
+ const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
149
+ let result = "";
150
+ for (let i = 0; i < length; i++) {
151
+ result += characters.charAt(Math.floor(Math.random() * characters.length));
152
+ }
153
+ return result;
154
+ }
155
+
156
+ function generateRandomName(): string {
157
+ const firstName = firstNames[Math.floor(Math.random() * firstNames.length)];
158
+ const lastName = lastNames[Math.floor(Math.random() * lastNames.length)];
159
+ return `${firstName} ${lastName}`;
160
+ }
161
+
162
+ function getRandomDate() {
163
+ const today = new Date();
164
+ const randomDays = Math.floor(Math.random() * 7); // Random number of days within the past week
165
+ const randomHours = Math.floor(Math.random() * 24); // Random hour of the day
166
+ const randomMinutes = Math.floor(Math.random() * 60); // Random minute of the hour
167
+ const randomSeconds = Math.floor(Math.random() * 60); // Random second of the minute
168
+ const randomDate = new Date(today);
169
+ randomDate.setDate(today.getDate() - randomDays);
170
+ randomDate.setHours(randomHours, randomMinutes, randomSeconds);
171
+ return randomDate;
172
+ }
173
+
174
+ export const generateLargeTableData = (rowCount: number): TableData[] => {
175
+ const variants = [
176
+ "attention",
177
+ "special",
178
+ "neutral",
179
+ "information",
180
+ "success",
181
+ "warning",
182
+ "error",
183
+ "black-friday",
184
+ ];
185
+
186
+ const tableData: TableData[] = [
187
+ {
188
+ id: 768,
189
+ name: "Some really long name to be truncated",
190
+ badge: {
191
+ variant: "success",
192
+ children: "Happy",
193
+ },
194
+ date: getRandomDate().toISOString(),
195
+ link: {
196
+ href: "#",
197
+ children: "Some long link name to be truncated",
198
+ },
199
+ longString: "Lorem, ipsum dolor sit amet consectetur adipisicing elit.",
200
+ position: 1,
201
+ button: {
202
+ type: "button",
203
+ children: "ClickMe",
204
+ variant: "text",
205
+ },
206
+ },
207
+ ];
208
+ for (let i = 1; i < rowCount; i++) {
209
+ const id = 1000 + i * 3;
210
+ const name = generateRandomName();
211
+ const variant = variants[Math.floor(Math.random() * variants.length)];
212
+ const badge = {
213
+ variant: variant as BadgeVariant,
214
+ children: variant.charAt(0).toUpperCase() + variant.slice(1),
215
+ };
216
+
217
+ const date = getRandomDate().toISOString();
218
+ const link = {
219
+ href: "#",
220
+ children: generateRandomString(5, 12),
221
+ };
222
+
223
+ const row: TableData = {
224
+ id,
225
+ name,
226
+ badge,
227
+ date,
228
+ link,
229
+ longString: "Lorem, ipsum dolor sit amet consectetur adipisicing elit.",
230
+ position: i + 1,
231
+ button: {
232
+ type: "button",
233
+ children: "ClickMe",
234
+ variant: "text",
235
+ },
236
+ buttonGroup: [
237
+ {
238
+ id: "button-1",
239
+ ariaLabel: "Remove",
240
+ variant: "tertiary-purple",
241
+ icon: <IconRemove data-testid="remove-icon" size="sm" />,
242
+ },
243
+ {
244
+ id: `button-2`,
245
+ ariaLabel: "Download",
246
+ variant: "tertiary-purple",
247
+ icon: <IconDownload data-testid="download-icon" size="sm" />,
248
+ },
249
+ {
250
+ id: `button-3`,
251
+ ariaLabel: "More stuff",
252
+ variant: "tertiary-purple",
253
+ icon: <IconMoreVertical data-testid="more-vertical-icon" size="sm" />,
254
+ },
255
+ ],
256
+ };
257
+
258
+ tableData.push(row);
259
+ }
260
+
261
+ return tableData;
262
+ };
@@ -0,0 +1,30 @@
1
+ import { PaginationState } from "../table";
2
+ import { generateLargeTableData, TableData } from "./table-data";
3
+
4
+ const data = generateLargeTableData(100);
5
+
6
+ export const useFetchTableData = ({
7
+ pagination,
8
+ selectedRows,
9
+ }: {
10
+ pagination: PaginationState;
11
+ selectedRows?: string[];
12
+ }): { data: TableData[]; totalRowCount: number } => {
13
+ if (selectedRows) {
14
+ const selectedData = data
15
+ .filter((row) => selectedRows.includes(`${row.id}`))
16
+ .slice(
17
+ pagination.pageIndex * pagination.pageSize,
18
+ (pagination.pageIndex + 1) * pagination.pageSize
19
+ );
20
+
21
+ return { data: selectedData, totalRowCount: selectedRows.length };
22
+ }
23
+
24
+ const paginatedData = data.slice(
25
+ pagination.pageIndex * pagination.pageSize,
26
+ (pagination.pageIndex + 1) * pagination.pageSize
27
+ );
28
+
29
+ return { data: paginatedData, totalRowCount: data.length };
30
+ };
@@ -0,0 +1,106 @@
1
+ @use "@purpurds/tokens/breakpoint/variables" as *;
2
+
3
+ .purpur-table-action-bar {
4
+ $root: &;
5
+ position: fixed;
6
+ bottom: 0;
7
+ left: 0;
8
+ box-sizing: border-box;
9
+ z-index: 6;
10
+ width: 100%;
11
+
12
+ display: flex;
13
+ padding: var(--purpur-spacing-150) 0px var(--purpur-spacing-150) var(--purpur-spacing-200);
14
+ flex-direction: column;
15
+ align-items: flex-start;
16
+ gap: var(--purpur-spacing-150);
17
+
18
+ border-top: 1px solid var(--purpur-color-border-weak);
19
+ background: var(--purpur-color-background-primary);
20
+
21
+ box-shadow: var(--purpur-shadow-lg);
22
+
23
+ visibility: hidden;
24
+ transform: translateY(100%);
25
+ transition: transform var(--purpur-motion-duration-300) var(--purpur-motion-easing-ease-in),
26
+ visibility var(--purpur-motion-duration-300) var(--purpur-motion-easing-ease-in);
27
+
28
+ &--is-visible {
29
+ visibility: visible;
30
+ transform: translateY(0);
31
+ }
32
+
33
+ &__selected-wrapper {
34
+ display: flex;
35
+ justify-content: space-between;
36
+ row-gap: var(--purpur-spacing-100);
37
+ align-self: stretch;
38
+ align-items: center;
39
+ flex-wrap: wrap;
40
+ gap: var(--purpur-spacing-100);
41
+ }
42
+
43
+ &__button-group {
44
+ display: flex;
45
+ padding-right: var(--purpur-spacing-200);
46
+ flex-direction: column-reverse;
47
+ justify-content: center;
48
+ align-items: flex-start;
49
+ gap: var(--purpur-spacing-200);
50
+ align-self: stretch;
51
+
52
+ #{$root}__primary-secondary-buttons {
53
+ display: flex;
54
+ flex-direction: column;
55
+ align-items: flex-start;
56
+ gap: var(--purpur-spacing-200);
57
+ align-self: stretch;
58
+ }
59
+ }
60
+
61
+ @media (min-width: $purpur-breakpoint-md) {
62
+ padding: var(--purpur-spacing-150) var(--purpur-spacing-100) var(--purpur-spacing-150)
63
+ var(--purpur-spacing-150);
64
+
65
+ &__selected-wrapper {
66
+ align-items: center;
67
+ align-content: center;
68
+ flex-direction: row;
69
+
70
+ span {
71
+ font-weight: 700;
72
+ }
73
+ }
74
+
75
+ &__button-group {
76
+ flex-direction: column-reverse;
77
+ align-items: center;
78
+ gap: var(--purpur-spacing-100);
79
+ align-self: stretch;
80
+ padding-right: 0;
81
+
82
+ #{$root}__primary-secondary-buttons {
83
+ align-items: center;
84
+ flex-direction: row;
85
+ }
86
+ }
87
+ }
88
+
89
+ @media (min-width: $purpur-breakpoint-lg) {
90
+ padding: var(--purpur-spacing-150) var(--purpur-spacing-400) var(--purpur-spacing-150)
91
+ var(--purpur-spacing-600);
92
+ justify-content: space-between;
93
+ align-items: center;
94
+ align-content: center;
95
+ flex-direction: row;
96
+
97
+ &__selected-wrapper {
98
+ gap: var(--purpur-spacing-100) var(--purpur-spacing-200);
99
+ flex-wrap: wrap;
100
+ }
101
+
102
+ &__button-group {
103
+ flex-direction: row;
104
+ }
105
+ }
106
+ }
@@ -0,0 +1,111 @@
1
+ import React from "react";
2
+ import { render, screen, within } from "@testing-library/react";
3
+ import userEvent from "@testing-library/user-event";
4
+ import { beforeEach, describe, expect, it, vi } from "vitest";
5
+ import { axe } from "vitest-axe";
6
+
7
+ import { TableActionBar } from "./table-action-bar";
8
+ import { copy, Selectors } from "./test-utils/helpers";
9
+
10
+ const onCancelMock = vi.fn();
11
+ const onToggleSelectedMock = vi.fn();
12
+ const onPrimaryButtonMock = vi.fn();
13
+ const onSecondaryButtonMock = vi.fn();
14
+
15
+ describe("Data Table - Action bar", () => {
16
+ let actionBar: HTMLElement;
17
+ let container: HTMLElement;
18
+
19
+ beforeEach(() => {
20
+ container = render(
21
+ <TableActionBar
22
+ onCancelSelection={onCancelMock}
23
+ onToggleSelected={onToggleSelectedMock}
24
+ onPrimaryButtonClick={onPrimaryButtonMock}
25
+ onSecondaryButtonClick={onSecondaryButtonMock}
26
+ rowCount={10}
27
+ selectedRowsCount={2}
28
+ copy={copy.actionBar}
29
+ isVisible={true}
30
+ />
31
+ ).container;
32
+
33
+ actionBar = screen.getByTestId(Selectors.ACTION_BAR.ROOT);
34
+ });
35
+
36
+ it("should display batch action bar with '2 of 10 selected", () => {
37
+ const selectedText = within(actionBar).getByTestId(Selectors.ACTION_BAR.SELECTED_TEXT);
38
+ expect(selectedText).toHaveTextContent("2 of 10 selected");
39
+ });
40
+
41
+ it("should have a primary button", () => {
42
+ const primaryButton = within(actionBar).getByTestId(Selectors.ACTION_BAR.PRIMARY_BUTTON);
43
+ expect(primaryButton).toBeVisible();
44
+ expect(primaryButton).toHaveTextContent("Primary button");
45
+ });
46
+
47
+ it("should have a secondary button", () => {
48
+ const secondaryButton = within(actionBar).getByTestId(Selectors.ACTION_BAR.SECONDARY_BUTTON);
49
+ expect(secondaryButton).toBeVisible();
50
+ });
51
+
52
+ it("should have a cancel selection button", () => {
53
+ const cancelButton = within(actionBar).getByTestId(Selectors.ACTION_BAR.CANCEL_BUTTON);
54
+ expect(cancelButton).toBeVisible();
55
+ });
56
+
57
+ it("should have a show only selected toggle", () => {
58
+ const toggleSelected = within(actionBar).getByTestId(Selectors.ACTION_BAR.TOGGLE_SELECTED);
59
+ expect(toggleSelected).toBeVisible();
60
+ });
61
+
62
+ it("should send an event when clicking on cancel button", async () => {
63
+ const closeButton = within(actionBar).getByTestId(Selectors.ACTION_BAR.CANCEL_BUTTON);
64
+ await userEvent.click(closeButton);
65
+ expect(onCancelMock).toHaveBeenCalled();
66
+ });
67
+
68
+ it("should send an event when clicking on toggle selection", async () => {
69
+ const toggleSelected = within(actionBar).getByTestId(Selectors.ACTION_BAR.TOGGLE_SELECTED);
70
+ await userEvent.click(toggleSelected);
71
+ expect(onToggleSelectedMock).toHaveBeenCalled();
72
+ });
73
+
74
+ it("should send an event when clicking on primary button", async () => {
75
+ const primaryButton = within(actionBar).getByTestId(Selectors.ACTION_BAR.PRIMARY_BUTTON);
76
+ await userEvent.click(primaryButton);
77
+ expect(onPrimaryButtonMock).toHaveBeenCalled();
78
+ });
79
+
80
+ it("should send an event when clicking on secondary button", async () => {
81
+ const secondaryButton = within(actionBar).getByTestId(Selectors.ACTION_BAR.SECONDARY_BUTTON);
82
+ await userEvent.click(secondaryButton);
83
+ expect(onSecondaryButtonMock).toHaveBeenCalled();
84
+ });
85
+
86
+ it("should be accessible", async () => {
87
+ const results = await axe(container, {
88
+ elementRef: true,
89
+ });
90
+ const buttonNameViolation = results.violations.find(
91
+ (violation) => violation.id === "button-name"
92
+ );
93
+
94
+ // "Buttons must have discernible text (button-name)"
95
+ // This is rule is failing on the toggles
96
+ if (buttonNameViolation) {
97
+ buttonNameViolation.nodes = buttonNameViolation.nodes.filter(
98
+ (node) => !node.element?.matches('[role="switch"]')
99
+ );
100
+
101
+ // remove the violation if there are no more nodes
102
+ if (!buttonNameViolation.nodes.length) {
103
+ results.violations.splice(results.violations.indexOf(buttonNameViolation), 1);
104
+ }
105
+ }
106
+
107
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
108
+ // @ts-ignore
109
+ expect(results).toHaveNoViolations();
110
+ });
111
+ });