@noya-app/noya-designsystem 0.1.45 → 0.1.47
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 +15 -0
- package/dist/index.css +1 -1
- package/dist/index.d.mts +1149 -1064
- package/dist/index.d.ts +1149 -1064
- package/dist/index.js +10161 -9662
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +10465 -9972
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/combobox.test.ts +137 -89
- package/src/components/Breadcrumbs.tsx +29 -0
- package/src/components/Collection.tsx +74 -0
- package/src/components/Combobox.tsx +69 -52
- package/src/components/ComboboxMenu.tsx +37 -19
- package/src/components/Dialog.tsx +11 -19
- package/src/components/DropdownMenu.tsx +0 -1
- package/src/components/EditableText.tsx +203 -0
- package/src/components/Grid.tsx +243 -0
- package/src/components/GridView.tsx +86 -96
- package/src/components/List.tsx +268 -0
- package/src/components/ListView.tsx +10 -9
- package/src/components/SearchCompletionMenu.tsx +15 -31
- package/src/components/SegmentedControl.tsx +7 -4
- package/src/components/SelectMenu.tsx +9 -5
- package/src/components/Switch.tsx +1 -1
- package/src/components/Toolbar.tsx +5 -4
- package/src/components/TreeView.tsx +1 -0
- package/src/components/WorkspaceLayout.tsx +28 -4
- package/src/components/internal/Menu.tsx +55 -14
- package/src/components/internal/MenuViewport.tsx +78 -77
- package/src/contexts/DialogContext.tsx +53 -7
- package/src/index.css +2 -1
- package/src/index.tsx +6 -4
- package/src/utils/combobox.ts +115 -103
- package/src/utils/createSectionedMenu.ts +3 -9
- package/src/utils/fuzzyScorer.ts +11 -8
- package/src/utils/selection.ts +16 -4
- package/src/components/SidebarList.tsx +0 -252
|
@@ -1,252 +0,0 @@
|
|
|
1
|
-
import { useFileDropTarget } from "@noya-app/react-utils";
|
|
2
|
-
import React, { useRef, useState } from "react";
|
|
3
|
-
import { cssVars } from "../theme";
|
|
4
|
-
import { cx } from "../utils/classNames";
|
|
5
|
-
import { updateSelection } from "../utils/selection";
|
|
6
|
-
import { MenuItem } from "./internal/Menu";
|
|
7
|
-
import type { ListViewRootProps } from "./ListView";
|
|
8
|
-
import { ListView } from "./ListView";
|
|
9
|
-
import { TreeView } from "./TreeView";
|
|
10
|
-
|
|
11
|
-
interface SidebarListProps<T, M extends string = string> {
|
|
12
|
-
className?: string;
|
|
13
|
-
items: T[];
|
|
14
|
-
getId: (item: T) => string;
|
|
15
|
-
getName: (item: T) => string;
|
|
16
|
-
getExpanded?: (item: T) => boolean | undefined;
|
|
17
|
-
expandable?: boolean;
|
|
18
|
-
menuItems?: MenuItem<M>[];
|
|
19
|
-
onSelectMenuItem?: (action: M, selectedItems: T[]) => void;
|
|
20
|
-
onRename?: (itemId: string, newName: string) => void;
|
|
21
|
-
renderThumbnail?: (item: T) => React.ReactNode;
|
|
22
|
-
renderHoverAction?: (item: T, selected: boolean) => React.ReactNode;
|
|
23
|
-
renderDetail?: (item: T) => React.ReactNode;
|
|
24
|
-
onSelectionChange?: (selectedItems: T[]) => void;
|
|
25
|
-
scrollable?: boolean;
|
|
26
|
-
itemRoleDescription?: string;
|
|
27
|
-
/** Position of the detail content. Defaults to 'end'. */
|
|
28
|
-
detailPosition?: "end" | "below";
|
|
29
|
-
/** Size of the list items. Defaults to 'medium'. */
|
|
30
|
-
size?: "medium" | "large";
|
|
31
|
-
/** For testing: Override the hover state with a specific item ID */
|
|
32
|
-
testHoveredId?: string;
|
|
33
|
-
/** For testing: Override the renaming state with a specific item ID */
|
|
34
|
-
testRenamingId?: string;
|
|
35
|
-
setExpanded?: (item: T, expanded: boolean) => void;
|
|
36
|
-
acceptsDrop?: ListViewRootProps["acceptsDrop"];
|
|
37
|
-
onMoveItem?: ListViewRootProps["onMoveItem"];
|
|
38
|
-
getDepth?: (item: T) => number;
|
|
39
|
-
onFilesDrop?: (event: React.DragEvent<Element>) => void;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function SidebarList<T, M extends string = string>({
|
|
43
|
-
className,
|
|
44
|
-
items,
|
|
45
|
-
getId,
|
|
46
|
-
getName,
|
|
47
|
-
expandable,
|
|
48
|
-
getExpanded,
|
|
49
|
-
getDepth,
|
|
50
|
-
setExpanded,
|
|
51
|
-
menuItems,
|
|
52
|
-
onSelectMenuItem,
|
|
53
|
-
onRename,
|
|
54
|
-
renderThumbnail,
|
|
55
|
-
renderHoverAction,
|
|
56
|
-
renderDetail,
|
|
57
|
-
onSelectionChange,
|
|
58
|
-
itemRoleDescription = "clickable item",
|
|
59
|
-
detailPosition = "end",
|
|
60
|
-
size = "medium",
|
|
61
|
-
testHoveredId,
|
|
62
|
-
testRenamingId,
|
|
63
|
-
scrollable,
|
|
64
|
-
acceptsDrop,
|
|
65
|
-
onMoveItem,
|
|
66
|
-
onFilesDrop,
|
|
67
|
-
}: SidebarListProps<T, M>) {
|
|
68
|
-
const [selectedIds, setSelectedIds] = useState<string[]>([]);
|
|
69
|
-
const [internalHoveredId, setHoveredId] = useState<string>();
|
|
70
|
-
const [internalRenamingId, setRenamingId] = useState<string>();
|
|
71
|
-
|
|
72
|
-
const hoveredId = internalHoveredId ?? testHoveredId;
|
|
73
|
-
const renamingId = testRenamingId ?? internalRenamingId;
|
|
74
|
-
|
|
75
|
-
const handleSelect = (itemId: string, event?: ListView.ClickInfo) => {
|
|
76
|
-
const newSelectedIds = updateSelection(
|
|
77
|
-
items.map(getId),
|
|
78
|
-
selectedIds,
|
|
79
|
-
itemId,
|
|
80
|
-
event
|
|
81
|
-
);
|
|
82
|
-
setSelectedIds(newSelectedIds);
|
|
83
|
-
|
|
84
|
-
if (onSelectionChange) {
|
|
85
|
-
const selectedItems = items.filter((item) =>
|
|
86
|
-
newSelectedIds.includes(getId(item))
|
|
87
|
-
);
|
|
88
|
-
onSelectionChange(selectedItems);
|
|
89
|
-
}
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
const handleMenuAction = (action: M) => {
|
|
93
|
-
if (!onSelectMenuItem) return;
|
|
94
|
-
|
|
95
|
-
const selectedItems = items.filter((item) =>
|
|
96
|
-
selectedIds.includes(getId(item))
|
|
97
|
-
);
|
|
98
|
-
onSelectMenuItem(action, selectedItems);
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
const ViewComponent = expandable ? TreeView : ListView;
|
|
102
|
-
|
|
103
|
-
const fileDropTargetRef = useRef<HTMLDivElement>(null);
|
|
104
|
-
const { isDropTargetActive, dropTargetProps } = useFileDropTarget(
|
|
105
|
-
fileDropTargetRef,
|
|
106
|
-
onFilesDrop
|
|
107
|
-
);
|
|
108
|
-
|
|
109
|
-
return (
|
|
110
|
-
<ViewComponent.Root
|
|
111
|
-
variant="bare"
|
|
112
|
-
className={cx(className, isDropTargetActive && "bg-primary-pastel")}
|
|
113
|
-
containerRef={fileDropTargetRef}
|
|
114
|
-
scrollable={scrollable}
|
|
115
|
-
data={items}
|
|
116
|
-
keyExtractor={getId}
|
|
117
|
-
role="listbox"
|
|
118
|
-
aria-orientation="vertical"
|
|
119
|
-
aria-multiselectable={true}
|
|
120
|
-
expandable={expandable}
|
|
121
|
-
sortable={expandable}
|
|
122
|
-
acceptsDrop={acceptsDrop}
|
|
123
|
-
onMoveItem={onMoveItem}
|
|
124
|
-
{...dropTargetProps}
|
|
125
|
-
renderItem={(
|
|
126
|
-
item: T,
|
|
127
|
-
_: number,
|
|
128
|
-
{ isDragging }: { isDragging: boolean }
|
|
129
|
-
) => {
|
|
130
|
-
const id = getId(item);
|
|
131
|
-
const isHovered = hoveredId === id && !isDragging;
|
|
132
|
-
const expanded = getExpanded?.(item);
|
|
133
|
-
const isSelected = selectedIds.includes(id);
|
|
134
|
-
const isRenaming = renamingId === id;
|
|
135
|
-
|
|
136
|
-
const thumbnail = renderThumbnail?.(item);
|
|
137
|
-
const action =
|
|
138
|
-
isHovered && !isRenaming && renderHoverAction?.(item, isSelected);
|
|
139
|
-
const end =
|
|
140
|
-
action ?? (detailPosition === "end" && renderDetail?.(item));
|
|
141
|
-
const below = detailPosition === "below" && renderDetail?.(item);
|
|
142
|
-
|
|
143
|
-
return (
|
|
144
|
-
<ViewComponent.Row
|
|
145
|
-
id={id}
|
|
146
|
-
key={id}
|
|
147
|
-
role="option"
|
|
148
|
-
sortable={expandable}
|
|
149
|
-
aria-roledescription={itemRoleDescription}
|
|
150
|
-
aria-label={getName(item)}
|
|
151
|
-
aria-selected={isSelected}
|
|
152
|
-
className="cursor-pointer p-1"
|
|
153
|
-
chevronClassName={expandable ? "relative left-2" : undefined}
|
|
154
|
-
hovered={isHovered}
|
|
155
|
-
selected={isSelected}
|
|
156
|
-
expanded={expanded}
|
|
157
|
-
depth={getDepth?.(item)}
|
|
158
|
-
onHoverChange={(hovered: boolean) =>
|
|
159
|
-
setHoveredId(hovered ? id : undefined)
|
|
160
|
-
}
|
|
161
|
-
onPress={(e: ListView.ClickInfo) => handleSelect(id, e)}
|
|
162
|
-
onKeyDown={(e: React.KeyboardEvent) => {
|
|
163
|
-
if (e.key !== "Enter" && e.key !== " ") return;
|
|
164
|
-
|
|
165
|
-
e.preventDefault();
|
|
166
|
-
handleSelect(id, e);
|
|
167
|
-
}}
|
|
168
|
-
menuItems={menuItems}
|
|
169
|
-
onSelectMenuItem={handleMenuAction}
|
|
170
|
-
onMenuOpenChange={(open: boolean) => {
|
|
171
|
-
if (open && !isSelected) {
|
|
172
|
-
handleSelect(id, {
|
|
173
|
-
shiftKey: false,
|
|
174
|
-
altKey: false,
|
|
175
|
-
metaKey: false,
|
|
176
|
-
ctrlKey: false,
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
}}
|
|
180
|
-
tabIndex={0}
|
|
181
|
-
onClickChevron={() => {
|
|
182
|
-
if (expanded === undefined) return;
|
|
183
|
-
setExpanded?.(item, !expanded);
|
|
184
|
-
}}
|
|
185
|
-
>
|
|
186
|
-
<div className="flex flex-1 items-center px-2 gap-3">
|
|
187
|
-
{thumbnail && (
|
|
188
|
-
<div
|
|
189
|
-
className={cx(
|
|
190
|
-
"rounded overflow-hidden flex-none flex items-center justify-center",
|
|
191
|
-
size === "medium" ? "w-8 h-8" : "w-16 h-16"
|
|
192
|
-
)}
|
|
193
|
-
style={{
|
|
194
|
-
backgroundColor: cssVars.colors.inputBackground,
|
|
195
|
-
}}
|
|
196
|
-
tabIndex={-1}
|
|
197
|
-
>
|
|
198
|
-
{thumbnail}
|
|
199
|
-
</div>
|
|
200
|
-
)}
|
|
201
|
-
<div className="flex flex-col flex-1 w-0">
|
|
202
|
-
<div className="flex items-center min-h-[27px] flex-1 gap-2">
|
|
203
|
-
<div className="flex-1 w-0 flex items-center" tabIndex={-1}>
|
|
204
|
-
{isRenaming ? (
|
|
205
|
-
<ViewComponent.EditableRowTitle
|
|
206
|
-
value={getName(item)}
|
|
207
|
-
placeholder="Enter name"
|
|
208
|
-
className={cx(
|
|
209
|
-
"font-medium",
|
|
210
|
-
testRenamingId && "bg-primary-pastel"
|
|
211
|
-
)}
|
|
212
|
-
autoFocus
|
|
213
|
-
onSubmitEditing={(value: string) => {
|
|
214
|
-
if (value !== getName(item) && onRename) {
|
|
215
|
-
onRename(id, value);
|
|
216
|
-
}
|
|
217
|
-
setRenamingId(undefined);
|
|
218
|
-
}}
|
|
219
|
-
/>
|
|
220
|
-
) : (
|
|
221
|
-
<ViewComponent.RowTitle
|
|
222
|
-
className="font-medium"
|
|
223
|
-
onDoubleClick={(e: React.MouseEvent) => {
|
|
224
|
-
e.stopPropagation();
|
|
225
|
-
if (onRename) {
|
|
226
|
-
setRenamingId(id);
|
|
227
|
-
}
|
|
228
|
-
}}
|
|
229
|
-
>
|
|
230
|
-
{getName(item)}
|
|
231
|
-
</ViewComponent.RowTitle>
|
|
232
|
-
)}
|
|
233
|
-
</div>
|
|
234
|
-
{end && (
|
|
235
|
-
<div className="flex items-center gap-2" tabIndex={-1}>
|
|
236
|
-
{end}
|
|
237
|
-
</div>
|
|
238
|
-
)}
|
|
239
|
-
</div>
|
|
240
|
-
{below && (
|
|
241
|
-
<div className="flex items-center" tabIndex={-1}>
|
|
242
|
-
{below}
|
|
243
|
-
</div>
|
|
244
|
-
)}
|
|
245
|
-
</div>
|
|
246
|
-
</div>
|
|
247
|
-
</ViewComponent.Row>
|
|
248
|
-
);
|
|
249
|
-
}}
|
|
250
|
-
/>
|
|
251
|
-
);
|
|
252
|
-
}
|