@navikt/ds-react 8.10.2 → 8.10.3

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 (98) hide show
  1. package/cjs/data/table/column-header/DataTableColumnHeader.js +3 -1
  2. package/cjs/data/table/column-header/DataTableColumnHeader.js.map +1 -1
  3. package/cjs/data/table/column-header/useTableColumnResize.d.ts +24 -2
  4. package/cjs/data/table/column-header/useTableColumnResize.js +31 -15
  5. package/cjs/data/table/column-header/useTableColumnResize.js.map +1 -1
  6. package/cjs/data/table/helpers/collectTableRowEntries.d.ts +10 -2
  7. package/cjs/data/table/helpers/collectTableRowEntries.js +25 -17
  8. package/cjs/data/table/helpers/collectTableRowEntries.js.map +1 -1
  9. package/cjs/data/table/helpers/selection/SelectionSubtreeHelper.d.ts +46 -0
  10. package/cjs/data/table/helpers/selection/SelectionSubtreeHelper.js +112 -0
  11. package/cjs/data/table/helpers/selection/SelectionSubtreeHelper.js.map +1 -0
  12. package/cjs/data/table/helpers/selection/getMultipleSelectProps.d.ts +3 -2
  13. package/cjs/data/table/helpers/selection/getMultipleSelectProps.js +43 -19
  14. package/cjs/data/table/helpers/selection/getMultipleSelectProps.js.map +1 -1
  15. package/cjs/data/table/helpers/selection/selection.types.d.ts +1 -0
  16. package/cjs/data/table/helpers/table-keyboard.d.ts +1 -2
  17. package/cjs/data/table/helpers/table-keyboard.js +1 -2
  18. package/cjs/data/table/helpers/table-keyboard.js.map +1 -1
  19. package/cjs/data/table/hooks/useTableExpansion.d.ts +1 -3
  20. package/cjs/data/table/hooks/useTableExpansion.js +7 -1
  21. package/cjs/data/table/hooks/useTableExpansion.js.map +1 -1
  22. package/cjs/data/table/hooks/useTableItems.d.ts +7 -3
  23. package/cjs/data/table/hooks/useTableItems.js +18 -7
  24. package/cjs/data/table/hooks/useTableItems.js.map +1 -1
  25. package/cjs/data/table/hooks/useTableSelection.d.ts +3 -2
  26. package/cjs/data/table/hooks/useTableSelection.js +5 -4
  27. package/cjs/data/table/hooks/useTableSelection.js.map +1 -1
  28. package/cjs/data/table/root/DataTable.types.d.ts +5 -4
  29. package/cjs/data/table/root/DataTableAuto.d.ts +9 -1
  30. package/cjs/data/table/root/DataTableAuto.js +50 -50
  31. package/cjs/data/table/root/DataTableAuto.js.map +1 -1
  32. package/cjs/data/table/root/DataTableRoot.js +2 -3
  33. package/cjs/data/table/root/DataTableRoot.js.map +1 -1
  34. package/cjs/form/checkbox/Checkbox.js +1 -0
  35. package/cjs/form/checkbox/Checkbox.js.map +1 -1
  36. package/cjs/form/radio/Radio.js +7 -1
  37. package/cjs/form/radio/Radio.js.map +1 -1
  38. package/cjs/modal/types.d.ts +8 -4
  39. package/esm/data/table/column-header/DataTableColumnHeader.js +3 -1
  40. package/esm/data/table/column-header/DataTableColumnHeader.js.map +1 -1
  41. package/esm/data/table/column-header/useTableColumnResize.d.ts +24 -2
  42. package/esm/data/table/column-header/useTableColumnResize.js +32 -16
  43. package/esm/data/table/column-header/useTableColumnResize.js.map +1 -1
  44. package/esm/data/table/helpers/collectTableRowEntries.d.ts +10 -2
  45. package/esm/data/table/helpers/collectTableRowEntries.js +25 -17
  46. package/esm/data/table/helpers/collectTableRowEntries.js.map +1 -1
  47. package/esm/data/table/helpers/selection/SelectionSubtreeHelper.d.ts +46 -0
  48. package/esm/data/table/helpers/selection/SelectionSubtreeHelper.js +109 -0
  49. package/esm/data/table/helpers/selection/SelectionSubtreeHelper.js.map +1 -0
  50. package/esm/data/table/helpers/selection/getMultipleSelectProps.d.ts +3 -2
  51. package/esm/data/table/helpers/selection/getMultipleSelectProps.js +43 -19
  52. package/esm/data/table/helpers/selection/getMultipleSelectProps.js.map +1 -1
  53. package/esm/data/table/helpers/selection/selection.types.d.ts +1 -0
  54. package/esm/data/table/helpers/table-keyboard.d.ts +1 -2
  55. package/esm/data/table/helpers/table-keyboard.js +1 -2
  56. package/esm/data/table/helpers/table-keyboard.js.map +1 -1
  57. package/esm/data/table/hooks/useTableExpansion.d.ts +1 -3
  58. package/esm/data/table/hooks/useTableExpansion.js +7 -1
  59. package/esm/data/table/hooks/useTableExpansion.js.map +1 -1
  60. package/esm/data/table/hooks/useTableItems.d.ts +7 -3
  61. package/esm/data/table/hooks/useTableItems.js +18 -7
  62. package/esm/data/table/hooks/useTableItems.js.map +1 -1
  63. package/esm/data/table/hooks/useTableSelection.d.ts +3 -2
  64. package/esm/data/table/hooks/useTableSelection.js +5 -4
  65. package/esm/data/table/hooks/useTableSelection.js.map +1 -1
  66. package/esm/data/table/root/DataTable.types.d.ts +5 -4
  67. package/esm/data/table/root/DataTableAuto.d.ts +9 -1
  68. package/esm/data/table/root/DataTableAuto.js +51 -51
  69. package/esm/data/table/root/DataTableAuto.js.map +1 -1
  70. package/esm/data/table/root/DataTableRoot.js +3 -4
  71. package/esm/data/table/root/DataTableRoot.js.map +1 -1
  72. package/esm/form/checkbox/Checkbox.js +1 -0
  73. package/esm/form/checkbox/Checkbox.js.map +1 -1
  74. package/esm/form/radio/Radio.js +7 -1
  75. package/esm/form/radio/Radio.js.map +1 -1
  76. package/esm/modal/types.d.ts +8 -4
  77. package/package.json +3 -3
  78. package/src/data/table/column-header/DataTableColumnHeader.tsx +5 -1
  79. package/src/data/table/column-header/useTableColumnResize.ts +73 -17
  80. package/src/data/table/helpers/collectTableRowEntries.ts +57 -25
  81. package/src/data/table/helpers/selection/SelectionSubtreeHelper.test.ts +66 -0
  82. package/src/data/table/helpers/selection/SelectionSubtreeHelper.ts +162 -0
  83. package/src/data/table/helpers/selection/getMultipleSelectProps.ts +57 -20
  84. package/src/data/table/helpers/selection/selection.types.ts +1 -0
  85. package/src/data/table/helpers/table-keyboard.ts +1 -2
  86. package/src/data/table/hooks/__tests__/useTableItems.test.ts +14 -0
  87. package/src/data/table/hooks/__tests__/useTableSelection.test.ts +132 -21
  88. package/src/data/table/hooks/useTableExpansion.tsx +8 -3
  89. package/src/data/table/hooks/useTableItems.ts +50 -27
  90. package/src/data/table/hooks/useTableSelection.ts +10 -6
  91. package/src/data/table/root/DataTable.types.ts +5 -4
  92. package/src/data/table/root/DataTableAuto.test.tsx +128 -2
  93. package/src/data/table/root/DataTableAuto.tsx +144 -135
  94. package/src/data/table/root/DataTableRoot.tsx +6 -7
  95. package/src/form/checkbox/Checkbox.tsx +1 -0
  96. package/src/form/radio/Radio.tsx +7 -1
  97. package/src/modal/types.ts +8 -4
  98. package/src/data/table/hooks/__tests__/useTableExpansion.test.tsx +0 -115
@@ -12,11 +12,12 @@ type ColumnDefinition<T> = {
12
12
  minWidth?: number | string;
13
13
  maxWidth?: number | string;
14
14
  /**
15
- * Currently only handles cell alignment.
16
- * TODO: Should this include centering?
17
- * type "icon" or something to avoid ellipsis on actions, tags etc
15
+ * Text alignment for cells in this column.
16
+ *
17
+ *
18
+ * @default "left"
18
19
  */
19
- type?: "string" | "number";
20
+ align?: "left" | "right" | "center";
20
21
  /**
21
22
  * Assigned to the cell's `th` element instead of `td` if true.
22
23
  *
@@ -20,6 +20,20 @@ const data: TestRow[] = [
20
20
  },
21
21
  ];
22
22
 
23
+ const deepNestedData: TestRow[] = [
24
+ {
25
+ id: "root",
26
+ name: "Root",
27
+ subRows: [
28
+ {
29
+ id: "child",
30
+ name: "Child",
31
+ subRows: [{ id: "grandchild", name: "Grandchild" }],
32
+ },
33
+ ],
34
+ },
35
+ ];
36
+
23
37
  const fallbackIdData: TestRow[] = [
24
38
  {
25
39
  id: "unused-root-1",
@@ -49,7 +63,7 @@ describe("DataTableAuto", () => {
49
63
  data={data}
50
64
  getRowId={(row) => row.id}
51
65
  getSubRows={(row) => row.subRows ?? []}
52
- selectionMode="multiple"
66
+ selection={{ selectionMode: "multiple" }}
53
67
  />,
54
68
  );
55
69
 
@@ -78,7 +92,7 @@ describe("DataTableAuto", () => {
78
92
  data={fallbackIdData}
79
93
  getSubRows={(row) => row.subRows ?? []}
80
94
  defaultExpandedSubRowIds={[0]}
81
- selectionMode="multiple"
95
+ selection={{ selectionMode: "multiple" }}
82
96
  />,
83
97
  );
84
98
 
@@ -94,6 +108,118 @@ describe("DataTableAuto", () => {
94
108
  expect(rowCheckboxes.every((checkbox) => checkbox.checked)).toBe(true);
95
109
  });
96
110
 
111
+ test("parent row selection follows visible nested rows", () => {
112
+ render(
113
+ <DataTableAuto
114
+ columnDefinitions={columns}
115
+ data={data}
116
+ getRowId={(row) => row.id}
117
+ getSubRows={(row) => row.subRows ?? []}
118
+ selection={{ selectionMode: "multiple" }}
119
+ />,
120
+ );
121
+
122
+ fireEvent.click(screen.getByRole("button", { name: "Vis under-rader" }));
123
+
124
+ const getCheckboxes = () =>
125
+ screen.getAllByRole("checkbox") as HTMLInputElement[];
126
+
127
+ fireEvent.click(getCheckboxes()[2]);
128
+
129
+ expect(getCheckboxes()[1].checked).toBe(false);
130
+ expect(getCheckboxes()[1].indeterminate).toBe(true);
131
+
132
+ fireEvent.click(getCheckboxes()[1]);
133
+
134
+ expect(getCheckboxes()[1].checked).toBe(true);
135
+ expect(getCheckboxes()[2].checked).toBe(true);
136
+
137
+ fireEvent.click(getCheckboxes()[1]);
138
+
139
+ expect(getCheckboxes()[1].checked).toBe(false);
140
+ expect(getCheckboxes()[2].checked).toBe(false);
141
+ });
142
+
143
+ test("collapsed parent selection includes hidden descendants and can clear them again", () => {
144
+ render(
145
+ <DataTableAuto
146
+ columnDefinitions={columns}
147
+ data={deepNestedData}
148
+ getRowId={(row) => row.id}
149
+ getSubRows={(row) => row.subRows ?? []}
150
+ selection={{ selectionMode: "multiple" }}
151
+ />,
152
+ );
153
+
154
+ const getCheckboxes = () =>
155
+ screen.getAllByRole("checkbox") as HTMLInputElement[];
156
+
157
+ fireEvent.click(getCheckboxes()[1]);
158
+
159
+ fireEvent.click(screen.getByRole("button", { name: "Vis under-rader" }));
160
+
161
+ expect(getCheckboxes()[1].checked).toBe(true);
162
+ expect(getCheckboxes()[2].checked).toBe(true);
163
+
164
+ fireEvent.click(screen.getByRole("button", { name: "Vis under-rader" }));
165
+
166
+ expect(getCheckboxes()[3].checked).toBe(true);
167
+
168
+ fireEvent.click(
169
+ screen.getAllByRole("button", { name: "Skjul under-rader" })[0],
170
+ );
171
+ fireEvent.click(getCheckboxes()[1]);
172
+
173
+ fireEvent.click(screen.getByRole("button", { name: "Vis under-rader" }));
174
+
175
+ expect(getCheckboxes()[1].checked).toBe(false);
176
+ expect(getCheckboxes()[2].checked).toBe(false);
177
+ expect(getCheckboxes()[3].checked).toBe(false);
178
+ });
179
+
180
+ test("select-all includes hidden descendants for collapsed parents and clears them again", () => {
181
+ render(
182
+ <DataTableAuto
183
+ columnDefinitions={columns}
184
+ data={deepNestedData}
185
+ getRowId={(row) => row.id}
186
+ getSubRows={(row) => row.subRows ?? []}
187
+ selection={{ selectionMode: "multiple" }}
188
+ />,
189
+ );
190
+
191
+ const getCheckboxes = () =>
192
+ screen.getAllByRole("checkbox") as HTMLInputElement[];
193
+
194
+ fireEvent.click(
195
+ screen.getByRole("checkbox", { name: "Velg alle synlige rader" }),
196
+ );
197
+
198
+ fireEvent.click(screen.getByRole("button", { name: "Vis under-rader" }));
199
+
200
+ expect(getCheckboxes()[1].checked).toBe(true);
201
+ expect(getCheckboxes()[2].checked).toBe(true);
202
+
203
+ fireEvent.click(screen.getByRole("button", { name: "Vis under-rader" }));
204
+
205
+ expect(getCheckboxes()[3].checked).toBe(true);
206
+
207
+ fireEvent.click(
208
+ screen.getByRole("checkbox", { name: "Fjern alle synlige valgte rader" }),
209
+ );
210
+
211
+ expect(getCheckboxes()[0].checked).toBe(false);
212
+
213
+ fireEvent.click(
214
+ screen.getAllByRole("button", { name: "Skjul under-rader" })[0],
215
+ );
216
+ fireEvent.click(screen.getByRole("button", { name: "Vis under-rader" }));
217
+
218
+ expect(getCheckboxes()[1].checked).toBe(false);
219
+ expect(getCheckboxes()[2].checked).toBe(false);
220
+ expect(getCheckboxes()[3].checked).toBe(false);
221
+ });
222
+
97
223
  test("does not render expansion controls in the manual table variant", () => {
98
224
  render(
99
225
  <DataTable>
@@ -1,5 +1,5 @@
1
1
  /** biome-ignore-all lint/correctness/useHookAtTopLevel: False positive because of the way forwardRef() is added */
2
- import React, { forwardRef, useMemo } from "react";
2
+ import React, { forwardRef } from "react";
3
3
  import { ChevronDownIcon, ChevronRightIcon } from "@navikt/aksel-icons";
4
4
  import { Button } from "../../../button";
5
5
  import { Skeleton } from "../../../skeleton";
@@ -38,10 +38,7 @@ import {
38
38
  } from "./DataTableRoot.context";
39
39
 
40
40
  interface DataTableProps<T>
41
- extends
42
- React.HTMLAttributes<HTMLTableElement>,
43
- SelectionProps,
44
- TableSortOptions {
41
+ extends React.HTMLAttributes<HTMLTableElement>, TableSortOptions {
45
42
  children?: never;
46
43
  /**
47
44
  * Controls vertical cell padding.
@@ -202,12 +199,20 @@ interface DataTableProps<T>
202
199
  /**
203
200
  * Function to get sub-rows for a given row, used for nested rows.
204
201
  * When provided, an expand toggle column is added automatically.
202
+ *
203
+ *
204
+ * TODO:
205
+ * - Table might need to be implemented with role="treegrid" for a11y when having nested rows.
205
206
  */
206
207
  getSubRows?: (rowData: T) => T[];
207
208
  expandedSubRowIds?: (string | number)[];
208
209
  defaultExpandedSubRowIds?: (string | number)[];
209
210
  isSubRowExpandable?: (rowData: T) => boolean;
210
211
  onExpandedSubRowIdsChange?: (ids: (string | number)[]) => void;
212
+ /**
213
+ * Props for row selection functionality.
214
+ */
215
+ selection?: SelectionProps;
211
216
  }
212
217
 
213
218
  function DataTableAutoInner<T>(
@@ -220,11 +225,7 @@ function DataTableAutoInner<T>(
220
225
  truncateContent = true,
221
226
  shouldBlockNavigation,
222
227
  layout = "fixed",
223
- selectionMode: selectionModeProp = "none",
224
- selectedKeys,
225
- defaultSelectedKeys,
226
- onSelectionChange,
227
- disabledSelectionKeys = [],
228
+ selection,
228
229
  data,
229
230
  columnDefinitions,
230
231
  getRowId,
@@ -239,7 +240,6 @@ function DataTableAutoInner<T>(
239
240
  loadingState,
240
241
  loadingRows,
241
242
  loadingLabel = "Laster innhold",
242
- disableRowSelectionOnClick = false,
243
243
  getDetailsPanelContent,
244
244
  isDetailsPanelExpandable,
245
245
  getDetailsPanelHeight,
@@ -279,15 +279,14 @@ function DataTableAutoInner<T>(
279
279
  onExpandedSubRowIdsChange,
280
280
  });
281
281
 
282
- const allRowKeys = useMemo(() => {
283
- const rowKeys: (string | number)[] = [];
284
-
285
- for (const details of tableItems.itemDetails.values()) {
286
- rowKeys.push(details.id);
287
- }
288
-
289
- return rowKeys;
290
- }, [tableItems.itemDetails]);
282
+ const {
283
+ selectionMode: selectionModeProp = "none",
284
+ selectedKeys,
285
+ defaultSelectedKeys,
286
+ onSelectionChange,
287
+ disabledSelectionKeys = [],
288
+ disableRowSelectionOnClick = false,
289
+ } = selection || {};
291
290
 
292
291
  const tableSelectionState = useTableSelection({
293
292
  selectionMode: selectionModeProp,
@@ -295,7 +294,8 @@ function DataTableAutoInner<T>(
295
294
  defaultSelectedKeys,
296
295
  onSelectionChange,
297
296
  disabledSelectionKeys,
298
- allRowKeys,
297
+ visibleRowIds: tableItems.visibleRowIds,
298
+ childRowIdsById: tableItems.childRowIdsById,
299
299
  });
300
300
 
301
301
  const { columns, stickySelection } = useColumnOptions<T>(columnDefinitions, {
@@ -326,62 +326,62 @@ function DataTableAutoInner<T>(
326
326
  showLoadingOverlay={isLoading && !loadingState && !loadingRows}
327
327
  columns={columns}
328
328
  >
329
- <DataTableExpansionProvider
330
- detailsPanelRowIds={detailsPanelRowIds}
331
- defaultDetailsPanelRowIds={defaultDetailsPanelRowIds}
332
- onDetailsPanelChange={onDetailsPanelChange}
329
+ <TableItemsProvider
333
330
  itemDetails={tableItems.itemDetails}
334
- getDetailsPanelContent={getDetailsPanelContent}
335
- isDetailsPanelExpandable={isDetailsPanelExpandable}
336
- getDetailsPanelHeight={getDetailsPanelHeight}
337
- showExpandAll={showExpandAll}
331
+ items={tableItems.items}
332
+ onExpandedSubRowIdsChange={tableItems.onExpandedSubRowIdsChange}
333
+ isSubRowExpanded={tableItems.isSubRowExpanded}
338
334
  >
339
- <div className="aksel-data-table__border-wrapper">
340
- <div className="aksel-data-table__scroll-wrapper">
341
- <table
342
- {...rest}
343
- ref={mergedRef}
344
- className={cl("aksel-data-table", className)}
345
- data-zebra-stripes={zebraStripes}
346
- data-truncate-content={truncateContent}
347
- data-density={rowDensity}
348
- data-layout={layout}
349
- data-loading={isLoading || undefined}
350
- tabIndex={tabIndex}
351
- aria-busy={isLoading || undefined}
352
- >
353
- <DataTableThead>
354
- <DataTableTr>
355
- {columns.map(({ isSticky, colDef }) => {
356
- const sortEntry = sortState.find(
357
- (s) => s.columnId === colDef.id,
358
- );
359
- const sortDirection = sortEntry?.direction ?? "none";
360
- return (
361
- <DataTableColumnHeader
362
- maxWidth={colDef.maxWidth}
363
- minWidth={colDef.minWidth}
364
- width={colDef.width}
365
- defaultWidth={colDef.defaultWidth ?? "100%"}
366
- textAlign={colDef.type === "number" ? "right" : "left"}
367
- key={colDef.id}
368
- isSticky={isSticky}
369
- sortable={colDef.sortable}
370
- sortDirection={sortDirection}
371
- onSortClick={(event) => onSortClick(colDef.id, event)}
372
- >
373
- {colDef.header}
374
- </DataTableColumnHeader>
375
- );
376
- })}
377
- </DataTableTr>
378
- </DataTableThead>
379
- <TableItemsProvider
380
- itemDetails={tableItems.itemDetails}
381
- items={tableItems.items}
382
- onExpandedSubRowIdsChange={tableItems.onExpandedSubRowIdsChange}
383
- isSubRowExpanded={tableItems.isSubRowExpanded}
335
+ <DataTableExpansionProvider
336
+ detailsPanelRowIds={detailsPanelRowIds}
337
+ defaultDetailsPanelRowIds={defaultDetailsPanelRowIds}
338
+ onDetailsPanelChange={onDetailsPanelChange}
339
+ getDetailsPanelContent={getDetailsPanelContent}
340
+ isDetailsPanelExpandable={isDetailsPanelExpandable}
341
+ getDetailsPanelHeight={getDetailsPanelHeight}
342
+ showExpandAll={showExpandAll}
343
+ >
344
+ <div className="aksel-data-table__border-wrapper">
345
+ <div className="aksel-data-table__scroll-wrapper">
346
+ <table
347
+ {...rest}
348
+ ref={mergedRef}
349
+ className={cl("aksel-data-table", className)}
350
+ data-zebra-stripes={zebraStripes}
351
+ data-truncate-content={truncateContent}
352
+ data-density={rowDensity}
353
+ data-layout={layout}
354
+ data-loading={isLoading || undefined}
355
+ tabIndex={tabIndex}
356
+ aria-busy={isLoading || undefined}
384
357
  >
358
+ <DataTableThead>
359
+ <DataTableTr>
360
+ {columns.map(({ isSticky, colDef }) => {
361
+ const sortEntry = sortState.find(
362
+ (s) => s.columnId === colDef.id,
363
+ );
364
+ const sortDirection = sortEntry?.direction ?? "none";
365
+ return (
366
+ <DataTableColumnHeader
367
+ maxWidth={colDef.maxWidth}
368
+ minWidth={colDef.minWidth}
369
+ width={colDef.width}
370
+ defaultWidth={colDef.defaultWidth ?? "100%"}
371
+ textAlign={colDef.align ?? "left"}
372
+ key={colDef.id}
373
+ isSticky={isSticky}
374
+ sortable={colDef.sortable}
375
+ sortDirection={sortDirection}
376
+ onSortClick={(event) => onSortClick(colDef.id, event)}
377
+ >
378
+ {colDef.header}
379
+ </DataTableColumnHeader>
380
+ );
381
+ })}
382
+ </DataTableTr>
383
+ </DataTableThead>
384
+
385
385
  <DataTableTbody>
386
386
  <DataTableAutoTBodyContent
387
387
  loadingState={loadingState}
@@ -391,11 +391,11 @@ function DataTableAutoInner<T>(
391
391
  fullWidthColSpan={fullWidthColSpan}
392
392
  />
393
393
  </DataTableTbody>
394
- </TableItemsProvider>
395
- </table>
394
+ </table>
395
+ </div>
396
396
  </div>
397
- </div>
398
- </DataTableExpansionProvider>
397
+ </DataTableExpansionProvider>
398
+ </TableItemsProvider>
399
399
  </DataTableContextProvider>
400
400
  );
401
401
  }
@@ -438,7 +438,7 @@ function DataTableAutoTBodyContent({
438
438
  <DataTableTr key={`skeleton-row-${rowIndex}`} aria-hidden>
439
439
  {columns.map(({ isSticky, colDef }, colDefIndex) => (
440
440
  <DataTableBaseCell
441
- textAlign={colDef.type === "number" ? "right" : "left"}
441
+ textAlign={colDef.align ?? "left"}
442
442
  key={colDef.id || colDefIndex}
443
443
  as={colDef.isRowHeader ? "th" : "td"}
444
444
  isSticky={isSticky}
@@ -462,59 +462,64 @@ function DataTableAutoTBodyContent({
462
462
 
463
463
  const renderLoadingAnnouncement = isLoading && !loadingState && !loadingRows;
464
464
 
465
- return items.map((rowData) => {
466
- const details = itemDetails.get(rowData);
467
-
468
- /* Should in theory be impossible. Look about typing this? */
469
- if (!details) {
470
- return null;
471
- }
472
-
473
- const hasSubRows = details.children.length > 0;
474
-
475
- return (
476
- <React.Fragment key={details.id}>
477
- {renderLoadingAnnouncement && (
478
- <tr>
479
- <td colSpan={fullWidthColSpan} className="aksel-sr-only">
480
- {loadingLabel}
481
- </td>
482
- </tr>
483
- )}
484
- <DataTableTr rowId={details.id}>
485
- {columns.map(({ isSticky, colDef }, colDefIndex) => {
486
- const renderNestedToggle = colDefIndex === 0 && hasSubRows;
487
- const renderNestedIndent =
488
- colDefIndex === 0 && (details.level > 0 || hasSubRows);
489
-
490
- const style: React.CSSProperties = {
491
- "--__axc-data-table-nested-depth": details.level,
492
- };
493
-
494
- return (
495
- <DataTableBaseCell
496
- /* TODO: Make this configurable */
497
- textAlign={colDef.type === "number" ? "right" : "left"}
498
- key={colDef.id || colDefIndex}
499
- as={colDef.isRowHeader ? "th" : "td"}
500
- isSticky={isSticky}
501
- data-nested={renderNestedIndent || undefined}
502
- style={style}
503
- >
504
- {renderNestedToggle && <NestedRowToggle details={details} />}
505
- {colDef.cell(rowData)}
506
- </DataTableBaseCell>
507
- );
508
- })}
509
- </DataTableTr>
510
- <DataTableExpandedRow
511
- rowId={details.id}
512
- rowData={rowData}
513
- fullWidthColSpan={fullWidthColSpan}
514
- />
515
- </React.Fragment>
516
- );
517
- });
465
+ return (
466
+ <>
467
+ {renderLoadingAnnouncement && (
468
+ <tr>
469
+ <td colSpan={fullWidthColSpan} className="aksel-sr-only">
470
+ {loadingLabel}
471
+ </td>
472
+ </tr>
473
+ )}
474
+ {items.map((rowData) => {
475
+ const details = itemDetails.get(rowData);
476
+
477
+ /* Should in theory be impossible. Look about typing this? */
478
+ if (!details) {
479
+ return null;
480
+ }
481
+
482
+ const hasSubRows = details.children.length > 0;
483
+
484
+ return (
485
+ <React.Fragment key={details.id}>
486
+ <DataTableTr rowId={details.id}>
487
+ {columns.map(({ isSticky, colDef }, colDefIndex) => {
488
+ const renderNestedToggle = colDefIndex === 0 && hasSubRows;
489
+ const renderNestedIndent =
490
+ colDefIndex === 0 && (details.level > 0 || hasSubRows);
491
+
492
+ const style: React.CSSProperties = {
493
+ "--__axc-data-table-nested-depth": details.level,
494
+ };
495
+
496
+ return (
497
+ <DataTableBaseCell
498
+ textAlign={colDef.align ?? "left"}
499
+ key={colDef.id || colDefIndex}
500
+ as={colDef.isRowHeader ? "th" : "td"}
501
+ isSticky={isSticky}
502
+ data-nested={renderNestedIndent || undefined}
503
+ style={style}
504
+ >
505
+ {renderNestedToggle && (
506
+ <NestedRowToggle details={details} />
507
+ )}
508
+ {colDef.cell(rowData)}
509
+ </DataTableBaseCell>
510
+ );
511
+ })}
512
+ </DataTableTr>
513
+ <DataTableExpandedRow
514
+ rowId={details.id}
515
+ rowData={rowData}
516
+ fullWidthColSpan={fullWidthColSpan}
517
+ />
518
+ </React.Fragment>
519
+ );
520
+ })}
521
+ </>
522
+ );
518
523
  }
519
524
 
520
525
  function NestedRowToggle({ details }: { details: ItemDetail<any> }) {
@@ -583,12 +588,16 @@ function DataTableExpandedRow<T>({
583
588
  return null;
584
589
  }
585
590
 
591
+ const panelHeight = getDetailsPanelHeight?.(rowData);
592
+
593
+ const style: React.CSSProperties = panelHeight
594
+ ? { height: panelHeight, overflow: "auto" }
595
+ : { height: "auto" };
596
+
586
597
  return (
587
598
  <tr>
588
599
  <td id={expansionId} colSpan={fullWidthColSpan}>
589
- <div style={{ height: getDetailsPanelHeight?.(rowData) }}>
590
- {content}
591
- </div>
600
+ <div style={style}>{content}</div>
592
601
  </td>
593
602
  </tr>
594
603
  );
@@ -11,10 +11,11 @@ import {
11
11
  type DataTableEmptyStateProps,
12
12
  } from "../empty-state/DataTableEmptyState";
13
13
  import { DataTableExpansionProvider } from "../hooks/useTableExpansion";
14
- import type { ItemDetail } from "../hooks/useTableItems";
15
14
  import { useTableKeyboardNav } from "../hooks/useTableKeyboardNav";
16
- import { type SelectionProps } from "../hooks/useTableSelection";
17
- import { noSelectionState } from "../hooks/useTableSelection";
15
+ import {
16
+ type SelectionProps,
17
+ noSelectionState,
18
+ } from "../hooks/useTableSelection";
18
19
  import {
19
20
  DataTableLoadingState,
20
21
  type DataTableLoadingStateProps,
@@ -36,8 +37,6 @@ import {
36
37
  import { DataTableTr, type DataTableTrProps } from "../tr/DataTableTr";
37
38
  import { DataTableContextProvider } from "./DataTableRoot.context";
38
39
 
39
- const EMPTY_ITEM_DETAILS = new Map<never, ItemDetail<never>>();
40
-
41
40
  interface DataTableProps
42
41
  extends React.HTMLAttributes<HTMLTableElement>, SelectionProps {
43
42
  children: React.ReactNode;
@@ -232,7 +231,7 @@ const DataTable = forwardRef<HTMLTableElement, DataTableProps>(
232
231
  withKeyboardNav={withKeyboardNav}
233
232
  selectionState={noSelectionState}
234
233
  stickySelection={false}
235
- stickyHeader={false}
234
+ stickyHeader={true}
236
235
  tableId={useId()}
237
236
  showLoadingSkeletons={false}
238
237
  onRowClick={undefined}
@@ -240,7 +239,7 @@ const DataTable = forwardRef<HTMLTableElement, DataTableProps>(
240
239
  showLoadingOverlay={false}
241
240
  columns={[]}
242
241
  >
243
- <DataTableExpansionProvider itemDetails={EMPTY_ITEM_DETAILS}>
242
+ <DataTableExpansionProvider>
244
243
  <div className="aksel-data-table__border-wrapper">
245
244
  <div className="aksel-data-table__scroll-wrapper">
246
245
  <table
@@ -35,6 +35,7 @@ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
35
35
  "indeterminate",
36
36
  "errorId",
37
37
  "readOnly",
38
+ "className",
38
39
  ])}
39
40
  {...omit(inputProps, ["aria-invalid", "aria-describedby"])}
40
41
  aria-describedby={
@@ -24,7 +24,13 @@ export const Radio = forwardRef<HTMLInputElement, RadioProps>(
24
24
  >
25
25
  <RadioInput
26
26
  ref={forwardedRef}
27
- {...omit(props, ["children", "size", "description", "readOnly"])}
27
+ {...omit(props, [
28
+ "children",
29
+ "size",
30
+ "description",
31
+ "readOnly",
32
+ "className",
33
+ ])}
28
34
  {...omit(inputProps, ["aria-invalid", "aria-describedby"])}
29
35
  aria-describedby={
30
36
  cl(inputProps["aria-describedby"], {
@@ -39,7 +39,8 @@ interface ModalPropsBase extends React.DialogHTMLAttributes<HTMLDialogElement> {
39
39
  /**
40
40
  * Called when the user tries to close the modal by one of the built-in methods.
41
41
  * Used if you want to ask the user for confirmation before closing.
42
- * @warning Will not always be called when pressing Esc. See `onCancel` for more info.
42
+ *
43
+ * **NB:** Will not always be called when pressing Esc. See `onCancel` for more info.
43
44
  * @returns Whether to close the modal or not
44
45
  */
45
46
  onBeforeClose?: () => boolean;
@@ -52,7 +53,8 @@ interface ModalPropsBase extends React.DialogHTMLAttributes<HTMLDialogElement> {
52
53
  onCancel?: React.ReactEventHandler<HTMLDialogElement>;
53
54
  /**
54
55
  * Whether to close when clicking on the backdrop.
55
- * @warning Users may click outside by accident. Don't use if closing can cause data loss, or the modal contains important info.
56
+ *
57
+ * **NB:** Users may click outside by accident. Don't use if closing can cause data loss, or the modal contains important info.
56
58
  * @default false
57
59
  */
58
60
  closeOnBackdropClick?: boolean;
@@ -77,13 +79,15 @@ interface ModalPropsBase extends React.DialogHTMLAttributes<HTMLDialogElement> {
77
79
  /**
78
80
  * ID of the element that labels the modal.
79
81
  * No need to set this manually if the `header` prop is used. A reference to `header.heading` will be created automatically.
80
- * @warning If not using `header`, you should set either `aria-labelledby` or `aria-label`.
82
+ *
83
+ * **NB:** If not using `header`, you should set either `aria-labelledby` or `aria-label`.
81
84
  */
82
85
  "aria-labelledby"?: string;
83
86
  /**
84
87
  * String value that labels the modal.
85
88
  * No need to set this if the `header` prop is used.
86
- * @warning If not using `header`, you should set either `aria-labelledby` or `aria-label`.
89
+ *
90
+ * **NB:** If not using `header`, you should set either `aria-labelledby` or `aria-label`.
87
91
  */
88
92
  "aria-label"?: string;
89
93
  }