@noya-app/noya-designsystem 0.1.47 → 0.1.49
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 +18 -0
- package/dist/index.css +1 -1
- package/dist/index.d.mts +389 -241
- package/dist/index.d.ts +389 -241
- package/dist/index.js +4802 -3990
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +4715 -3915
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -7
- package/src/__tests__/validateDropIndicator.test.ts +263 -0
- package/src/components/ActionMenu.tsx +51 -0
- package/src/components/ActivityIndicator.tsx +7 -1
- package/src/components/BaseToolbar.tsx +2 -2
- package/src/components/Checkbox.tsx +2 -2
- package/src/components/Chip.tsx +7 -6
- package/src/components/Collection.tsx +30 -2
- package/src/components/Combobox.tsx +27 -15
- package/src/components/ComboboxMenu.tsx +15 -6
- package/src/components/Dialog.tsx +17 -12
- package/src/components/DimensionInput.tsx +14 -12
- package/src/components/Drawer.tsx +98 -0
- package/src/components/EditableText.tsx +3 -1
- package/src/components/FillInputField.tsx +2 -2
- package/src/components/Grid.tsx +161 -111
- package/src/components/GridView.tsx +73 -41
- package/src/components/InputField.tsx +148 -208
- package/src/components/Label.tsx +4 -68
- package/src/components/LabeledField.tsx +7 -1
- package/src/components/List.tsx +135 -50
- package/src/components/ListView.tsx +63 -43
- package/src/components/MediaThumbnail.tsx +117 -0
- package/src/components/Message.tsx +8 -8
- package/src/components/NoyaLogo.tsx +41 -0
- package/src/components/SearchCompletionMenu.tsx +30 -16
- package/src/components/SegmentedControl.tsx +1 -1
- package/src/components/SelectMenu.tsx +28 -21
- package/src/components/Slider.tsx +16 -7
- package/src/components/Sortable.tsx +125 -62
- package/src/components/internal/Menu.tsx +45 -25
- package/src/components/internal/MenuViewport.tsx +7 -1
- package/src/components/internal/SelectItem.tsx +53 -28
- package/src/components/internal/__tests__/Menu.test.tsx +12 -0
- package/src/components/workspace/DrawerWorkspaceLayout.tsx +86 -0
- package/src/components/workspace/PanelWorkspaceLayout.tsx +102 -0
- package/src/components/{WorkspaceLayout.tsx → workspace/WorkspaceLayout.tsx} +109 -106
- package/src/components/workspace/types.ts +31 -0
- package/src/contexts/DialogContext.tsx +49 -34
- package/src/hooks/usePreservePanelSize.tsx +1 -5
- package/src/index.css +8 -1
- package/src/index.tsx +6 -1
- package/src/theme/index.ts +2 -0
- package/src/utils/classNames.ts +51 -3
- package/src/utils/inputs.ts +21 -0
- package/tailwind.config.ts +8 -0
package/src/components/List.tsx
CHANGED
|
@@ -3,12 +3,14 @@ import {
|
|
|
3
3
|
memoGeneric,
|
|
4
4
|
useFileDropTarget,
|
|
5
5
|
} from "@noya-app/react-utils";
|
|
6
|
-
import React, { useImperativeHandle, useRef, useState } from "react";
|
|
6
|
+
import React, { memo, useImperativeHandle, useRef, useState } from "react";
|
|
7
7
|
import { cssVars } from "../theme";
|
|
8
8
|
import { cx } from "../utils/classNames";
|
|
9
9
|
import { updateSelection } from "../utils/selection";
|
|
10
|
+
import { ActionMenu } from "./ActionMenu";
|
|
10
11
|
import { CollectionProps, CollectionRef } from "./Collection";
|
|
11
|
-
import {
|
|
12
|
+
import { GridViewSize } from "./Grid";
|
|
13
|
+
import { ListView, ListViewItemInfo } from "./ListView";
|
|
12
14
|
import { TreeView } from "./TreeView";
|
|
13
15
|
|
|
14
16
|
const cssVarOverrides = {
|
|
@@ -19,7 +21,7 @@ const emptyArray: string[] = [];
|
|
|
19
21
|
|
|
20
22
|
export const List = memoGeneric(
|
|
21
23
|
forwardRefGeneric(function List<T, M extends string = string>(
|
|
22
|
-
props: CollectionProps<T, M
|
|
24
|
+
props: CollectionProps<T, M>,
|
|
23
25
|
ref: React.ForwardedRef<CollectionRef>
|
|
24
26
|
) {
|
|
25
27
|
const {
|
|
@@ -42,8 +44,10 @@ export const List = memoGeneric(
|
|
|
42
44
|
itemRoleDescription = "clickable item",
|
|
43
45
|
detailPosition = "end",
|
|
44
46
|
size = "medium",
|
|
47
|
+
thumbnailSize = "auto",
|
|
45
48
|
testHoveredId,
|
|
46
49
|
testRenamingId,
|
|
50
|
+
testShowDropIndicatorId,
|
|
47
51
|
scrollable = false,
|
|
48
52
|
acceptsDrop,
|
|
49
53
|
onMoveItem,
|
|
@@ -51,11 +55,16 @@ export const List = memoGeneric(
|
|
|
51
55
|
onDoubleClickItem,
|
|
52
56
|
renamable,
|
|
53
57
|
selectedIds = emptyArray,
|
|
58
|
+
renderEmptyState,
|
|
59
|
+
sortable,
|
|
60
|
+
getDropTargetParentIndex,
|
|
61
|
+
getPlaceholder,
|
|
62
|
+
onClickItem,
|
|
54
63
|
} = props;
|
|
55
64
|
|
|
56
65
|
const [internalHoveredId, setHoveredId] = useState<string>();
|
|
57
66
|
const [internalRenamingId, setRenamingId] = useState<string>();
|
|
58
|
-
|
|
67
|
+
const [dropdownOpenId, setDropdownOpenId] = useState<string>();
|
|
59
68
|
const hoveredId = internalHoveredId ?? testHoveredId;
|
|
60
69
|
const renamingId = testRenamingId ?? internalRenamingId;
|
|
61
70
|
|
|
@@ -72,13 +81,17 @@ export const List = memoGeneric(
|
|
|
72
81
|
onSelectionChange(newSelectedIds, event);
|
|
73
82
|
};
|
|
74
83
|
|
|
75
|
-
const handleMenuAction = (action: M) => {
|
|
84
|
+
const handleMenuAction = (action: M, actionItem: T) => {
|
|
76
85
|
if (!onSelectMenuItem) return;
|
|
77
86
|
|
|
78
87
|
const selectedItems = items.filter((item) =>
|
|
79
88
|
selectedIds.includes(getId(item))
|
|
80
89
|
);
|
|
81
|
-
|
|
90
|
+
|
|
91
|
+
onSelectMenuItem(
|
|
92
|
+
action,
|
|
93
|
+
selectedItems.length === 0 ? [actionItem] : selectedItems
|
|
94
|
+
);
|
|
82
95
|
};
|
|
83
96
|
|
|
84
97
|
const ViewComponent = expandable ? TreeView : ListView;
|
|
@@ -98,7 +111,11 @@ export const List = memoGeneric(
|
|
|
98
111
|
return (
|
|
99
112
|
<ViewComponent.Root
|
|
100
113
|
variant="bare"
|
|
101
|
-
className={cx(
|
|
114
|
+
className={cx(
|
|
115
|
+
className,
|
|
116
|
+
isDropTargetActive && "bg-primary-pastel",
|
|
117
|
+
scrollable && "basis-0 min-h-0"
|
|
118
|
+
)}
|
|
102
119
|
containerRef={fileDropTargetRef}
|
|
103
120
|
scrollable={scrollable}
|
|
104
121
|
data={items}
|
|
@@ -107,28 +124,53 @@ export const List = memoGeneric(
|
|
|
107
124
|
aria-orientation="vertical"
|
|
108
125
|
aria-multiselectable={true}
|
|
109
126
|
expandable={expandable}
|
|
110
|
-
sortable={
|
|
127
|
+
sortable={sortable}
|
|
111
128
|
acceptsDrop={acceptsDrop}
|
|
112
129
|
onMoveItem={onMoveItem}
|
|
130
|
+
renderEmptyState={renderEmptyState}
|
|
131
|
+
getDropTargetParentIndex={getDropTargetParentIndex}
|
|
113
132
|
{...dropTargetProps}
|
|
114
133
|
renderItem={(
|
|
115
134
|
item: T,
|
|
116
135
|
_: number,
|
|
117
|
-
{
|
|
136
|
+
{ isDragOverlay }: ListViewItemInfo
|
|
118
137
|
) => {
|
|
119
138
|
const id = getId(item);
|
|
120
|
-
const
|
|
139
|
+
const isDropdownOpen = dropdownOpenId === id;
|
|
140
|
+
const isHovered =
|
|
141
|
+
(hoveredId === id || isDropdownOpen) && !isDragOverlay;
|
|
121
142
|
const expanded = getExpanded?.(item);
|
|
122
143
|
const itemIsRenamable = getRenamable?.(item) ?? renamable;
|
|
123
144
|
const isSelected = selectedIds.includes(id);
|
|
124
145
|
const isRenaming = itemIsRenamable && onRename && renamingId === id;
|
|
125
146
|
const isTestingRenaming = testRenamingId === id;
|
|
126
147
|
|
|
127
|
-
const thumbnail = renderThumbnail?.(
|
|
148
|
+
const thumbnail = renderThumbnail?.({
|
|
149
|
+
item,
|
|
150
|
+
selected: isSelected,
|
|
151
|
+
});
|
|
152
|
+
const onOpenChange = (open: boolean) => {
|
|
153
|
+
setDropdownOpenId(open ? id : undefined);
|
|
154
|
+
|
|
155
|
+
if (open && !isSelected) {
|
|
156
|
+
handleSelect(id);
|
|
157
|
+
}
|
|
158
|
+
};
|
|
128
159
|
const action =
|
|
129
160
|
(isHovered || isSelected) &&
|
|
130
161
|
!isRenaming &&
|
|
131
|
-
|
|
162
|
+
(typeof renderAction === "function"
|
|
163
|
+
? renderAction({ item, selected: isSelected, onOpenChange })
|
|
164
|
+
: renderAction === "menu" &&
|
|
165
|
+
menuItems && (
|
|
166
|
+
<ActionMenu
|
|
167
|
+
menuItems={menuItems}
|
|
168
|
+
onSelect={(action) => handleMenuAction(action, item)}
|
|
169
|
+
selected={isSelected}
|
|
170
|
+
onOpenChange={onOpenChange}
|
|
171
|
+
/>
|
|
172
|
+
));
|
|
173
|
+
|
|
132
174
|
const end =
|
|
133
175
|
action ||
|
|
134
176
|
(detailPosition === "end" && renderDetail?.(item, isSelected));
|
|
@@ -140,14 +182,17 @@ export const List = memoGeneric(
|
|
|
140
182
|
id={id}
|
|
141
183
|
key={id}
|
|
142
184
|
role="option"
|
|
143
|
-
sortable={
|
|
185
|
+
sortable={sortable}
|
|
144
186
|
aria-roledescription={itemRoleDescription}
|
|
145
187
|
aria-label={getName(item)}
|
|
146
188
|
aria-selected={isSelected}
|
|
147
|
-
className={cx("cursor-pointer
|
|
189
|
+
className={cx("cursor-pointer rounded px-1", pyMap[size])}
|
|
148
190
|
backgroundColor={
|
|
149
191
|
isSelected ? cssVars.colors.primaryPastel : undefined
|
|
150
192
|
}
|
|
193
|
+
testRelativeDropPosition={
|
|
194
|
+
testShowDropIndicatorId === id ? "inside" : undefined
|
|
195
|
+
}
|
|
151
196
|
chevronClassName={cx(expandable && "relative left-2")}
|
|
152
197
|
style={cssVarOverrides as React.CSSProperties}
|
|
153
198
|
hovered={isHovered}
|
|
@@ -159,6 +204,7 @@ export const List = memoGeneric(
|
|
|
159
204
|
}
|
|
160
205
|
onPress={(e: ListView.ClickInfo) => {
|
|
161
206
|
handleSelect(id, e);
|
|
207
|
+
onClickItem?.(id);
|
|
162
208
|
}}
|
|
163
209
|
onDoubleClick={() => {
|
|
164
210
|
onDoubleClickItem?.(id);
|
|
@@ -172,15 +218,10 @@ export const List = memoGeneric(
|
|
|
172
218
|
}
|
|
173
219
|
}}
|
|
174
220
|
menuItems={menuItems}
|
|
175
|
-
onSelectMenuItem={handleMenuAction}
|
|
221
|
+
onSelectMenuItem={(action) => handleMenuAction(action, item)}
|
|
176
222
|
onContextMenu={() => {
|
|
177
223
|
if (!isSelected) {
|
|
178
|
-
handleSelect(id
|
|
179
|
-
shiftKey: false,
|
|
180
|
-
altKey: false,
|
|
181
|
-
metaKey: false,
|
|
182
|
-
ctrlKey: false,
|
|
183
|
-
});
|
|
224
|
+
handleSelect(id);
|
|
184
225
|
}
|
|
185
226
|
}}
|
|
186
227
|
tabIndex={0}
|
|
@@ -189,44 +230,44 @@ export const List = memoGeneric(
|
|
|
189
230
|
setExpanded?.(item, !expanded);
|
|
190
231
|
}}
|
|
191
232
|
>
|
|
192
|
-
<div
|
|
233
|
+
<div
|
|
234
|
+
className={cx("flex flex-1 items-center px-2", rowGapMap[size])}
|
|
235
|
+
>
|
|
193
236
|
{thumbnail && (
|
|
194
237
|
<div
|
|
195
238
|
className={cx(
|
|
196
239
|
"rounded overflow-hidden flex-none flex items-center justify-center",
|
|
197
|
-
|
|
240
|
+
thumbnailSize === "auto" && thumbnailSizeMap[size],
|
|
198
241
|
isSelected && "text-primary"
|
|
199
242
|
)}
|
|
200
|
-
style={{
|
|
201
|
-
backgroundColor: isSelected
|
|
202
|
-
? cssVars.colors.primaryPastel
|
|
203
|
-
: cssVars.colors.inputBackground,
|
|
204
|
-
}}
|
|
205
243
|
tabIndex={-1}
|
|
206
244
|
>
|
|
207
245
|
{thumbnail}
|
|
208
246
|
</div>
|
|
209
247
|
)}
|
|
210
248
|
<div className="flex flex-col flex-1 w-0">
|
|
211
|
-
<div className="flex items-center min-h-
|
|
249
|
+
<div className="flex items-center min-h-input-height flex-1 gap-2">
|
|
212
250
|
<div className="flex-1 w-0 flex items-center" tabIndex={-1}>
|
|
213
251
|
{isRenaming ? (
|
|
214
252
|
<ViewComponent.EditableRowTitle
|
|
215
253
|
value={getName(item)}
|
|
216
|
-
placeholder="Enter name"
|
|
217
|
-
className=
|
|
254
|
+
placeholder={getPlaceholder?.(item) ?? "Enter name"}
|
|
255
|
+
className={fontStyleMap[size]}
|
|
218
256
|
autoFocus={!isTestingRenaming}
|
|
219
257
|
onSubmitEditing={(value: string) => {
|
|
220
|
-
if (value !== getName(item)) {
|
|
258
|
+
if (value !== getName(item) && value !== "") {
|
|
221
259
|
onRename?.(item, value);
|
|
222
260
|
}
|
|
223
261
|
setRenamingId(undefined);
|
|
224
262
|
}}
|
|
263
|
+
onKeyDown={(e: React.KeyboardEvent) => {
|
|
264
|
+
e.stopPropagation();
|
|
265
|
+
}}
|
|
225
266
|
/>
|
|
226
267
|
) : (
|
|
227
268
|
<ViewComponent.RowTitle
|
|
228
269
|
className={cx(
|
|
229
|
-
|
|
270
|
+
fontStyleMap[size],
|
|
230
271
|
isSelected && "text-primary"
|
|
231
272
|
)}
|
|
232
273
|
>
|
|
@@ -234,30 +275,21 @@ export const List = memoGeneric(
|
|
|
234
275
|
</ViewComponent.RowTitle>
|
|
235
276
|
)}
|
|
236
277
|
</div>
|
|
237
|
-
{end && (
|
|
238
|
-
<
|
|
239
|
-
className={cx(
|
|
240
|
-
"flex items-center gap-2",
|
|
241
|
-
isSelected && "text-primary"
|
|
242
|
-
)}
|
|
243
|
-
tabIndex={-1}
|
|
244
|
-
>
|
|
278
|
+
{end && detailPosition === "end" && (
|
|
279
|
+
<DetailContainer selected={isSelected}>
|
|
245
280
|
{end}
|
|
246
|
-
</
|
|
281
|
+
</DetailContainer>
|
|
247
282
|
)}
|
|
248
283
|
</div>
|
|
249
284
|
{below && (
|
|
250
|
-
<
|
|
251
|
-
className={cx(
|
|
252
|
-
"flex items-center",
|
|
253
|
-
isSelected && "text-primary"
|
|
254
|
-
)}
|
|
255
|
-
tabIndex={-1}
|
|
256
|
-
>
|
|
285
|
+
<DetailContainer selected={isSelected}>
|
|
257
286
|
{below}
|
|
258
|
-
</
|
|
287
|
+
</DetailContainer>
|
|
259
288
|
)}
|
|
260
289
|
</div>
|
|
290
|
+
{end && detailPosition === "below" && (
|
|
291
|
+
<DetailContainer selected={isSelected}>{end}</DetailContainer>
|
|
292
|
+
)}
|
|
261
293
|
</div>
|
|
262
294
|
</ViewComponent.Row>
|
|
263
295
|
);
|
|
@@ -266,3 +298,56 @@ export const List = memoGeneric(
|
|
|
266
298
|
);
|
|
267
299
|
})
|
|
268
300
|
);
|
|
301
|
+
|
|
302
|
+
const DetailContainer = memo(function DetailContainer({
|
|
303
|
+
children,
|
|
304
|
+
selected,
|
|
305
|
+
}: {
|
|
306
|
+
children: React.ReactNode;
|
|
307
|
+
selected: boolean;
|
|
308
|
+
}) {
|
|
309
|
+
return (
|
|
310
|
+
<div
|
|
311
|
+
className={cx("flex items-center gap-2", selected && "text-primary")}
|
|
312
|
+
tabIndex={-1}
|
|
313
|
+
>
|
|
314
|
+
{children}
|
|
315
|
+
</div>
|
|
316
|
+
);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
const pyMap: Record<GridViewSize, string> = {
|
|
320
|
+
xxs: "py-1",
|
|
321
|
+
xs: "py-1",
|
|
322
|
+
small: "py-1",
|
|
323
|
+
medium: "py-1",
|
|
324
|
+
large: "py-2",
|
|
325
|
+
xl: "py-3",
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
const thumbnailSizeMap: Record<GridViewSize, string> = {
|
|
329
|
+
xxs: "w-4 h-4",
|
|
330
|
+
xs: "w-4 h-4",
|
|
331
|
+
small: "w-6 h-6",
|
|
332
|
+
medium: "w-8 h-8",
|
|
333
|
+
large: "w-16 h-16",
|
|
334
|
+
xl: "w-24 h-24",
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
const rowGapMap: Record<GridViewSize, string> = {
|
|
338
|
+
xxs: "gap-3",
|
|
339
|
+
xs: "gap-3",
|
|
340
|
+
small: "gap-3",
|
|
341
|
+
medium: "gap-3",
|
|
342
|
+
large: "gap-3",
|
|
343
|
+
xl: "gap-4",
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
const fontStyleMap: Record<GridViewSize, string> = {
|
|
347
|
+
xxs: "font-medium",
|
|
348
|
+
xs: "font-medium",
|
|
349
|
+
small: "font-medium",
|
|
350
|
+
medium: "font-medium",
|
|
351
|
+
large: "font-medium",
|
|
352
|
+
xl: "font-medium text-heading4 text-text",
|
|
353
|
+
};
|
|
@@ -25,7 +25,7 @@ import { WindowScroller, WindowScrollerChildProps } from "react-virtualized";
|
|
|
25
25
|
import { ListChildComponentProps, VariableSizeList } from "react-window";
|
|
26
26
|
import { mergeEventHandlers } from "../hooks/mergeEventHandlers";
|
|
27
27
|
import { useHover } from "../hooks/useHover";
|
|
28
|
-
import { cssVars } from "../theme";
|
|
28
|
+
import { cssVars, INPUT_HEIGHT } from "../theme";
|
|
29
29
|
import { cx } from "../utils/classNames";
|
|
30
30
|
import { ContextMenu } from "./ContextMenu";
|
|
31
31
|
import { InputField } from "./InputField";
|
|
@@ -33,9 +33,10 @@ import { MenuItem } from "./internal/Menu";
|
|
|
33
33
|
import { ScrollArea } from "./ScrollArea";
|
|
34
34
|
import {
|
|
35
35
|
DropValidator,
|
|
36
|
-
|
|
36
|
+
normalizeListTargetIndex,
|
|
37
37
|
RelativeDropPosition,
|
|
38
38
|
Sortable,
|
|
39
|
+
type SortableItemContextProps,
|
|
39
40
|
} from "./Sortable";
|
|
40
41
|
import { Spacer } from "./Spacer";
|
|
41
42
|
|
|
@@ -43,7 +44,7 @@ export type ListRowMarginType = "none" | "top" | "bottom" | "vertical";
|
|
|
43
44
|
export type ListRowPosition = "only" | "first" | "middle" | "last";
|
|
44
45
|
|
|
45
46
|
const ROW_HEIGHT = 31;
|
|
46
|
-
const SECTION_HEADER_LABEL_HEIGHT =
|
|
47
|
+
const SECTION_HEADER_LABEL_HEIGHT = INPUT_HEIGHT;
|
|
47
48
|
|
|
48
49
|
type ListColorScheme = "primary" | "secondary";
|
|
49
50
|
|
|
@@ -71,7 +72,7 @@ type ListRowContextValue = {
|
|
|
71
72
|
// and the dragged row doesn't have a ListRowContextValue.
|
|
72
73
|
type ListViewDraggingContextValue = {
|
|
73
74
|
indentation: number;
|
|
74
|
-
|
|
75
|
+
isDragOverlay?: boolean;
|
|
75
76
|
};
|
|
76
77
|
|
|
77
78
|
const ListViewDraggingContext = createContext<ListViewDraggingContextValue>({
|
|
@@ -127,6 +128,7 @@ export interface EditableRowProps {
|
|
|
127
128
|
autoFocus: boolean;
|
|
128
129
|
placeholder?: string;
|
|
129
130
|
className?: string;
|
|
131
|
+
onKeyDown?: (e: React.KeyboardEvent) => void;
|
|
130
132
|
}
|
|
131
133
|
|
|
132
134
|
function ListViewEditableRowTitle({
|
|
@@ -135,6 +137,7 @@ function ListViewEditableRowTitle({
|
|
|
135
137
|
autoFocus,
|
|
136
138
|
placeholder,
|
|
137
139
|
className,
|
|
140
|
+
onKeyDown,
|
|
138
141
|
}: EditableRowProps) {
|
|
139
142
|
const inputRef = useRef<HTMLInputElement | null>(null);
|
|
140
143
|
|
|
@@ -161,6 +164,7 @@ function ListViewEditableRowTitle({
|
|
|
161
164
|
onSubmit={onSubmitEditing}
|
|
162
165
|
allowSubmittingWithSameValue
|
|
163
166
|
className={cx("-mx-1.5 -my-1 bg-listview-editing-background", className)}
|
|
167
|
+
onKeyDown={onKeyDown}
|
|
164
168
|
/>
|
|
165
169
|
);
|
|
166
170
|
}
|
|
@@ -266,9 +270,10 @@ const RowContainer = forwardRef<
|
|
|
266
270
|
borderBottomRightRadius: "0px",
|
|
267
271
|
borderBottomLeftRadius: "0px",
|
|
268
272
|
}),
|
|
269
|
-
...($hovered &&
|
|
270
|
-
|
|
271
|
-
|
|
273
|
+
...($hovered &&
|
|
274
|
+
!$selected && {
|
|
275
|
+
background: cssVars.colors.listViewHoverBackground,
|
|
276
|
+
}),
|
|
272
277
|
...($showsActiveState && {
|
|
273
278
|
"&:active": {
|
|
274
279
|
backgroundColor: $selected
|
|
@@ -301,7 +306,7 @@ const RowContainer = forwardRef<
|
|
|
301
306
|
const ListViewDragIndicatorElement = forwardRef<
|
|
302
307
|
HTMLDivElement,
|
|
303
308
|
React.HTMLAttributes<HTMLDivElement> & {
|
|
304
|
-
$relativeDropPosition
|
|
309
|
+
$relativeDropPosition?: RelativeDropPosition;
|
|
305
310
|
$gap: number;
|
|
306
311
|
$offsetLeft: number;
|
|
307
312
|
$colorScheme: ListColorScheme;
|
|
@@ -398,13 +403,16 @@ interface ListViewRowProps<MenuItemType extends string = string> {
|
|
|
398
403
|
className?: string;
|
|
399
404
|
dragIndicatorStyle?:
|
|
400
405
|
| CSSProperties
|
|
401
|
-
| ((props:
|
|
402
|
-
|
|
403
|
-
indentation: number;
|
|
404
|
-
position: RelativeDropPosition;
|
|
405
|
-
}) => CSSProperties);
|
|
406
|
+
| ((props: DragIndicatorStyleProps) => CSSProperties);
|
|
407
|
+
testRelativeDropPosition?: RelativeDropPosition;
|
|
406
408
|
}
|
|
407
409
|
|
|
410
|
+
export type DragIndicatorStyleProps = {
|
|
411
|
+
depth: number;
|
|
412
|
+
indentation: number;
|
|
413
|
+
position: RelativeDropPosition;
|
|
414
|
+
};
|
|
415
|
+
|
|
408
416
|
const ListViewRow = forwardRefGeneric(function ListViewRow<
|
|
409
417
|
MenuItemType extends string,
|
|
410
418
|
>(
|
|
@@ -432,6 +440,7 @@ const ListViewRow = forwardRefGeneric(function ListViewRow<
|
|
|
432
440
|
style,
|
|
433
441
|
dragIndicatorStyle,
|
|
434
442
|
className,
|
|
443
|
+
testRelativeDropPosition,
|
|
435
444
|
}: ListViewRowProps<MenuItemType>,
|
|
436
445
|
forwardedRef: ForwardedRef<HTMLElement>
|
|
437
446
|
) {
|
|
@@ -451,7 +460,7 @@ const ListViewRow = forwardRefGeneric(function ListViewRow<
|
|
|
451
460
|
const { hoverProps } = useHover({
|
|
452
461
|
onHoverChange,
|
|
453
462
|
});
|
|
454
|
-
const { indentation,
|
|
463
|
+
const { indentation, isDragOverlay } = useContext(ListViewDraggingContext);
|
|
455
464
|
|
|
456
465
|
const handlePress = useCallback(
|
|
457
466
|
(event: React.MouseEvent) => {
|
|
@@ -478,7 +487,7 @@ const ListViewRow = forwardRefGeneric(function ListViewRow<
|
|
|
478
487
|
);
|
|
479
488
|
|
|
480
489
|
const mergedStyle = useMemo(() => {
|
|
481
|
-
return
|
|
490
|
+
return isDragOverlay
|
|
482
491
|
? {
|
|
483
492
|
// TODO: Where do these offsets actually come from?
|
|
484
493
|
transform: `translate3d(-36px, -6px, 0)`,
|
|
@@ -486,11 +495,11 @@ const ListViewRow = forwardRefGeneric(function ListViewRow<
|
|
|
486
495
|
...style,
|
|
487
496
|
}
|
|
488
497
|
: style;
|
|
489
|
-
}, [
|
|
498
|
+
}, [isDragOverlay, style]);
|
|
490
499
|
|
|
491
500
|
const renderContent = (
|
|
492
501
|
{
|
|
493
|
-
relativeDropPosition,
|
|
502
|
+
relativeDropPosition: relativeDropPositionProp,
|
|
494
503
|
...renderProps
|
|
495
504
|
}: React.ComponentProps<typeof RowContainer> & {
|
|
496
505
|
relativeDropPosition?: RelativeDropPosition;
|
|
@@ -498,6 +507,8 @@ const ListViewRow = forwardRefGeneric(function ListViewRow<
|
|
|
498
507
|
ref: Ref<HTMLElement>
|
|
499
508
|
) => {
|
|
500
509
|
const Component = RowContainer;
|
|
510
|
+
const relativeDropPosition =
|
|
511
|
+
testRelativeDropPosition ?? relativeDropPositionProp;
|
|
501
512
|
|
|
502
513
|
const element = (
|
|
503
514
|
<Component
|
|
@@ -520,7 +531,7 @@ const ListViewRow = forwardRefGeneric(function ListViewRow<
|
|
|
520
531
|
$selectedPosition={selectedPosition}
|
|
521
532
|
$showsActiveState={pressEventName === "onClick"}
|
|
522
533
|
aria-selected={selected}
|
|
523
|
-
$divider={!
|
|
534
|
+
$divider={!isDragOverlay && divider}
|
|
524
535
|
onKeyDown={onKeyDown}
|
|
525
536
|
style={mergedStyle}
|
|
526
537
|
{...(renderProps as Partial<React.ComponentProps<typeof RowContainer>>)}
|
|
@@ -534,9 +545,10 @@ const ListViewRow = forwardRefGeneric(function ListViewRow<
|
|
|
534
545
|
>
|
|
535
546
|
{relativeDropPosition && (
|
|
536
547
|
<ListViewDragIndicatorElement
|
|
548
|
+
key={relativeDropPosition}
|
|
537
549
|
$colorScheme={colorScheme}
|
|
538
550
|
$relativeDropPosition={relativeDropPosition}
|
|
539
|
-
$offsetLeft={
|
|
551
|
+
$offsetLeft={depth * indentation}
|
|
540
552
|
$gap={listGap + (divider ? 1 : 0)}
|
|
541
553
|
style={
|
|
542
554
|
typeof dragIndicatorStyle === "function"
|
|
@@ -724,8 +736,9 @@ const VirtualizedList = memo(
|
|
|
724
736
|
* Root
|
|
725
737
|
* ------------------------------------------------------------------------- */
|
|
726
738
|
|
|
727
|
-
type ListViewItemInfo = {
|
|
728
|
-
|
|
739
|
+
export type ListViewItemInfo = {
|
|
740
|
+
isDragOverlay: boolean;
|
|
741
|
+
activeDragIndex?: number;
|
|
729
742
|
};
|
|
730
743
|
|
|
731
744
|
type ChildrenProps = {
|
|
@@ -762,7 +775,7 @@ export type ListViewRootProps = {
|
|
|
762
775
|
expandable?: boolean;
|
|
763
776
|
onMoveItem?: (
|
|
764
777
|
sourceIndex: number,
|
|
765
|
-
|
|
778
|
+
targetIndex: number,
|
|
766
779
|
position: RelativeDropPosition
|
|
767
780
|
) => void;
|
|
768
781
|
indentation?: number;
|
|
@@ -778,6 +791,8 @@ export type ListViewRootProps = {
|
|
|
778
791
|
onDragEnter?: React.DragEventHandler<HTMLDivElement>;
|
|
779
792
|
onDragLeave?: React.DragEventHandler<HTMLDivElement>;
|
|
780
793
|
onDrop?: React.DragEventHandler<HTMLDivElement>;
|
|
794
|
+
renderEmptyState?: () => ReactNode;
|
|
795
|
+
getDropTargetParentIndex?: SortableItemContextProps["getDropTargetParentIndex"];
|
|
781
796
|
};
|
|
782
797
|
|
|
783
798
|
const ListViewRootInner = forwardRefGeneric(function ListViewRootInner<T>(
|
|
@@ -811,6 +826,8 @@ const ListViewRootInner = forwardRefGeneric(function ListViewRootInner<T>(
|
|
|
811
826
|
onDragEnter,
|
|
812
827
|
onDragLeave,
|
|
813
828
|
onDrop,
|
|
829
|
+
renderEmptyState,
|
|
830
|
+
getDropTargetParentIndex,
|
|
814
831
|
}: RenderProps<T> & ListViewRootProps,
|
|
815
832
|
forwardedRef: ForwardedRef<IVirtualizedList>
|
|
816
833
|
) {
|
|
@@ -832,7 +849,7 @@ const ListViewRootInner = forwardRefGeneric(function ListViewRootInner<T>(
|
|
|
832
849
|
);
|
|
833
850
|
|
|
834
851
|
const renderChild = useCallback(
|
|
835
|
-
(index: number) => renderItem(data[index], index, {
|
|
852
|
+
(index: number) => renderItem(data[index], index, { isDragOverlay: false }),
|
|
836
853
|
[data, renderItem]
|
|
837
854
|
);
|
|
838
855
|
|
|
@@ -947,7 +964,7 @@ const ListViewRootInner = forwardRefGeneric(function ListViewRootInner<T>(
|
|
|
947
964
|
);
|
|
948
965
|
|
|
949
966
|
const draggingContextOverlayValue = useMemo(
|
|
950
|
-
() => ({ indentation,
|
|
967
|
+
(): ListViewDraggingContextValue => ({ indentation, isDragOverlay: true }),
|
|
951
968
|
[indentation]
|
|
952
969
|
);
|
|
953
970
|
|
|
@@ -955,7 +972,7 @@ const ListViewRootInner = forwardRefGeneric(function ListViewRootInner<T>(
|
|
|
955
972
|
(index: number) => (
|
|
956
973
|
<div style={{ opacity: 0.25 }}>
|
|
957
974
|
<ListViewDraggingContext.Provider value={draggingContextOverlayValue}>
|
|
958
|
-
{renderItem(data[index], index, {
|
|
975
|
+
{renderItem(data[index], index, { isDragOverlay: true })}
|
|
959
976
|
</ListViewDraggingContext.Provider>
|
|
960
977
|
</div>
|
|
961
978
|
),
|
|
@@ -987,6 +1004,7 @@ const ListViewRootInner = forwardRefGeneric(function ListViewRootInner<T>(
|
|
|
987
1004
|
keys={ids}
|
|
988
1005
|
renderOverlay={renderOverlay}
|
|
989
1006
|
acceptsDrop={acceptsDrop}
|
|
1007
|
+
getDropTargetParentIndex={getDropTargetParentIndex}
|
|
990
1008
|
>
|
|
991
1009
|
{children}
|
|
992
1010
|
</Sortable.Root>
|
|
@@ -1040,23 +1058,25 @@ const ListViewRootInner = forwardRefGeneric(function ListViewRootInner<T>(
|
|
|
1040
1058
|
onDragLeave={onDragLeave}
|
|
1041
1059
|
onDrop={onDrop}
|
|
1042
1060
|
>
|
|
1043
|
-
{
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1061
|
+
{data.length === 0 && renderEmptyState
|
|
1062
|
+
? renderEmptyState()
|
|
1063
|
+
: withScrollable((scrollElementRef: HTMLDivElement | null) =>
|
|
1064
|
+
withSortable(
|
|
1065
|
+
virtualized ? (
|
|
1066
|
+
<VirtualizedList<T>
|
|
1067
|
+
ref={forwardedRef}
|
|
1068
|
+
scrollElement={scrollElementRef!}
|
|
1069
|
+
items={data}
|
|
1070
|
+
size={virtualized}
|
|
1071
|
+
getItemHeight={getItemHeight}
|
|
1072
|
+
keyExtractor={getKey}
|
|
1073
|
+
renderItem={renderWrappedChild}
|
|
1074
|
+
/>
|
|
1075
|
+
) : (
|
|
1076
|
+
range(0, data.length).map(renderWrappedChild)
|
|
1077
|
+
)
|
|
1078
|
+
)
|
|
1079
|
+
)}
|
|
1060
1080
|
</div>
|
|
1061
1081
|
</ListViewDraggingContext.Provider>
|
|
1062
1082
|
);
|
|
@@ -1136,5 +1156,5 @@ export namespace ListView {
|
|
|
1136
1156
|
menuItemsCount * rowHeight + sectionHeaderCount * sectionHeaderLabelHeight
|
|
1137
1157
|
);
|
|
1138
1158
|
};
|
|
1139
|
-
export const
|
|
1159
|
+
export const normalizeTargetIndex = normalizeListTargetIndex;
|
|
1140
1160
|
}
|