@navikt/ds-react 8.9.0 → 8.9.1

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 (107) hide show
  1. package/cjs/data/drag-and-drop/drag-handler/DragAndDropDragHandler.d.ts +1 -0
  2. package/cjs/data/drag-and-drop/drag-handler/DragAndDropDragHandler.js +5 -3
  3. package/cjs/data/drag-and-drop/drag-handler/DragAndDropDragHandler.js.map +1 -1
  4. package/cjs/data/drag-and-drop/item/DragAndDropItem.d.ts +4 -0
  5. package/cjs/data/drag-and-drop/item/DragAndDropItem.js +3 -7
  6. package/cjs/data/drag-and-drop/item/DragAndDropItem.js.map +1 -1
  7. package/cjs/data/drag-and-drop/root/DragAndDrop.context.d.ts +2 -1
  8. package/cjs/data/drag-and-drop/root/DragAndDrop.context.js.map +1 -1
  9. package/cjs/data/drag-and-drop/root/DragAndDropRoot.d.ts +12 -10
  10. package/cjs/data/drag-and-drop/root/DragAndDropRoot.js +71 -39
  11. package/cjs/data/drag-and-drop/root/DragAndDropRoot.js.map +1 -1
  12. package/cjs/data/table/helpers/selection/getMultipleSelectProps.d.ts +3 -3
  13. package/cjs/data/table/helpers/selection/getMultipleSelectProps.js +4 -4
  14. package/cjs/data/table/helpers/selection/getMultipleSelectProps.js.map +1 -1
  15. package/cjs/data/table/helpers/selection/getSingleSelectProps.d.ts +2 -2
  16. package/cjs/data/table/helpers/selection/getSingleSelectProps.js +4 -1
  17. package/cjs/data/table/helpers/selection/getSingleSelectProps.js.map +1 -1
  18. package/cjs/data/table/helpers/selection/selection.types.d.ts +5 -5
  19. package/cjs/data/table/root/DataTableAuto.js +9 -11
  20. package/cjs/data/table/root/DataTableAuto.js.map +1 -1
  21. package/cjs/data/table/td/DataTableTd.d.ts +4 -0
  22. package/cjs/data/table/td/DataTableTd.js +4 -2
  23. package/cjs/data/table/td/DataTableTd.js.map +1 -1
  24. package/cjs/data/table/th/DataTableTh.d.ts +4 -0
  25. package/cjs/data/table/th/DataTableTh.js +6 -3
  26. package/cjs/data/table/th/DataTableTh.js.map +1 -1
  27. package/cjs/data/token-filter/FilterChip.d.ts +10 -0
  28. package/cjs/data/token-filter/FilterChip.js +65 -0
  29. package/cjs/data/token-filter/FilterChip.js.map +1 -0
  30. package/cjs/data/token-filter/TokenFilter.js +3 -10
  31. package/cjs/data/token-filter/TokenFilter.js.map +1 -1
  32. package/cjs/form/checkbox/Checkbox.js +19 -33
  33. package/cjs/form/checkbox/Checkbox.js.map +1 -1
  34. package/cjs/form/checkbox/checkbox-input/CheckboxInput.d.ts +21 -0
  35. package/cjs/form/checkbox/checkbox-input/CheckboxInput.js +65 -0
  36. package/cjs/form/checkbox/checkbox-input/CheckboxInput.js.map +1 -0
  37. package/cjs/form/checkbox/types.d.ts +1 -1
  38. package/cjs/form/fieldset/Fieldset.js +2 -6
  39. package/cjs/form/fieldset/Fieldset.js.map +1 -1
  40. package/cjs/form/radio/Radio.js +9 -7
  41. package/cjs/form/radio/Radio.js.map +1 -1
  42. package/cjs/form/radio/radio-input/RadioInput.d.ts +19 -0
  43. package/cjs/form/radio/radio-input/RadioInput.js +55 -0
  44. package/cjs/form/radio/radio-input/RadioInput.js.map +1 -0
  45. package/esm/data/drag-and-drop/drag-handler/DragAndDropDragHandler.d.ts +1 -0
  46. package/esm/data/drag-and-drop/drag-handler/DragAndDropDragHandler.js +5 -3
  47. package/esm/data/drag-and-drop/drag-handler/DragAndDropDragHandler.js.map +1 -1
  48. package/esm/data/drag-and-drop/item/DragAndDropItem.d.ts +4 -0
  49. package/esm/data/drag-and-drop/item/DragAndDropItem.js +3 -7
  50. package/esm/data/drag-and-drop/item/DragAndDropItem.js.map +1 -1
  51. package/esm/data/drag-and-drop/root/DragAndDrop.context.d.ts +2 -1
  52. package/esm/data/drag-and-drop/root/DragAndDrop.context.js.map +1 -1
  53. package/esm/data/drag-and-drop/root/DragAndDropRoot.d.ts +12 -10
  54. package/esm/data/drag-and-drop/root/DragAndDropRoot.js +71 -39
  55. package/esm/data/drag-and-drop/root/DragAndDropRoot.js.map +1 -1
  56. package/esm/data/table/helpers/selection/getMultipleSelectProps.d.ts +3 -3
  57. package/esm/data/table/helpers/selection/getMultipleSelectProps.js +4 -4
  58. package/esm/data/table/helpers/selection/getMultipleSelectProps.js.map +1 -1
  59. package/esm/data/table/helpers/selection/getSingleSelectProps.d.ts +2 -2
  60. package/esm/data/table/helpers/selection/getSingleSelectProps.js +4 -1
  61. package/esm/data/table/helpers/selection/getSingleSelectProps.js.map +1 -1
  62. package/esm/data/table/helpers/selection/selection.types.d.ts +5 -5
  63. package/esm/data/table/root/DataTableAuto.js +9 -11
  64. package/esm/data/table/root/DataTableAuto.js.map +1 -1
  65. package/esm/data/table/td/DataTableTd.d.ts +4 -0
  66. package/esm/data/table/td/DataTableTd.js +4 -2
  67. package/esm/data/table/td/DataTableTd.js.map +1 -1
  68. package/esm/data/table/th/DataTableTh.d.ts +4 -0
  69. package/esm/data/table/th/DataTableTh.js +6 -3
  70. package/esm/data/table/th/DataTableTh.js.map +1 -1
  71. package/esm/data/token-filter/FilterChip.d.ts +10 -0
  72. package/esm/data/token-filter/FilterChip.js +30 -0
  73. package/esm/data/token-filter/FilterChip.js.map +1 -0
  74. package/esm/data/token-filter/TokenFilter.js +3 -10
  75. package/esm/data/token-filter/TokenFilter.js.map +1 -1
  76. package/esm/form/checkbox/Checkbox.js +19 -33
  77. package/esm/form/checkbox/Checkbox.js.map +1 -1
  78. package/esm/form/checkbox/checkbox-input/CheckboxInput.d.ts +21 -0
  79. package/esm/form/checkbox/checkbox-input/CheckboxInput.js +29 -0
  80. package/esm/form/checkbox/checkbox-input/CheckboxInput.js.map +1 -0
  81. package/esm/form/checkbox/types.d.ts +1 -1
  82. package/esm/form/fieldset/Fieldset.js +2 -6
  83. package/esm/form/fieldset/Fieldset.js.map +1 -1
  84. package/esm/form/radio/Radio.js +9 -7
  85. package/esm/form/radio/Radio.js.map +1 -1
  86. package/esm/form/radio/radio-input/RadioInput.d.ts +19 -0
  87. package/esm/form/radio/radio-input/RadioInput.js +19 -0
  88. package/esm/form/radio/radio-input/RadioInput.js.map +1 -0
  89. package/package.json +3 -3
  90. package/src/data/drag-and-drop/drag-handler/DragAndDropDragHandler.tsx +7 -3
  91. package/src/data/drag-and-drop/item/DragAndDropItem.tsx +15 -11
  92. package/src/data/drag-and-drop/root/DragAndDrop.context.tsx +4 -2
  93. package/src/data/drag-and-drop/root/DragAndDropRoot.tsx +94 -45
  94. package/src/data/table/helpers/selection/getMultipleSelectProps.ts +7 -7
  95. package/src/data/table/helpers/selection/getSingleSelectProps.ts +6 -3
  96. package/src/data/table/helpers/selection/selection.types.ts +5 -5
  97. package/src/data/table/root/DataTableAuto.tsx +25 -15
  98. package/src/data/table/td/DataTableTd.tsx +15 -2
  99. package/src/data/table/th/DataTableTh.tsx +13 -2
  100. package/src/data/token-filter/FilterChip.tsx +100 -0
  101. package/src/data/token-filter/TokenFilter.tsx +8 -24
  102. package/src/form/checkbox/Checkbox.tsx +37 -64
  103. package/src/form/checkbox/checkbox-input/CheckboxInput.tsx +69 -0
  104. package/src/form/checkbox/types.ts +1 -1
  105. package/src/form/fieldset/Fieldset.tsx +4 -6
  106. package/src/form/radio/Radio.tsx +43 -38
  107. package/src/form/radio/radio-input/RadioInput.tsx +32 -0
@@ -5,7 +5,7 @@ import { DragAndDropElement } from "../types";
5
5
  import { DragAndDropProvider } from "./DragAndDrop.context";
6
6
 
7
7
  interface DragAndDropProps extends React.HTMLAttributes<HTMLDivElement> {
8
- children: any[];
8
+ children: React.ReactElement<DragAndDropItemProps>[];
9
9
  setItems: React.Dispatch<React.SetStateAction<any[]>>;
10
10
  }
11
11
 
@@ -28,34 +28,68 @@ interface DataDragAndDropRootComponent extends React.ForwardRefExoticComponent<
28
28
 
29
29
  /**
30
30
  * TODO
31
- * setItems on root
32
- * state : active element
33
- * pointer over listener / state, onPointerEnter, onPointerLeave
34
- * Overlay - Use floating component
35
- * Keyboard navigation
36
- * Handle - button, arrows also button
37
- * UU - announce on drag start, item moved, drag end
38
- *
39
- * []
31
+ * [x] setItems on root
32
+ * [x] state : active element
33
+ * [x] pointer over listener / state, onPointerEnter, onPointerLeave
34
+ * [x] Overlay - Use floating component
35
+ * [x] Keyboard navigation
36
+ * [ ] UU - announce on drag start, item moved, drag end
37
+ * [x] Make overlay same width as the OG item, currently jumps to content width
38
+ * [ ] Look into adding a cancel listener event
39
+ * [ ] Make onClick work on drag handler button, currently blocked by pointer down/up listeners
40
+ * [ ] Talk to design about what should happen on ESC key press, currently just cancels dragging, should it also reset position?
41
+ * [ ] Make arrow icons into buttons that react to keyboard events, currently just decorative
40
42
  */
41
43
 
42
44
  const DragAndDrop = forwardRef<HTMLDivElement, DragAndDropProps>(
43
45
  ({ setItems, children }, forwardedRef) => {
46
+ const DRAG_THRESHOLD = 4; // Minimum movement in pixels to start dragging
47
+
44
48
  const [activeItem, setActiveItem] =
45
49
  React.useState<DragAndDropElement | null>(null);
46
50
  const [dropTarget, setDropTarget] =
47
51
  React.useState<DragAndDropElement | null>(null);
48
52
  const [dragHandlerActive, setDragHandlerActive] =
49
53
  React.useState<DragAndDropElement | null>(null);
54
+ const [overlayWidth, setOverlayWidth] = React.useState<number | null>(null);
50
55
 
51
56
  const activeItemRef = React.useRef<DragAndDropElement | null>(null);
52
57
  const dropTargetRef = React.useRef<DragAndDropElement | null>(null);
58
+ const activeChild = children.find(
59
+ (child) => child.props.id === activeItem?.id,
60
+ );
53
61
 
54
62
  const [virtualRef, setVirtualRef] = React.useState({
55
63
  getBoundingClientRect: () =>
56
64
  DOMRect.fromRect({ width: 0, height: 0, x: 0, y: 0 }),
57
65
  });
58
66
 
67
+ const pendingDragStartRef = React.useRef<{
68
+ item: DragAndDropElement;
69
+ element: HTMLElement | null;
70
+ pointerId: number;
71
+ startX: number;
72
+ startY: number;
73
+ } | null>(null);
74
+
75
+ const startPendingDragStart = (
76
+ event: React.PointerEvent,
77
+ item: DragAndDropElement,
78
+ element?: HTMLElement | null,
79
+ ) => {
80
+ pendingDragStartRef.current = {
81
+ item,
82
+ element: element || null,
83
+ pointerId: event.pointerId,
84
+ startX: event.clientX,
85
+ startY: event.clientY,
86
+ };
87
+ };
88
+
89
+ const cancelDragStart = () => {
90
+ pendingDragStartRef.current = null;
91
+ };
92
+
59
93
  const setCombinedActiveItem = React.useCallback(
60
94
  (item: DragAndDropElement | null) => {
61
95
  activeItemRef.current = item;
@@ -73,24 +107,50 @@ const DragAndDrop = forwardRef<HTMLDivElement, DragAndDropProps>(
73
107
  );
74
108
 
75
109
  useEffect(() => {
110
+ /* This useEffect is used to toggle a class on the html element when dragging,
111
+ to prevent cursor issues when dragging over interactive elements,
112
+ and to prevent text selection during dragging. */
113
+
76
114
  if (activeItem) {
77
- document.documentElement.setAttribute("data-dragging", "true");
115
+ document.documentElement.setAttribute("data-dragging-cursor", "true");
78
116
  document.body.style.userSelect = "none";
79
117
  } else {
80
- document.documentElement.removeAttribute("data-dragging");
118
+ document.documentElement.removeAttribute("data-dragging-cursor");
81
119
  document.body.style.userSelect = "";
82
120
  }
83
121
 
84
122
  return () => {
85
- document.documentElement.removeAttribute("data-dragging");
123
+ document.documentElement.removeAttribute("data-dragging-cursor");
86
124
  document.body.style.userSelect = "";
87
125
  };
88
126
  }, [activeItem]);
89
127
 
90
128
  useEffect(() => {
91
- if (!activeItem) return;
92
-
93
129
  const handlePointerMove = (event: PointerEvent) => {
130
+ const pendingStart = pendingDragStartRef.current;
131
+ const activeRef = activeItemRef.current;
132
+ const element = pendingStart?.element;
133
+
134
+ if (!activeRef && pendingStart) {
135
+ const deltaX = Math.abs(event.clientX - pendingStart.startX);
136
+ const deltaY = Math.abs(event.clientY - pendingStart.startY);
137
+
138
+ if (deltaX > DRAG_THRESHOLD || deltaY > DRAG_THRESHOLD) {
139
+ if (element) {
140
+ element.setPointerCapture(pendingStart.pointerId);
141
+ }
142
+
143
+ setOverlayWidth(element?.getBoundingClientRect().width ?? null);
144
+ setCombinedActiveItem(pendingStart.item);
145
+ setCombinedDropTarget(pendingStart.item);
146
+ pendingDragStartRef.current = null;
147
+ }
148
+ return;
149
+ }
150
+
151
+ const active = activeItemRef.current;
152
+ if (!active) return;
153
+
94
154
  setVirtualRef({
95
155
  getBoundingClientRect: () =>
96
156
  DOMRect.fromRect({
@@ -101,9 +161,6 @@ const DragAndDrop = forwardRef<HTMLDivElement, DragAndDropProps>(
101
161
  }),
102
162
  });
103
163
 
104
- const active = activeItemRef.current;
105
- if (!active) return;
106
-
107
164
  const elements = document.elementsFromPoint(
108
165
  event.clientX,
109
166
  event.clientY,
@@ -142,6 +199,11 @@ const DragAndDrop = forwardRef<HTMLDivElement, DragAndDropProps>(
142
199
  };
143
200
 
144
201
  const handlePointerUp = () => {
202
+ if (!activeItemRef.current) {
203
+ pendingDragStartRef.current = null;
204
+ return;
205
+ }
206
+
145
207
  const active = activeItemRef.current;
146
208
  const target = dropTargetRef.current;
147
209
 
@@ -153,8 +215,11 @@ const DragAndDrop = forwardRef<HTMLDivElement, DragAndDropProps>(
153
215
  return newItems;
154
216
  });
155
217
  }
218
+ setOverlayWidth(null);
219
+ setDragHandlerActive(null);
156
220
  setCombinedActiveItem(null);
157
221
  setCombinedDropTarget(null);
222
+ pendingDragStartRef.current = null;
158
223
  };
159
224
 
160
225
  // TODO - Look into adding a cancel listener event
@@ -165,7 +230,7 @@ const DragAndDrop = forwardRef<HTMLDivElement, DragAndDropProps>(
165
230
  window.removeEventListener("pointermove", handlePointerMove);
166
231
  window.removeEventListener("pointerup", handlePointerUp);
167
232
  };
168
- }, [activeItem, setCombinedDropTarget, setCombinedActiveItem, setItems]);
233
+ }, [setCombinedDropTarget, setCombinedActiveItem, setItems]);
169
234
 
170
235
  const onKeyboardDragEnd = (diff: number) => {
171
236
  if (!dragHandlerActive) return;
@@ -184,25 +249,6 @@ const DragAndDrop = forwardRef<HTMLDivElement, DragAndDropProps>(
184
249
  setDragHandlerActive({ ...dragHandlerActive, index: targetIndex });
185
250
  };
186
251
 
187
- const onDragStart = (
188
- event: React.PointerEvent | React.MouseEvent,
189
- item: DragAndDropElement,
190
- ) => {
191
- setVirtualRef({
192
- getBoundingClientRect: () =>
193
- DOMRect.fromRect({
194
- width: 0,
195
- height: 0,
196
- x: event.clientX,
197
- y: event.clientY,
198
- }),
199
- });
200
- setCombinedActiveItem(item);
201
- setCombinedDropTarget(item);
202
- };
203
-
204
- // TODO - Make overlay same width as the OG item, currently jumps to content width
205
-
206
252
  return (
207
253
  <DragAndDropProvider
208
254
  activeItem={activeItem}
@@ -212,10 +258,11 @@ const DragAndDrop = forwardRef<HTMLDivElement, DragAndDropProps>(
212
258
  dragHandlerActive={dragHandlerActive}
213
259
  setDragHandlerActive={setDragHandlerActive}
214
260
  onKeyboardDragEnd={onKeyboardDragEnd}
215
- onDragStart={onDragStart}
261
+ startPendingDragStart={startPendingDragStart}
262
+ cancelDragStart={cancelDragStart}
216
263
  >
217
264
  <div ref={forwardedRef}>{children}</div>
218
- {activeItem && (
265
+ {activeItem && activeChild && (
219
266
  <Floating>
220
267
  <Floating.Anchor virtualRef={virtualRef}>
221
268
  <span />
@@ -223,12 +270,14 @@ const DragAndDrop = forwardRef<HTMLDivElement, DragAndDropProps>(
223
270
  <Floating.Content
224
271
  align="start"
225
272
  updatePositionStrategy="always"
226
- style={{ pointerEvents: "none" }}
273
+ style={{
274
+ pointerEvents: "none",
275
+ boxSizing: "border-box",
276
+ width: overlayWidth ? `${overlayWidth}px` : "fit-content",
277
+ }}
227
278
  >
228
- {React.cloneElement(children[activeItem.index], {
229
- "data-dnd-id": undefined,
230
- "data-dnd-index": undefined,
231
- "data-overlay": true,
279
+ {React.cloneElement(activeChild, {
280
+ isOverlay: true,
232
281
  })}
233
282
  </Floating.Content>
234
283
  </Floating>
@@ -1,4 +1,4 @@
1
- import type { CheckboxProps } from "../../../../form/checkbox/types";
1
+ import type { CheckboxInputProps } from "../../../../form/checkbox/checkbox-input/CheckboxInput";
2
2
  import type { SelectionT } from "./selection.types";
3
3
 
4
4
  type GetMultipleSelectPropsArgs = {
@@ -39,14 +39,15 @@ function getMultipleSelectProps({
39
39
  (Array.isArray(selectedKeys) && selectedKeys.includes(key));
40
40
 
41
41
  return {
42
- getTheadCheckboxProps: (): CheckboxProps => {
42
+ getTheadCheckboxProps: (): CheckboxInputProps => {
43
43
  const indeterminate =
44
44
  Array.isArray(selectedKeys) &&
45
45
  selectedKeys.length > 0 &&
46
46
  selectedKeys.length < totalCount;
47
47
 
48
48
  return {
49
- children: "Select all rows",
49
+ /* TODO: Add support for label visuallyhidden */
50
+ /* children: "Select all rows", */
50
51
  onChange: handleToggleAll,
51
52
  checked:
52
53
  (selectedKeys === "all" ||
@@ -54,15 +55,14 @@ function getMultipleSelectProps({
54
55
  !indeterminate,
55
56
  indeterminate,
56
57
  disabled: disabledKeys.length === totalCount,
57
- hideLabel: true,
58
58
  };
59
59
  },
60
- getRowCheckboxProps: (key: string | number): CheckboxProps => ({
61
- children: `Select row with id ${key}`,
60
+ getRowCheckboxProps: (key: string | number): CheckboxInputProps => ({
61
+ /* TODO: Add support for label visuallyhidden */
62
+ /* children: `Select row with id ${key}`, */
62
63
  onChange: () => handleToggleRow(key),
63
64
  checked: isChecked(key),
64
65
  disabled: disabledKeys.includes(key),
65
- hideLabel: true,
66
66
  }),
67
67
  };
68
68
  }
@@ -1,4 +1,4 @@
1
- import type { RadioProps } from "../../../../form/radio/types";
1
+ import type { RadioInputProps } from "../../../../form/radio/radio-input/RadioInput";
2
2
 
3
3
  type GetSingleSelectPropsArgs = {
4
4
  selectedKeys: (string | number)[];
@@ -20,12 +20,15 @@ function getSingleSelectProps({
20
20
  };
21
21
 
22
22
  return {
23
- getRowRadioProps: (key: string | number): RadioProps => ({
24
- children: `Select row with id ${key}`,
23
+ getRowRadioProps: (key: string | number): RadioInputProps => ({
24
+ /* TODO: Add support for label visuallyhidden */
25
+ /* children: `Select row with id ${key}`, */
25
26
  checked: selectedKeys.includes(key),
26
27
  onChange: () => handleSelectionChange(key),
27
28
  disabled: disabledKeys.includes(key),
28
29
  value: key,
30
+ /* TODO: Make this unique to avoid issue with multipe tables */
31
+ name: "data-table-single-select",
29
32
  }),
30
33
  };
31
34
  }
@@ -1,5 +1,5 @@
1
- import type { CheckboxProps } from "../../../../form/checkbox/types";
2
- import type { RadioProps } from "../../../../form/radio/types";
1
+ import type { CheckboxInputProps } from "../../../../form/checkbox/checkbox-input/CheckboxInput";
2
+ import type { RadioInputProps } from "../../../../form/radio/radio-input/RadioInput";
3
3
 
4
4
  type SelectionT = (string | number)[] | "all";
5
5
 
@@ -32,7 +32,7 @@ type SingleSelection = {
32
32
  allKeys: (string | number)[];
33
33
  selectedKeys: (string | number)[];
34
34
  disabledKeys: (string | number)[];
35
- getRowRadioProps: (key: string | number) => RadioProps;
35
+ getRowRadioProps: (key: string | number) => RadioInputProps;
36
36
  };
37
37
 
38
38
  type MultipleSelection = {
@@ -40,8 +40,8 @@ type MultipleSelection = {
40
40
  allKeys: (string | number)[];
41
41
  selectedKeys: SelectionT;
42
42
  disabledKeys: (string | number)[];
43
- getTheadCheckboxProps: () => CheckboxProps;
44
- getRowCheckboxProps: (key: string | number) => CheckboxProps;
43
+ getTheadCheckboxProps: () => CheckboxInputProps;
44
+ getRowCheckboxProps: (key: string | number) => CheckboxInputProps;
45
45
  };
46
46
 
47
47
  type TableSelection = NoneSelection | SingleSelection | MultipleSelection;
@@ -1,6 +1,7 @@
1
1
  /** biome-ignore-all lint/correctness/useHookAtTopLevel: False positive because of the way forwardRef() is added */
2
2
  import React, { forwardRef, useState } from "react";
3
- import { Checkbox } from "../../../form/checkbox";
3
+ import { CheckboxInput } from "../../../form/checkbox/checkbox-input/CheckboxInput";
4
+ import { RadioInput } from "../../../form/radio/radio-input/RadioInput";
4
5
  import { cl } from "../../../utils/helpers";
5
6
  import { useMergeRefs } from "../../../utils/hooks";
6
7
  import { useTableKeyboardNav } from "../hooks/useTableKeyboardNav";
@@ -130,14 +131,19 @@ function DataTableAutoInner<T>(
130
131
  <DataTableThead>
131
132
  <DataTableTr>
132
133
  {selection.selectionMode === "multiple" && (
133
- /* TODO: Overflow/focus is clipped. Alignment is off */
134
- /* TODO: Should not be resizable */
135
- <DataTableTh textAlign="center" width="60px">
136
- <Checkbox {...selection.getTheadCheckboxProps()} />
134
+ <DataTableTh
135
+ textAlign="center"
136
+ width="60px"
137
+ UNSAFE_isSelection
138
+ >
139
+ <CheckboxInput
140
+ {...selection.getTheadCheckboxProps()}
141
+ compact
142
+ />
137
143
  </DataTableTh>
138
144
  )}
139
145
  {selection.selectionMode === "single" && (
140
- <DataTableTd align="center" width="60px" />
146
+ <DataTableTh width="64px" UNSAFE_isSelection />
141
147
  )}
142
148
  {columnDefinitions.map((colDef, colDefIndex) => {
143
149
  return (
@@ -161,18 +167,22 @@ function DataTableAutoInner<T>(
161
167
  return (
162
168
  <DataTableTr key={rowId}>
163
169
  {selection.selectionMode === "multiple" && (
164
- <DataTableTd align="center" width="60px">
165
- <Checkbox {...selection.getRowCheckboxProps(rowId)} />
170
+ <DataTableTd
171
+ align="center"
172
+ width="60px"
173
+ UNSAFE_isSelection
174
+ >
175
+ <CheckboxInput
176
+ {...selection.getRowCheckboxProps(rowId)}
177
+ compact
178
+ />
166
179
  </DataTableTd>
167
180
  )}
181
+
168
182
  {selection.selectionMode === "single" && (
169
- <DataTableTd align="center" width="60px">
170
- {/**
171
- * TODO: This should be a radio, but our current Radio implementation has some issues:
172
- * - Checked cant be controlled outside of radiogroup
173
- * - Cant hide label
174
- * */}
175
- <Checkbox {...selection.getRowRadioProps(rowId)} />
183
+ <DataTableTd width="64px" UNSAFE_isSelection>
184
+ {/* used with keyboard nav is funky, no longer auto-selects on keyboard-nav. Probably preventDefault somewhere breaking it. */}
185
+ <RadioInput {...selection.getRowRadioProps(rowId)} />
176
186
  </DataTableTd>
177
187
  )}
178
188
  {columnDefinitions.map((colDef, colDefIndex) => {
@@ -20,11 +20,22 @@ interface DataTableTdProps extends React.TdHTMLAttributes<HTMLTableCellElement>
20
20
  * @default "left"
21
21
  */
22
22
  textAlign?: "left" | "center" | "right";
23
+ /**
24
+ * Temp hack to solve overflow and alignment
25
+ */
26
+ UNSAFE_isSelection?: boolean;
23
27
  }
24
28
 
25
29
  const DataTableTd = forwardRef<HTMLTableCellElement, DataTableTdProps>(
26
30
  (
27
- { className, children, contentMaxWidth, textAlign = "left", ...rest },
31
+ {
32
+ className,
33
+ children,
34
+ contentMaxWidth,
35
+ textAlign = "left",
36
+ UNSAFE_isSelection,
37
+ ...rest
38
+ },
28
39
  forwardedRef,
29
40
  ) => {
30
41
  const { withKeyboardNav } = useDataTableContext();
@@ -33,7 +44,9 @@ const DataTableTd = forwardRef<HTMLTableCellElement, DataTableTdProps>(
33
44
  <td
34
45
  {...rest}
35
46
  ref={forwardedRef}
36
- className={cl("aksel-data-table__td", className)}
47
+ className={cl("aksel-data-table__td", className, {
48
+ "aksel-data-table--UNSAFE_isSelection": UNSAFE_isSelection,
49
+ })}
37
50
  tabIndex={withKeyboardNav ? -1 : undefined}
38
51
  data-align={textAlign}
39
52
  >
@@ -52,6 +52,10 @@ interface DataTableThProps
52
52
  */
53
53
  colSpan?: number;
54
54
  rowSpan?: number;
55
+ /**
56
+ * Temp hack to solve overflow and alignment
57
+ */
58
+ UNSAFE_isSelection?: boolean;
55
59
  }
56
60
 
57
61
  const SORT_ICON: Record<SortDirection, React.ElementType | null> = {
@@ -82,6 +86,7 @@ const DataTableTh = forwardRef<HTMLTableCellElement, DataTableThProps>(
82
86
  onWidthChange,
83
87
  defaultWidth,
84
88
  colSpan,
89
+ UNSAFE_isSelection,
85
90
  ...rest
86
91
  },
87
92
  forwardedRef,
@@ -143,12 +148,18 @@ const DataTableTh = forwardRef<HTMLTableCellElement, DataTableThProps>(
143
148
  )}
144
149
  </button>
145
150
  ) : (
146
- <div ref={contentRef} className="aksel-data-table__th-content">
151
+ <div
152
+ ref={contentRef}
153
+ className={cl({
154
+ "aksel-data-table__th-content": !UNSAFE_isSelection,
155
+ "aksel-data-table--UNSAFE_isSelection": UNSAFE_isSelection,
156
+ })}
157
+ >
147
158
  {children}
148
159
  </div>
149
160
  )}
150
161
 
151
- {resizeResult.enabled && (
162
+ {resizeResult.enabled && !UNSAFE_isSelection && (
152
163
  <button
153
164
  {...resizeResult.resizeHandlerProps}
154
165
  className="aksel-data-table__th-resize-handle"
@@ -0,0 +1,100 @@
1
+ import React, { useState } from "react";
2
+ import { XMarkIcon } from "@navikt/aksel-icons";
3
+ import { ActionMenu } from "../../action-menu";
4
+ import { Popover } from "../../popover";
5
+ import type { ExternalToken, OperationT } from "./TokenFilter.types";
6
+
7
+ type TokenFilterChipsProps = {
8
+ tokens: ExternalToken[];
9
+ removeToken: (index: number) => void;
10
+ operation: OperationT;
11
+ updateOperation: (operation: OperationT) => void;
12
+ };
13
+
14
+ function TokenFilterChips(props: TokenFilterChipsProps) {
15
+ const { tokens, removeToken, operation, updateOperation } = props;
16
+
17
+ if (tokens.length === 0) {
18
+ return null;
19
+ }
20
+
21
+ return (
22
+ <ul className="aksel-property-filter__chips">
23
+ {tokens.map((token, index) => (
24
+ <TokenFilterChip
25
+ key={index}
26
+ onRemove={() => removeToken(index)}
27
+ token={token}
28
+ showOperation={index > 0}
29
+ operation={operation}
30
+ updateOperation={updateOperation}
31
+ />
32
+ ))}
33
+ </ul>
34
+ );
35
+ }
36
+
37
+ type TokenFilterChipProps = {
38
+ token: ExternalToken;
39
+ onRemove: () => void;
40
+ showOperation: boolean;
41
+ operation?: OperationT;
42
+ updateOperation?: (operation: OperationT) => void;
43
+ };
44
+
45
+ function TokenFilterChip(props: TokenFilterChipProps) {
46
+ const { token, onRemove, showOperation, operation } = props;
47
+ const [popupAnchor, setPopupAnchor] = useState<HTMLButtonElement | null>(
48
+ null,
49
+ );
50
+ const [isPopupOpen, setIsPopupOpen] = useState(false);
51
+
52
+ return (
53
+ <li className="aksel-property-filter__chip">
54
+ {showOperation && (
55
+ <ActionMenu>
56
+ <ActionMenu.Trigger>
57
+ <button
58
+ className="aksel-property-filter__chip-button"
59
+ data-type="operation"
60
+ /* onClick={onRemove} */
61
+ >
62
+ {operation === "and" ? "og" : "eller"}
63
+ </button>
64
+ </ActionMenu.Trigger>
65
+ <ActionMenu.Content>
66
+ <ActionMenu.Item onSelect={() => props.updateOperation?.("and")}>
67
+ AND
68
+ </ActionMenu.Item>
69
+ <ActionMenu.Item onSelect={() => props.updateOperation?.("or")}>
70
+ OR
71
+ </ActionMenu.Item>
72
+ </ActionMenu.Content>
73
+ </ActionMenu>
74
+ )}
75
+ <button
76
+ data-type="value"
77
+ className="aksel-property-filter__chip-button"
78
+ ref={setPopupAnchor}
79
+ onClick={() => setIsPopupOpen((open) => !open)}
80
+ >{`${token.propertyKey} ${token.operator} ${token.value}`}</button>
81
+ <Popover
82
+ open={isPopupOpen}
83
+ onClose={() => setIsPopupOpen(false)}
84
+ anchorEl={popupAnchor}
85
+ placement="bottom-start"
86
+ >
87
+ <Popover.Content>Edit filter</Popover.Content>
88
+ </Popover>
89
+ <button
90
+ data-type="remove"
91
+ className="aksel-property-filter__chip-button"
92
+ onClick={onRemove}
93
+ >
94
+ <XMarkIcon aria-hidden fontSize="1.25rem" />
95
+ </button>
96
+ </li>
97
+ );
98
+ }
99
+
100
+ export { TokenFilterChips };
@@ -1,9 +1,8 @@
1
1
  import React, { forwardRef, useState } from "react";
2
- import { Chips } from "../../chips";
3
- import { HStack } from "../../primitives/stack";
4
2
  import { cl } from "../../utils/helpers";
5
3
  import { AutoSuggest } from "./AutoSuggest";
6
4
  import { AutoCompleteOption } from "./AutoSuggest.types";
5
+ import { TokenFilterChips } from "./FilterChip";
7
6
  import type {
8
7
  ExternalOptions,
9
8
  ExternalPropertyDefinitions,
@@ -46,7 +45,7 @@ export const TokenFilter = forwardRef<HTMLDivElement, TokenFilterProps>(
46
45
  parsedPropertyOptions,
47
46
  );
48
47
 
49
- const { addToken, removeToken } = createActionHandlers({
48
+ const { addToken, removeToken, updateOperation } = createActionHandlers({
50
49
  query,
51
50
  onChange,
52
51
  });
@@ -116,27 +115,12 @@ export const TokenFilter = forwardRef<HTMLDivElement, TokenFilterProps>(
116
115
  open={open}
117
116
  setOpen={setOpen}
118
117
  />
119
- <HStack marginBlock="space-8" gap="space-8">
120
- {query.tokens.map((token, index) => {
121
- return (
122
- <React.Fragment
123
- key={`${token.propertyKey}-${token.operator}-${token.value}-${index}`}
124
- >
125
- <Chips.Removable
126
- key={index}
127
- onClick={() => {
128
- removeToken(index);
129
- }}
130
- >
131
- {`${token.propertyKey} ${token.operator} ${token.value}`}
132
- </Chips.Removable>
133
- {index < query.tokens.length - 1 && (
134
- <span>{query.operation}</span>
135
- )}
136
- </React.Fragment>
137
- );
138
- })}
139
- </HStack>
118
+ <TokenFilterChips
119
+ tokens={query.tokens}
120
+ removeToken={removeToken}
121
+ updateOperation={updateOperation}
122
+ operation={query.operation}
123
+ />
140
124
  </div>
141
125
  );
142
126
  },