@navikt/ds-react 8.10.3 → 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 (159) 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 +2 -2
  8. package/cjs/data/table/column-header/DataTableColumnHeader.js.map +1 -1
  9. package/cjs/data/table/column-header/useTableColumnResize.d.ts +21 -18
  10. package/cjs/data/table/column-header/useTableColumnResize.js +7 -25
  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 +4 -4
  16. package/cjs/data/table/helpers/collectTableRowEntries.js +6 -7
  17. package/cjs/data/table/helpers/collectTableRowEntries.js.map +1 -1
  18. package/cjs/data/table/hooks/useColumnOptions.js +18 -5
  19. package/cjs/data/table/hooks/useColumnOptions.js.map +1 -1
  20. package/cjs/data/table/hooks/useTableDetailsPanel.d.ts +62 -0
  21. package/cjs/data/table/hooks/{useTableExpansion.js → useTableDetailsPanel.js} +20 -19
  22. package/cjs/data/table/hooks/useTableDetailsPanel.js.map +1 -0
  23. package/cjs/data/table/hooks/useTableItems.d.ts +13 -16
  24. package/cjs/data/table/hooks/useTableItems.js +9 -8
  25. package/cjs/data/table/hooks/useTableItems.js.map +1 -1
  26. package/cjs/data/table/hooks/useTableSelection.d.ts +4 -2
  27. package/cjs/data/table/hooks/useTableSelection.js +6 -1
  28. package/cjs/data/table/hooks/useTableSelection.js.map +1 -1
  29. package/cjs/data/table/index.d.ts +1 -2
  30. package/cjs/data/table/index.js +22 -12
  31. package/cjs/data/table/index.js.map +1 -1
  32. package/cjs/data/table/root/DataTable.types.d.ts +7 -6
  33. package/cjs/data/table/root/DataTableRoot.context.d.ts +5 -1
  34. package/cjs/data/table/root/DataTableRoot.context.js.map +1 -1
  35. package/cjs/data/table/root/DataTableRoot.d.ts +79 -115
  36. package/cjs/data/table/root/DataTableRoot.js +167 -38
  37. package/cjs/data/table/root/DataTableRoot.js.map +1 -1
  38. package/cjs/data/table/root/DataTableRoot.legacy.d.ts +177 -0
  39. package/cjs/data/table/root/DataTableRoot.legacy.js +104 -0
  40. package/cjs/data/table/root/DataTableRoot.legacy.js.map +1 -0
  41. package/cjs/data/table/sub-row-toggle/DataTableSubRowToggle.d.ts +6 -0
  42. package/cjs/data/table/sub-row-toggle/DataTableSubRowToggle.js +21 -0
  43. package/cjs/data/table/sub-row-toggle/DataTableSubRowToggle.js.map +1 -0
  44. package/cjs/data/table/tr/DataTableTr.js +11 -11
  45. package/cjs/data/table/tr/DataTableTr.js.map +1 -1
  46. package/cjs/utils/components/dismissablelayer/DismissableLayer.js +1 -1
  47. package/cjs/utils/components/dismissablelayer/DismissableLayer.js.map +1 -1
  48. package/cjs/utils/components/floating/Floating.d.ts +16 -1
  49. package/cjs/utils/components/floating/Floating.js +50 -13
  50. package/cjs/utils/components/floating/Floating.js.map +1 -1
  51. package/cjs/utils/components/floating-menu/Menu.js +1 -1
  52. package/cjs/utils/components/floating-menu/Menu.js.map +1 -1
  53. package/cjs/utils/helpers/create-strict-context.js +1 -1
  54. package/cjs/utils/helpers/create-strict-context.js.map +1 -1
  55. package/cjs/utils/hooks/useControllableState.d.ts +5 -5
  56. package/cjs/utils/hooks/useControllableState.js.map +1 -1
  57. package/cjs/utils/hooks/useValueAsRef.js +1 -1
  58. package/cjs/utils/hooks/useValueAsRef.js.map +1 -1
  59. package/cjs/utils-external/hooks/useId.js +1 -1
  60. package/cjs/utils-external/hooks/useId.js.map +1 -1
  61. package/esm/action-menu/ActionMenu.js +1 -1
  62. package/esm/action-menu/ActionMenu.js.map +1 -1
  63. package/esm/data/stories/Data.test-data.d.ts +24 -0
  64. package/esm/data/stories/Data.test-data.js +1607 -0
  65. package/esm/data/stories/Data.test-data.js.map +1 -0
  66. package/esm/data/table/column-header/DataTableColumnHeader.d.ts +4 -1
  67. package/esm/data/table/column-header/DataTableColumnHeader.js +2 -2
  68. package/esm/data/table/column-header/DataTableColumnHeader.js.map +1 -1
  69. package/esm/data/table/column-header/useTableColumnResize.d.ts +21 -18
  70. package/esm/data/table/column-header/useTableColumnResize.js +7 -25
  71. package/esm/data/table/column-header/useTableColumnResize.js.map +1 -1
  72. package/esm/data/table/details-panel-row/DataTableDetailsPanelRow.d.ts +6 -0
  73. package/esm/data/table/details-panel-row/DataTableDetailsPanelRow.js +27 -0
  74. package/esm/data/table/details-panel-row/DataTableDetailsPanelRow.js.map +1 -0
  75. package/esm/data/table/helpers/collectTableRowEntries.d.ts +4 -4
  76. package/esm/data/table/helpers/collectTableRowEntries.js +6 -7
  77. package/esm/data/table/helpers/collectTableRowEntries.js.map +1 -1
  78. package/esm/data/table/hooks/useColumnOptions.js +18 -5
  79. package/esm/data/table/hooks/useColumnOptions.js.map +1 -1
  80. package/esm/data/table/hooks/useTableDetailsPanel.d.ts +62 -0
  81. package/esm/data/table/hooks/{useTableExpansion.js → useTableDetailsPanel.js} +17 -16
  82. package/esm/data/table/hooks/useTableDetailsPanel.js.map +1 -0
  83. package/esm/data/table/hooks/useTableItems.d.ts +13 -16
  84. package/esm/data/table/hooks/useTableItems.js +9 -8
  85. package/esm/data/table/hooks/useTableItems.js.map +1 -1
  86. package/esm/data/table/hooks/useTableSelection.d.ts +4 -2
  87. package/esm/data/table/hooks/useTableSelection.js +6 -1
  88. package/esm/data/table/hooks/useTableSelection.js.map +1 -1
  89. package/esm/data/table/index.d.ts +1 -2
  90. package/esm/data/table/index.js +21 -1
  91. package/esm/data/table/index.js.map +1 -1
  92. package/esm/data/table/root/DataTable.types.d.ts +7 -6
  93. package/esm/data/table/root/DataTableRoot.context.d.ts +5 -1
  94. package/esm/data/table/root/DataTableRoot.context.js.map +1 -1
  95. package/esm/data/table/root/DataTableRoot.d.ts +79 -115
  96. package/esm/data/table/root/DataTableRoot.js +174 -36
  97. package/esm/data/table/root/DataTableRoot.js.map +1 -1
  98. package/esm/data/table/root/DataTableRoot.legacy.d.ts +177 -0
  99. package/esm/data/table/root/DataTableRoot.legacy.js +59 -0
  100. package/esm/data/table/root/DataTableRoot.legacy.js.map +1 -0
  101. package/esm/data/table/sub-row-toggle/DataTableSubRowToggle.d.ts +6 -0
  102. package/esm/data/table/sub-row-toggle/DataTableSubRowToggle.js +16 -0
  103. package/esm/data/table/sub-row-toggle/DataTableSubRowToggle.js.map +1 -0
  104. package/esm/data/table/tr/DataTableTr.js +11 -11
  105. package/esm/data/table/tr/DataTableTr.js.map +1 -1
  106. package/esm/utils/components/dismissablelayer/DismissableLayer.js +1 -1
  107. package/esm/utils/components/dismissablelayer/DismissableLayer.js.map +1 -1
  108. package/esm/utils/components/floating/Floating.d.ts +16 -1
  109. package/esm/utils/components/floating/Floating.js +48 -13
  110. package/esm/utils/components/floating/Floating.js.map +1 -1
  111. package/esm/utils/components/floating-menu/Menu.js +2 -2
  112. package/esm/utils/components/floating-menu/Menu.js.map +1 -1
  113. package/esm/utils/helpers/create-strict-context.js +1 -1
  114. package/esm/utils/helpers/create-strict-context.js.map +1 -1
  115. package/esm/utils/hooks/useControllableState.d.ts +5 -5
  116. package/esm/utils/hooks/useControllableState.js.map +1 -1
  117. package/esm/utils/hooks/useValueAsRef.js +1 -1
  118. package/esm/utils/hooks/useValueAsRef.js.map +1 -1
  119. package/esm/utils-external/hooks/useId.js +1 -1
  120. package/esm/utils-external/hooks/useId.js.map +1 -1
  121. package/package.json +3 -3
  122. package/src/action-menu/ActionMenu.tsx +1 -1
  123. package/src/data/stories/Data.test-data.tsx +1703 -0
  124. package/src/data/table/column-header/DataTableColumnHeader.tsx +6 -6
  125. package/src/data/table/column-header/useTableColumnResize.ts +29 -44
  126. package/src/data/table/details-panel-row/DataTableDetailsPanelRow.tsx +53 -0
  127. package/src/data/table/helpers/collectTableRowEntries.ts +10 -18
  128. package/src/data/table/hooks/__tests__/useTableItems.test.ts +14 -7
  129. package/src/data/table/hooks/__tests__/useTableSelection.test.ts +57 -44
  130. package/src/data/table/hooks/useColumnOptions.ts +19 -5
  131. package/src/data/table/hooks/{useTableExpansion.tsx → useTableDetailsPanel.tsx} +81 -45
  132. package/src/data/table/hooks/useTableItems.ts +27 -36
  133. package/src/data/table/hooks/useTableSelection.ts +17 -6
  134. package/src/data/table/index.tsx +5 -3
  135. package/src/data/table/root/DataTable.types.ts +20 -6
  136. package/src/data/table/root/DataTableRoot.context.ts +5 -1
  137. package/src/data/table/root/DataTableRoot.legacy.tsx +297 -0
  138. package/src/data/table/root/DataTableRoot.tsx +482 -217
  139. package/src/data/table/sub-row-toggle/DataTableSubRowToggle.tsx +39 -0
  140. package/src/data/table/tr/DataTableTr.tsx +14 -13
  141. package/src/utils/components/dismissablelayer/DismissableLayer.tsx +1 -1
  142. package/src/utils/components/floating/Floating.tsx +56 -13
  143. package/src/utils/components/floating-menu/Menu.tsx +4 -1
  144. package/src/utils/helpers/create-strict-context.tsx +1 -1
  145. package/src/utils/hooks/useControllableState.ts +11 -8
  146. package/src/utils/hooks/useValueAsRef.ts +1 -1
  147. package/src/utils-external/hooks/useId.ts +1 -1
  148. package/cjs/data/table/hooks/useTableExpansion.d.ts +0 -27
  149. package/cjs/data/table/hooks/useTableExpansion.js.map +0 -1
  150. package/cjs/data/table/root/DataTableAuto.d.ts +0 -182
  151. package/cjs/data/table/root/DataTableAuto.js +0 -206
  152. package/cjs/data/table/root/DataTableAuto.js.map +0 -1
  153. package/esm/data/table/hooks/useTableExpansion.d.ts +0 -27
  154. package/esm/data/table/hooks/useTableExpansion.js.map +0 -1
  155. package/esm/data/table/root/DataTableAuto.d.ts +0 -182
  156. package/esm/data/table/root/DataTableAuto.js +0 -170
  157. package/esm/data/table/root/DataTableAuto.js.map +0 -1
  158. package/src/data/table/root/DataTableAuto.test.tsx +0 -244
  159. package/src/data/table/root/DataTableAuto.tsx +0 -612
@@ -1,45 +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";
25
+ type SubRowsProps,
26
+ TableItemsProvider,
27
+ useTableItems,
28
+ useTableItemsContext,
29
+ } from "../hooks/useTableItems";
14
30
  import { useTableKeyboardNav } from "../hooks/useTableKeyboardNav";
15
31
  import {
16
32
  type SelectionProps,
17
- noSelectionState,
33
+ useTableSelection,
18
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";
19
42
  import {
20
- DataTableLoadingState,
21
- type DataTableLoadingStateProps,
22
- } from "../loading-state/DataTableLoadingState";
23
- import {
24
- DataTableTbody,
25
- type DataTableTbodyProps,
26
- } from "../tbody/DataTableTbody";
27
- import { DataTableTd, type DataTableTdProps } from "../td/DataTableTd";
28
- import {
29
- DataTableTfoot,
30
- type DataTableTfootProps,
31
- } from "../tfoot/DataTableTfoot";
32
- import { DataTableTh, type DataTableThProps } from "../th/DataTableTh";
33
- import {
34
- DataTableThead,
35
- type DataTableTheadProps,
36
- } from "../thead/DataTableThead";
37
- import { DataTableTr, type DataTableTrProps } from "../tr/DataTableTr";
38
- import { DataTableContextProvider } from "./DataTableRoot.context";
39
-
40
- interface DataTableProps
41
- extends React.HTMLAttributes<HTMLTableElement>, SelectionProps {
42
- children: React.ReactNode;
43
+ DataTableContextProvider,
44
+ useDataTableContext,
45
+ } from "./DataTableRoot.context";
46
+
47
+ interface DataTableProps<T>
48
+ extends React.HTMLAttributes<HTMLTableElement>, TableSortOptions {
49
+ children?: never;
43
50
  /**
44
51
  * Controls vertical cell padding.
45
52
  * @default "normal"
@@ -59,7 +66,7 @@ interface DataTableProps
59
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.
60
67
  /**
61
68
  * Enables keyboard navigation for table rows and cells.
62
- * @default false
69
+ * @default true
63
70
  */
64
71
  withKeyboardNav?: boolean;
65
72
  /**
@@ -83,215 +90,473 @@ interface DataTableProps
83
90
  * @default "fixed"
84
91
  */
85
92
  layout?: "fixed" | "auto";
86
- }
87
-
88
- interface DataTableRootComponent extends React.ForwardRefExoticComponent<
89
- DataTableProps & React.RefAttributes<HTMLTableElement>
90
- > {
91
93
  /**
92
- * @see 🏷️ {@link DataTableCaptionProps}
93
- * @example
94
- * ```jsx
95
- * <DataTable>
96
- * <DataTable.Caption>
97
- * Lorem ipsum
98
- * </DataTable.Caption
99
- * </DataTable>
100
- * ```
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.
101
98
  */
102
- Caption: typeof DataTableCaption;
99
+ columnDefinitions: ColumnDefinitions<T>;
103
100
  /**
104
- * @see 🏷️ {@link DataTableTheadProps}
105
- * @example
106
- * ```jsx
107
- * <DataTable>
108
- * <DataTable.Thead>
109
- * ... TODO
110
- * </DataTable.Thead>
111
- * </DataTable>
112
- * ```
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`.
113
105
  */
114
- Thead: typeof DataTableThead;
106
+ data: T[];
115
107
  /**
116
- * @see 🏷️ {@link DataTableTbodyProps}
117
- * @example
118
- * ```jsx
119
- * <DataTable>
120
- * <DataTable.Tbody>
121
- * ... TODO
122
- * </DataTable.Tbody>
123
- * </DataTable>
124
- * ```
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.
125
112
  */
126
- Tbody: typeof DataTableTbody;
113
+ getRowId?: (rowData: T, index: number) => string | number;
127
114
  /**
128
- * @see 🏷️ {@link DataTableTrProps}
129
- * @example
130
- * ```jsx
131
- * <DataTable>
132
- * <DataTable.Tr>
133
- * ... TODO
134
- * </DataTable.Tr
135
- * </DataTable>
136
- * ```
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.
137
118
  */
138
- Tr: typeof DataTableTr;
119
+ stickyColumns?: {
120
+ first?: "1";
121
+ last?: "1";
122
+ };
139
123
  /**
140
- * @see 🏷️ {@link DataTableThProps}
141
- * @example
142
- * ```jsx
143
- * ```
124
+ * @default true
144
125
  */
145
- Th: typeof DataTableTh;
126
+ stickyHeader?: boolean;
146
127
  /**
147
- * @see 🏷️ {@link DataTableTdProps}
148
- * @example
149
- * ```jsx
150
- * <DataTable>
151
- * <DataTable.Tbody>
152
- * <DataTable.Td>
153
- * Lorem ipsum
154
- * </DataTable.Td>
155
- * <DataTable.Td>
156
- * Dolor sit amet
157
- * </DataTable.Td>
158
- * </DataTable.Tbody>
159
- * </DataTable>
160
- * ```
128
+ * Callback invoked when a data row is clicked.
129
+ * Not called when clicking header, loading, or empty-state rows.
161
130
  */
162
- Td: typeof DataTableTd;
131
+ onRowClick?: (
132
+ rowId: string | number,
133
+ event: React.MouseEvent<HTMLTableRowElement>,
134
+ ) => void;
163
135
  /**
164
- * @see 🏷️ {@link DataTableTfootProps}
165
- * @example
166
- * ```jsx
167
- * <DataTable>
168
- * <DataTable.Tfoot>
169
- * ...
170
- * </DataTable.Tfoot>
171
- * </DataTable>
172
- * ```
136
+ * Content to render when `data` is empty.
137
+ * Rendered inside a `DataTable.EmptyState` row spanning all columns.
173
138
  */
174
- 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
+
175
170
  /**
176
- * @see 🏷️ {@link DataTableEmptyStateProps}
177
- * @example
178
- * ```jsx
179
- * <DataTable>
180
- * <DataTable.TBody>
181
- * <DataTable.EmptyState />
182
- * </DataTable.TBody>
183
- * </DataTable>
184
- * ```
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.
185
177
  */
186
- 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
+
187
217
  /**
188
- * @see 🏷️ {@link DataTableEmptyStateProps}
189
- * @example
190
- * ```jsx
191
- * <DataTable>
192
- * <DataTable.TBody>
193
- * <DataTable.LoadingState />
194
- * </DataTable.TBody>
195
- * </DataTable>
196
- * ```
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.
197
221
  */
198
- 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
+ );
199
344
  }
200
345
 
201
346
  /**
202
- * TODO Component description etc.
203
- *
204
- * **NB:** To get sticky headers, you have to set a height restriction on the table container. You can use VStack for this:
205
- * TODO example
347
+ * Temp optimization to avoid re-renders on every keyboard-move, selection change etc
206
348
  */
207
- const DataTable = forwardRef<HTMLTableElement, DataTableProps>(
208
- (
209
- {
210
- className,
211
- rowDensity = "normal",
212
- withKeyboardNav = false,
213
- zebraStripes = false,
214
- truncateContent = true,
215
- shouldBlockNavigation,
216
- layout = "fixed",
217
- ...rest
218
- },
219
- forwardedRef,
220
- ) => {
221
- const { tabIndex, setTableRef } = useTableKeyboardNav({
222
- enabled: withKeyboardNav,
223
- 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();
224
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
+ }
225
446
 
226
- 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();
227
455
 
456
+ if (isLoading && loadingState != null) {
228
457
  return (
229
- <DataTableContextProvider
230
- layout={layout}
231
- withKeyboardNav={withKeyboardNav}
232
- selectionState={noSelectionState}
233
- stickySelection={false}
234
- stickyHeader={true}
235
- tableId={useId()}
236
- showLoadingSkeletons={false}
237
- onRowClick={undefined}
238
- disableRowSelectionOnClick={false}
239
- showLoadingOverlay={false}
240
- columns={[]}
241
- >
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>
256
- </div>
257
- </DataTableExpansionProvider>
258
- </DataTableContextProvider>
458
+ <DataTableLoadingState colSpan={fullWidthColSpan}>
459
+ {loadingState}
460
+ </DataTableLoadingState>
259
461
  );
260
- },
261
- ) as DataTableRootComponent;
262
-
263
- DataTable.Caption = DataTableCaption;
264
- DataTable.Thead = DataTableThead;
265
- DataTable.Tbody = DataTableTbody;
266
- DataTable.Th = DataTableTh;
267
- DataTable.Tr = DataTableTr;
268
- DataTable.Td = DataTableTd;
269
- DataTable.Tfoot = DataTableTfoot;
270
- DataTable.EmptyState = DataTableEmptyState;
271
- DataTable.LoadingState = DataTableLoadingState;
272
-
273
- export {
274
- DataTable,
275
- DataTableCaption,
276
- DataTableEmptyState,
277
- DataTableLoadingState,
278
- DataTableTbody,
279
- DataTableTd,
280
- DataTableTfoot,
281
- DataTableTh,
282
- DataTableThead,
283
- DataTableTr,
284
- };
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 };
285
562
  export default DataTable;
286
- export type {
287
- DataTableCaptionProps,
288
- DataTableEmptyStateProps,
289
- DataTableLoadingStateProps,
290
- DataTableProps,
291
- DataTableTbodyProps,
292
- DataTableTdProps,
293
- DataTableTfootProps,
294
- DataTableTheadProps,
295
- DataTableThProps,
296
- DataTableTrProps,
297
- };