@noya-app/noya-designsystem 0.1.54 → 0.1.56
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/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +16 -0
- package/dist/index.css +1 -1
- package/dist/index.d.mts +136 -44
- package/dist/index.d.ts +136 -44
- package/dist/index.js +1240 -802
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1484 -1054
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/validateDropIndicator.test.ts +35 -9
- package/src/components/Button.tsx +13 -0
- package/src/components/Dialog.tsx +3 -5
- package/src/components/Grid.tsx +3 -1
- package/src/components/GridView.tsx +1 -0
- package/src/components/InputField.tsx +10 -3
- package/src/components/ListView.tsx +5 -8
- package/src/components/Popover.tsx +68 -4
- package/src/components/SegmentedControl.tsx +41 -18
- package/src/components/SelectionToolbar.tsx +35 -22
- package/src/components/Toolbar.tsx +22 -12
- package/src/components/connected-users-menu/ConnectedUsersMenuLayout.tsx +3 -26
- package/src/components/sorting/DragRegistration.tsx +110 -0
- package/src/components/sorting/SharedDragProvider.tsx +307 -0
- package/src/components/sorting/Sortable.tsx +404 -0
- package/src/components/sorting/createSharedDrag.tsx +46 -0
- package/src/components/sorting/sorting.ts +180 -0
- package/src/contexts/DesignSystemConfiguration.tsx +8 -5
- package/src/contexts/OpenPortalsContext.tsx +67 -0
- package/src/index.css +2 -2
- package/src/index.tsx +5 -2
- package/src/utils/moveTreeItem.ts +4 -2
- package/src/components/Sortable.tsx +0 -468
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
import { UniqueIdentifier, useDndContext, useDroppable } from "@dnd-kit/core";
|
|
2
|
+
import {
|
|
3
|
+
horizontalListSortingStrategy,
|
|
4
|
+
SortableContext,
|
|
5
|
+
useSortable,
|
|
6
|
+
verticalListSortingStrategy,
|
|
7
|
+
} from "@dnd-kit/sortable";
|
|
8
|
+
import * as React from "react";
|
|
9
|
+
import {
|
|
10
|
+
ActiveDragContext,
|
|
11
|
+
ActiveDragContextType,
|
|
12
|
+
DragRegistrationContext,
|
|
13
|
+
RegisteredList,
|
|
14
|
+
SharedDragProviderContexts,
|
|
15
|
+
useActiveDrag,
|
|
16
|
+
useDragRegistration,
|
|
17
|
+
} from "./DragRegistration";
|
|
18
|
+
import { withDragProvider } from "./SharedDragProvider";
|
|
19
|
+
import {
|
|
20
|
+
AcceptsFromList,
|
|
21
|
+
defaultAcceptsDrop,
|
|
22
|
+
DropValidator,
|
|
23
|
+
MoveDragItemHandler,
|
|
24
|
+
RelativeDropPosition,
|
|
25
|
+
validateDropIndicator,
|
|
26
|
+
} from "./sorting";
|
|
27
|
+
|
|
28
|
+
type UseSortableReturnType = ReturnType<typeof useSortable>;
|
|
29
|
+
|
|
30
|
+
type ItemChildrenProps<T> = {
|
|
31
|
+
ref: React.Ref<T>;
|
|
32
|
+
relativeDropPosition?: RelativeDropPosition;
|
|
33
|
+
style?: React.CSSProperties;
|
|
34
|
+
} & UseSortableReturnType["attributes"];
|
|
35
|
+
|
|
36
|
+
// Context for drop indicators within a sortable list
|
|
37
|
+
interface SortableItemContextProps {
|
|
38
|
+
keys: UniqueIdentifier[];
|
|
39
|
+
acceptsDrop: DropValidator;
|
|
40
|
+
axis: "x" | "y";
|
|
41
|
+
listId: string;
|
|
42
|
+
getDropTargetParentIndex?: (index: number) => number | undefined;
|
|
43
|
+
activeDragContext: React.Context<ActiveDragContextType | null>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const SortableItemContext = React.createContext<SortableItemContextProps>({
|
|
47
|
+
keys: [],
|
|
48
|
+
acceptsDrop: defaultAcceptsDrop,
|
|
49
|
+
axis: "y",
|
|
50
|
+
listId: "",
|
|
51
|
+
getDropTargetParentIndex: undefined,
|
|
52
|
+
activeDragContext: ActiveDragContext,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Custom hook for drop indicators in sortable lists
|
|
56
|
+
function useSortableDropIndicator({
|
|
57
|
+
id,
|
|
58
|
+
index,
|
|
59
|
+
isDragging,
|
|
60
|
+
overIndex,
|
|
61
|
+
activeIndex,
|
|
62
|
+
keys,
|
|
63
|
+
acceptsDrop,
|
|
64
|
+
axis,
|
|
65
|
+
listId,
|
|
66
|
+
getDropTargetParentIndex,
|
|
67
|
+
}: {
|
|
68
|
+
id: UniqueIdentifier;
|
|
69
|
+
index: number;
|
|
70
|
+
isDragging: boolean;
|
|
71
|
+
overIndex: number;
|
|
72
|
+
activeIndex: number;
|
|
73
|
+
keys: UniqueIdentifier[];
|
|
74
|
+
acceptsDrop: DropValidator;
|
|
75
|
+
axis: "x" | "y";
|
|
76
|
+
listId: string;
|
|
77
|
+
getDropTargetParentIndex?: (index: number) => number | undefined;
|
|
78
|
+
}): RelativeDropPosition | undefined {
|
|
79
|
+
const { active, over, activatorEvent } = useDndContext();
|
|
80
|
+
const { activeDragContext } = React.useContext(SortableItemContext);
|
|
81
|
+
const { activeItem, delta: position } = useActiveDrag(activeDragContext);
|
|
82
|
+
|
|
83
|
+
if (
|
|
84
|
+
index < 0 ||
|
|
85
|
+
isDragging ||
|
|
86
|
+
!active ||
|
|
87
|
+
!over ||
|
|
88
|
+
!activeItem ||
|
|
89
|
+
!(
|
|
90
|
+
// Same-list drop: check normal overIndex matching
|
|
91
|
+
(
|
|
92
|
+
(activeItem.listId === listId && index === overIndex) ||
|
|
93
|
+
// Cross-list drop: check if we're hovering over this specific item
|
|
94
|
+
(activeItem.listId !== listId && over.id === id)
|
|
95
|
+
)
|
|
96
|
+
)
|
|
97
|
+
) {
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const isCrossContainer = activeItem.listId !== listId;
|
|
102
|
+
|
|
103
|
+
// For cross-list drops, use combined keys like MultiSortable does
|
|
104
|
+
const validationKeys = isCrossContainer ? [active.id, ...keys] : keys;
|
|
105
|
+
|
|
106
|
+
const eventX = (activatorEvent as PointerEvent | null)?.clientX ?? 0;
|
|
107
|
+
const eventY = (activatorEvent as PointerEvent | null)?.clientY ?? 0;
|
|
108
|
+
const offsetLeft = eventX + position.x;
|
|
109
|
+
const offsetTop = eventY + position.y;
|
|
110
|
+
|
|
111
|
+
const parentIndex =
|
|
112
|
+
overIndex === -1 ? undefined : getDropTargetParentIndex?.(overIndex);
|
|
113
|
+
|
|
114
|
+
const isValidParent =
|
|
115
|
+
parentIndex === undefined || parentIndex === -1
|
|
116
|
+
? false
|
|
117
|
+
: acceptsDrop(activeIndex, parentIndex, "inside", listId, listId);
|
|
118
|
+
|
|
119
|
+
const overIdAcceptsDrop =
|
|
120
|
+
overIndex === -1
|
|
121
|
+
? undefined
|
|
122
|
+
: acceptsDrop(activeIndex, overIndex, "inside", listId, listId);
|
|
123
|
+
|
|
124
|
+
const relativeDropPosition =
|
|
125
|
+
index >= 0 && index === overIndex && !isDragging && active && over
|
|
126
|
+
? validateDropIndicator({
|
|
127
|
+
acceptsDrop,
|
|
128
|
+
keys: validationKeys,
|
|
129
|
+
activeId: active.id,
|
|
130
|
+
overId: over.id,
|
|
131
|
+
offsetStart: axis === "x" ? offsetLeft : offsetTop,
|
|
132
|
+
elementStart: axis === "x" ? over.rect.left : over.rect.top,
|
|
133
|
+
elementSize: axis === "x" ? over.rect.width : over.rect.height,
|
|
134
|
+
sourceListId: listId,
|
|
135
|
+
targetListId: listId,
|
|
136
|
+
})
|
|
137
|
+
: undefined;
|
|
138
|
+
|
|
139
|
+
const showDragInsideIndicatorOnParent =
|
|
140
|
+
!overIdAcceptsDrop && isValidParent && parentIndex === index;
|
|
141
|
+
|
|
142
|
+
return (
|
|
143
|
+
relativeDropPosition ??
|
|
144
|
+
(showDragInsideIndicatorOnParent ? "inside" : undefined)
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
interface RootProps {
|
|
149
|
+
id?: string;
|
|
150
|
+
keys: UniqueIdentifier[];
|
|
151
|
+
axis?: "x" | "y";
|
|
152
|
+
children?: React.ReactNode;
|
|
153
|
+
renderOverlay?: (index: number) => React.ReactNode;
|
|
154
|
+
onMoveItem?: MoveDragItemHandler;
|
|
155
|
+
acceptsFromList?: AcceptsFromList;
|
|
156
|
+
acceptsDrop?: DropValidator;
|
|
157
|
+
getDropTargetParentIndex?: (index: number) => number | undefined;
|
|
158
|
+
sharedDragProviderContexts?: SharedDragProviderContexts;
|
|
159
|
+
containerRef?: React.RefObject<HTMLElement>;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function SortableItem<TElement extends HTMLElement>({
|
|
163
|
+
id,
|
|
164
|
+
disabled,
|
|
165
|
+
children,
|
|
166
|
+
}: {
|
|
167
|
+
id: UniqueIdentifier;
|
|
168
|
+
disabled?: boolean;
|
|
169
|
+
children: (props: ItemChildrenProps<TElement>) => React.ReactNode;
|
|
170
|
+
}) {
|
|
171
|
+
const { keys, acceptsDrop, axis, listId, getDropTargetParentIndex } =
|
|
172
|
+
React.useContext(SortableItemContext);
|
|
173
|
+
|
|
174
|
+
const { attributes, listeners, setNodeRef, isDragging, index, overIndex } =
|
|
175
|
+
useSortable({ id, disabled });
|
|
176
|
+
|
|
177
|
+
const ref = React.useCallback(
|
|
178
|
+
(node: TElement | null) => setNodeRef(node),
|
|
179
|
+
[setNodeRef]
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
const relativeDropPosition = useSortableDropIndicator({
|
|
183
|
+
id,
|
|
184
|
+
index,
|
|
185
|
+
isDragging,
|
|
186
|
+
overIndex,
|
|
187
|
+
keys,
|
|
188
|
+
acceptsDrop,
|
|
189
|
+
axis,
|
|
190
|
+
listId,
|
|
191
|
+
activeIndex: index,
|
|
192
|
+
getDropTargetParentIndex,
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
return children({
|
|
196
|
+
ref,
|
|
197
|
+
...attributes,
|
|
198
|
+
...listeners,
|
|
199
|
+
relativeDropPosition,
|
|
200
|
+
style: {
|
|
201
|
+
opacity: isDragging ? 0.5 : 1,
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function SortableRoot_({
|
|
207
|
+
id: idProp,
|
|
208
|
+
keys,
|
|
209
|
+
onMoveItem,
|
|
210
|
+
renderOverlay,
|
|
211
|
+
acceptsFromList,
|
|
212
|
+
acceptsDrop = defaultAcceptsDrop,
|
|
213
|
+
axis = "y",
|
|
214
|
+
children,
|
|
215
|
+
getDropTargetParentIndex,
|
|
216
|
+
sharedDragProviderContexts,
|
|
217
|
+
containerRef,
|
|
218
|
+
}: RootProps) {
|
|
219
|
+
const defaultId = React.useId();
|
|
220
|
+
const id = idProp ?? defaultId;
|
|
221
|
+
|
|
222
|
+
const { registerList, unregisterList } = useDragRegistration(
|
|
223
|
+
sharedDragProviderContexts?.dragRegistrationContext ??
|
|
224
|
+
DragRegistrationContext
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
const { setNodeRef: setDroppableRef } = useDroppable({
|
|
228
|
+
id,
|
|
229
|
+
// Make the container droppable only when the list is empty
|
|
230
|
+
// This avoids conflicts with individual sortable items.
|
|
231
|
+
// This isn't strictly necessary since we also use a custom collision detection
|
|
232
|
+
// that prioritizes items over containers, but it helps with dropping into
|
|
233
|
+
// the best position (either above or below, rather than always below).
|
|
234
|
+
disabled: keys.length > 0,
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// Register this list with the global drag system
|
|
238
|
+
React.useEffect(() => {
|
|
239
|
+
const listConfig: RegisteredList = {
|
|
240
|
+
id,
|
|
241
|
+
keys,
|
|
242
|
+
onMoveItem: onMoveItem ?? (() => {}),
|
|
243
|
+
renderOverlay: (id) => {
|
|
244
|
+
const index = keys.findIndex((key) => key === id);
|
|
245
|
+
return renderOverlay?.(index) ?? null;
|
|
246
|
+
},
|
|
247
|
+
acceptsFromList:
|
|
248
|
+
acceptsFromList ??
|
|
249
|
+
(({ sourceListId, targetListId }) => sourceListId === targetListId),
|
|
250
|
+
acceptsDrop,
|
|
251
|
+
getDropIndicator: (parameters) => {
|
|
252
|
+
const { absolutePosition, active, over, sourceListId, targetListId } =
|
|
253
|
+
parameters;
|
|
254
|
+
|
|
255
|
+
const offsetStart =
|
|
256
|
+
axis === "x" ? absolutePosition.x : absolutePosition.y;
|
|
257
|
+
const elementStart = axis === "x" ? over.rect.left : over.rect.top;
|
|
258
|
+
const elementSize = axis === "x" ? over.rect.width : over.rect.height;
|
|
259
|
+
|
|
260
|
+
const relativeDropPosition = validateDropIndicator({
|
|
261
|
+
acceptsDrop,
|
|
262
|
+
keys,
|
|
263
|
+
activeId: active.id,
|
|
264
|
+
overId: over.id,
|
|
265
|
+
offsetStart,
|
|
266
|
+
elementStart,
|
|
267
|
+
elementSize,
|
|
268
|
+
sourceListId,
|
|
269
|
+
targetListId,
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
return relativeDropPosition;
|
|
273
|
+
},
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
registerList(listConfig);
|
|
277
|
+
|
|
278
|
+
return () => {
|
|
279
|
+
unregisterList(id);
|
|
280
|
+
};
|
|
281
|
+
}, [
|
|
282
|
+
id,
|
|
283
|
+
onMoveItem,
|
|
284
|
+
renderOverlay,
|
|
285
|
+
acceptsFromList,
|
|
286
|
+
acceptsDrop,
|
|
287
|
+
axis,
|
|
288
|
+
registerList,
|
|
289
|
+
unregisterList,
|
|
290
|
+
keys,
|
|
291
|
+
]);
|
|
292
|
+
|
|
293
|
+
const contextValue: SortableItemContextProps = React.useMemo(
|
|
294
|
+
() => ({
|
|
295
|
+
keys,
|
|
296
|
+
acceptsDrop,
|
|
297
|
+
axis,
|
|
298
|
+
listId: id,
|
|
299
|
+
getDropTargetParentIndex,
|
|
300
|
+
activeDragContext:
|
|
301
|
+
sharedDragProviderContexts?.activeDragContext ?? ActiveDragContext,
|
|
302
|
+
}),
|
|
303
|
+
[
|
|
304
|
+
keys,
|
|
305
|
+
acceptsDrop,
|
|
306
|
+
axis,
|
|
307
|
+
id,
|
|
308
|
+
getDropTargetParentIndex,
|
|
309
|
+
sharedDragProviderContexts,
|
|
310
|
+
]
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
// Set the droppable ref after every render.
|
|
314
|
+
// Since we don't render the dom node internally, we aggressively set it
|
|
315
|
+
// after every render to make sure we are using the latest.
|
|
316
|
+
React.useEffect(() => {
|
|
317
|
+
if (containerRef) {
|
|
318
|
+
setDroppableRef(containerRef.current);
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
return (
|
|
323
|
+
<SortableItemContext.Provider value={contextValue}>
|
|
324
|
+
<SortableContext
|
|
325
|
+
items={keys}
|
|
326
|
+
strategy={
|
|
327
|
+
axis === "x"
|
|
328
|
+
? horizontalListSortingStrategy
|
|
329
|
+
: verticalListSortingStrategy
|
|
330
|
+
}
|
|
331
|
+
>
|
|
332
|
+
{children}
|
|
333
|
+
</SortableContext>
|
|
334
|
+
</SortableItemContext.Provider>
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const SortableRoot = withDragProvider(SortableRoot_) as typeof SortableRoot_;
|
|
339
|
+
|
|
340
|
+
export interface SortableProps<TItem, TElement extends HTMLElement>
|
|
341
|
+
extends Omit<RootProps, "children" | "keys" | "renderOverlay"> {
|
|
342
|
+
items: TItem[];
|
|
343
|
+
keyExtractor: (item: TItem) => UniqueIdentifier;
|
|
344
|
+
shouldRenderOverlay?: boolean;
|
|
345
|
+
renderItem: (
|
|
346
|
+
item: TItem,
|
|
347
|
+
props: ItemChildrenProps<TElement>,
|
|
348
|
+
{ isOverlay }: { isOverlay: boolean }
|
|
349
|
+
) => React.ReactNode;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const Sortable_ = function Sortable_<TItem, TElement extends HTMLElement>({
|
|
353
|
+
items,
|
|
354
|
+
keyExtractor,
|
|
355
|
+
shouldRenderOverlay = true,
|
|
356
|
+
renderItem,
|
|
357
|
+
...rest
|
|
358
|
+
}: SortableProps<TItem, TElement>) {
|
|
359
|
+
const keys = React.useMemo(
|
|
360
|
+
() => items.map(keyExtractor),
|
|
361
|
+
[items, keyExtractor]
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
const renderOverlay = React.useCallback(
|
|
365
|
+
(index: number) => {
|
|
366
|
+
return renderItem(
|
|
367
|
+
items[index],
|
|
368
|
+
{
|
|
369
|
+
ref: () => {},
|
|
370
|
+
style: {},
|
|
371
|
+
tabIndex: -1,
|
|
372
|
+
"aria-disabled": true,
|
|
373
|
+
"aria-pressed": false,
|
|
374
|
+
"aria-roledescription": "Item being dragged",
|
|
375
|
+
"aria-describedby": "Item being dragged",
|
|
376
|
+
role: "button",
|
|
377
|
+
},
|
|
378
|
+
{ isOverlay: true }
|
|
379
|
+
);
|
|
380
|
+
},
|
|
381
|
+
[renderItem, items]
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
return (
|
|
385
|
+
<SortableRoot
|
|
386
|
+
{...rest}
|
|
387
|
+
keys={keys}
|
|
388
|
+
renderOverlay={shouldRenderOverlay ? renderOverlay : undefined}
|
|
389
|
+
>
|
|
390
|
+
{keys.map((key, index) => (
|
|
391
|
+
<SortableItem<TElement> key={key} id={key}>
|
|
392
|
+
{(props) => {
|
|
393
|
+
return renderItem(items[index], props, { isOverlay: false });
|
|
394
|
+
}}
|
|
395
|
+
</SortableItem>
|
|
396
|
+
))}
|
|
397
|
+
</SortableRoot>
|
|
398
|
+
);
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
export const Sortable = Object.assign(Sortable_, {
|
|
402
|
+
Root: SortableRoot,
|
|
403
|
+
Item: SortableItem,
|
|
404
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import {
|
|
3
|
+
ActiveDragContextType,
|
|
4
|
+
DragRegistrationContextType,
|
|
5
|
+
SharedDragProviderContexts,
|
|
6
|
+
} from "./DragRegistration";
|
|
7
|
+
import { SharedDragProvider } from "./SharedDragProvider";
|
|
8
|
+
import { Sortable, SortableProps } from "./Sortable";
|
|
9
|
+
|
|
10
|
+
export function createSharedDrag(): {
|
|
11
|
+
Provider: typeof SharedDragProvider;
|
|
12
|
+
props: SharedDragProviderContexts;
|
|
13
|
+
Sortable: <TItem, TElement extends HTMLElement>(
|
|
14
|
+
props: SortableProps<TItem, TElement>
|
|
15
|
+
) => React.ReactNode;
|
|
16
|
+
} {
|
|
17
|
+
const dragRegistrationContext =
|
|
18
|
+
React.createContext<DragRegistrationContextType | null>(null);
|
|
19
|
+
const activeDragContext = React.createContext<ActiveDragContextType | null>(
|
|
20
|
+
null
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
const Provider = ({ children }: { children: React.ReactNode }) => (
|
|
24
|
+
<SharedDragProvider
|
|
25
|
+
contexts={{
|
|
26
|
+
dragRegistrationContext,
|
|
27
|
+
activeDragContext,
|
|
28
|
+
}}
|
|
29
|
+
>
|
|
30
|
+
{children}
|
|
31
|
+
</SharedDragProvider>
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const dragProps = {
|
|
35
|
+
dragRegistrationContext,
|
|
36
|
+
activeDragContext,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
Provider,
|
|
41
|
+
Sortable: <TItem, TElement extends HTMLElement>(
|
|
42
|
+
props: SortableProps<TItem, TElement>
|
|
43
|
+
) => <Sortable sharedDragProviderContexts={dragProps} {...props} />,
|
|
44
|
+
props: dragProps,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { UniqueIdentifier } from "@dnd-kit/core";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
|
|
4
|
+
export type RelativeDropPosition = "above" | "below" | "inside";
|
|
5
|
+
|
|
6
|
+
export type DropValidator = (
|
|
7
|
+
sourceIndex: number,
|
|
8
|
+
targetIndex: number,
|
|
9
|
+
position: RelativeDropPosition,
|
|
10
|
+
sourceListId: string,
|
|
11
|
+
targetListId: string
|
|
12
|
+
) => boolean;
|
|
13
|
+
|
|
14
|
+
export type AcceptsDrop = (
|
|
15
|
+
sourceIndex: number,
|
|
16
|
+
targetIndex: number,
|
|
17
|
+
position: RelativeDropPosition,
|
|
18
|
+
sourceListId: string,
|
|
19
|
+
targetListId: string
|
|
20
|
+
) => boolean;
|
|
21
|
+
|
|
22
|
+
export type MoveDragItemHandler = (
|
|
23
|
+
sourceIndex: number,
|
|
24
|
+
targetIndex: number,
|
|
25
|
+
position: RelativeDropPosition,
|
|
26
|
+
sourceListId: string,
|
|
27
|
+
targetListId: string
|
|
28
|
+
) => void;
|
|
29
|
+
|
|
30
|
+
export type AcceptsFromList = ({
|
|
31
|
+
sourceListId,
|
|
32
|
+
targetListId,
|
|
33
|
+
}: {
|
|
34
|
+
sourceListId: string;
|
|
35
|
+
targetListId: string;
|
|
36
|
+
}) => boolean;
|
|
37
|
+
|
|
38
|
+
export interface DragItem {
|
|
39
|
+
id: UniqueIdentifier;
|
|
40
|
+
listId: string;
|
|
41
|
+
index: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface DropTarget {
|
|
45
|
+
id: UniqueIdentifier;
|
|
46
|
+
listId: string;
|
|
47
|
+
index: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const normalizeListTargetIndex = (
|
|
51
|
+
index: number,
|
|
52
|
+
position: "above" | "below"
|
|
53
|
+
): number => {
|
|
54
|
+
return position === "above" ? index : index + 1;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const defaultAcceptsDrop: DropValidator = (
|
|
58
|
+
sourceIndex,
|
|
59
|
+
targetIndex,
|
|
60
|
+
position
|
|
61
|
+
) => {
|
|
62
|
+
if (position === "inside") return false;
|
|
63
|
+
|
|
64
|
+
const normalized = normalizeListTargetIndex(targetIndex, position);
|
|
65
|
+
|
|
66
|
+
if (sourceIndex === normalized || sourceIndex + 1 === normalized) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return true;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export type SortableItemContextProps = {
|
|
74
|
+
keys: UniqueIdentifier[];
|
|
75
|
+
acceptsDrop: DropValidator;
|
|
76
|
+
axis: "x" | "y";
|
|
77
|
+
position: { x: number; y: number };
|
|
78
|
+
setActivatorEvent: (event: PointerEvent) => void;
|
|
79
|
+
getDropTargetParentIndex?: (index: number) => number | undefined;
|
|
80
|
+
listId: string;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export const SortableItemContext =
|
|
84
|
+
React.createContext<SortableItemContextProps>({
|
|
85
|
+
keys: [],
|
|
86
|
+
acceptsDrop: defaultAcceptsDrop,
|
|
87
|
+
axis: "y",
|
|
88
|
+
position: { x: 0, y: 0 },
|
|
89
|
+
setActivatorEvent: () => {},
|
|
90
|
+
getDropTargetParentIndex: undefined,
|
|
91
|
+
listId: "",
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
export type ValidateDropIndicatorParams = {
|
|
95
|
+
acceptsDrop: DropValidator; // Function that validates if a drop is allowed
|
|
96
|
+
keys: UniqueIdentifier[]; // Array of all sortable item IDs
|
|
97
|
+
activeId: UniqueIdentifier; // ID of item being dragged
|
|
98
|
+
overId: UniqueIdentifier; // ID of item being dragged over
|
|
99
|
+
offsetStart: number; // Current drag position (x or y coordinate)
|
|
100
|
+
elementStart: number; // Start position of target element (left or top)
|
|
101
|
+
elementSize: number; // Size of target element (width or height)
|
|
102
|
+
sourceListId: string; // ID of the list the item is being dragged from
|
|
103
|
+
targetListId: string; // ID of the list the item is being dragged to
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export function validateDropIndicator({
|
|
107
|
+
acceptsDrop,
|
|
108
|
+
keys,
|
|
109
|
+
activeId,
|
|
110
|
+
overId,
|
|
111
|
+
offsetStart,
|
|
112
|
+
elementStart,
|
|
113
|
+
elementSize,
|
|
114
|
+
sourceListId,
|
|
115
|
+
targetListId,
|
|
116
|
+
}: ValidateDropIndicatorParams): RelativeDropPosition | undefined {
|
|
117
|
+
const activeIndex = keys.findIndex((id) => id === activeId); // index of item being dragged
|
|
118
|
+
const overIndex = keys.findIndex((id) => id === overId); // index of item being dragged over
|
|
119
|
+
|
|
120
|
+
if (overIndex === -1) return undefined;
|
|
121
|
+
if (activeIndex === -1 && sourceListId === targetListId) return undefined;
|
|
122
|
+
|
|
123
|
+
const acceptsDropInside = acceptsDrop(
|
|
124
|
+
activeIndex,
|
|
125
|
+
overIndex,
|
|
126
|
+
"inside",
|
|
127
|
+
sourceListId,
|
|
128
|
+
targetListId
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
// Drop into the middle third if possible
|
|
132
|
+
if (
|
|
133
|
+
isContainedInMiddleThird(elementStart, elementSize, offsetStart) &&
|
|
134
|
+
acceptsDropInside
|
|
135
|
+
) {
|
|
136
|
+
return "inside";
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const containingHalf = getContainingHalf(
|
|
140
|
+
elementStart,
|
|
141
|
+
elementSize,
|
|
142
|
+
offsetStart
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
// Drop above or below if possible
|
|
146
|
+
const acceptedHalf = acceptsDrop(
|
|
147
|
+
activeIndex,
|
|
148
|
+
overIndex,
|
|
149
|
+
containingHalf,
|
|
150
|
+
sourceListId,
|
|
151
|
+
targetListId
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
return acceptedHalf
|
|
155
|
+
? containingHalf
|
|
156
|
+
: // Lastly, check if inside but not middle third
|
|
157
|
+
acceptsDropInside
|
|
158
|
+
? "inside"
|
|
159
|
+
: undefined;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function isContainedInMiddleThird(
|
|
163
|
+
elementStart: number,
|
|
164
|
+
elementSize: number,
|
|
165
|
+
offsetStart: number
|
|
166
|
+
): boolean {
|
|
167
|
+
return (
|
|
168
|
+
offsetStart >= elementStart + elementSize / 3 &&
|
|
169
|
+
offsetStart <= elementStart + (elementSize * 2) / 3
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function getContainingHalf(
|
|
174
|
+
elementStart: number,
|
|
175
|
+
elementSize: number,
|
|
176
|
+
offsetStart: number
|
|
177
|
+
): "above" | "below" {
|
|
178
|
+
if (offsetStart < elementStart + elementSize / 2) return "above";
|
|
179
|
+
return "below";
|
|
180
|
+
}
|
|
@@ -3,6 +3,7 @@ import * as React from "react";
|
|
|
3
3
|
import { ToastProvider } from "../components/Toast";
|
|
4
4
|
import { DialogProvider } from "./DialogContext";
|
|
5
5
|
import { FloatingWindowProvider } from "./FloatingWindowContext";
|
|
6
|
+
import { OpenPortalsProvider } from "./OpenPortalsContext";
|
|
6
7
|
|
|
7
8
|
export type DesignSystemConfigurationContextValue = {
|
|
8
9
|
platform: PlatformName;
|
|
@@ -39,11 +40,13 @@ export const DesignSystemConfigurationProvider = React.memo(
|
|
|
39
40
|
|
|
40
41
|
return (
|
|
41
42
|
<DesignSystemConfigurationContext.Provider value={contextValue}>
|
|
42
|
-
<
|
|
43
|
-
<
|
|
44
|
-
<
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
<OpenPortalsProvider>
|
|
44
|
+
<DialogProvider>
|
|
45
|
+
<ToastProvider>
|
|
46
|
+
<FloatingWindowProvider>{children}</FloatingWindowProvider>
|
|
47
|
+
</ToastProvider>
|
|
48
|
+
</DialogProvider>
|
|
49
|
+
</OpenPortalsProvider>
|
|
47
50
|
</DesignSystemConfigurationContext.Provider>
|
|
48
51
|
);
|
|
49
52
|
}
|