@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.
- package/cjs/data/drag-and-drop/drag-handler/DragAndDropDragHandler.d.ts +1 -0
- package/cjs/data/drag-and-drop/drag-handler/DragAndDropDragHandler.js +5 -3
- package/cjs/data/drag-and-drop/drag-handler/DragAndDropDragHandler.js.map +1 -1
- package/cjs/data/drag-and-drop/item/DragAndDropItem.d.ts +4 -0
- package/cjs/data/drag-and-drop/item/DragAndDropItem.js +3 -7
- package/cjs/data/drag-and-drop/item/DragAndDropItem.js.map +1 -1
- package/cjs/data/drag-and-drop/root/DragAndDrop.context.d.ts +2 -1
- package/cjs/data/drag-and-drop/root/DragAndDrop.context.js.map +1 -1
- package/cjs/data/drag-and-drop/root/DragAndDropRoot.d.ts +12 -10
- package/cjs/data/drag-and-drop/root/DragAndDropRoot.js +71 -39
- package/cjs/data/drag-and-drop/root/DragAndDropRoot.js.map +1 -1
- package/cjs/data/table/helpers/selection/getMultipleSelectProps.d.ts +3 -3
- package/cjs/data/table/helpers/selection/getMultipleSelectProps.js +4 -4
- package/cjs/data/table/helpers/selection/getMultipleSelectProps.js.map +1 -1
- package/cjs/data/table/helpers/selection/getSingleSelectProps.d.ts +2 -2
- package/cjs/data/table/helpers/selection/getSingleSelectProps.js +4 -1
- package/cjs/data/table/helpers/selection/getSingleSelectProps.js.map +1 -1
- package/cjs/data/table/helpers/selection/selection.types.d.ts +5 -5
- package/cjs/data/table/root/DataTableAuto.js +9 -11
- package/cjs/data/table/root/DataTableAuto.js.map +1 -1
- package/cjs/data/table/td/DataTableTd.d.ts +4 -0
- package/cjs/data/table/td/DataTableTd.js +4 -2
- package/cjs/data/table/td/DataTableTd.js.map +1 -1
- package/cjs/data/table/th/DataTableTh.d.ts +4 -0
- package/cjs/data/table/th/DataTableTh.js +6 -3
- package/cjs/data/table/th/DataTableTh.js.map +1 -1
- package/cjs/data/token-filter/FilterChip.d.ts +10 -0
- package/cjs/data/token-filter/FilterChip.js +65 -0
- package/cjs/data/token-filter/FilterChip.js.map +1 -0
- package/cjs/data/token-filter/TokenFilter.js +3 -10
- package/cjs/data/token-filter/TokenFilter.js.map +1 -1
- package/cjs/form/checkbox/Checkbox.js +19 -33
- package/cjs/form/checkbox/Checkbox.js.map +1 -1
- package/cjs/form/checkbox/checkbox-input/CheckboxInput.d.ts +21 -0
- package/cjs/form/checkbox/checkbox-input/CheckboxInput.js +65 -0
- package/cjs/form/checkbox/checkbox-input/CheckboxInput.js.map +1 -0
- package/cjs/form/checkbox/types.d.ts +1 -1
- package/cjs/form/fieldset/Fieldset.js +2 -6
- package/cjs/form/fieldset/Fieldset.js.map +1 -1
- package/cjs/form/radio/Radio.js +9 -7
- package/cjs/form/radio/Radio.js.map +1 -1
- package/cjs/form/radio/radio-input/RadioInput.d.ts +19 -0
- package/cjs/form/radio/radio-input/RadioInput.js +55 -0
- package/cjs/form/radio/radio-input/RadioInput.js.map +1 -0
- package/esm/data/drag-and-drop/drag-handler/DragAndDropDragHandler.d.ts +1 -0
- package/esm/data/drag-and-drop/drag-handler/DragAndDropDragHandler.js +5 -3
- package/esm/data/drag-and-drop/drag-handler/DragAndDropDragHandler.js.map +1 -1
- package/esm/data/drag-and-drop/item/DragAndDropItem.d.ts +4 -0
- package/esm/data/drag-and-drop/item/DragAndDropItem.js +3 -7
- package/esm/data/drag-and-drop/item/DragAndDropItem.js.map +1 -1
- package/esm/data/drag-and-drop/root/DragAndDrop.context.d.ts +2 -1
- package/esm/data/drag-and-drop/root/DragAndDrop.context.js.map +1 -1
- package/esm/data/drag-and-drop/root/DragAndDropRoot.d.ts +12 -10
- package/esm/data/drag-and-drop/root/DragAndDropRoot.js +71 -39
- package/esm/data/drag-and-drop/root/DragAndDropRoot.js.map +1 -1
- package/esm/data/table/helpers/selection/getMultipleSelectProps.d.ts +3 -3
- package/esm/data/table/helpers/selection/getMultipleSelectProps.js +4 -4
- package/esm/data/table/helpers/selection/getMultipleSelectProps.js.map +1 -1
- package/esm/data/table/helpers/selection/getSingleSelectProps.d.ts +2 -2
- package/esm/data/table/helpers/selection/getSingleSelectProps.js +4 -1
- package/esm/data/table/helpers/selection/getSingleSelectProps.js.map +1 -1
- package/esm/data/table/helpers/selection/selection.types.d.ts +5 -5
- package/esm/data/table/root/DataTableAuto.js +9 -11
- package/esm/data/table/root/DataTableAuto.js.map +1 -1
- package/esm/data/table/td/DataTableTd.d.ts +4 -0
- package/esm/data/table/td/DataTableTd.js +4 -2
- package/esm/data/table/td/DataTableTd.js.map +1 -1
- package/esm/data/table/th/DataTableTh.d.ts +4 -0
- package/esm/data/table/th/DataTableTh.js +6 -3
- package/esm/data/table/th/DataTableTh.js.map +1 -1
- package/esm/data/token-filter/FilterChip.d.ts +10 -0
- package/esm/data/token-filter/FilterChip.js +30 -0
- package/esm/data/token-filter/FilterChip.js.map +1 -0
- package/esm/data/token-filter/TokenFilter.js +3 -10
- package/esm/data/token-filter/TokenFilter.js.map +1 -1
- package/esm/form/checkbox/Checkbox.js +19 -33
- package/esm/form/checkbox/Checkbox.js.map +1 -1
- package/esm/form/checkbox/checkbox-input/CheckboxInput.d.ts +21 -0
- package/esm/form/checkbox/checkbox-input/CheckboxInput.js +29 -0
- package/esm/form/checkbox/checkbox-input/CheckboxInput.js.map +1 -0
- package/esm/form/checkbox/types.d.ts +1 -1
- package/esm/form/fieldset/Fieldset.js +2 -6
- package/esm/form/fieldset/Fieldset.js.map +1 -1
- package/esm/form/radio/Radio.js +9 -7
- package/esm/form/radio/Radio.js.map +1 -1
- package/esm/form/radio/radio-input/RadioInput.d.ts +19 -0
- package/esm/form/radio/radio-input/RadioInput.js +19 -0
- package/esm/form/radio/radio-input/RadioInput.js.map +1 -0
- package/package.json +3 -3
- package/src/data/drag-and-drop/drag-handler/DragAndDropDragHandler.tsx +7 -3
- package/src/data/drag-and-drop/item/DragAndDropItem.tsx +15 -11
- package/src/data/drag-and-drop/root/DragAndDrop.context.tsx +4 -2
- package/src/data/drag-and-drop/root/DragAndDropRoot.tsx +94 -45
- package/src/data/table/helpers/selection/getMultipleSelectProps.ts +7 -7
- package/src/data/table/helpers/selection/getSingleSelectProps.ts +6 -3
- package/src/data/table/helpers/selection/selection.types.ts +5 -5
- package/src/data/table/root/DataTableAuto.tsx +25 -15
- package/src/data/table/td/DataTableTd.tsx +15 -2
- package/src/data/table/th/DataTableTh.tsx +13 -2
- package/src/data/token-filter/FilterChip.tsx +100 -0
- package/src/data/token-filter/TokenFilter.tsx +8 -24
- package/src/form/checkbox/Checkbox.tsx +37 -64
- package/src/form/checkbox/checkbox-input/CheckboxInput.tsx +69 -0
- package/src/form/checkbox/types.ts +1 -1
- package/src/form/fieldset/Fieldset.tsx +4 -6
- package/src/form/radio/Radio.tsx +43 -38
- 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:
|
|
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
|
-
*
|
|
37
|
-
*
|
|
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
|
-
}, [
|
|
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
|
-
|
|
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={{
|
|
273
|
+
style={{
|
|
274
|
+
pointerEvents: "none",
|
|
275
|
+
boxSizing: "border-box",
|
|
276
|
+
width: overlayWidth ? `${overlayWidth}px` : "fit-content",
|
|
277
|
+
}}
|
|
227
278
|
>
|
|
228
|
-
{React.cloneElement(
|
|
229
|
-
|
|
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 {
|
|
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: ():
|
|
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
|
-
|
|
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):
|
|
61
|
-
|
|
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 {
|
|
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):
|
|
24
|
-
|
|
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 {
|
|
2
|
-
import type {
|
|
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) =>
|
|
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: () =>
|
|
44
|
-
getRowCheckboxProps: (key: string | number) =>
|
|
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 {
|
|
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
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
<
|
|
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
|
|
165
|
-
|
|
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
|
|
170
|
-
{
|
|
171
|
-
|
|
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
|
-
{
|
|
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
|
|
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
|
-
<
|
|
120
|
-
{query.tokens
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
},
|