@navikt/ds-react 8.10.2 → 8.10.4

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 (200) hide show
  1. package/cjs/action-menu/ActionMenu.js +1 -1
  2. package/cjs/action-menu/ActionMenu.js.map +1 -1
  3. package/cjs/data/stories/Data.test-data.d.ts +24 -0
  4. package/cjs/data/stories/Data.test-data.js +1616 -0
  5. package/cjs/data/stories/Data.test-data.js.map +1 -0
  6. package/cjs/data/table/column-header/DataTableColumnHeader.d.ts +4 -1
  7. package/cjs/data/table/column-header/DataTableColumnHeader.js +4 -2
  8. package/cjs/data/table/column-header/DataTableColumnHeader.js.map +1 -1
  9. package/cjs/data/table/column-header/useTableColumnResize.d.ts +39 -14
  10. package/cjs/data/table/column-header/useTableColumnResize.js +37 -39
  11. package/cjs/data/table/column-header/useTableColumnResize.js.map +1 -1
  12. package/cjs/data/table/details-panel-row/DataTableDetailsPanelRow.d.ts +6 -0
  13. package/cjs/data/table/details-panel-row/DataTableDetailsPanelRow.js +32 -0
  14. package/cjs/data/table/details-panel-row/DataTableDetailsPanelRow.js.map +1 -0
  15. package/cjs/data/table/helpers/collectTableRowEntries.d.ts +13 -5
  16. package/cjs/data/table/helpers/collectTableRowEntries.js +26 -19
  17. package/cjs/data/table/helpers/collectTableRowEntries.js.map +1 -1
  18. package/cjs/data/table/helpers/selection/SelectionSubtreeHelper.d.ts +46 -0
  19. package/cjs/data/table/helpers/selection/SelectionSubtreeHelper.js +112 -0
  20. package/cjs/data/table/helpers/selection/SelectionSubtreeHelper.js.map +1 -0
  21. package/cjs/data/table/helpers/selection/getMultipleSelectProps.d.ts +3 -2
  22. package/cjs/data/table/helpers/selection/getMultipleSelectProps.js +43 -19
  23. package/cjs/data/table/helpers/selection/getMultipleSelectProps.js.map +1 -1
  24. package/cjs/data/table/helpers/selection/selection.types.d.ts +1 -0
  25. package/cjs/data/table/helpers/table-keyboard.d.ts +1 -2
  26. package/cjs/data/table/helpers/table-keyboard.js +1 -2
  27. package/cjs/data/table/helpers/table-keyboard.js.map +1 -1
  28. package/cjs/data/table/hooks/useColumnOptions.js +18 -5
  29. package/cjs/data/table/hooks/useColumnOptions.js.map +1 -1
  30. package/cjs/data/table/hooks/useTableDetailsPanel.d.ts +62 -0
  31. package/cjs/data/table/hooks/{useTableExpansion.js → useTableDetailsPanel.js} +26 -19
  32. package/cjs/data/table/hooks/useTableDetailsPanel.js.map +1 -0
  33. package/cjs/data/table/hooks/useTableItems.d.ts +18 -17
  34. package/cjs/data/table/hooks/useTableItems.js +27 -15
  35. package/cjs/data/table/hooks/useTableItems.js.map +1 -1
  36. package/cjs/data/table/hooks/useTableSelection.d.ts +6 -3
  37. package/cjs/data/table/hooks/useTableSelection.js +10 -4
  38. package/cjs/data/table/hooks/useTableSelection.js.map +1 -1
  39. package/cjs/data/table/index.d.ts +1 -2
  40. package/cjs/data/table/index.js +22 -12
  41. package/cjs/data/table/index.js.map +1 -1
  42. package/cjs/data/table/root/DataTable.types.d.ts +12 -10
  43. package/cjs/data/table/root/DataTableRoot.context.d.ts +5 -1
  44. package/cjs/data/table/root/DataTableRoot.context.js.map +1 -1
  45. package/cjs/data/table/root/DataTableRoot.d.ts +79 -115
  46. package/cjs/data/table/root/DataTableRoot.js +167 -39
  47. package/cjs/data/table/root/DataTableRoot.js.map +1 -1
  48. package/cjs/data/table/root/DataTableRoot.legacy.d.ts +177 -0
  49. package/cjs/data/table/root/DataTableRoot.legacy.js +104 -0
  50. package/cjs/data/table/root/DataTableRoot.legacy.js.map +1 -0
  51. package/cjs/data/table/sub-row-toggle/DataTableSubRowToggle.d.ts +6 -0
  52. package/cjs/data/table/sub-row-toggle/DataTableSubRowToggle.js +21 -0
  53. package/cjs/data/table/sub-row-toggle/DataTableSubRowToggle.js.map +1 -0
  54. package/cjs/data/table/tr/DataTableTr.js +11 -11
  55. package/cjs/data/table/tr/DataTableTr.js.map +1 -1
  56. package/cjs/form/checkbox/Checkbox.js +1 -0
  57. package/cjs/form/checkbox/Checkbox.js.map +1 -1
  58. package/cjs/form/radio/Radio.js +7 -1
  59. package/cjs/form/radio/Radio.js.map +1 -1
  60. package/cjs/modal/types.d.ts +8 -4
  61. package/cjs/utils/components/dismissablelayer/DismissableLayer.js +1 -1
  62. package/cjs/utils/components/dismissablelayer/DismissableLayer.js.map +1 -1
  63. package/cjs/utils/components/floating/Floating.d.ts +16 -1
  64. package/cjs/utils/components/floating/Floating.js +50 -13
  65. package/cjs/utils/components/floating/Floating.js.map +1 -1
  66. package/cjs/utils/components/floating-menu/Menu.js +1 -1
  67. package/cjs/utils/components/floating-menu/Menu.js.map +1 -1
  68. package/cjs/utils/helpers/create-strict-context.js +1 -1
  69. package/cjs/utils/helpers/create-strict-context.js.map +1 -1
  70. package/cjs/utils/hooks/useControllableState.d.ts +5 -5
  71. package/cjs/utils/hooks/useControllableState.js.map +1 -1
  72. package/cjs/utils/hooks/useValueAsRef.js +1 -1
  73. package/cjs/utils/hooks/useValueAsRef.js.map +1 -1
  74. package/cjs/utils-external/hooks/useId.js +1 -1
  75. package/cjs/utils-external/hooks/useId.js.map +1 -1
  76. package/esm/action-menu/ActionMenu.js +1 -1
  77. package/esm/action-menu/ActionMenu.js.map +1 -1
  78. package/esm/data/stories/Data.test-data.d.ts +24 -0
  79. package/esm/data/stories/Data.test-data.js +1607 -0
  80. package/esm/data/stories/Data.test-data.js.map +1 -0
  81. package/esm/data/table/column-header/DataTableColumnHeader.d.ts +4 -1
  82. package/esm/data/table/column-header/DataTableColumnHeader.js +4 -2
  83. package/esm/data/table/column-header/DataTableColumnHeader.js.map +1 -1
  84. package/esm/data/table/column-header/useTableColumnResize.d.ts +39 -14
  85. package/esm/data/table/column-header/useTableColumnResize.js +38 -40
  86. package/esm/data/table/column-header/useTableColumnResize.js.map +1 -1
  87. package/esm/data/table/details-panel-row/DataTableDetailsPanelRow.d.ts +6 -0
  88. package/esm/data/table/details-panel-row/DataTableDetailsPanelRow.js +27 -0
  89. package/esm/data/table/details-panel-row/DataTableDetailsPanelRow.js.map +1 -0
  90. package/esm/data/table/helpers/collectTableRowEntries.d.ts +13 -5
  91. package/esm/data/table/helpers/collectTableRowEntries.js +26 -19
  92. package/esm/data/table/helpers/collectTableRowEntries.js.map +1 -1
  93. package/esm/data/table/helpers/selection/SelectionSubtreeHelper.d.ts +46 -0
  94. package/esm/data/table/helpers/selection/SelectionSubtreeHelper.js +109 -0
  95. package/esm/data/table/helpers/selection/SelectionSubtreeHelper.js.map +1 -0
  96. package/esm/data/table/helpers/selection/getMultipleSelectProps.d.ts +3 -2
  97. package/esm/data/table/helpers/selection/getMultipleSelectProps.js +43 -19
  98. package/esm/data/table/helpers/selection/getMultipleSelectProps.js.map +1 -1
  99. package/esm/data/table/helpers/selection/selection.types.d.ts +1 -0
  100. package/esm/data/table/helpers/table-keyboard.d.ts +1 -2
  101. package/esm/data/table/helpers/table-keyboard.js +1 -2
  102. package/esm/data/table/helpers/table-keyboard.js.map +1 -1
  103. package/esm/data/table/hooks/useColumnOptions.js +18 -5
  104. package/esm/data/table/hooks/useColumnOptions.js.map +1 -1
  105. package/esm/data/table/hooks/useTableDetailsPanel.d.ts +62 -0
  106. package/esm/data/table/hooks/useTableDetailsPanel.js +58 -0
  107. package/esm/data/table/hooks/useTableDetailsPanel.js.map +1 -0
  108. package/esm/data/table/hooks/useTableItems.d.ts +18 -17
  109. package/esm/data/table/hooks/useTableItems.js +27 -15
  110. package/esm/data/table/hooks/useTableItems.js.map +1 -1
  111. package/esm/data/table/hooks/useTableSelection.d.ts +6 -3
  112. package/esm/data/table/hooks/useTableSelection.js +10 -4
  113. package/esm/data/table/hooks/useTableSelection.js.map +1 -1
  114. package/esm/data/table/index.d.ts +1 -2
  115. package/esm/data/table/index.js +21 -1
  116. package/esm/data/table/index.js.map +1 -1
  117. package/esm/data/table/root/DataTable.types.d.ts +12 -10
  118. package/esm/data/table/root/DataTableRoot.context.d.ts +5 -1
  119. package/esm/data/table/root/DataTableRoot.context.js.map +1 -1
  120. package/esm/data/table/root/DataTableRoot.d.ts +79 -115
  121. package/esm/data/table/root/DataTableRoot.js +174 -37
  122. package/esm/data/table/root/DataTableRoot.js.map +1 -1
  123. package/esm/data/table/root/DataTableRoot.legacy.d.ts +177 -0
  124. package/esm/data/table/root/DataTableRoot.legacy.js +59 -0
  125. package/esm/data/table/root/DataTableRoot.legacy.js.map +1 -0
  126. package/esm/data/table/sub-row-toggle/DataTableSubRowToggle.d.ts +6 -0
  127. package/esm/data/table/sub-row-toggle/DataTableSubRowToggle.js +16 -0
  128. package/esm/data/table/sub-row-toggle/DataTableSubRowToggle.js.map +1 -0
  129. package/esm/data/table/tr/DataTableTr.js +11 -11
  130. package/esm/data/table/tr/DataTableTr.js.map +1 -1
  131. package/esm/form/checkbox/Checkbox.js +1 -0
  132. package/esm/form/checkbox/Checkbox.js.map +1 -1
  133. package/esm/form/radio/Radio.js +7 -1
  134. package/esm/form/radio/Radio.js.map +1 -1
  135. package/esm/modal/types.d.ts +8 -4
  136. package/esm/utils/components/dismissablelayer/DismissableLayer.js +1 -1
  137. package/esm/utils/components/dismissablelayer/DismissableLayer.js.map +1 -1
  138. package/esm/utils/components/floating/Floating.d.ts +16 -1
  139. package/esm/utils/components/floating/Floating.js +48 -13
  140. package/esm/utils/components/floating/Floating.js.map +1 -1
  141. package/esm/utils/components/floating-menu/Menu.js +2 -2
  142. package/esm/utils/components/floating-menu/Menu.js.map +1 -1
  143. package/esm/utils/helpers/create-strict-context.js +1 -1
  144. package/esm/utils/helpers/create-strict-context.js.map +1 -1
  145. package/esm/utils/hooks/useControllableState.d.ts +5 -5
  146. package/esm/utils/hooks/useControllableState.js.map +1 -1
  147. package/esm/utils/hooks/useValueAsRef.js +1 -1
  148. package/esm/utils/hooks/useValueAsRef.js.map +1 -1
  149. package/esm/utils-external/hooks/useId.js +1 -1
  150. package/esm/utils-external/hooks/useId.js.map +1 -1
  151. package/package.json +3 -3
  152. package/src/action-menu/ActionMenu.tsx +1 -1
  153. package/src/data/stories/Data.test-data.tsx +1703 -0
  154. package/src/data/table/column-header/DataTableColumnHeader.tsx +11 -7
  155. package/src/data/table/column-header/useTableColumnResize.ts +95 -54
  156. package/src/data/table/details-panel-row/DataTableDetailsPanelRow.tsx +53 -0
  157. package/src/data/table/helpers/collectTableRowEntries.ts +55 -31
  158. package/src/data/table/helpers/selection/SelectionSubtreeHelper.test.ts +66 -0
  159. package/src/data/table/helpers/selection/SelectionSubtreeHelper.ts +162 -0
  160. package/src/data/table/helpers/selection/getMultipleSelectProps.ts +57 -20
  161. package/src/data/table/helpers/selection/selection.types.ts +1 -0
  162. package/src/data/table/helpers/table-keyboard.ts +1 -2
  163. package/src/data/table/hooks/__tests__/useTableItems.test.ts +27 -6
  164. package/src/data/table/hooks/__tests__/useTableSelection.test.ts +182 -58
  165. package/src/data/table/hooks/useColumnOptions.ts +19 -5
  166. package/src/data/table/hooks/useTableDetailsPanel.tsx +182 -0
  167. package/src/data/table/hooks/useTableItems.ts +74 -60
  168. package/src/data/table/hooks/useTableSelection.ts +27 -12
  169. package/src/data/table/index.tsx +5 -3
  170. package/src/data/table/root/DataTable.types.ts +25 -10
  171. package/src/data/table/root/DataTableRoot.context.ts +5 -1
  172. package/src/data/table/root/DataTableRoot.legacy.tsx +297 -0
  173. package/src/data/table/root/DataTableRoot.tsx +483 -219
  174. package/src/data/table/sub-row-toggle/DataTableSubRowToggle.tsx +39 -0
  175. package/src/data/table/tr/DataTableTr.tsx +14 -13
  176. package/src/form/checkbox/Checkbox.tsx +1 -0
  177. package/src/form/radio/Radio.tsx +7 -1
  178. package/src/modal/types.ts +8 -4
  179. package/src/utils/components/dismissablelayer/DismissableLayer.tsx +1 -1
  180. package/src/utils/components/floating/Floating.tsx +56 -13
  181. package/src/utils/components/floating-menu/Menu.tsx +4 -1
  182. package/src/utils/helpers/create-strict-context.tsx +1 -1
  183. package/src/utils/hooks/useControllableState.ts +11 -8
  184. package/src/utils/hooks/useValueAsRef.ts +1 -1
  185. package/src/utils-external/hooks/useId.ts +1 -1
  186. package/cjs/data/table/hooks/useTableExpansion.d.ts +0 -29
  187. package/cjs/data/table/hooks/useTableExpansion.js.map +0 -1
  188. package/cjs/data/table/root/DataTableAuto.d.ts +0 -174
  189. package/cjs/data/table/root/DataTableAuto.js +0 -206
  190. package/cjs/data/table/root/DataTableAuto.js.map +0 -1
  191. package/esm/data/table/hooks/useTableExpansion.d.ts +0 -29
  192. package/esm/data/table/hooks/useTableExpansion.js +0 -51
  193. package/esm/data/table/hooks/useTableExpansion.js.map +0 -1
  194. package/esm/data/table/root/DataTableAuto.d.ts +0 -174
  195. package/esm/data/table/root/DataTableAuto.js +0 -170
  196. package/esm/data/table/root/DataTableAuto.js.map +0 -1
  197. package/src/data/table/hooks/__tests__/useTableExpansion.test.tsx +0 -115
  198. package/src/data/table/hooks/useTableExpansion.tsx +0 -141
  199. package/src/data/table/root/DataTableAuto.test.tsx +0 -118
  200. package/src/data/table/root/DataTableAuto.tsx +0 -603
@@ -1,46 +1,52 @@
1
- import React, { forwardRef } from "react";
1
+ /** biome-ignore-all lint/correctness/useHookAtTopLevel: False positive because of the way forwardRef() is added */
2
+ import React, {
3
+ forwardRef,
4
+ useCallback,
5
+ useEffect,
6
+ useMemo,
7
+ useRef,
8
+ useState,
9
+ } from "react";
10
+ import { Skeleton } from "../../../skeleton";
2
11
  import { useId } from "../../../utils-external";
12
+ import { Slot } from "../../../utils/components/slot/Slot";
3
13
  import { cl } from "../../../utils/helpers";
4
14
  import { useMergeRefs } from "../../../utils/hooks";
15
+ import { DataTableBaseCell } from "../base-cell/DataTableBaseCell";
16
+ import { DataTableColumnHeader } from "../column-header/DataTableColumnHeader";
17
+ import { DataTableDetailsPanelRow } from "../details-panel-row/DataTableDetailsPanelRow";
18
+ import { DataTableEmptyState } from "../empty-state/DataTableEmptyState";
19
+ import { useColumnOptions } from "../hooks/useColumnOptions";
5
20
  import {
6
- DataTableCaption,
7
- type DataTableCaptionProps,
8
- } from "../caption/DataTableCaption";
21
+ DataTableDetailsPanelProvider,
22
+ type DetailsPanelProps,
23
+ } from "../hooks/useTableDetailsPanel";
9
24
  import {
10
- DataTableEmptyState,
11
- type DataTableEmptyStateProps,
12
- } from "../empty-state/DataTableEmptyState";
13
- import { DataTableExpansionProvider } from "../hooks/useTableExpansion";
14
- import type { ItemDetail } from "../hooks/useTableItems";
25
+ type SubRowsProps,
26
+ TableItemsProvider,
27
+ useTableItems,
28
+ useTableItemsContext,
29
+ } from "../hooks/useTableItems";
15
30
  import { useTableKeyboardNav } from "../hooks/useTableKeyboardNav";
16
- import { type SelectionProps } from "../hooks/useTableSelection";
17
- import { noSelectionState } from "../hooks/useTableSelection";
18
31
  import {
19
- DataTableLoadingState,
20
- type DataTableLoadingStateProps,
21
- } from "../loading-state/DataTableLoadingState";
32
+ type SelectionProps,
33
+ useTableSelection,
34
+ } from "../hooks/useTableSelection";
35
+ import { type TableSortOptions, useTableSort } from "../hooks/useTableSort";
36
+ import { DataTableLoadingState } from "../loading-state/DataTableLoadingState";
37
+ import { DataTableSubRowToggle } from "../sub-row-toggle/DataTableSubRowToggle";
38
+ import { DataTableTbody } from "../tbody/DataTableTbody";
39
+ import { DataTableThead } from "../thead/DataTableThead";
40
+ import { DataTableTr } from "../tr/DataTableTr";
41
+ import type { ColumnDefinitions } from "./DataTable.types";
22
42
  import {
23
- DataTableTbody,
24
- type DataTableTbodyProps,
25
- } from "../tbody/DataTableTbody";
26
- import { DataTableTd, type DataTableTdProps } from "../td/DataTableTd";
27
- import {
28
- DataTableTfoot,
29
- type DataTableTfootProps,
30
- } from "../tfoot/DataTableTfoot";
31
- import { DataTableTh, type DataTableThProps } from "../th/DataTableTh";
32
- import {
33
- DataTableThead,
34
- type DataTableTheadProps,
35
- } from "../thead/DataTableThead";
36
- import { DataTableTr, type DataTableTrProps } from "../tr/DataTableTr";
37
- import { DataTableContextProvider } from "./DataTableRoot.context";
38
-
39
- const EMPTY_ITEM_DETAILS = new Map<never, ItemDetail<never>>();
43
+ DataTableContextProvider,
44
+ useDataTableContext,
45
+ } from "./DataTableRoot.context";
40
46
 
41
- interface DataTableProps
42
- extends React.HTMLAttributes<HTMLTableElement>, SelectionProps {
43
- children: React.ReactNode;
47
+ interface DataTableProps<T>
48
+ extends React.HTMLAttributes<HTMLTableElement>, TableSortOptions {
49
+ children?: never;
44
50
  /**
45
51
  * Controls vertical cell padding.
46
52
  * @default "normal"
@@ -60,7 +66,7 @@ interface DataTableProps
60
66
  truncateContent?: boolean; // TODO: Consider making this default false when layout=auto, and maybe disallow it but add a wrap prop on the td-comp.
61
67
  /**
62
68
  * Enables keyboard navigation for table rows and cells.
63
- * @default false
69
+ * @default true
64
70
  */
65
71
  withKeyboardNav?: boolean;
66
72
  /**
@@ -84,215 +90,473 @@ interface DataTableProps
84
90
  * @default "fixed"
85
91
  */
86
92
  layout?: "fixed" | "auto";
87
- }
88
-
89
- interface DataTableRootComponent extends React.ForwardRefExoticComponent<
90
- DataTableProps & React.RefAttributes<HTMLTableElement>
91
- > {
92
93
  /**
93
- * @see 🏷️ {@link DataTableCaptionProps}
94
- * @example
95
- * ```jsx
96
- * <DataTable>
97
- * <DataTable.Caption>
98
- * Lorem ipsum
99
- * </DataTable.Caption
100
- * </DataTable>
101
- * ```
94
+ * Defines the columns of the table and how to render them.
95
+ *
96
+ *
97
+ * Each column definition should have a unique `id` (or use the column index as fallback) and a `cell`-renderer function that takes the row data as argument and returns a React node.
102
98
  */
103
- Caption: typeof DataTableCaption;
99
+ columnDefinitions: ColumnDefinitions<T>;
104
100
  /**
105
- * @see 🏷️ {@link DataTableTheadProps}
106
- * @example
107
- * ```jsx
108
- * <DataTable>
109
- * <DataTable.Thead>
110
- * ... TODO
111
- * </DataTable.Thead>
112
- * </DataTable>
113
- * ```
101
+ * The data to display in the table.
102
+ *
103
+ *
104
+ * Each object in the array represents a row, and the properties of the object are used to render the cells based on the `columnDefinitions`.
114
105
  */
115
- Thead: typeof DataTableThead;
106
+ data: T[];
116
107
  /**
117
- * @see 🏷️ {@link DataTableTbodyProps}
118
- * @example
119
- * ```jsx
120
- * <DataTable>
121
- * <DataTable.Tbody>
122
- * ... TODO
123
- * </DataTable.Tbody>
124
- * </DataTable>
125
- * ```
108
+ * Function to get unique row id from row data.
109
+ *
110
+ *
111
+ * If not provided, the row index will be used as id. This can cause issues if your data changes dynamically, so it's recommended to provide a stable id if possible.
126
112
  */
127
- Tbody: typeof DataTableTbody;
113
+ getRowId?: (rowData: T, index: number) => string | number;
128
114
  /**
129
- * @see 🏷️ {@link DataTableTrProps}
130
- * @example
131
- * ```jsx
132
- * <DataTable>
133
- * <DataTable.Tr>
134
- * ... TODO
135
- * </DataTable.Tr
136
- * </DataTable>
137
- * ```
115
+ * Sticky columns that remain visible when horizontally scrolling the table.
116
+ *
117
+ * You can specify 1 sticky column on the left and 1 on the right.
138
118
  */
139
- Tr: typeof DataTableTr;
119
+ stickyColumns?: {
120
+ first?: "1";
121
+ last?: "1";
122
+ };
140
123
  /**
141
- * @see 🏷️ {@link DataTableThProps}
142
- * @example
143
- * ```jsx
144
- * ```
124
+ * @default true
145
125
  */
146
- Th: typeof DataTableTh;
126
+ stickyHeader?: boolean;
147
127
  /**
148
- * @see 🏷️ {@link DataTableTdProps}
149
- * @example
150
- * ```jsx
151
- * <DataTable>
152
- * <DataTable.Tbody>
153
- * <DataTable.Td>
154
- * Lorem ipsum
155
- * </DataTable.Td>
156
- * <DataTable.Td>
157
- * Dolor sit amet
158
- * </DataTable.Td>
159
- * </DataTable.Tbody>
160
- * </DataTable>
161
- * ```
128
+ * Callback invoked when a data row is clicked.
129
+ * Not called when clicking header, loading, or empty-state rows.
162
130
  */
163
- Td: typeof DataTableTd;
131
+ onRowClick?: (
132
+ rowId: string | number,
133
+ event: React.MouseEvent<HTMLTableRowElement>,
134
+ ) => void;
164
135
  /**
165
- * @see 🏷️ {@link DataTableTfootProps}
166
- * @example
167
- * ```jsx
168
- * <DataTable>
169
- * <DataTable.Tfoot>
170
- * ...
171
- * </DataTable.Tfoot>
172
- * </DataTable>
173
- * ```
136
+ * Content to render when `data` is empty.
137
+ * Rendered inside a `DataTable.EmptyState` row spanning all columns.
174
138
  */
175
- Tfoot: typeof DataTableTfoot;
139
+ emptyState?: React.ReactNode;
140
+ loading?: {
141
+ /**
142
+ * Shows the table in a loading state.
143
+ *
144
+ * - When `loadingState` is provided, it is rendered inside a `DataTable.LoadingState` row.
145
+ * - When `loadingState` is **not** provided, skeleton placeholder rows are rendered instead.
146
+ * @default false
147
+ */
148
+ isLoading?: boolean;
149
+ /**
150
+ * Custom content to render when `isLoading` is `true`.
151
+ * Rendered inside a `DataTable.LoadingState` row spanning all columns.
152
+ * When omitted, skeleton rows are rendered based on `loadingRows`.
153
+ */
154
+ loadingState?: React.ReactNode;
155
+ /**
156
+ * Number of skeleton rows to render when `isLoading` is `true` and no `loadingState` is provided.
157
+ *
158
+ *
159
+ * If not provided, the rendered content will get a temporarily overlay while loading
160
+ */
161
+ loadingRows?: number;
162
+ /**
163
+ * Visually hidden label announced to screen readers when skeleton rows are shown.
164
+ * Only used when `isLoading` is `true` and no `loadingState` is provided.
165
+ * @default "Laster innhold"
166
+ */
167
+ loadingLabel?: string;
168
+ };
169
+
176
170
  /**
177
- * @see 🏷️ {@link DataTableEmptyStateProps}
178
- * @example
179
- * ```jsx
180
- * <DataTable>
181
- * <DataTable.TBody>
182
- * <DataTable.EmptyState />
183
- * </DataTable.TBody>
184
- * </DataTable>
185
- * ```
171
+ * Function to get sub-rows for a given row, used for nested rows.
172
+ * When provided, an expand toggle column is added automatically.
173
+ *
174
+ *
175
+ * TODO:
176
+ * - Table might need to be implemented with role="treegrid" for a11y when having nested rows.
186
177
  */
187
- EmptyState: typeof DataTableEmptyState;
178
+ selection?: SelectionProps;
179
+ subRows?: SubRowsProps<T>;
180
+ detailsPanel?: DetailsPanelProps<T>;
181
+ }
182
+
183
+ function DataTableInner<T>(
184
+ {
185
+ className,
186
+ id,
187
+ rowDensity = "normal",
188
+ withKeyboardNav = true,
189
+ zebraStripes = false,
190
+ truncateContent = true,
191
+ shouldBlockNavigation,
192
+ layout = "fixed",
193
+ selection,
194
+ data,
195
+ columnDefinitions,
196
+ getRowId,
197
+ stickyColumns,
198
+ stickyHeader = true,
199
+ sort: sortProp,
200
+ defaultSort = [],
201
+ onSortChange,
202
+ onRowClick,
203
+ emptyState,
204
+ loading,
205
+ detailsPanel,
206
+ subRows,
207
+ ...rest
208
+ }: DataTableProps<T>,
209
+ forwardedRef: React.ForwardedRef<HTMLTableElement>,
210
+ ) {
211
+ const { sortState, onSortClick } = useTableSort({
212
+ defaultSort,
213
+ onSortChange,
214
+ sort: sortProp,
215
+ });
216
+
188
217
  /**
189
- * @see 🏷️ {@link DataTableEmptyStateProps}
190
- * @example
191
- * ```jsx
192
- * <DataTable>
193
- * <DataTable.TBody>
194
- * <DataTable.LoadingState />
195
- * </DataTable.TBody>
196
- * </DataTable>
197
- * ```
218
+ * TODO:
219
+ * - If user currently does not give a getRowsId function, and data is nested (getSubRows)
220
+ * we end up in an infinite loop since the index based ids repeat for children and causes chaos.
198
221
  */
199
- LoadingState: typeof DataTableLoadingState;
222
+ const tableItems = useTableItems({
223
+ items: data,
224
+ getRowId: getRowId ?? ((_, index) => index),
225
+ subRows,
226
+ });
227
+
228
+ const tableSelectionState = useTableSelection({
229
+ selection,
230
+ visibleRowIds: tableItems.visibleRowIds,
231
+ childRowIdsById: tableItems.childRowIdsById,
232
+ });
233
+
234
+ const { columns, stickySelection } = useColumnOptions<T>(columnDefinitions, {
235
+ stickyColumns,
236
+ selectionMode: tableSelectionState.selection.selectionMode,
237
+ });
238
+
239
+ const {
240
+ isLoading = false,
241
+ loadingState,
242
+ loadingRows,
243
+ loadingLabel = "Laster innhold",
244
+ } = loading || {};
245
+
246
+ const fullWidthColSpan = useMemo(() => {
247
+ return (
248
+ columns.length +
249
+ (layout === "fixed" ? 1 : 0) +
250
+ (tableSelectionState.selection.selectionMode !== "none" ? 1 : 0) +
251
+ (detailsPanel?.getContent ? 1 : 0)
252
+ );
253
+ }, [
254
+ columns,
255
+ layout,
256
+ tableSelectionState.selection.selectionMode,
257
+ detailsPanel,
258
+ ]);
259
+
260
+ const tableId = useId(id);
261
+
262
+ return (
263
+ <DataTableContextProvider
264
+ layout={layout}
265
+ withKeyboardNav={withKeyboardNav}
266
+ selectionState={tableSelectionState}
267
+ stickySelection={stickySelection}
268
+ stickyHeader={stickyHeader}
269
+ tableId={tableId}
270
+ showLoadingSkeletons={isLoading && loadingState == null}
271
+ onRowClick={onRowClick}
272
+ isLoading={isLoading}
273
+ showLoadingOverlay={isLoading && !loadingState && !loadingRows}
274
+ columns={columns}
275
+ fullWidthColSpan={fullWidthColSpan}
276
+ >
277
+ <TableItemsProvider
278
+ itemDetails={tableItems.itemDetails}
279
+ items={tableItems.items}
280
+ onExpandedRowIdsChange={tableItems.onExpandedRowIdsChange}
281
+ isSubRowExpanded={tableItems.isSubRowExpanded}
282
+ >
283
+ <DataTableDetailsPanelProvider detailsPanel={detailsPanel}>
284
+ <TableElementWrapper
285
+ shouldBlockNavigation={shouldBlockNavigation}
286
+ enabled={withKeyboardNav}
287
+ >
288
+ <table
289
+ {...rest}
290
+ ref={forwardedRef}
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
+ aria-busy={isLoading || undefined}
298
+ >
299
+ <DataTableThead>
300
+ <DataTableTr>
301
+ {columns.map(({ isSticky, colDef }) => {
302
+ const sortEntry = sortState.find(
303
+ (s) => s.columnId === colDef.id,
304
+ );
305
+ const sortDirection = sortEntry?.direction ?? "none";
306
+ return (
307
+ <DataTableColumnHeader
308
+ resizable={colDef.resizable}
309
+ width={colDef.width}
310
+ defaultWidth={colDef.defaultWidth}
311
+ autoWidth={colDef.autoWidth}
312
+ minWidth={colDef.minWidth}
313
+ maxWidth={colDef.maxWidth}
314
+ onWidthChange={colDef.onWidthChange}
315
+ textAlign={colDef.align ?? "left"}
316
+ key={colDef.id}
317
+ isSticky={isSticky}
318
+ sortable={colDef.sortable}
319
+ sortDirection={sortDirection}
320
+ onSortClick={(event) => onSortClick(colDef.id, event)}
321
+ label={colDef.label}
322
+ >
323
+ {colDef.header ?? colDef.label}
324
+ </DataTableColumnHeader>
325
+ );
326
+ })}
327
+ </DataTableTr>
328
+ </DataTableThead>
329
+
330
+ <DataTableTbody>
331
+ <DataTableTBodyContent
332
+ loadingState={loadingState}
333
+ loadingRows={loadingRows}
334
+ loadingLabel={loadingLabel}
335
+ emptyState={emptyState}
336
+ />
337
+ </DataTableTbody>
338
+ </table>
339
+ </TableElementWrapper>
340
+ </DataTableDetailsPanelProvider>
341
+ </TableItemsProvider>
342
+ </DataTableContextProvider>
343
+ );
200
344
  }
201
345
 
202
346
  /**
203
- * TODO Component description etc.
204
- *
205
- * **NB:** To get sticky headers, you have to set a height restriction on the table container. You can use VStack for this:
206
- * TODO example
347
+ * Temp optimization to avoid re-renders on every keyboard-move, selection change etc
207
348
  */
208
- const DataTable = forwardRef<HTMLTableElement, DataTableProps>(
209
- (
210
- {
211
- className,
212
- rowDensity = "normal",
213
- withKeyboardNav = false,
214
- zebraStripes = false,
215
- truncateContent = true,
216
- shouldBlockNavigation,
217
- layout = "fixed",
218
- ...rest
219
- },
220
- forwardedRef,
221
- ) => {
222
- const { tabIndex, setTableRef } = useTableKeyboardNav({
223
- enabled: withKeyboardNav,
224
- shouldBlockNavigation,
349
+ function TableElementWrapper({
350
+ children,
351
+ enabled,
352
+ shouldBlockNavigation,
353
+ }: {
354
+ children: React.ReactNode;
355
+ shouldBlockNavigation?: (event: KeyboardEvent) => boolean;
356
+ enabled: boolean;
357
+ }) {
358
+ const [applyStickyStyles, setApplyStickyStyles] = useState<boolean>(false);
359
+
360
+ const tableWrapperRef = useRef<HTMLDivElement>(null);
361
+ const tableRef = useRef<HTMLTableElement>(null);
362
+ const rafRef = useRef<number | null>(null);
363
+ const { tabIndex, setTableRef } = useTableKeyboardNav({
364
+ enabled,
365
+ shouldBlockNavigation,
366
+ });
367
+
368
+ const mergedTableRefs = useMergeRefs(tableRef, setTableRef);
369
+
370
+ const updateStickyStyles = useCallback(() => {
371
+ if (!tableWrapperRef.current) {
372
+ return;
373
+ }
374
+
375
+ const doesWrapperHasScroll =
376
+ tableWrapperRef.current.scrollWidth > tableWrapperRef.current.clientWidth;
377
+
378
+ setApplyStickyStyles(doesWrapperHasScroll);
379
+ }, []);
380
+
381
+ const scheduleStickyStylesUpdate = useCallback(() => {
382
+ if (rafRef.current !== null) {
383
+ return;
384
+ }
385
+
386
+ rafRef.current = requestAnimationFrame(() => {
387
+ rafRef.current = null;
388
+ updateStickyStyles();
225
389
  });
390
+ }, [updateStickyStyles]);
391
+
392
+ useEffect(() => {
393
+ const tableWrapperElement = tableWrapperRef.current;
394
+
395
+ if (!tableWrapperElement) {
396
+ return;
397
+ }
398
+
399
+ const handleResize = () => scheduleStickyStylesUpdate();
400
+
401
+ window.addEventListener("resize", handleResize);
402
+
403
+ let resizeObserver: ResizeObserver | undefined;
404
+ if (typeof ResizeObserver !== "undefined") {
405
+ resizeObserver = new ResizeObserver(handleResize);
406
+ resizeObserver.observe(tableWrapperElement);
407
+ if (tableRef.current) {
408
+ resizeObserver.observe(tableRef.current);
409
+ }
410
+ }
411
+
412
+ scheduleStickyStylesUpdate();
413
+
414
+ return () => {
415
+ window.removeEventListener("resize", handleResize);
416
+ resizeObserver?.disconnect();
417
+ if (rafRef.current !== null) {
418
+ cancelAnimationFrame(rafRef.current);
419
+ rafRef.current = null;
420
+ }
421
+ };
422
+ }, [scheduleStickyStylesUpdate]);
423
+
424
+ return (
425
+ <div className="aksel-data-table__border-wrapper">
426
+ <div ref={tableWrapperRef} className="aksel-data-table__scroll-wrapper">
427
+ <Slot
428
+ tabIndex={tabIndex}
429
+ /* @ts-expect-error Ref is not typed correctly to handle this case */
430
+ ref={mergedTableRefs}
431
+ data-scroll={applyStickyStyles ? "true" : undefined}
432
+ >
433
+ {children}
434
+ </Slot>
435
+ </div>
436
+ </div>
437
+ );
438
+ }
439
+
440
+ interface DataTableTBodyContentProps {
441
+ loadingState: React.ReactNode;
442
+ loadingLabel: string;
443
+ loadingRows?: number;
444
+ emptyState: React.ReactNode;
445
+ }
226
446
 
227
- const mergedRef = useMergeRefs(forwardedRef, setTableRef);
447
+ function DataTableTBodyContent({
448
+ loadingState,
449
+ loadingRows,
450
+ loadingLabel,
451
+ emptyState,
452
+ }: DataTableTBodyContentProps) {
453
+ const { items, itemDetails } = useTableItemsContext();
454
+ const { columns, isLoading, fullWidthColSpan } = useDataTableContext();
228
455
 
456
+ if (isLoading && loadingState != null) {
229
457
  return (
230
- <DataTableContextProvider
231
- layout={layout}
232
- withKeyboardNav={withKeyboardNav}
233
- selectionState={noSelectionState}
234
- stickySelection={false}
235
- stickyHeader={false}
236
- tableId={useId()}
237
- showLoadingSkeletons={false}
238
- onRowClick={undefined}
239
- disableRowSelectionOnClick={false}
240
- showLoadingOverlay={false}
241
- columns={[]}
242
- >
243
- <DataTableExpansionProvider itemDetails={EMPTY_ITEM_DETAILS}>
244
- <div className="aksel-data-table__border-wrapper">
245
- <div className="aksel-data-table__scroll-wrapper">
246
- <table
247
- {...rest}
248
- ref={mergedRef}
249
- className={cl("aksel-data-table", className)}
250
- data-zebra-stripes={zebraStripes}
251
- data-truncate-content={truncateContent}
252
- data-density={rowDensity}
253
- data-layout={layout}
254
- tabIndex={tabIndex}
255
- />
256
- </div>
257
- </div>
258
- </DataTableExpansionProvider>
259
- </DataTableContextProvider>
458
+ <DataTableLoadingState colSpan={fullWidthColSpan}>
459
+ {loadingState}
460
+ </DataTableLoadingState>
260
461
  );
261
- },
262
- ) as DataTableRootComponent;
263
-
264
- DataTable.Caption = DataTableCaption;
265
- DataTable.Thead = DataTableThead;
266
- DataTable.Tbody = DataTableTbody;
267
- DataTable.Th = DataTableTh;
268
- DataTable.Tr = DataTableTr;
269
- DataTable.Td = DataTableTd;
270
- DataTable.Tfoot = DataTableTfoot;
271
- DataTable.EmptyState = DataTableEmptyState;
272
- DataTable.LoadingState = DataTableLoadingState;
273
-
274
- export {
275
- DataTable,
276
- DataTableCaption,
277
- DataTableEmptyState,
278
- DataTableLoadingState,
279
- DataTableTbody,
280
- DataTableTd,
281
- DataTableTfoot,
282
- DataTableTh,
283
- DataTableThead,
284
- DataTableTr,
285
- };
462
+ }
463
+
464
+ if (isLoading && loadingRows) {
465
+ return (
466
+ <>
467
+ <tr>
468
+ <td colSpan={fullWidthColSpan} className="aksel-sr-only">
469
+ {loadingLabel}
470
+ </td>
471
+ </tr>
472
+ {Array.from({ length: loadingRows }, (_, rowIndex) => (
473
+ <DataTableTr key={`skeleton-row-${rowIndex}`} aria-hidden>
474
+ {columns.map(({ isSticky, colDef }, colDefIndex) => (
475
+ <DataTableBaseCell
476
+ textAlign={colDef.align ?? "left"}
477
+ key={colDef.id || colDefIndex}
478
+ as={colDef.isRowHeader ? "th" : "td"}
479
+ isSticky={isSticky}
480
+ >
481
+ <Skeleton variant="text" />
482
+ </DataTableBaseCell>
483
+ ))}
484
+ </DataTableTr>
485
+ ))}
486
+ </>
487
+ );
488
+ }
489
+
490
+ if (items.length === 0 && emptyState !== undefined) {
491
+ return (
492
+ <DataTableEmptyState colSpan={fullWidthColSpan}>
493
+ {emptyState}
494
+ </DataTableEmptyState>
495
+ );
496
+ }
497
+
498
+ const renderLoadingAnnouncement = isLoading && !loadingState && !loadingRows;
499
+
500
+ return (
501
+ <>
502
+ {renderLoadingAnnouncement && (
503
+ <tr>
504
+ <td colSpan={fullWidthColSpan} className="aksel-sr-only">
505
+ {loadingLabel}
506
+ </td>
507
+ </tr>
508
+ )}
509
+ {items.map((rowData) => {
510
+ const details = itemDetails.get(rowData);
511
+
512
+ /* Should in theory be impossible. Look about typing this? */
513
+ if (!details) {
514
+ return null;
515
+ }
516
+
517
+ const hasSubRows = details.children.length > 0;
518
+
519
+ return (
520
+ <React.Fragment key={details.id}>
521
+ <DataTableTr rowId={details.id}>
522
+ {columns.map(({ isSticky, colDef }, colDefIndex) => {
523
+ const renderNestedToggle = colDefIndex === 0 && hasSubRows;
524
+ const renderNestedIndent =
525
+ colDefIndex === 0 && (details.level > 0 || hasSubRows);
526
+
527
+ const style: React.CSSProperties = {
528
+ "--__axc-data-table-nested-depth": details.level,
529
+ };
530
+
531
+ return (
532
+ <DataTableBaseCell
533
+ textAlign={colDef.align ?? "left"}
534
+ key={colDef.id || colDefIndex}
535
+ as={colDef.isRowHeader ? "th" : "td"}
536
+ isSticky={isSticky}
537
+ data-nested={renderNestedIndent || undefined}
538
+ style={style}
539
+ >
540
+ {renderNestedToggle && (
541
+ <DataTableSubRowToggle details={details} />
542
+ )}
543
+ {colDef.cell(rowData)}
544
+ </DataTableBaseCell>
545
+ );
546
+ })}
547
+ </DataTableTr>
548
+ <DataTableDetailsPanelRow rowId={details.id} rowData={rowData} />
549
+ </React.Fragment>
550
+ );
551
+ })}
552
+ </>
553
+ );
554
+ }
555
+
556
+ const DataTable = forwardRef(DataTableInner) as <T>(
557
+ props: DataTableProps<T> & React.RefAttributes<HTMLTableElement>,
558
+ ) => React.ReactElement | null;
559
+
560
+ export { DataTable };
561
+ export type { DataTableProps };
286
562
  export default DataTable;
287
- export type {
288
- DataTableCaptionProps,
289
- DataTableEmptyStateProps,
290
- DataTableLoadingStateProps,
291
- DataTableProps,
292
- DataTableTbodyProps,
293
- DataTableTdProps,
294
- DataTableTfootProps,
295
- DataTableTheadProps,
296
- DataTableThProps,
297
- DataTableTrProps,
298
- };