@navikt/ds-react 8.10.1 → 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 (114) hide show
  1. package/cjs/data/table/column-header/DataTableColumnHeader.d.ts +1 -1
  2. package/cjs/data/table/column-header/DataTableColumnHeader.js +16 -11
  3. package/cjs/data/table/column-header/DataTableColumnHeader.js.map +1 -1
  4. package/cjs/data/table/column-header/useTableColumnResize.d.ts +28 -4
  5. package/cjs/data/table/column-header/useTableColumnResize.js +144 -53
  6. package/cjs/data/table/column-header/useTableColumnResize.js.map +1 -1
  7. package/cjs/data/table/helpers/collectTableRowEntries.d.ts +24 -0
  8. package/cjs/data/table/helpers/collectTableRowEntries.js +35 -0
  9. package/cjs/data/table/helpers/collectTableRowEntries.js.map +1 -0
  10. package/cjs/data/table/helpers/selection/SelectionSubtreeHelper.d.ts +46 -0
  11. package/cjs/data/table/helpers/selection/SelectionSubtreeHelper.js +112 -0
  12. package/cjs/data/table/helpers/selection/SelectionSubtreeHelper.js.map +1 -0
  13. package/cjs/data/table/helpers/selection/getMultipleSelectProps.d.ts +3 -2
  14. package/cjs/data/table/helpers/selection/getMultipleSelectProps.js +43 -19
  15. package/cjs/data/table/helpers/selection/getMultipleSelectProps.js.map +1 -1
  16. package/cjs/data/table/helpers/selection/selection.types.d.ts +1 -0
  17. package/cjs/data/table/helpers/table-keyboard.d.ts +1 -2
  18. package/cjs/data/table/helpers/table-keyboard.js +1 -5
  19. package/cjs/data/table/helpers/table-keyboard.js.map +1 -1
  20. package/cjs/data/table/hooks/useTableExpansion.d.ts +7 -6
  21. package/cjs/data/table/hooks/useTableExpansion.js +42 -15
  22. package/cjs/data/table/hooks/useTableExpansion.js.map +1 -1
  23. package/cjs/data/table/hooks/useTableItems.d.ts +33 -0
  24. package/cjs/data/table/hooks/useTableItems.js +74 -0
  25. package/cjs/data/table/hooks/useTableItems.js.map +1 -0
  26. package/cjs/data/table/hooks/useTableKeyboardNav.js +3 -3
  27. package/cjs/data/table/hooks/useTableKeyboardNav.js.map +1 -1
  28. package/cjs/data/table/hooks/useTableSelection.d.ts +3 -2
  29. package/cjs/data/table/hooks/useTableSelection.js +5 -4
  30. package/cjs/data/table/hooks/useTableSelection.js.map +1 -1
  31. package/cjs/data/table/root/DataTable.types.d.ts +5 -4
  32. package/cjs/data/table/root/DataTableAuto.d.ts +27 -1
  33. package/cjs/data/table/root/DataTableAuto.js +92 -50
  34. package/cjs/data/table/root/DataTableAuto.js.map +1 -1
  35. package/cjs/data/table/root/DataTableRoot.context.d.ts +5 -3
  36. package/cjs/data/table/root/DataTableRoot.context.js.map +1 -1
  37. package/cjs/data/table/root/DataTableRoot.js +6 -4
  38. package/cjs/data/table/root/DataTableRoot.js.map +1 -1
  39. package/cjs/data/table/tr/DataTableTr.js +30 -32
  40. package/cjs/data/table/tr/DataTableTr.js.map +1 -1
  41. package/cjs/form/checkbox/Checkbox.js +1 -0
  42. package/cjs/form/checkbox/Checkbox.js.map +1 -1
  43. package/cjs/form/radio/Radio.js +7 -1
  44. package/cjs/form/radio/Radio.js.map +1 -1
  45. package/cjs/modal/types.d.ts +8 -4
  46. package/esm/data/table/column-header/DataTableColumnHeader.d.ts +1 -1
  47. package/esm/data/table/column-header/DataTableColumnHeader.js +17 -12
  48. package/esm/data/table/column-header/DataTableColumnHeader.js.map +1 -1
  49. package/esm/data/table/column-header/useTableColumnResize.d.ts +28 -4
  50. package/esm/data/table/column-header/useTableColumnResize.js +145 -54
  51. package/esm/data/table/column-header/useTableColumnResize.js.map +1 -1
  52. package/esm/data/table/helpers/collectTableRowEntries.d.ts +24 -0
  53. package/esm/data/table/helpers/collectTableRowEntries.js +33 -0
  54. package/esm/data/table/helpers/collectTableRowEntries.js.map +1 -0
  55. package/esm/data/table/helpers/selection/SelectionSubtreeHelper.d.ts +46 -0
  56. package/esm/data/table/helpers/selection/SelectionSubtreeHelper.js +109 -0
  57. package/esm/data/table/helpers/selection/SelectionSubtreeHelper.js.map +1 -0
  58. package/esm/data/table/helpers/selection/getMultipleSelectProps.d.ts +3 -2
  59. package/esm/data/table/helpers/selection/getMultipleSelectProps.js +43 -19
  60. package/esm/data/table/helpers/selection/getMultipleSelectProps.js.map +1 -1
  61. package/esm/data/table/helpers/selection/selection.types.d.ts +1 -0
  62. package/esm/data/table/helpers/table-keyboard.d.ts +1 -2
  63. package/esm/data/table/helpers/table-keyboard.js +1 -5
  64. package/esm/data/table/helpers/table-keyboard.js.map +1 -1
  65. package/esm/data/table/hooks/useTableExpansion.d.ts +7 -6
  66. package/esm/data/table/hooks/useTableExpansion.js +42 -16
  67. package/esm/data/table/hooks/useTableExpansion.js.map +1 -1
  68. package/esm/data/table/hooks/useTableItems.d.ts +33 -0
  69. package/esm/data/table/hooks/useTableItems.js +69 -0
  70. package/esm/data/table/hooks/useTableItems.js.map +1 -0
  71. package/esm/data/table/hooks/useTableKeyboardNav.js +3 -3
  72. package/esm/data/table/hooks/useTableKeyboardNav.js.map +1 -1
  73. package/esm/data/table/hooks/useTableSelection.d.ts +3 -2
  74. package/esm/data/table/hooks/useTableSelection.js +5 -4
  75. package/esm/data/table/hooks/useTableSelection.js.map +1 -1
  76. package/esm/data/table/root/DataTable.types.d.ts +5 -4
  77. package/esm/data/table/root/DataTableAuto.d.ts +27 -1
  78. package/esm/data/table/root/DataTableAuto.js +94 -52
  79. package/esm/data/table/root/DataTableAuto.js.map +1 -1
  80. package/esm/data/table/root/DataTableRoot.context.d.ts +5 -3
  81. package/esm/data/table/root/DataTableRoot.context.js.map +1 -1
  82. package/esm/data/table/root/DataTableRoot.js +7 -5
  83. package/esm/data/table/root/DataTableRoot.js.map +1 -1
  84. package/esm/data/table/tr/DataTableTr.js +32 -34
  85. package/esm/data/table/tr/DataTableTr.js.map +1 -1
  86. package/esm/form/checkbox/Checkbox.js +1 -0
  87. package/esm/form/checkbox/Checkbox.js.map +1 -1
  88. package/esm/form/radio/Radio.js +7 -1
  89. package/esm/form/radio/Radio.js.map +1 -1
  90. package/esm/modal/types.d.ts +8 -4
  91. package/package.json +7 -7
  92. package/src/data/table/column-header/DataTableColumnHeader.tsx +26 -14
  93. package/src/data/table/column-header/useTableColumnResize.ts +209 -80
  94. package/src/data/table/helpers/collectTableRowEntries.ts +90 -0
  95. package/src/data/table/helpers/selection/SelectionSubtreeHelper.test.ts +66 -0
  96. package/src/data/table/helpers/selection/SelectionSubtreeHelper.ts +162 -0
  97. package/src/data/table/helpers/selection/getMultipleSelectProps.ts +57 -20
  98. package/src/data/table/helpers/selection/selection.types.ts +1 -0
  99. package/src/data/table/helpers/table-keyboard.ts +1 -6
  100. package/src/data/table/hooks/__tests__/useTableItems.test.ts +145 -0
  101. package/src/data/table/hooks/__tests__/useTableSelection.test.ts +132 -21
  102. package/src/data/table/hooks/useTableExpansion.tsx +68 -22
  103. package/src/data/table/hooks/useTableItems.ts +146 -0
  104. package/src/data/table/hooks/useTableKeyboardNav.ts +3 -3
  105. package/src/data/table/hooks/useTableSelection.ts +10 -6
  106. package/src/data/table/root/DataTable.types.ts +5 -4
  107. package/src/data/table/root/DataTableAuto.test.tsx +244 -0
  108. package/src/data/table/root/DataTableAuto.tsx +260 -141
  109. package/src/data/table/root/DataTableRoot.context.ts +4 -2
  110. package/src/data/table/root/DataTableRoot.tsx +22 -16
  111. package/src/data/table/tr/DataTableTr.tsx +48 -47
  112. package/src/form/checkbox/Checkbox.tsx +1 -0
  113. package/src/form/radio/Radio.tsx +7 -1
  114. package/src/modal/types.ts +8 -4
@@ -1,5 +1,7 @@
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
+ import { ChevronDownIcon, ChevronRightIcon } from "@navikt/aksel-icons";
4
+ import { Button } from "../../../button";
3
5
  import { Skeleton } from "../../../skeleton";
4
6
  import { useId } from "../../../utils-external";
5
7
  import { cl } from "../../../utils/helpers";
@@ -7,12 +9,18 @@ import { useMergeRefs } from "../../../utils/hooks";
7
9
  import { DataTableBaseCell } from "../base-cell/DataTableBaseCell";
8
10
  import { DataTableColumnHeader } from "../column-header/DataTableColumnHeader";
9
11
  import { DataTableEmptyState } from "../empty-state/DataTableEmptyState";
10
- import type { UseColumnOptionsResult } from "../hooks/useColumnOptions";
11
12
  import { useColumnOptions } from "../hooks/useColumnOptions";
12
13
  import {
13
14
  DataTableExpansionProvider,
15
+ getDataTableExpansionId,
14
16
  useDataTableExpansion,
15
17
  } from "../hooks/useTableExpansion";
18
+ import {
19
+ type ItemDetail,
20
+ TableItemsProvider,
21
+ useTableItems,
22
+ useTableItemsContext,
23
+ } from "../hooks/useTableItems";
16
24
  import { useTableKeyboardNav } from "../hooks/useTableKeyboardNav";
17
25
  import {
18
26
  type SelectionProps,
@@ -30,10 +38,7 @@ import {
30
38
  } from "./DataTableRoot.context";
31
39
 
32
40
  interface DataTableProps<T>
33
- extends
34
- React.HTMLAttributes<HTMLTableElement>,
35
- SelectionProps,
36
- TableSortOptions {
41
+ extends React.HTMLAttributes<HTMLTableElement>, TableSortOptions {
37
42
  children?: never;
38
43
  /**
39
44
  * Controls vertical cell padding.
@@ -157,6 +162,11 @@ interface DataTableProps<T>
157
162
  * When provided, an expand toggle column is added automatically.
158
163
  */
159
164
  getDetailsPanelContent?: (rowData: T) => React.ReactNode;
165
+ /**
166
+ * Determines whether a row can be expanded to show details panel content.
167
+ * @default () => true
168
+ */
169
+ isDetailsPanelExpandable?: (rowData: T) => boolean;
160
170
  /**
161
171
  * Controlled list of expanded row IDs.
162
172
  * Use with `onDetailsPanelChange` for controlled usage, or `defaultDetailsPanelRowIds` for uncontrolled.
@@ -169,6 +179,10 @@ interface DataTableProps<T>
169
179
  defaultDetailsPanelRowIds?: (string | number)[];
170
180
  /**
171
181
  * Called when the list of expanded row IDs changes.
182
+ *
183
+ *
184
+ * TODO:
185
+ * - Docs: This pattern is called "Master / Detail" in general terms
172
186
  */
173
187
  onDetailsPanelChange?: (ids: (string | number)[]) => void;
174
188
  /**
@@ -182,6 +196,23 @@ interface DataTableProps<T>
182
196
  * @default false
183
197
  */
184
198
  showExpandAll?: boolean;
199
+ /**
200
+ * Function to get sub-rows for a given row, used for nested rows.
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.
206
+ */
207
+ getSubRows?: (rowData: T) => T[];
208
+ expandedSubRowIds?: (string | number)[];
209
+ defaultExpandedSubRowIds?: (string | number)[];
210
+ isSubRowExpandable?: (rowData: T) => boolean;
211
+ onExpandedSubRowIdsChange?: (ids: (string | number)[]) => void;
212
+ /**
213
+ * Props for row selection functionality.
214
+ */
215
+ selection?: SelectionProps;
185
216
  }
186
217
 
187
218
  function DataTableAutoInner<T>(
@@ -194,11 +225,7 @@ function DataTableAutoInner<T>(
194
225
  truncateContent = true,
195
226
  shouldBlockNavigation,
196
227
  layout = "fixed",
197
- selectionMode: selectionModeProp = "none",
198
- selectedKeys,
199
- defaultSelectedKeys,
200
- onSelectionChange,
201
- disabledSelectionKeys = [],
228
+ selection,
202
229
  data,
203
230
  columnDefinitions,
204
231
  getRowId,
@@ -213,13 +240,18 @@ function DataTableAutoInner<T>(
213
240
  loadingState,
214
241
  loadingRows,
215
242
  loadingLabel = "Laster innhold",
216
- disableRowSelectionOnClick = false,
217
243
  getDetailsPanelContent,
244
+ isDetailsPanelExpandable,
218
245
  getDetailsPanelHeight,
219
246
  showExpandAll = false,
220
247
  detailsPanelRowIds,
221
248
  defaultDetailsPanelRowIds,
222
249
  onDetailsPanelChange,
250
+ getSubRows,
251
+ expandedSubRowIds,
252
+ defaultExpandedSubRowIds,
253
+ isSubRowExpandable,
254
+ onExpandedSubRowIdsChange,
223
255
  ...rest
224
256
  }: DataTableProps<T>,
225
257
  forwardedRef: React.ForwardedRef<HTMLTableElement>,
@@ -237,12 +269,24 @@ function DataTableAutoInner<T>(
237
269
 
238
270
  const mergedRef = useMergeRefs(forwardedRef, setTableRef);
239
271
 
240
- const allRowKeys = useMemo(() => {
241
- const resolvedGetRowId = (item: T, index: number): string | number =>
242
- getRowId?.(item, index) ?? index;
272
+ const tableItems = useTableItems({
273
+ items: data,
274
+ getRowId,
275
+ getSubRows,
276
+ expandedSubRowIds,
277
+ defaultExpandedSubRowIds,
278
+ isSubRowExpandable,
279
+ onExpandedSubRowIdsChange,
280
+ });
243
281
 
244
- return data.map((item, index) => resolvedGetRowId(item, index));
245
- }, [data, getRowId]);
282
+ const {
283
+ selectionMode: selectionModeProp = "none",
284
+ selectedKeys,
285
+ defaultSelectedKeys,
286
+ onSelectionChange,
287
+ disabledSelectionKeys = [],
288
+ disableRowSelectionOnClick = false,
289
+ } = selection || {};
246
290
 
247
291
  const tableSelectionState = useTableSelection({
248
292
  selectionMode: selectionModeProp,
@@ -250,7 +294,8 @@ function DataTableAutoInner<T>(
250
294
  defaultSelectedKeys,
251
295
  onSelectionChange,
252
296
  disabledSelectionKeys,
253
- allRowKeys,
297
+ visibleRowIds: tableItems.visibleRowIds,
298
+ childRowIdsById: tableItems.childRowIdsById,
254
299
  });
255
300
 
256
301
  const { columns, stickySelection } = useColumnOptions<T>(columnDefinitions, {
@@ -258,6 +303,12 @@ function DataTableAutoInner<T>(
258
303
  selectionMode: tableSelectionState.selection.selectionMode,
259
304
  });
260
305
 
306
+ const fullWidthColSpan =
307
+ columns.length +
308
+ (layout === "fixed" ? 1 : 0) +
309
+ (tableSelectionState.selection.selectionMode !== "none" ? 1 : 0) +
310
+ (getDetailsPanelContent ? 1 : 0);
311
+
261
312
  const tableId = useId(id);
262
313
 
263
314
  return (
@@ -273,99 +324,103 @@ function DataTableAutoInner<T>(
273
324
  disableRowSelectionOnClick={disableRowSelectionOnClick}
274
325
  isLoading={isLoading}
275
326
  showLoadingOverlay={isLoading && !loadingState && !loadingRows}
327
+ columns={columns}
276
328
  >
277
- <DataTableExpansionProvider
278
- detailsPanelRowIds={detailsPanelRowIds}
279
- defaultDetailsPanelRowIds={defaultDetailsPanelRowIds}
280
- onDetailsPanelChange={onDetailsPanelChange}
281
- allRowKeys={allRowKeys}
282
- getDetailsPanelContent={getDetailsPanelContent}
283
- getDetailsPanelHeight={getDetailsPanelHeight}
284
- showExpandAll={showExpandAll}
329
+ <TableItemsProvider
330
+ itemDetails={tableItems.itemDetails}
331
+ items={tableItems.items}
332
+ onExpandedSubRowIdsChange={tableItems.onExpandedSubRowIdsChange}
333
+ isSubRowExpanded={tableItems.isSubRowExpanded}
285
334
  >
286
- <div className="aksel-data-table__border-wrapper">
287
- <div className="aksel-data-table__scroll-wrapper">
288
- <table
289
- {...rest}
290
- ref={mergedRef}
291
- className={cl("aksel-data-table", className)}
292
- data-zebra-stripes={zebraStripes}
293
- data-truncate-content={truncateContent}
294
- data-density={rowDensity}
295
- data-layout={layout}
296
- data-loading={isLoading || undefined}
297
- tabIndex={tabIndex}
298
- aria-busy={isLoading || undefined}
299
- >
300
- <DataTableThead>
301
- <DataTableTr>
302
- {columns.map(({ isSticky, colDef }) => {
303
- const sortEntry = sortState.find(
304
- (s) => s.columnId === colDef.id,
305
- );
306
- const sortDirection = sortEntry?.direction ?? "none";
307
- return (
308
- <DataTableColumnHeader
309
- maxWidth={colDef.maxWidth}
310
- minWidth={colDef.minWidth}
311
- width={colDef.width}
312
- defaultWidth={colDef.defaultWidth ?? "100%"}
313
- textAlign={colDef.type === "number" ? "right" : "left"}
314
- key={colDef.id}
315
- isSticky={isSticky}
316
- sortable={colDef.sortable}
317
- sortDirection={sortDirection}
318
- onSortClick={(event) => onSortClick(colDef.id, event)}
319
- >
320
- {colDef.header}
321
- </DataTableColumnHeader>
322
- );
323
- })}
324
- </DataTableTr>
325
- </DataTableThead>
326
- <DataTableTbody>
327
- <DataTableAutoTBodyContent
328
- columns={columns}
329
- data={data}
330
- allRowKeys={allRowKeys}
331
- loadingState={loadingState}
332
- loadingRows={loadingRows}
333
- loadingLabel={loadingLabel}
334
- emptyState={emptyState}
335
- />
336
- </DataTableTbody>
337
- </table>
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}
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
+ <DataTableTbody>
386
+ <DataTableAutoTBodyContent
387
+ loadingState={loadingState}
388
+ loadingRows={loadingRows}
389
+ loadingLabel={loadingLabel}
390
+ emptyState={emptyState}
391
+ fullWidthColSpan={fullWidthColSpan}
392
+ />
393
+ </DataTableTbody>
394
+ </table>
395
+ </div>
338
396
  </div>
339
- </div>
340
- </DataTableExpansionProvider>
397
+ </DataTableExpansionProvider>
398
+ </TableItemsProvider>
341
399
  </DataTableContextProvider>
342
400
  );
343
401
  }
344
402
 
345
- interface DataTableAutoTBodyContentProps<T> {
346
- columns: UseColumnOptionsResult<T>["columns"];
347
- data: T[];
348
- allRowKeys: (string | number)[];
403
+ interface DataTableAutoTBodyContentProps {
349
404
  loadingState: React.ReactNode;
350
405
  loadingLabel: string;
351
406
  loadingRows?: number;
352
407
  emptyState: React.ReactNode;
408
+ fullWidthColSpan: number;
353
409
  }
354
410
 
355
- function DataTableAutoTBodyContent<T>({
356
- columns,
357
- data,
358
- allRowKeys,
411
+ function DataTableAutoTBodyContent({
359
412
  loadingState,
360
413
  loadingRows,
361
414
  loadingLabel,
362
415
  emptyState,
363
- }: DataTableAutoTBodyContentProps<T>) {
364
- const { isLoading } = useDataTableContext();
416
+ fullWidthColSpan,
417
+ }: DataTableAutoTBodyContentProps) {
418
+ const { items, itemDetails } = useTableItemsContext();
419
+ const { columns, isLoading } = useDataTableContext();
365
420
 
366
421
  if (isLoading && loadingState != null) {
367
422
  return (
368
- <DataTableLoadingState colSpan={columns.length}>
423
+ <DataTableLoadingState colSpan={fullWidthColSpan}>
369
424
  {loadingState}
370
425
  </DataTableLoadingState>
371
426
  );
@@ -375,7 +430,7 @@ function DataTableAutoTBodyContent<T>({
375
430
  return (
376
431
  <>
377
432
  <tr>
378
- <td colSpan={columns.length} className="aksel-sr-only">
433
+ <td colSpan={fullWidthColSpan} className="aksel-sr-only">
379
434
  {loadingLabel}
380
435
  </td>
381
436
  </tr>
@@ -383,7 +438,7 @@ function DataTableAutoTBodyContent<T>({
383
438
  <DataTableTr key={`skeleton-row-${rowIndex}`} aria-hidden>
384
439
  {columns.map(({ isSticky, colDef }, colDefIndex) => (
385
440
  <DataTableBaseCell
386
- textAlign={colDef.type === "number" ? "right" : "left"}
441
+ textAlign={colDef.align ?? "left"}
387
442
  key={colDef.id || colDefIndex}
388
443
  as={colDef.isRowHeader ? "th" : "td"}
389
444
  isSticky={isSticky}
@@ -397,88 +452,152 @@ function DataTableAutoTBodyContent<T>({
397
452
  );
398
453
  }
399
454
 
400
- if (data.length === 0 && emptyState !== undefined) {
455
+ if (items.length === 0 && emptyState !== undefined) {
401
456
  return (
402
- <DataTableEmptyState colSpan={columns.length}>
457
+ <DataTableEmptyState colSpan={fullWidthColSpan}>
403
458
  {emptyState}
404
459
  </DataTableEmptyState>
405
460
  );
406
461
  }
407
462
 
408
- const renderLoadingAnnoucement = isLoading && !loadingState && !loadingRows;
463
+ const renderLoadingAnnouncement = isLoading && !loadingState && !loadingRows;
409
464
 
410
- return data.map((rowData, rowIndex) => {
411
- const rowId = allRowKeys[rowIndex];
412
- return (
413
- <React.Fragment key={rowId}>
414
- {renderLoadingAnnoucement && (
415
- <tr>
416
- <td colSpan={columns.length} className="aksel-sr-only">
417
- {loadingLabel}
418
- </td>
419
- </tr>
420
- )}
421
- <DataTableTr rowId={rowId}>
422
- {columns.map(({ isSticky, colDef }, colDefIndex) => {
423
- return (
424
- <DataTableBaseCell
425
- /* TODO: Make this configurable */
426
- textAlign={colDef.type === "number" ? "right" : "left"}
427
- key={colDef.id || colDefIndex}
428
- as={colDef.isRowHeader ? "th" : "td"}
429
- isSticky={isSticky}
430
- >
431
- {colDef.cell(rowData)}
432
- </DataTableBaseCell>
433
- );
434
- })}
435
- </DataTableTr>
436
- <DataTableExpandedRow
437
- rowId={rowId}
438
- rowData={rowData}
439
- columnCount={columns.length}
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
+ );
523
+ }
524
+
525
+ function NestedRowToggle({ details }: { details: ItemDetail<any> }) {
526
+ const { isSubRowExpanded, onExpandedSubRowIdsChange } =
527
+ useTableItemsContext();
528
+
529
+ const subRows = details.children;
530
+ const hasSubRows = subRows && subRows.length > 0;
531
+ const isRowExpanded = isSubRowExpanded(details.id);
532
+
533
+ return (
534
+ <div className="aksel-data-table__nested-toggle">
535
+ {hasSubRows && (
536
+ <Button
537
+ variant="tertiary"
538
+ data-color="neutral"
539
+ size="small"
540
+ onClick={(e) => {
541
+ e.stopPropagation();
542
+ onExpandedSubRowIdsChange(details.id);
543
+ }}
544
+ aria-expanded={isRowExpanded}
545
+ aria-label={isRowExpanded ? "Skjul under-rader" : "Vis under-rader"}
546
+ icon={
547
+ isRowExpanded ? (
548
+ <ChevronDownIcon aria-hidden />
549
+ ) : (
550
+ <ChevronRightIcon aria-hidden />
551
+ )
552
+ }
440
553
  />
441
- </React.Fragment>
442
- );
443
- });
554
+ )}
555
+ </div>
556
+ );
444
557
  }
445
558
 
446
559
  function DataTableExpandedRow<T>({
447
560
  rowId,
448
561
  rowData,
449
- columnCount,
562
+ fullWidthColSpan,
450
563
  }: {
451
564
  rowId: string | number;
452
565
  rowData: T;
453
- columnCount: number;
566
+ fullWidthColSpan: number;
454
567
  }) {
455
568
  const { tableId } = useDataTableContext();
456
- const expansionContext = useDataTableExpansion(false);
569
+ const {
570
+ enableDetailsPanel,
571
+ isExpanded,
572
+ getDetailsPanelContent,
573
+ getDetailsPanelHeight,
574
+ } = useDataTableExpansion();
457
575
 
458
- /* TODO: Is this the way we want to opt out? Might just be temp until auto and root is merged so they use same context */
459
- if (!expansionContext) {
576
+ if (!enableDetailsPanel) {
460
577
  return null;
461
578
  }
462
579
 
463
- const { isExpanded, getDetailsPanelContent, getDetailsPanelHeight } =
464
- expansionContext;
465
-
466
580
  if (!isExpanded(rowId)) {
467
581
  return null;
468
582
  }
469
583
 
470
584
  const content = getDetailsPanelContent?.(rowData);
585
+ const expansionId = getDataTableExpansionId(tableId, rowId);
471
586
 
472
587
  if (!content) {
473
588
  return null;
474
589
  }
475
590
 
591
+ const panelHeight = getDetailsPanelHeight?.(rowData);
592
+
593
+ const style: React.CSSProperties = panelHeight
594
+ ? { height: panelHeight, overflow: "auto" }
595
+ : { height: "auto" };
596
+
476
597
  return (
477
598
  <tr>
478
- <td id={`${tableId}-expansion-${rowId}`} colSpan={columnCount}>
479
- <div style={{ height: getDetailsPanelHeight?.(rowData) }}>
480
- {content}
481
- </div>
599
+ <td id={expansionId} colSpan={fullWidthColSpan}>
600
+ <div style={style}>{content}</div>
482
601
  </td>
483
602
  </tr>
484
603
  );
@@ -1,7 +1,8 @@
1
1
  import { createStrictContext } from "../../../utils/helpers";
2
+ import type { UseColumnOptionsResult } from "../hooks/useColumnOptions";
2
3
  import type { UseTableSelectionReturn } from "../hooks/useTableSelection";
3
4
 
4
- type DataTableContextProps = {
5
+ type DataTableContextProps<T> = {
5
6
  layout: "fixed" | "auto";
6
7
  withKeyboardNav: boolean;
7
8
  selectionState: UseTableSelectionReturn;
@@ -16,10 +17,11 @@ type DataTableContextProps = {
16
17
  disableRowSelectionOnClick: boolean;
17
18
  isLoading?: boolean;
18
19
  showLoadingOverlay: boolean;
20
+ columns: UseColumnOptionsResult<T>["columns"];
19
21
  };
20
22
 
21
23
  const { Provider: DataTableContextProvider, useContext: useDataTableContext } =
22
- createStrictContext<DataTableContextProps>({
24
+ createStrictContext<DataTableContextProps<any>>({
23
25
  name: "DataTableContext",
24
26
  errorMessage: "useDataTableContext must be used within DataTable",
25
27
  });
@@ -10,9 +10,12 @@ import {
10
10
  DataTableEmptyState,
11
11
  type DataTableEmptyStateProps,
12
12
  } from "../empty-state/DataTableEmptyState";
13
+ import { DataTableExpansionProvider } from "../hooks/useTableExpansion";
13
14
  import { useTableKeyboardNav } from "../hooks/useTableKeyboardNav";
14
- import { type SelectionProps } from "../hooks/useTableSelection";
15
- import { noSelectionState } from "../hooks/useTableSelection";
15
+ import {
16
+ type SelectionProps,
17
+ noSelectionState,
18
+ } from "../hooks/useTableSelection";
16
19
  import {
17
20
  DataTableLoadingState,
18
21
  type DataTableLoadingStateProps,
@@ -228,27 +231,30 @@ const DataTable = forwardRef<HTMLTableElement, DataTableProps>(
228
231
  withKeyboardNav={withKeyboardNav}
229
232
  selectionState={noSelectionState}
230
233
  stickySelection={false}
231
- stickyHeader={false}
234
+ stickyHeader={true}
232
235
  tableId={useId()}
233
236
  showLoadingSkeletons={false}
234
237
  onRowClick={undefined}
235
238
  disableRowSelectionOnClick={false}
236
239
  showLoadingOverlay={false}
240
+ columns={[]}
237
241
  >
238
- <div className="aksel-data-table__border-wrapper">
239
- <div className="aksel-data-table__scroll-wrapper">
240
- <table
241
- {...rest}
242
- ref={mergedRef}
243
- className={cl("aksel-data-table", className)}
244
- data-zebra-stripes={zebraStripes}
245
- data-truncate-content={truncateContent}
246
- data-density={rowDensity}
247
- data-layout={layout}
248
- tabIndex={tabIndex}
249
- />
242
+ <DataTableExpansionProvider>
243
+ <div className="aksel-data-table__border-wrapper">
244
+ <div className="aksel-data-table__scroll-wrapper">
245
+ <table
246
+ {...rest}
247
+ ref={mergedRef}
248
+ className={cl("aksel-data-table", className)}
249
+ data-zebra-stripes={zebraStripes}
250
+ data-truncate-content={truncateContent}
251
+ data-density={rowDensity}
252
+ data-layout={layout}
253
+ tabIndex={tabIndex}
254
+ />
255
+ </div>
250
256
  </div>
251
- </div>
257
+ </DataTableExpansionProvider>
252
258
  </DataTableContextProvider>
253
259
  );
254
260
  },