@navikt/ds-react 8.6.0 → 8.7.0

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 (250) hide show
  1. package/cjs/data/drag-and-drop/item/DataDragAndDropItem.d.ts +27 -0
  2. package/cjs/data/drag-and-drop/item/DataDragAndDropItem.js +91 -0
  3. package/cjs/data/drag-and-drop/item/DataDragAndDropItem.js.map +1 -0
  4. package/cjs/data/drag-and-drop/root/DataDragAndDrop.context.d.ts +5 -0
  5. package/cjs/data/drag-and-drop/root/DataDragAndDrop.context.js +6 -0
  6. package/cjs/data/drag-and-drop/root/DataDragAndDrop.context.js.map +1 -0
  7. package/cjs/data/drag-and-drop/root/DataDragAndDropRoot.d.ts +24 -0
  8. package/cjs/data/drag-and-drop/root/DataDragAndDropRoot.js +111 -0
  9. package/cjs/data/drag-and-drop/root/DataDragAndDropRoot.js.map +1 -0
  10. package/cjs/data/table/helpers/table-keyboard.d.ts +1 -0
  11. package/cjs/data/table/helpers/table-keyboard.js +5 -3
  12. package/cjs/data/table/helpers/table-keyboard.js.map +1 -1
  13. package/cjs/data/table/root/DataTableRoot.context.d.ts +8 -0
  14. package/cjs/data/table/root/DataTableRoot.context.js +11 -0
  15. package/cjs/data/table/root/DataTableRoot.context.js.map +1 -0
  16. package/cjs/data/table/root/DataTableRoot.js +5 -3
  17. package/cjs/data/table/root/DataTableRoot.js.map +1 -1
  18. package/cjs/data/table/th/DataTableTh.d.ts +18 -2
  19. package/cjs/data/table/th/DataTableTh.js +45 -20
  20. package/cjs/data/table/th/DataTableTh.js.map +1 -1
  21. package/cjs/data/table/tr/DataTableTr.js +9 -2
  22. package/cjs/data/table/tr/DataTableTr.js.map +1 -1
  23. package/cjs/data/token-filter/AutoSuggest.d.ts +6 -2
  24. package/cjs/data/token-filter/AutoSuggest.js +46 -11
  25. package/cjs/data/token-filter/AutoSuggest.js.map +1 -1
  26. package/cjs/data/token-filter/TokenFilter.d.ts +5 -5
  27. package/cjs/data/token-filter/TokenFilter.js +105 -42
  28. package/cjs/data/token-filter/TokenFilter.js.map +1 -1
  29. package/cjs/data/token-filter/TokenFilter.types.d.ts +51 -33
  30. package/cjs/data/token-filter/helpers/generate-autocomplete-options.d.ts +2 -3
  31. package/cjs/data/token-filter/helpers/generate-autocomplete-options.js +11 -15
  32. package/cjs/data/token-filter/helpers/generate-autocomplete-options.js.map +1 -1
  33. package/cjs/data/token-filter/helpers/operators.d.ts +6 -6
  34. package/cjs/data/token-filter/helpers/operators.js +3 -4
  35. package/cjs/data/token-filter/helpers/operators.js.map +1 -1
  36. package/cjs/data/token-filter/helpers/parse-query-text.d.ts +2 -20
  37. package/cjs/data/token-filter/helpers/parse-query-text.js +1 -1
  38. package/cjs/data/token-filter/helpers/parse-query-text.js.map +1 -1
  39. package/cjs/data/token-filter/helpers/query-builder.d.ts +2 -2
  40. package/cjs/data/token-filter/helpers/query-builder.js.map +1 -1
  41. package/cjs/date/Date.Dialog.d.ts +5 -1
  42. package/cjs/date/Date.Dialog.js +6 -2
  43. package/cjs/date/Date.Dialog.js.map +1 -1
  44. package/cjs/date/datepicker/DatePicker.js +3 -2
  45. package/cjs/date/datepicker/DatePicker.js.map +1 -1
  46. package/cjs/date/datepicker/hooks/useDatepicker.js +5 -2
  47. package/cjs/date/datepicker/hooks/useDatepicker.js.map +1 -1
  48. package/cjs/date/datepicker/hooks/useRangeDatepicker.js +3 -1
  49. package/cjs/date/datepicker/hooks/useRangeDatepicker.js.map +1 -1
  50. package/cjs/date/datepicker/parts/DatePicker.Months.d.ts +2 -1
  51. package/cjs/date/datepicker/parts/DatePicker.Months.js +3 -3
  52. package/cjs/date/datepicker/parts/DatePicker.Months.js.map +1 -1
  53. package/cjs/date/datepicker/parts/DatePicker.RDP.d.ts +5 -1
  54. package/cjs/date/datepicker/parts/DatePicker.RDP.js +2 -2
  55. package/cjs/date/datepicker/parts/DatePicker.RDP.js.map +1 -1
  56. package/cjs/date/monthpicker/MonthPicker.js +3 -2
  57. package/cjs/date/monthpicker/MonthPicker.js.map +1 -1
  58. package/cjs/date/monthpicker/hooks/useMonthPicker.js +3 -1
  59. package/cjs/date/monthpicker/hooks/useMonthPicker.js.map +1 -1
  60. package/cjs/date/monthpicker/parts/MonthPicker.Caption.d.ts +4 -1
  61. package/cjs/date/monthpicker/parts/MonthPicker.Caption.js +3 -2
  62. package/cjs/date/monthpicker/parts/MonthPicker.Caption.js.map +1 -1
  63. package/cjs/dropdown/Toggle.js +5 -12
  64. package/cjs/dropdown/Toggle.js.map +1 -1
  65. package/cjs/form/combobox/Input/Input.js +1 -1
  66. package/cjs/form/combobox/Input/Input.js.map +1 -1
  67. package/cjs/inline-message/root/InlineMessage.js +2 -2
  68. package/cjs/inline-message/root/InlineMessage.js.map +1 -1
  69. package/cjs/provider/Provider.d.ts +2 -2
  70. package/cjs/tooltip/Tooltip.js +1 -3
  71. package/cjs/tooltip/Tooltip.js.map +1 -1
  72. package/cjs/utils/components/HighlightText/HighlightText.d.ts +8 -0
  73. package/cjs/utils/components/HighlightText/HighlightText.js +27 -0
  74. package/cjs/utils/components/HighlightText/HighlightText.js.map +1 -0
  75. package/cjs/utils/components/Listbox/group/ListboxGroup.d.ts +7 -0
  76. package/cjs/utils/components/Listbox/group/ListboxGroup.js +15 -0
  77. package/cjs/utils/components/Listbox/group/ListboxGroup.js.map +1 -0
  78. package/cjs/utils/components/Listbox/input-slot/ListboxInputSlot.d.ts +7 -0
  79. package/cjs/utils/components/Listbox/input-slot/ListboxInputSlot.js +15 -0
  80. package/cjs/utils/components/Listbox/input-slot/ListboxInputSlot.js.map +1 -0
  81. package/cjs/utils/components/Listbox/item/ListboxItem.d.ts +24 -0
  82. package/cjs/utils/components/Listbox/item/ListboxItem.js +33 -0
  83. package/cjs/utils/components/Listbox/item/ListboxItem.js.map +1 -0
  84. package/cjs/utils/components/Listbox/list/ListboxList.d.ts +8 -0
  85. package/cjs/utils/components/Listbox/list/ListboxList.js +32 -0
  86. package/cjs/utils/components/Listbox/list/ListboxList.js.map +1 -0
  87. package/cjs/utils/components/Listbox/root/ListboxRoot.d.ts +20 -0
  88. package/cjs/utils/components/Listbox/root/ListboxRoot.js +84 -0
  89. package/cjs/utils/components/Listbox/root/ListboxRoot.js.map +1 -0
  90. package/cjs/utils/components/Listbox/root/domHelpers.d.ts +3 -0
  91. package/cjs/utils/components/Listbox/root/domHelpers.js +53 -0
  92. package/cjs/utils/components/Listbox/root/domHelpers.js.map +1 -0
  93. package/cjs/utils/components/focus-boundary/FocusBoundary.js +9 -64
  94. package/cjs/utils/components/focus-boundary/FocusBoundary.js.map +1 -1
  95. package/cjs/utils/helpers/focus.d.ts +14 -0
  96. package/cjs/utils/helpers/focus.js +63 -0
  97. package/cjs/utils/helpers/focus.js.map +1 -0
  98. package/cjs/utils/hooks/useDeferredValue.d.ts +1 -0
  99. package/cjs/utils/hooks/useDeferredValue.js +14 -0
  100. package/cjs/utils/hooks/useDeferredValue.js.map +1 -0
  101. package/esm/data/drag-and-drop/item/DataDragAndDropItem.d.ts +27 -0
  102. package/esm/data/drag-and-drop/item/DataDragAndDropItem.js +55 -0
  103. package/esm/data/drag-and-drop/item/DataDragAndDropItem.js.map +1 -0
  104. package/esm/data/drag-and-drop/root/DataDragAndDrop.context.d.ts +5 -0
  105. package/esm/data/drag-and-drop/root/DataDragAndDrop.context.js +3 -0
  106. package/esm/data/drag-and-drop/root/DataDragAndDrop.context.js.map +1 -0
  107. package/esm/data/drag-and-drop/root/DataDragAndDropRoot.d.ts +24 -0
  108. package/esm/data/drag-and-drop/root/DataDragAndDropRoot.js +71 -0
  109. package/esm/data/drag-and-drop/root/DataDragAndDropRoot.js.map +1 -0
  110. package/esm/data/table/helpers/table-keyboard.d.ts +1 -0
  111. package/esm/data/table/helpers/table-keyboard.js +5 -3
  112. package/esm/data/table/helpers/table-keyboard.js.map +1 -1
  113. package/esm/data/table/root/DataTableRoot.context.d.ts +8 -0
  114. package/esm/data/table/root/DataTableRoot.context.js +7 -0
  115. package/esm/data/table/root/DataTableRoot.context.js.map +1 -0
  116. package/esm/data/table/root/DataTableRoot.js +5 -3
  117. package/esm/data/table/root/DataTableRoot.js.map +1 -1
  118. package/esm/data/table/th/DataTableTh.d.ts +18 -2
  119. package/esm/data/table/th/DataTableTh.js +46 -21
  120. package/esm/data/table/th/DataTableTh.js.map +1 -1
  121. package/esm/data/table/tr/DataTableTr.js +9 -2
  122. package/esm/data/table/tr/DataTableTr.js.map +1 -1
  123. package/esm/data/token-filter/AutoSuggest.d.ts +6 -2
  124. package/esm/data/token-filter/AutoSuggest.js +45 -13
  125. package/esm/data/token-filter/AutoSuggest.js.map +1 -1
  126. package/esm/data/token-filter/TokenFilter.d.ts +5 -5
  127. package/esm/data/token-filter/TokenFilter.js +105 -42
  128. package/esm/data/token-filter/TokenFilter.js.map +1 -1
  129. package/esm/data/token-filter/TokenFilter.types.d.ts +51 -33
  130. package/esm/data/token-filter/helpers/generate-autocomplete-options.d.ts +2 -3
  131. package/esm/data/token-filter/helpers/generate-autocomplete-options.js +11 -15
  132. package/esm/data/token-filter/helpers/generate-autocomplete-options.js.map +1 -1
  133. package/esm/data/token-filter/helpers/operators.d.ts +6 -6
  134. package/esm/data/token-filter/helpers/operators.js +3 -4
  135. package/esm/data/token-filter/helpers/operators.js.map +1 -1
  136. package/esm/data/token-filter/helpers/parse-query-text.d.ts +2 -20
  137. package/esm/data/token-filter/helpers/parse-query-text.js +1 -1
  138. package/esm/data/token-filter/helpers/parse-query-text.js.map +1 -1
  139. package/esm/data/token-filter/helpers/query-builder.d.ts +2 -2
  140. package/esm/data/token-filter/helpers/query-builder.js.map +1 -1
  141. package/esm/date/Date.Dialog.d.ts +5 -1
  142. package/esm/date/Date.Dialog.js +6 -2
  143. package/esm/date/Date.Dialog.js.map +1 -1
  144. package/esm/date/datepicker/DatePicker.js +3 -2
  145. package/esm/date/datepicker/DatePicker.js.map +1 -1
  146. package/esm/date/datepicker/hooks/useDatepicker.js +5 -2
  147. package/esm/date/datepicker/hooks/useDatepicker.js.map +1 -1
  148. package/esm/date/datepicker/hooks/useRangeDatepicker.js +3 -1
  149. package/esm/date/datepicker/hooks/useRangeDatepicker.js.map +1 -1
  150. package/esm/date/datepicker/parts/DatePicker.Months.d.ts +2 -1
  151. package/esm/date/datepicker/parts/DatePicker.Months.js +3 -3
  152. package/esm/date/datepicker/parts/DatePicker.Months.js.map +1 -1
  153. package/esm/date/datepicker/parts/DatePicker.RDP.d.ts +5 -1
  154. package/esm/date/datepicker/parts/DatePicker.RDP.js +2 -2
  155. package/esm/date/datepicker/parts/DatePicker.RDP.js.map +1 -1
  156. package/esm/date/monthpicker/MonthPicker.js +3 -2
  157. package/esm/date/monthpicker/MonthPicker.js.map +1 -1
  158. package/esm/date/monthpicker/hooks/useMonthPicker.js +3 -1
  159. package/esm/date/monthpicker/hooks/useMonthPicker.js.map +1 -1
  160. package/esm/date/monthpicker/parts/MonthPicker.Caption.d.ts +4 -1
  161. package/esm/date/monthpicker/parts/MonthPicker.Caption.js +3 -2
  162. package/esm/date/monthpicker/parts/MonthPicker.Caption.js.map +1 -1
  163. package/esm/dropdown/Toggle.js +5 -12
  164. package/esm/dropdown/Toggle.js.map +1 -1
  165. package/esm/form/combobox/Input/Input.js +1 -1
  166. package/esm/form/combobox/Input/Input.js.map +1 -1
  167. package/esm/inline-message/root/InlineMessage.js +3 -3
  168. package/esm/inline-message/root/InlineMessage.js.map +1 -1
  169. package/esm/provider/Provider.d.ts +2 -2
  170. package/esm/tooltip/Tooltip.js +1 -3
  171. package/esm/tooltip/Tooltip.js.map +1 -1
  172. package/esm/utils/components/HighlightText/HighlightText.d.ts +8 -0
  173. package/esm/utils/components/HighlightText/HighlightText.js +21 -0
  174. package/esm/utils/components/HighlightText/HighlightText.js.map +1 -0
  175. package/esm/utils/components/Listbox/group/ListboxGroup.d.ts +7 -0
  176. package/esm/utils/components/Listbox/group/ListboxGroup.js +10 -0
  177. package/esm/utils/components/Listbox/group/ListboxGroup.js.map +1 -0
  178. package/esm/utils/components/Listbox/input-slot/ListboxInputSlot.d.ts +7 -0
  179. package/esm/utils/components/Listbox/input-slot/ListboxInputSlot.js +9 -0
  180. package/esm/utils/components/Listbox/input-slot/ListboxInputSlot.js.map +1 -0
  181. package/esm/utils/components/Listbox/item/ListboxItem.d.ts +24 -0
  182. package/esm/utils/components/Listbox/item/ListboxItem.js +27 -0
  183. package/esm/utils/components/Listbox/item/ListboxItem.js.map +1 -0
  184. package/esm/utils/components/Listbox/list/ListboxList.d.ts +8 -0
  185. package/esm/utils/components/Listbox/list/ListboxList.js +27 -0
  186. package/esm/utils/components/Listbox/list/ListboxList.js.map +1 -0
  187. package/esm/utils/components/Listbox/root/ListboxRoot.d.ts +20 -0
  188. package/esm/utils/components/Listbox/root/ListboxRoot.js +79 -0
  189. package/esm/utils/components/Listbox/root/ListboxRoot.js.map +1 -0
  190. package/esm/utils/components/Listbox/root/domHelpers.d.ts +3 -0
  191. package/esm/utils/components/Listbox/root/domHelpers.js +50 -0
  192. package/esm/utils/components/Listbox/root/domHelpers.js.map +1 -0
  193. package/esm/utils/components/focus-boundary/FocusBoundary.js +8 -63
  194. package/esm/utils/components/focus-boundary/FocusBoundary.js.map +1 -1
  195. package/esm/utils/helpers/focus.d.ts +14 -0
  196. package/esm/utils/helpers/focus.js +60 -0
  197. package/esm/utils/helpers/focus.js.map +1 -0
  198. package/esm/utils/hooks/useDeferredValue.d.ts +1 -0
  199. package/esm/utils/hooks/useDeferredValue.js +7 -0
  200. package/esm/utils/hooks/useDeferredValue.js.map +1 -0
  201. package/package.json +7 -7
  202. package/src/data/drag-and-drop/item/DataDragAndDropItem.tsx +101 -0
  203. package/src/data/drag-and-drop/root/DataDragAndDrop.context.tsx +9 -0
  204. package/src/data/drag-and-drop/root/DataDragAndDropRoot.tsx +98 -0
  205. package/src/data/table/helpers/table-keyboard.ts +7 -3
  206. package/src/data/table/root/DataTableRoot.context.ts +13 -0
  207. package/src/data/table/root/DataTableRoot.tsx +16 -13
  208. package/src/data/table/th/DataTableTh.tsx +110 -54
  209. package/src/data/table/tr/DataTableTr.tsx +13 -2
  210. package/src/data/token-filter/AutoSuggest.tsx +142 -29
  211. package/src/data/token-filter/TokenFilter.tsx +174 -79
  212. package/src/data/token-filter/TokenFilter.types.ts +70 -42
  213. package/src/data/token-filter/helpers/generate-autocomplete-options.test.ts +97 -97
  214. package/src/data/token-filter/helpers/generate-autocomplete-options.ts +31 -38
  215. package/src/data/token-filter/helpers/operators.test.ts +29 -29
  216. package/src/data/token-filter/helpers/operators.ts +16 -16
  217. package/src/data/token-filter/helpers/parse-query-text.test.ts +37 -35
  218. package/src/data/token-filter/helpers/parse-query-text.ts +7 -26
  219. package/src/data/token-filter/helpers/query-builder.ts +2 -2
  220. package/src/date/Date.Dialog.tsx +15 -0
  221. package/src/date/datepicker/DatePicker.tsx +3 -0
  222. package/src/date/datepicker/hooks/useDatepicker.tsx +7 -2
  223. package/src/date/datepicker/hooks/useRangeDatepicker.tsx +5 -1
  224. package/src/date/datepicker/parts/DatePicker.Months.tsx +9 -1
  225. package/src/date/datepicker/parts/DatePicker.RDP.tsx +7 -1
  226. package/src/date/monthpicker/MonthPicker.tsx +3 -1
  227. package/src/date/monthpicker/hooks/useMonthPicker.tsx +5 -1
  228. package/src/date/monthpicker/parts/MonthPicker.Caption.tsx +20 -2
  229. package/src/dropdown/Toggle.tsx +6 -12
  230. package/src/form/combobox/Input/Input.tsx +2 -2
  231. package/src/inline-message/root/InlineMessage.tsx +5 -5
  232. package/src/provider/Provider.tsx +2 -2
  233. package/src/tooltip/Tooltip.tsx +1 -3
  234. package/src/utils/components/HighlightText/HighlightText.tsx +34 -0
  235. package/src/utils/components/Listbox/group/ListboxGroup.tsx +26 -0
  236. package/src/utils/components/Listbox/input-slot/ListboxInputSlot.tsx +22 -0
  237. package/src/utils/components/Listbox/item/ListboxItem.tsx +57 -0
  238. package/src/utils/components/Listbox/list/ListboxList.tsx +38 -0
  239. package/src/utils/components/Listbox/root/ListboxRoot.tsx +104 -0
  240. package/src/utils/components/Listbox/root/domHelpers.ts +59 -0
  241. package/src/utils/components/focus-boundary/FocusBoundary.tsx +8 -78
  242. package/src/utils/helpers/focus.ts +75 -0
  243. package/src/utils/hooks/useDeferredValue.ts +12 -0
  244. package/cjs/data/table/th/DataTableThSortHandle.d.ts +0 -6
  245. package/cjs/data/table/th/DataTableThSortHandle.js +0 -82
  246. package/cjs/data/table/th/DataTableThSortHandle.js.map +0 -1
  247. package/esm/data/table/th/DataTableThSortHandle.d.ts +0 -6
  248. package/esm/data/table/th/DataTableThSortHandle.js +0 -47
  249. package/esm/data/table/th/DataTableThSortHandle.js.map +0 -1
  250. package/src/data/table/th/DataTableThSortHandle.tsx +0 -67
@@ -0,0 +1,98 @@
1
+ import { DragDropProvider, DragOverlay } from "@dnd-kit/react";
2
+ import { isSortable } from "@dnd-kit/react/sortable";
3
+ import React, { forwardRef, isValidElement } from "react";
4
+ import { VStack } from "../../../primitives/stack";
5
+ import DataDragAndDropItem, {
6
+ DataDragAndDropItemProps,
7
+ } from "../item/DataDragAndDropItem";
8
+ import { DataDragAndDropContext } from "./DataDragAndDrop.context";
9
+
10
+ interface DataDragAndDropProps extends React.HTMLAttributes<HTMLTableElement> {
11
+ children: any[];
12
+ setItems: React.Dispatch<React.SetStateAction<any[]>>;
13
+ }
14
+
15
+ interface DataDragAndDropRootComponent extends React.ForwardRefExoticComponent<
16
+ DataDragAndDropProps & React.RefAttributes<HTMLTableElement>
17
+ > {
18
+ /**
19
+ * @see 🏷️ {@link DataDragAndDropItemProps}
20
+ * * @example
21
+ * ```jsx
22
+ * <DragAndDrop>
23
+ * <DragAndDrop.Item id="1" index={0}>
24
+ * ...
25
+ * </DragAndDrop.Item>
26
+ * </DragAndDrop>
27
+ * ```
28
+ */
29
+ Item: typeof DataDragAndDropItem;
30
+ }
31
+
32
+ const DataDragAndDrop = forwardRef<HTMLDivElement, DataDragAndDropProps>(
33
+ ({ setItems, children, ...rest }, forwardedRef) => {
34
+ const [inputMethod, setInputMethod] = React.useState<
35
+ "mouse" | "keyboard" | null
36
+ >(null);
37
+
38
+ const setItemOrder = (initalIndex: number, targetIndex: number) => {
39
+ setItems((items) => {
40
+ const newItems = [...items];
41
+ const [movedItem] = newItems.splice(initalIndex, 1);
42
+ newItems.splice(targetIndex, 0, movedItem);
43
+ return newItems;
44
+ });
45
+ };
46
+
47
+ return (
48
+ <DataDragAndDropContext.Provider value={{ inputMethod }}>
49
+ <DragDropProvider
50
+ // TODO Look into overriding default keybinds, might eliminate context need
51
+ onBeforeDragStart={(event) =>
52
+ setInputMethod(
53
+ event.operation.activatorEvent?.type === "pointerdown"
54
+ ? "mouse"
55
+ : "keyboard",
56
+ )
57
+ }
58
+ onDragOver={(event) => {
59
+ if (event.operation.activatorEvent?.type === "pointerdown") {
60
+ // Prevents items to rearrange while dragging with mouse, but allows keyboard dragging to work as intended
61
+ event.preventDefault();
62
+ }
63
+ }}
64
+ onDragEnd={(event) => {
65
+ if (event.operation.activatorEvent?.type === "pointerdown") {
66
+ const { source, target } = event.operation;
67
+ if (!isSortable(source) || !isSortable(target)) return;
68
+ setItemOrder(source.initialIndex, target.index);
69
+ }
70
+ }}
71
+ >
72
+ <VStack asChild gap="space-12">
73
+ <div {...rest} ref={forwardedRef}>
74
+ {children}
75
+ </div>
76
+ </VStack>
77
+ <DragOverlay>
78
+ {(source) => {
79
+ // Overlay not needed and causes glitching when using keyboard
80
+ if (inputMethod === "keyboard") return null;
81
+ if (!isSortable(source)) return null;
82
+ if (isValidElement(children[source.initialIndex])) {
83
+ return children[source.initialIndex];
84
+ }
85
+ return null;
86
+ }}
87
+ </DragOverlay>
88
+ </DragDropProvider>
89
+ </DataDragAndDropContext.Provider>
90
+ );
91
+ },
92
+ ) as DataDragAndDropRootComponent;
93
+
94
+ DataDragAndDrop.Item = DataDragAndDropItem;
95
+
96
+ export { DataDragAndDrop, DataDragAndDropItem };
97
+ export default DataDragAndDrop;
98
+ export type { DataDragAndDropItemProps, DataDragAndDropProps };
@@ -52,6 +52,7 @@ function getNavigationAction(event: KeyboardEvent): NavigationAction | null {
52
52
  * - Select arrow down/up for opening popup
53
53
  * - User is navigating inside multiline textarea
54
54
  * - contenteditable attrb is in use
55
+ * - element has attribute data-block-keyboard-nav="true"
55
56
  */
56
57
  function shouldBlockNavigation(event: KeyboardEvent): boolean {
57
58
  const key = event.key;
@@ -68,9 +69,9 @@ function shouldBlockNavigation(event: KeyboardEvent): boolean {
68
69
  return true;
69
70
  }
70
71
 
71
- /* If not any of these elements, assueme "safe" to navigate */
72
+ /* If not any of these elements, assume "safe" to navigate */
72
73
  const editable = el.closest(
73
- 'input, textarea, select, [contenteditable="true"]',
74
+ 'input, textarea, select, [contenteditable="true"], [data-block-keyboard-nav="true"]',
74
75
  );
75
76
 
76
77
  if (!editable) {
@@ -92,7 +93,10 @@ function shouldBlockNavigation(event: KeyboardEvent): boolean {
92
93
  return false;
93
94
  }
94
95
 
95
- return editable.hasAttribute("contenteditable");
96
+ return (
97
+ editable.hasAttribute("contenteditable") ||
98
+ editable.hasAttribute("data-block-keyboard-nav")
99
+ );
96
100
  }
97
101
 
98
102
  function shouldBlockInputArrow(input: HTMLInputElement, key: string): boolean {
@@ -0,0 +1,13 @@
1
+ import { createStrictContext } from "../../../utils/helpers";
2
+
3
+ interface DataTableContextProps {
4
+ layout: "fixed" | "auto";
5
+ }
6
+
7
+ const { Provider: DataTableContextProvider, useContext: useDataTableContext } =
8
+ createStrictContext<DataTableContextProps>({
9
+ name: "DataTableContext",
10
+ errorMessage: "useDataTableContext must be used within DataTable",
11
+ });
12
+
13
+ export { DataTableContextProvider, useDataTableContext };
@@ -20,6 +20,7 @@ import {
20
20
  type DataTableTheadProps,
21
21
  } from "../thead/DataTableThead";
22
22
  import { DataTableTr, type DataTableTrProps } from "../tr/DataTableTr";
23
+ import { DataTableContextProvider } from "./DataTableRoot.context";
23
24
  import { useTableKeyboardNav } from "./useTableKeyboardNav";
24
25
 
25
26
  interface DataTableProps extends React.HTMLAttributes<HTMLTableElement> {
@@ -193,20 +194,22 @@ const DataTable = forwardRef<HTMLTableElement, DataTableProps>(
193
194
  });
194
195
 
195
196
  return (
196
- <div className="aksel-data-table__border-wrapper">
197
- <div className="aksel-data-table__scroll-wrapper">
198
- <table
199
- {...rest}
200
- ref={mergedRef}
201
- className={cl("aksel-data-table", className)}
202
- data-zebra-stripes={zebraStripes}
203
- data-truncate-content={truncateContent}
204
- data-density={rowDensity}
205
- data-layout={layout}
206
- tabIndex={tabIndex}
207
- />
197
+ <DataTableContextProvider layout={layout}>
198
+ <div className="aksel-data-table__border-wrapper">
199
+ <div className="aksel-data-table__scroll-wrapper">
200
+ <table
201
+ {...rest}
202
+ ref={mergedRef}
203
+ className={cl("aksel-data-table", className)}
204
+ data-zebra-stripes={zebraStripes}
205
+ data-truncate-content={truncateContent}
206
+ data-density={rowDensity}
207
+ data-layout={layout}
208
+ tabIndex={tabIndex}
209
+ />
210
+ </div>
208
211
  </div>
209
- </div>
212
+ </DataTableContextProvider>
210
213
  );
211
214
  },
212
215
  ) as DataTableRootComponent;
@@ -1,10 +1,14 @@
1
1
  import React, { forwardRef } from "react";
2
- import { FunnelIcon, SortDownIcon, SortUpIcon } from "@navikt/aksel-icons";
3
- import { ActionMenu } from "../../../action-menu";
4
- import { HStack, Spacer } from "../../../primitives/stack";
2
+ import {
3
+ ArrowsUpDownIcon,
4
+ CaretLeftCircleFillIcon,
5
+ CaretRightCircleFillIcon,
6
+ SortDownIcon,
7
+ SortUpIcon,
8
+ } from "@navikt/aksel-icons";
5
9
  import { cl } from "../../../utils/helpers";
6
- import { DataTableThActions } from "./DataTableThActions";
7
- import { DataTableThSortHandle } from "./DataTableThSortHandle";
10
+
11
+ type SortDirection = "asc" | "desc" | "none";
8
12
 
9
13
  interface DataTableThProps extends React.HTMLAttributes<HTMLTableCellElement> {
10
14
  resizeHandler?: (
@@ -13,8 +17,22 @@ interface DataTableThProps extends React.HTMLAttributes<HTMLTableCellElement> {
13
17
  | React.TouchEvent<HTMLButtonElement>,
14
18
  ) => void;
15
19
  size?: number; // TODO: size should be required when resizeHandler is set
16
- sortDirection?: "asc" | "desc" | "none" | false;
17
- onSortChange?: (direction: "asc" | "desc" | "none", event: Event) => void;
20
+ /**
21
+ * Makes the column header sortable. The entire header cell content becomes
22
+ * a clickable button when true.
23
+ */
24
+ sortable?: boolean;
25
+ /**
26
+ * Current sort direction. Only relevant when `sortable` is true.
27
+ * Uses values matching the `aria-sort` attribute directly.
28
+ * @default "none"
29
+ */
30
+ sortDirection?: SortDirection;
31
+ /**
32
+ * Called when the user clicks the sortable header.
33
+ * The consumer is responsible for determining and setting the next sort state.
34
+ */
35
+ onSortClick?: (event: React.MouseEvent<HTMLElement>) => void;
18
36
  render?: {
19
37
  filterMenu?: {
20
38
  title: string;
@@ -26,8 +44,15 @@ interface DataTableThProps extends React.HTMLAttributes<HTMLTableCellElement> {
26
44
  */
27
45
  colSpan?: number;
28
46
  rowSpan?: number;
47
+ keyboardResizingHandler?: (size: number) => void;
29
48
  }
30
49
 
50
+ const SORT_ICON: Record<SortDirection, React.ElementType | null> = {
51
+ asc: SortUpIcon,
52
+ desc: SortDownIcon,
53
+ none: ArrowsUpDownIcon,
54
+ };
55
+
31
56
  /**
32
57
  * TODO:
33
58
  * - Plan for pinning: Move it into "settings" dialog like here: https://cloudscape.design/examples/react/table.html
@@ -39,79 +64,110 @@ const DataTableTh = forwardRef<HTMLTableCellElement, DataTableThProps>(
39
64
  children,
40
65
  resizeHandler,
41
66
  size,
42
- sortDirection,
43
- onSortChange,
67
+ sortable = false,
68
+ sortDirection = "none",
69
+ onSortClick,
44
70
  style,
45
- render,
71
+ keyboardResizingHandler,
46
72
  ...rest
47
73
  },
48
74
  forwardedRef,
49
75
  ) => {
50
- const { filterMenu } = render || {};
76
+ const [resizeHandlerActive, setResizeHandlerActive] = React.useState(false);
77
+ const [isOverflowing, setIsOverflowing] = React.useState(false);
78
+ const contentRef = React.useRef<HTMLDivElement>(null);
79
+
80
+ const keyDownHandler = (event: React.KeyboardEvent<HTMLButtonElement>) => {
81
+ if (keyboardResizingHandler) {
82
+ if (event.key === "Enter" || event.key === " ") {
83
+ event.preventDefault();
84
+ setResizeHandlerActive((active) => !active);
85
+ } else if (
86
+ resizeHandlerActive &&
87
+ (event.key === "ArrowLeft" || event.key === "ArrowRight")
88
+ ) {
89
+ event.preventDefault();
90
+ keyboardResizingHandler(event.key === "ArrowRight" ? 10 : -10);
91
+ }
92
+ }
93
+ };
94
+
95
+ const SortIcon = sortable ? SORT_ICON[sortDirection] : null;
51
96
 
52
97
  return (
53
98
  <th
54
99
  {...rest}
55
100
  ref={forwardedRef}
56
101
  className={cl("aksel-data-table__th", className)}
102
+ data-sortable={sortable}
57
103
  style={{ width: size, ...style }}
104
+ aria-sort={sortable ? getAriaSort(sortDirection) : undefined}
105
+ onPointerEnter={() => {
106
+ const el = contentRef.current;
107
+ setIsOverflowing(el ? el.scrollWidth > el.offsetWidth : false);
108
+ console.info("is overflowing", isOverflowing);
109
+ }}
110
+ onPointerLeave={() => setIsOverflowing(false)}
58
111
  >
59
- <HStack align="center" gap="space-8" wrap={false}>
60
- <div className="aksel-data-table__th-content">{children}</div>
61
- {/* TODO: If the column is too narrow, the sort button will move when hovering b.c. the actions menu button slides in */}
62
- <DataTableThSortHandle
63
- sortDirection={sortDirection}
64
- onSortChange={onSortChange}
65
- />
66
- <Spacer />
67
-
68
- <DataTableThActions>
69
- {/* TODO: onSortChange just rotates between the three states now */}
70
- {/* TODO: Sorting texts do not handle different data-types now */}
71
- {sortDirection && (
72
- <ActionMenu.Group label="Sortering">
73
- <ActionMenu.Item
74
- onSelect={(event) => onSortChange?.("desc", event)}
75
- icon={<SortUpIcon aria-hidden />}
76
- >
77
- {sortDirection === "desc" ? "Fjern sortering" : "A-Z"}
78
- </ActionMenu.Item>
79
- <ActionMenu.Item
80
- onSelect={(event) => onSortChange?.("asc", event)}
81
- icon={<SortDownIcon aria-hidden />}
82
- >
83
- {sortDirection === "asc" ? "Fjern sortering" : "Z-A"}
84
- </ActionMenu.Item>
85
- </ActionMenu.Group>
86
- )}
87
- {filterMenu && (
88
- <ActionMenu.Group label="Filter">
89
- <ActionMenu.Sub>
90
- <ActionMenu.SubTrigger icon={<FunnelIcon aria-hidden />}>
91
- {filterMenu.title}
92
- </ActionMenu.SubTrigger>
93
- <ActionMenu.SubContent>
94
- {/* TODO: ActionMenu stops tab from working, so user cant tab "into" filter now even when wrapper has focus */}
95
- {filterMenu.content}
96
- </ActionMenu.SubContent>
97
- </ActionMenu.Sub>
98
- </ActionMenu.Group>
112
+ {sortable ? (
113
+ <button
114
+ className="aksel-data-table__th-sort-button"
115
+ onClick={sortable ? onSortClick : undefined}
116
+ >
117
+ {SortIcon && (
118
+ <SortIcon
119
+ aria-hidden
120
+ data-sort-direction={sortDirection}
121
+ className="aksel-data-table__th-sort-icon"
122
+ />
99
123
  )}
100
- </DataTableThActions>
101
- </HStack>
124
+ <div ref={contentRef} className="aksel-data-table__th-content">
125
+ {children}
126
+ </div>
127
+ </button>
128
+ ) : (
129
+ <div ref={contentRef} className="aksel-data-table__th-content">
130
+ {children}
131
+ </div>
132
+ )}
102
133
 
103
134
  {resizeHandler && (
104
135
  <button
105
136
  // TODO: Should probably not be a button since it doesn't have onClick
106
137
  onMouseDown={resizeHandler}
107
138
  onTouchStart={resizeHandler}
139
+ onBlur={() => setResizeHandlerActive(false)}
108
140
  className="aksel-data-table__th-resize-handle"
109
- />
141
+ data-active={resizeHandlerActive}
142
+ // TODO Very open to a better name for this
143
+ data-block-keyboard-nav
144
+ onKeyDown={keyDownHandler}
145
+ >
146
+ {resizeHandlerActive && (
147
+ <>
148
+ <span className="aksel-data-table__th-resize-handle-indicator aksel-data-table__th-resize-handle-indicator--start">
149
+ <CaretLeftCircleFillIcon aria-hidden fontSize="1.5rem" />
150
+ </span>
151
+ <span className="aksel-data-table__th-resize-handle-indicator aksel-data-table__th-resize-handle-indicator--end">
152
+ <CaretRightCircleFillIcon aria-hidden fontSize="1.5rem" />
153
+ </span>
154
+ </>
155
+ )}
156
+ </button>
110
157
  )}
111
158
  </th>
112
159
  );
113
160
  },
114
161
  );
115
162
 
163
+ function getAriaSort(
164
+ sortDirection: SortDirection | undefined,
165
+ ): "ascending" | "descending" | "none" | undefined {
166
+ if (sortDirection === "asc") return "ascending";
167
+ if (sortDirection === "desc") return "descending";
168
+ if (sortDirection === "none") return "none";
169
+ return undefined;
170
+ }
171
+
116
172
  export { DataTableTh };
117
173
  export type { DataTableThProps };
@@ -1,12 +1,17 @@
1
1
  import React, { forwardRef } from "react";
2
2
  import { cl } from "../../../utils/helpers";
3
+ import { useDataTableContext } from "../root/DataTableRoot.context";
3
4
 
4
5
  type DataTableTrProps = React.HTMLAttributes<HTMLTableRowElement> & {
5
6
  selected?: boolean;
6
7
  };
7
8
 
8
9
  const DataTableTr = forwardRef<HTMLTableRowElement, DataTableTrProps>(
9
- ({ className, selected = false, ...rest }, forwardedRef) => {
10
+ ({ className, children, selected = false, ...rest }, forwardedRef) => {
11
+ const rootContext = useDataTableContext();
12
+
13
+ const renderFillerCell = rootContext.layout === "fixed" && children;
14
+
10
15
  return (
11
16
  <tr
12
17
  {...rest}
@@ -14,7 +19,13 @@ const DataTableTr = forwardRef<HTMLTableRowElement, DataTableTrProps>(
14
19
  className={cl("aksel-data-table__tr", className, {
15
20
  "aksel-data-table__tr--selected": selected,
16
21
  })}
17
- />
22
+ >
23
+ {children}
24
+ {renderFillerCell && (
25
+ /* TODO: Consider chaning between th and td based on context */
26
+ <div className="aksel-data-table__th aksel-data-table__filler-cell" />
27
+ )}
28
+ </tr>
18
29
  );
19
30
  },
20
31
  );
@@ -1,47 +1,160 @@
1
- import React, { forwardRef } from "react";
2
- import { Box } from "../../primitives/box";
1
+ import React, { forwardRef, useState } from "react";
2
+ import { Search } from "../../form/search";
3
3
  import { VStack } from "../../primitives/stack";
4
- import { Label } from "../../typography";
4
+ import { Detail, Label } from "../../typography";
5
+ import { ListboxGroup } from "../../utils/components/Listbox/group/ListboxGroup";
6
+ import { ListboxItem } from "../../utils/components/Listbox/item/ListboxItem";
7
+ import Listbox from "../../utils/components/Listbox/root/ListboxRoot";
8
+ import { DismissableLayer } from "../../utils/components/dismissablelayer/DismissableLayer";
9
+ import { Floating } from "../../utils/components/floating/Floating";
10
+ import { useMergeRefsN } from "../../utils/hooks";
5
11
  import type { AutoCompleteOption, OptionGroup } from "./AutoSuggest.types";
6
12
 
7
13
  interface AutoSuggestProps {
8
14
  options: OptionGroup<AutoCompleteOption>[];
9
- onSelect: (value: string) => void;
15
+ onSelect: (option: AutoCompleteOption) => boolean;
10
16
  className?: string;
17
+ value: string;
18
+ onChange: (newValue: string) => void;
19
+ open: boolean;
20
+ setOpen: (open: boolean) => void;
11
21
  }
12
22
 
13
- const AutoSuggest = forwardRef<HTMLDivElement, AutoSuggestProps>(
14
- ({ options, onSelect }, ref) => {
23
+ const AutoSuggest = forwardRef<HTMLInputElement, AutoSuggestProps>(
24
+ ({ options, onSelect, value, onChange, open, setOpen }, ref) => {
25
+ const [virtuallyFocusedItemId, setVirtuallyFocusedItemId] = useState("");
26
+
27
+ const [inputRef, setInputRef] = useState<HTMLInputElement | null>(null);
28
+
29
+ /* Unsure why N version works, but not regular here */
30
+ const mergedRef = useMergeRefsN([setInputRef, ref]);
31
+
32
+ const handleClose = () => {
33
+ setOpen(false);
34
+ };
35
+
36
+ const handleChange = (newValue: string) => {
37
+ onChange(newValue);
38
+ setOpen(true);
39
+ };
40
+
41
+ const handleSelectOption = (option: AutoCompleteOption) => {
42
+ const createdNewToken = onSelect(option);
43
+
44
+ if (createdNewToken) {
45
+ inputRef?.focus();
46
+ setOpen(false);
47
+ }
48
+ };
49
+
50
+ return (
51
+ <Floating>
52
+ <Listbox setVirtuallyFocusedItemId={setVirtuallyFocusedItemId}>
53
+ <Floating.Anchor>
54
+ <Listbox.InputSlot>
55
+ <Search
56
+ label="Tabellsøk"
57
+ variant="simple"
58
+ className="aksel-property-filter__input"
59
+ placeholder="Type to filter..."
60
+ ref={mergedRef}
61
+ value={value}
62
+ onChange={handleChange}
63
+ onClick={() => {
64
+ setOpen(true);
65
+ }}
66
+ onFocus={() => setOpen(true)}
67
+ /* onKeyDown={(e) => {
68
+ if (e.key === "Enter") {
69
+ createToken(filterText);
70
+ }
71
+ }} */
72
+ />
73
+ </Listbox.InputSlot>
74
+ </Floating.Anchor>
75
+ {open && (
76
+ <AutoSuggestPopup
77
+ options={options}
78
+ onSelect={handleSelectOption}
79
+ focusedValue={virtuallyFocusedItemId}
80
+ setFocusedValue={setVirtuallyFocusedItemId}
81
+ onClose={handleClose}
82
+ safeZoneAnchor={inputRef}
83
+ />
84
+ )}
85
+ </Listbox>
86
+ </Floating>
87
+ );
88
+ },
89
+ );
90
+
91
+ type AutoSuggestPopupProps = {
92
+ options: OptionGroup<AutoCompleteOption>[];
93
+ onSelect: (option: AutoCompleteOption) => void;
94
+ focusedValue: string;
95
+ setFocusedValue: (value: string) => void;
96
+ onClose: () => void;
97
+ safeZoneAnchor: HTMLInputElement | null;
98
+ };
99
+
100
+ const AutoSuggestPopup = forwardRef<HTMLDivElement, AutoSuggestPopupProps>(
101
+ (
102
+ {
103
+ options,
104
+ onSelect,
105
+ focusedValue,
106
+ setFocusedValue,
107
+ onClose,
108
+ safeZoneAnchor,
109
+ },
110
+ ref,
111
+ ) => {
15
112
  return (
16
- <Box ref={ref} padding="space-6">
17
- {options.map((group) => (
18
- <div key={group.label}>
19
- <Label as="div">{group.label}</Label>
20
- <VStack gap="space-4">
21
- {group.options.map((option) => {
22
- return (
23
- <div key={option.value}>
24
- <button
25
- type="button"
26
- onClick={() => onSelect(option.value)}
113
+ <DismissableLayer
114
+ asChild
115
+ onDismiss={onClose}
116
+ safeZone={{ anchor: safeZoneAnchor }}
117
+ >
118
+ <Floating.Content
119
+ ref={ref}
120
+ align="start"
121
+ side="bottom"
122
+ fallbackPlacements={[]}
123
+ sideOffset={8}
124
+ className="aksel-property-filter__popup"
125
+ >
126
+ <div className="aksel-property-filter__popup-inner">
127
+ <Listbox.List setVirtuallyFocusedItemId={setFocusedValue}>
128
+ {options.map((group) => (
129
+ <ListboxGroup key={group.label} label={group.label}>
130
+ {group.options.map((item) => (
131
+ <ListboxItem
132
+ key={item.value}
133
+ id={item.value}
134
+ onClick={() => onSelect(item)}
135
+ hasVirtualFocus={focusedValue === item.value}
27
136
  >
28
- <span>{option.label}</span>
29
- {option.description && <span>{option.description}</span>}
30
- {option.tags && option.tags.length > 0 && (
137
+ <VStack gap="space-0">
138
+ <Label as="div">{item.label}</Label>
139
+ {item.description && (
140
+ <Detail as="div">{item.description}</Detail>
141
+ )}
142
+ </VStack>
143
+ {/* {item.tags && item.tags.length > 0 && (
31
144
  <div>
32
- {option.tags.map((tag) => (
145
+ {item.tags.map((tag) => (
33
146
  <span key={tag}>{tag}</span>
34
147
  ))}
35
148
  </div>
36
- )}
37
- </button>
38
- </div>
39
- );
40
- })}
41
- </VStack>
149
+ )} */}
150
+ </ListboxItem>
151
+ ))}
152
+ </ListboxGroup>
153
+ ))}
154
+ </Listbox.List>
42
155
  </div>
43
- ))}
44
- </Box>
156
+ </Floating.Content>
157
+ </DismissableLayer>
45
158
  );
46
159
  },
47
160
  );