@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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noya-app/noya-designsystem",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.49",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"module": "./dist/index.mjs",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -20,12 +20,11 @@
|
|
|
20
20
|
"lint": "eslint . --max-warnings 0"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@dnd-kit/core": "3.1
|
|
24
|
-
"@dnd-kit/
|
|
25
|
-
"@
|
|
26
|
-
"@noya-app/noya-
|
|
27
|
-
"@noya-app/noya-
|
|
28
|
-
"@noya-app/noya-geometry": "0.1.7",
|
|
23
|
+
"@dnd-kit/core": "6.3.1",
|
|
24
|
+
"@dnd-kit/sortable": "10.0.0",
|
|
25
|
+
"@noya-app/noya-colorpicker": "0.1.18",
|
|
26
|
+
"@noya-app/noya-utils": "0.1.4",
|
|
27
|
+
"@noya-app/noya-geometry": "0.1.9",
|
|
29
28
|
"@noya-app/noya-icons": "0.1.6",
|
|
30
29
|
"@noya-app/noya-keymap": "0.1.3",
|
|
31
30
|
"@radix-ui/primitive": "^1.0.0",
|
|
@@ -51,6 +50,7 @@
|
|
|
51
50
|
"vscode-fuzzy-scorer": "^0.0.4"
|
|
52
51
|
},
|
|
53
52
|
"devDependencies": {
|
|
53
|
+
"@tailwindcss/container-queries": "^0.1.1",
|
|
54
54
|
"@types/react": "*",
|
|
55
55
|
"@types/react-dom": "*",
|
|
56
56
|
"@types/react-virtualized": "^9.21.21",
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
defaultAcceptsDrop,
|
|
4
|
+
validateDropIndicator,
|
|
5
|
+
} from "../components/Sortable";
|
|
6
|
+
|
|
7
|
+
describe("defaultAcceptsDrop", () => {
|
|
8
|
+
it("should return false if the active item is the same as the over item", () => {
|
|
9
|
+
const result = defaultAcceptsDrop(0, 0, "inside");
|
|
10
|
+
expect(result).toBe(false);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("should return false for 'inside' position", () => {
|
|
14
|
+
const result = defaultAcceptsDrop(0, 1, "inside");
|
|
15
|
+
expect(result).toBe(false);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("should return false when dropping above the same item", () => {
|
|
19
|
+
const result = defaultAcceptsDrop(0, 0, "above");
|
|
20
|
+
expect(result).toBe(false);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("should return false when dropping below the same item", () => {
|
|
24
|
+
const result = defaultAcceptsDrop(0, 0, "below");
|
|
25
|
+
expect(result).toBe(false);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("should return false when dropping above the item below the source", () => {
|
|
29
|
+
const result = defaultAcceptsDrop(0, 1, "above");
|
|
30
|
+
expect(result).toBe(false);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("should return false when dropping below the item above the source", () => {
|
|
34
|
+
const result = defaultAcceptsDrop(1, 0, "below");
|
|
35
|
+
expect(result).toBe(false);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("should return true when dropping above a valid target", () => {
|
|
39
|
+
const result = defaultAcceptsDrop(0, 2, "above");
|
|
40
|
+
expect(result).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("should return true when dropping below a valid target", () => {
|
|
44
|
+
const result = defaultAcceptsDrop(2, 0, "below");
|
|
45
|
+
expect(result).toBe(true);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe("validateDropIndicator", () => {
|
|
50
|
+
const mockAcceptsDrop = (
|
|
51
|
+
sourceIndex: number,
|
|
52
|
+
targetIndex: number,
|
|
53
|
+
position: "above" | "below" | "inside"
|
|
54
|
+
) => {
|
|
55
|
+
// Mock that only allows dropping inside
|
|
56
|
+
if (position === "inside") return true;
|
|
57
|
+
return false;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const mockAcceptsDropAboveBelow = (
|
|
61
|
+
sourceIndex: number,
|
|
62
|
+
targetIndex: number,
|
|
63
|
+
position: "above" | "below" | "inside"
|
|
64
|
+
) => {
|
|
65
|
+
// Mock that only allows dropping above/below
|
|
66
|
+
if (position === "inside") return false;
|
|
67
|
+
return true;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const mockAcceptsAll = (
|
|
71
|
+
sourceIndex: number,
|
|
72
|
+
targetIndex: number,
|
|
73
|
+
position: "above" | "below" | "inside"
|
|
74
|
+
) => {
|
|
75
|
+
// Mock that allows all drop positions
|
|
76
|
+
return true;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const mockAcceptsAboveAndInside = (
|
|
80
|
+
sourceIndex: number,
|
|
81
|
+
targetIndex: number,
|
|
82
|
+
position: "above" | "below" | "inside"
|
|
83
|
+
) => {
|
|
84
|
+
// Mock that allows dropping above and inside, but not below
|
|
85
|
+
return position !== "below";
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const keys = ["item1", "item2", "item3"];
|
|
89
|
+
|
|
90
|
+
describe("with above and inside positions allowed, but not below", () => {
|
|
91
|
+
it("should return 'inside' when cursor is in the middle third", () => {
|
|
92
|
+
const result = validateDropIndicator({
|
|
93
|
+
acceptsDrop: mockAcceptsAboveAndInside,
|
|
94
|
+
keys,
|
|
95
|
+
activeId: "item1",
|
|
96
|
+
overId: "item2",
|
|
97
|
+
offsetStart: 50, // offsetStart in middle third
|
|
98
|
+
elementStart: 0, // elementStart
|
|
99
|
+
elementSize: 100, // elementSize
|
|
100
|
+
});
|
|
101
|
+
expect(result).toBe("inside");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("should return 'above' when cursor is in the top third", () => {
|
|
105
|
+
const result = validateDropIndicator({
|
|
106
|
+
acceptsDrop: mockAcceptsAboveAndInside,
|
|
107
|
+
keys,
|
|
108
|
+
activeId: "item1",
|
|
109
|
+
overId: "item2",
|
|
110
|
+
offsetStart: 20, // offsetStart in top third
|
|
111
|
+
elementStart: 0, // elementStart
|
|
112
|
+
elementSize: 100, // elementSize
|
|
113
|
+
});
|
|
114
|
+
expect(result).toBe("above");
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("should fallback to 'inside' when cursor is in bottom third since 'below' is not allowed", () => {
|
|
118
|
+
const result = validateDropIndicator({
|
|
119
|
+
acceptsDrop: mockAcceptsAboveAndInside,
|
|
120
|
+
keys,
|
|
121
|
+
activeId: "item1",
|
|
122
|
+
overId: "item2",
|
|
123
|
+
offsetStart: 80, // offsetStart in bottom third
|
|
124
|
+
elementStart: 0, // elementStart
|
|
125
|
+
elementSize: 100, // elementSize
|
|
126
|
+
});
|
|
127
|
+
expect(result).toBe("inside");
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe("with all drop positions allowed", () => {
|
|
132
|
+
it("should prefer 'inside' when cursor is in the middle third", () => {
|
|
133
|
+
const result = validateDropIndicator({
|
|
134
|
+
acceptsDrop: mockAcceptsAll,
|
|
135
|
+
keys,
|
|
136
|
+
activeId: "item1",
|
|
137
|
+
overId: "item2",
|
|
138
|
+
offsetStart: 50, // offsetStart in middle third
|
|
139
|
+
elementStart: 0, // elementStart
|
|
140
|
+
elementSize: 100, // elementSize
|
|
141
|
+
});
|
|
142
|
+
expect(result).toBe("inside");
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("should prefer 'above' when cursor is in the top third", () => {
|
|
146
|
+
const result = validateDropIndicator({
|
|
147
|
+
acceptsDrop: mockAcceptsAll,
|
|
148
|
+
keys,
|
|
149
|
+
activeId: "item1",
|
|
150
|
+
overId: "item2",
|
|
151
|
+
offsetStart: 20, // offsetStart in top third
|
|
152
|
+
elementStart: 0, // elementStart
|
|
153
|
+
elementSize: 100, // elementSize
|
|
154
|
+
});
|
|
155
|
+
expect(result).toBe("above");
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("should prefer 'below' when cursor is in the bottom third", () => {
|
|
159
|
+
const result = validateDropIndicator({
|
|
160
|
+
acceptsDrop: mockAcceptsAll,
|
|
161
|
+
keys,
|
|
162
|
+
activeId: "item1",
|
|
163
|
+
overId: "item2",
|
|
164
|
+
offsetStart: 80, // offsetStart in bottom third
|
|
165
|
+
elementStart: 0, // elementStart
|
|
166
|
+
elementSize: 100, // elementSize
|
|
167
|
+
});
|
|
168
|
+
expect(result).toBe("below");
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("should handle non-zero elementStart position", () => {
|
|
172
|
+
const result = validateDropIndicator({
|
|
173
|
+
acceptsDrop: mockAcceptsAll,
|
|
174
|
+
keys,
|
|
175
|
+
activeId: "item1",
|
|
176
|
+
overId: "item2",
|
|
177
|
+
offsetStart: 120, // offsetStart in middle third relative to elementStart
|
|
178
|
+
elementStart: 100, // elementStart
|
|
179
|
+
elementSize: 60, // elementSize
|
|
180
|
+
});
|
|
181
|
+
expect(result).toBe("inside");
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("should return 'inside' when cursor is in the middle third and inside drops are allowed", () => {
|
|
186
|
+
const result = validateDropIndicator({
|
|
187
|
+
acceptsDrop: mockAcceptsDrop,
|
|
188
|
+
keys,
|
|
189
|
+
activeId: "item1",
|
|
190
|
+
overId: "item2",
|
|
191
|
+
offsetStart: 50, // offsetStart in middle third
|
|
192
|
+
elementStart: 0, // elementStart
|
|
193
|
+
elementSize: 100, // elementSize
|
|
194
|
+
});
|
|
195
|
+
expect(result).toBe("inside");
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("should return 'above' when cursor is in top half and above drops are allowed", () => {
|
|
199
|
+
const result = validateDropIndicator({
|
|
200
|
+
acceptsDrop: mockAcceptsDropAboveBelow,
|
|
201
|
+
keys,
|
|
202
|
+
activeId: "item1",
|
|
203
|
+
overId: "item2",
|
|
204
|
+
offsetStart: 20, // offsetStart in top half
|
|
205
|
+
elementStart: 0, // elementStart
|
|
206
|
+
elementSize: 100, // elementSize
|
|
207
|
+
});
|
|
208
|
+
expect(result).toBe("above");
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it("should return 'below' when cursor is in bottom half and below drops are allowed", () => {
|
|
212
|
+
const result = validateDropIndicator({
|
|
213
|
+
acceptsDrop: mockAcceptsDropAboveBelow,
|
|
214
|
+
keys,
|
|
215
|
+
activeId: "item1",
|
|
216
|
+
overId: "item2",
|
|
217
|
+
offsetStart: 70, // offsetStart in bottom half
|
|
218
|
+
elementStart: 0, // elementStart
|
|
219
|
+
elementSize: 100, // elementSize
|
|
220
|
+
});
|
|
221
|
+
expect(result).toBe("below");
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("should fallback to inside when above/below not allowed and inside is allowed", () => {
|
|
225
|
+
const result = validateDropIndicator({
|
|
226
|
+
acceptsDrop: mockAcceptsDrop,
|
|
227
|
+
keys,
|
|
228
|
+
activeId: "item1",
|
|
229
|
+
overId: "item2",
|
|
230
|
+
offsetStart: 20, // offsetStart in top half
|
|
231
|
+
elementStart: 0, // elementStart
|
|
232
|
+
elementSize: 100, // elementSize
|
|
233
|
+
});
|
|
234
|
+
expect(result).toBe("inside");
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it("should return undefined when no drop positions are allowed", () => {
|
|
238
|
+
const neverAllowDrop = () => false;
|
|
239
|
+
const result = validateDropIndicator({
|
|
240
|
+
acceptsDrop: neverAllowDrop,
|
|
241
|
+
keys,
|
|
242
|
+
activeId: "item1",
|
|
243
|
+
overId: "item2",
|
|
244
|
+
offsetStart: 50,
|
|
245
|
+
elementStart: 0,
|
|
246
|
+
elementSize: 100,
|
|
247
|
+
});
|
|
248
|
+
expect(result).toBeUndefined();
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it("should ignore items not in the keys array", () => {
|
|
252
|
+
const result = validateDropIndicator({
|
|
253
|
+
acceptsDrop: mockAcceptsDrop,
|
|
254
|
+
keys,
|
|
255
|
+
activeId: "nonexistent",
|
|
256
|
+
overId: "item2",
|
|
257
|
+
offsetStart: 50,
|
|
258
|
+
elementStart: 0,
|
|
259
|
+
elementSize: 100,
|
|
260
|
+
});
|
|
261
|
+
expect(result).toBeUndefined();
|
|
262
|
+
});
|
|
263
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { memoGeneric } from "@noya-app/react-utils";
|
|
2
|
+
import React, { useMemo } from "react";
|
|
3
|
+
import { cssVars } from "../theme";
|
|
4
|
+
import { DropdownMenu } from "./DropdownMenu";
|
|
5
|
+
import { IconButton } from "./IconButton";
|
|
6
|
+
import { MenuItem } from "./internal/Menu";
|
|
7
|
+
|
|
8
|
+
type ActionMenuProps<TMenu extends string> = {
|
|
9
|
+
menuItems: MenuItem<TMenu>[];
|
|
10
|
+
onSelect: (action: TMenu) => void;
|
|
11
|
+
selected: boolean;
|
|
12
|
+
onOpenChange: (open: boolean) => void;
|
|
13
|
+
className?: string;
|
|
14
|
+
style?: React.CSSProperties;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const ActionMenu = memoGeneric(function ActionMenu<
|
|
18
|
+
TMenu extends string,
|
|
19
|
+
>({
|
|
20
|
+
menuItems,
|
|
21
|
+
onSelect,
|
|
22
|
+
selected,
|
|
23
|
+
onOpenChange,
|
|
24
|
+
className,
|
|
25
|
+
style,
|
|
26
|
+
}: ActionMenuProps<TMenu>) {
|
|
27
|
+
const internalStyle = useMemo(() => {
|
|
28
|
+
return {
|
|
29
|
+
backgroundColor: selected
|
|
30
|
+
? cssVars.colors.primaryPastel
|
|
31
|
+
: cssVars.colors.inputBackground,
|
|
32
|
+
padding: "4px",
|
|
33
|
+
...style,
|
|
34
|
+
};
|
|
35
|
+
}, [selected, style]);
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<DropdownMenu
|
|
39
|
+
items={menuItems}
|
|
40
|
+
onSelect={onSelect}
|
|
41
|
+
onOpenChange={onOpenChange}
|
|
42
|
+
>
|
|
43
|
+
<IconButton
|
|
44
|
+
style={internalStyle}
|
|
45
|
+
className={className}
|
|
46
|
+
iconName="DotsVerticalIcon"
|
|
47
|
+
color={selected ? cssVars.colors.primary : undefined}
|
|
48
|
+
/>
|
|
49
|
+
</DropdownMenu>
|
|
50
|
+
);
|
|
51
|
+
});
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
+
import { cx } from "../utils/classNames";
|
|
2
3
|
|
|
3
4
|
interface Props {
|
|
4
5
|
size?: number;
|
|
5
6
|
opacity?: number;
|
|
6
7
|
color?: string;
|
|
7
8
|
trackColor?: string;
|
|
9
|
+
className?: string;
|
|
8
10
|
}
|
|
9
11
|
|
|
10
12
|
export const ActivityIndicator = React.memo(function ActivityIndicator({
|
|
@@ -12,6 +14,7 @@ export const ActivityIndicator = React.memo(function ActivityIndicator({
|
|
|
12
14
|
opacity = 1,
|
|
13
15
|
color = "black",
|
|
14
16
|
trackColor = "rgba(0, 0, 0, 0.1)",
|
|
17
|
+
className,
|
|
15
18
|
}: Props) {
|
|
16
19
|
const opacityStyle = React.useMemo(() => ({ opacity }), [opacity]);
|
|
17
20
|
const dynamicStyles = React.useMemo(
|
|
@@ -24,7 +27,10 @@ export const ActivityIndicator = React.memo(function ActivityIndicator({
|
|
|
24
27
|
[size, color, trackColor]
|
|
25
28
|
);
|
|
26
29
|
return (
|
|
27
|
-
<div
|
|
30
|
+
<div
|
|
31
|
+
className={cx("flex justify-center items-center", className)}
|
|
32
|
+
style={opacityStyle}
|
|
33
|
+
>
|
|
28
34
|
<div style={dynamicStyles} className="animate-spin rounded-[50%]" />
|
|
29
35
|
</div>
|
|
30
36
|
);
|
|
@@ -21,7 +21,7 @@ export function BaseToolbar({
|
|
|
21
21
|
return (
|
|
22
22
|
<div className="flex flex-col">
|
|
23
23
|
<div
|
|
24
|
-
className="flex items-center pr-2.5 bg-sidebar-background flex-none relative"
|
|
24
|
+
className="flex items-center pr-2.5 bg-sidebar-background flex-none relative @container/toolbar"
|
|
25
25
|
style={{
|
|
26
26
|
flex: `0 0 ${cssVars.spacing.toolbarHeight}`,
|
|
27
27
|
}}
|
|
@@ -39,7 +39,7 @@ export function BaseToolbar({
|
|
|
39
39
|
<Spacer.Horizontal size={10} />
|
|
40
40
|
</>
|
|
41
41
|
)}
|
|
42
|
-
<div className="flex items-center justify-center text-text-muted pointer-events-none
|
|
42
|
+
<div className="flex items-center justify-center text-text-muted pointer-events-none @2xl/toolbar:absolute inset-0">
|
|
43
43
|
<div
|
|
44
44
|
style={{
|
|
45
45
|
display: "flex",
|
package/src/components/Chip.tsx
CHANGED
|
@@ -52,16 +52,16 @@ const ICON_BASE_STYLES = "relative opacity-50 cursor-pointer hover:opacity-85";
|
|
|
52
52
|
|
|
53
53
|
const ICON_SIZE_STYLES = {
|
|
54
54
|
large: {
|
|
55
|
-
add: "ml-[-2px] mr-[2px] -top-[1px] scale-75",
|
|
56
|
-
delete: "mr-[-2px] ml-[2px] scale-75",
|
|
55
|
+
add: "ml-[-2px] mr-[2px] -top-[1px] scale-[0.75]",
|
|
56
|
+
delete: "mr-[-2px] ml-[2px] scale-[0.75]",
|
|
57
57
|
},
|
|
58
58
|
medium: {
|
|
59
|
-
add: "ml-[-2px] mr-[-2px] scale-60",
|
|
60
|
-
delete: "mr-[-2px] scale-60",
|
|
59
|
+
add: "ml-[-2px] mr-[-2px] scale-[0.60]",
|
|
60
|
+
delete: "mr-[-2px] scale-[0.60]",
|
|
61
61
|
},
|
|
62
62
|
small: {
|
|
63
|
-
add: "ml-[-2px] mr-[-2px] scale-60",
|
|
64
|
-
delete: "mr-[-2px] scale-60",
|
|
63
|
+
add: "ml-[-2px] mr-[-2px] scale-[0.60]",
|
|
64
|
+
delete: "mr-[-2px] scale-[0.60]",
|
|
65
65
|
},
|
|
66
66
|
} as const;
|
|
67
67
|
|
|
@@ -82,6 +82,7 @@ export interface ChipProps {
|
|
|
82
82
|
onHoverDeleteChange?: (hovering: boolean) => void;
|
|
83
83
|
style?: React.CSSProperties;
|
|
84
84
|
tabIndex?: number;
|
|
85
|
+
role?: string;
|
|
85
86
|
}
|
|
86
87
|
|
|
87
88
|
export const Chip = memo(
|
|
@@ -7,16 +7,30 @@ import { List } from "./List";
|
|
|
7
7
|
|
|
8
8
|
export type CollectionViewType = "grid" | "list";
|
|
9
9
|
|
|
10
|
+
export type CollectionThumbnailSize = "auto" | "custom";
|
|
11
|
+
|
|
10
12
|
export type CollectionRef = {
|
|
11
13
|
editName: (id: string) => void;
|
|
12
14
|
};
|
|
13
15
|
|
|
16
|
+
export type CollectionThumbnailProps<T> = {
|
|
17
|
+
item: T;
|
|
18
|
+
selected: boolean;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type CollectionRenderActionProps<T> = {
|
|
22
|
+
item: T;
|
|
23
|
+
selected: boolean;
|
|
24
|
+
onOpenChange: (open: boolean) => void;
|
|
25
|
+
};
|
|
26
|
+
|
|
14
27
|
export interface CollectionProps<T, M extends string = string> {
|
|
15
28
|
className?: string;
|
|
16
29
|
items: T[];
|
|
17
30
|
getId: (item: T) => string;
|
|
18
31
|
getName: (item: T) => string;
|
|
19
32
|
getExpanded?: (item: T) => boolean | undefined;
|
|
33
|
+
getPlaceholder?: (item: T) => string;
|
|
20
34
|
/**
|
|
21
35
|
* Whether directories should be expandable.
|
|
22
36
|
* @default true
|
|
@@ -30,8 +44,13 @@ export interface CollectionProps<T, M extends string = string> {
|
|
|
30
44
|
menuItems?: MenuItem<M>[];
|
|
31
45
|
onSelectMenuItem?: (action: M, selectedItems: T[]) => void;
|
|
32
46
|
onRename?: (item: T, newName: string) => void;
|
|
33
|
-
renderThumbnail?: (
|
|
34
|
-
|
|
47
|
+
renderThumbnail?: ({
|
|
48
|
+
item,
|
|
49
|
+
selected,
|
|
50
|
+
}: CollectionThumbnailProps<T>) => React.ReactNode;
|
|
51
|
+
renderAction?:
|
|
52
|
+
| "menu"
|
|
53
|
+
| ((props: CollectionRenderActionProps<T>) => React.ReactNode);
|
|
35
54
|
renderDetail?: (item: T, selected: boolean) => React.ReactNode;
|
|
36
55
|
/** Callback when selection changes. If not provided, selection will be disabled. */
|
|
37
56
|
onSelectionChange?: (
|
|
@@ -44,20 +63,29 @@ export interface CollectionProps<T, M extends string = string> {
|
|
|
44
63
|
detailPosition?: "end" | "below";
|
|
45
64
|
/** Size of the list items. Defaults to 'medium'. */
|
|
46
65
|
size?: GridViewSize;
|
|
66
|
+
/** Size of the thumbnail. Defaults to 'auto', which will be based on the size prop. */
|
|
67
|
+
thumbnailSize?: CollectionThumbnailSize;
|
|
47
68
|
/** For testing: Override the hover state with a specific item ID */
|
|
48
69
|
testHoveredId?: string;
|
|
49
70
|
/** For testing: Override the renaming state with a specific item ID */
|
|
50
71
|
testRenamingId?: string;
|
|
72
|
+
/** For testing: Override the drop indicator state with a specific item ID */
|
|
73
|
+
testShowDropIndicatorId?: string;
|
|
51
74
|
setExpanded?: (item: T, expanded: boolean) => void;
|
|
52
75
|
acceptsDrop?: ListViewRootProps["acceptsDrop"];
|
|
53
76
|
onMoveItem?: ListViewRootProps["onMoveItem"];
|
|
77
|
+
getDropTargetParentIndex?: ListViewRootProps["getDropTargetParentIndex"];
|
|
54
78
|
getDepth?: (item: T) => number;
|
|
55
79
|
onFilesDrop?: (event: React.DragEvent<Element>) => void;
|
|
56
80
|
getRenamable?: (item: T) => boolean;
|
|
57
81
|
/** Currently selected file IDs */
|
|
58
82
|
selectedIds?: string[];
|
|
59
83
|
viewType?: CollectionViewType;
|
|
84
|
+
onClickItem?: (itemId: string) => void;
|
|
60
85
|
onDoubleClickItem?: (itemId: string) => void;
|
|
86
|
+
renderEmptyState?: () => React.ReactElement;
|
|
87
|
+
/** @default false */
|
|
88
|
+
sortable?: boolean;
|
|
61
89
|
}
|
|
62
90
|
|
|
63
91
|
export const Collection = forwardRefGeneric(function Collection<
|
|
@@ -441,11 +441,37 @@ export const Combobox = memoGeneric(
|
|
|
441
441
|
[filteredItems]
|
|
442
442
|
);
|
|
443
443
|
|
|
444
|
+
const hasCheckedItems = items.some(
|
|
445
|
+
(item) => isSelectableMenuItem(item) && item.checked
|
|
446
|
+
);
|
|
447
|
+
|
|
444
448
|
return (
|
|
445
449
|
<InputField.Root
|
|
446
450
|
id={id}
|
|
447
451
|
size={size}
|
|
448
452
|
sideOffset={6}
|
|
453
|
+
end={
|
|
454
|
+
<>
|
|
455
|
+
{loading && (
|
|
456
|
+
<div className="pr-1.5">
|
|
457
|
+
<ActivityIndicator />
|
|
458
|
+
</div>
|
|
459
|
+
)}
|
|
460
|
+
{!readOnly && (
|
|
461
|
+
<InputField.Button
|
|
462
|
+
variant="floating"
|
|
463
|
+
onClick={handleChevronClick}
|
|
464
|
+
>
|
|
465
|
+
<DropdownChevronIcon
|
|
466
|
+
className={cx(
|
|
467
|
+
"transition-transform duration-100",
|
|
468
|
+
shouldShowMenu ? "rotate-180" : "rotate-0"
|
|
469
|
+
)}
|
|
470
|
+
/>
|
|
471
|
+
</InputField.Button>
|
|
472
|
+
)}
|
|
473
|
+
</>
|
|
474
|
+
}
|
|
449
475
|
renderPopoverContent={({ width }) => {
|
|
450
476
|
const height = Math.min(
|
|
451
477
|
ListView.calculateHeight({
|
|
@@ -475,6 +501,7 @@ export const Combobox = memoGeneric(
|
|
|
475
501
|
style={{
|
|
476
502
|
flex: `0 0 ${height}px`,
|
|
477
503
|
}}
|
|
504
|
+
indented={hasCheckedItems}
|
|
478
505
|
/>
|
|
479
506
|
) : (
|
|
480
507
|
<div className="flex flex-col h-[50px] p-5 items-center justify-center">
|
|
@@ -519,21 +546,6 @@ export const Combobox = memoGeneric(
|
|
|
519
546
|
/>
|
|
520
547
|
)}
|
|
521
548
|
{children}
|
|
522
|
-
{loading && (
|
|
523
|
-
<InputField.Label htmlFor={id}>
|
|
524
|
-
<ActivityIndicator />
|
|
525
|
-
</InputField.Label>
|
|
526
|
-
)}
|
|
527
|
-
{!readOnly && (
|
|
528
|
-
<InputField.Button variant="floating" onClick={handleChevronClick}>
|
|
529
|
-
<DropdownChevronIcon
|
|
530
|
-
className={cx(
|
|
531
|
-
"transition-transform duration-100",
|
|
532
|
-
shouldShowMenu ? "rotate-180" : "rotate-0"
|
|
533
|
-
)}
|
|
534
|
-
/>
|
|
535
|
-
</InputField.Button>
|
|
536
|
-
)}
|
|
537
549
|
</InputField.Root>
|
|
538
550
|
);
|
|
539
551
|
})
|
|
@@ -9,6 +9,7 @@ import { renderIcon } from "./Icons";
|
|
|
9
9
|
import { IVirtualizedList, ListView } from "./ListView";
|
|
10
10
|
import { Spacer } from "./Spacer";
|
|
11
11
|
import {
|
|
12
|
+
CHECKBOX_INDENT_WIDTH,
|
|
12
13
|
CHECKBOX_RIGHT_INSET,
|
|
13
14
|
CHECKBOX_WIDTH,
|
|
14
15
|
KeyboardShortcut,
|
|
@@ -23,6 +24,7 @@ interface ComboboxMenuProps<T extends string> {
|
|
|
23
24
|
onHoverIndex: (index: number) => void;
|
|
24
25
|
listSize: Size;
|
|
25
26
|
style?: React.CSSProperties;
|
|
27
|
+
indented?: boolean;
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
export const ComboboxMenu = memoGeneric(
|
|
@@ -34,13 +36,10 @@ export const ComboboxMenu = memoGeneric(
|
|
|
34
36
|
onHoverIndex,
|
|
35
37
|
listSize,
|
|
36
38
|
style,
|
|
39
|
+
indented,
|
|
37
40
|
}: ComboboxMenuProps<T>,
|
|
38
41
|
forwardedRef: ForwardedRef<IVirtualizedList>
|
|
39
42
|
) {
|
|
40
|
-
const hasCheckedItems = items
|
|
41
|
-
.filter((item) => item.type === undefined)
|
|
42
|
-
.some((item) => item.checked);
|
|
43
|
-
|
|
44
43
|
return (
|
|
45
44
|
<ListView.Root
|
|
46
45
|
ref={forwardedRef}
|
|
@@ -59,6 +58,15 @@ export const ComboboxMenu = memoGeneric(
|
|
|
59
58
|
if (item.type === "sectionHeader") {
|
|
60
59
|
return (
|
|
61
60
|
<ListView.Row key={item.id} isSectionHeader>
|
|
61
|
+
{indented && (
|
|
62
|
+
<Spacer.Horizontal
|
|
63
|
+
size={
|
|
64
|
+
CHECKBOX_WIDTH -
|
|
65
|
+
CHECKBOX_RIGHT_INSET +
|
|
66
|
+
CHECKBOX_INDENT_WIDTH
|
|
67
|
+
}
|
|
68
|
+
/>
|
|
69
|
+
)}
|
|
62
70
|
{item.title}
|
|
63
71
|
</ListView.Row>
|
|
64
72
|
);
|
|
@@ -82,11 +90,12 @@ export const ComboboxMenu = memoGeneric(
|
|
|
82
90
|
}}
|
|
83
91
|
>
|
|
84
92
|
<div className="flex flex-1 min-w-0 items-center">
|
|
93
|
+
{indented && <Spacer.Horizontal size={CHECKBOX_INDENT_WIDTH} />}
|
|
85
94
|
{item.checked ? (
|
|
86
|
-
<div
|
|
95
|
+
<div {...styles.itemIndicator}>
|
|
87
96
|
<CheckIcon />
|
|
88
97
|
</div>
|
|
89
|
-
) :
|
|
98
|
+
) : indented ? (
|
|
90
99
|
<Spacer.Horizontal
|
|
91
100
|
size={CHECKBOX_WIDTH - CHECKBOX_RIGHT_INSET}
|
|
92
101
|
/>
|
|
@@ -25,22 +25,27 @@ const StyledOverlay = forwardRef<
|
|
|
25
25
|
const StyledContent = forwardRef<
|
|
26
26
|
React.ElementRef<typeof DialogPrimitive.Content>,
|
|
27
27
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
|
28
|
-
>(({ className, ...props }, ref) =>
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
>(({ className, children, ...props }, ref) => {
|
|
29
|
+
return (
|
|
30
|
+
<DialogPrimitive.Content
|
|
31
|
+
ref={ref}
|
|
32
|
+
className={cx(
|
|
33
|
+
`
|
|
33
34
|
fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2
|
|
34
35
|
w-[90vw] max-w-[450px] max-h-[85vh] p-dialog-padding rounded-[2px]
|
|
35
36
|
font-sans text-heading5 font-normal leading-[19px] text-text-muted
|
|
36
|
-
bg-popover-background
|
|
37
|
-
shadow-[0_10px_38px_-10px_hsla(206,22%,7%,.35),0_10px_20px_-15px_hsla(206,22%,7%,.2)]
|
|
37
|
+
bg-popover-background pointer-events-all z-[1000] focus:outline-none
|
|
38
|
+
shadow-[0_10px_38px_-10px_hsla(206,22%,7%,.35),0_10px_20px_-15px_hsla(206,22%,7%,.2)]
|
|
39
|
+
flex flex-col
|
|
38
40
|
`,
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
className
|
|
42
|
+
)}
|
|
43
|
+
{...props}
|
|
44
|
+
>
|
|
45
|
+
{children}
|
|
46
|
+
</DialogPrimitive.Content>
|
|
47
|
+
);
|
|
48
|
+
});
|
|
44
49
|
|
|
45
50
|
const StyledTitle = forwardRef<
|
|
46
51
|
React.ElementRef<typeof DialogPrimitive.Title>,
|