@noya-app/noya-designsystem 0.1.48 → 0.1.50
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 +19 -0
- package/dist/index.css +1 -1
- package/dist/index.d.mts +347 -173
- package/dist/index.d.ts +347 -173
- package/dist/index.js +3602 -2811
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +4761 -3993
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -5
- package/src/__tests__/validateDropIndicator.test.ts +4 -4
- package/src/components/ActionMenu.tsx +15 -4
- package/src/components/Avatar.tsx +2 -2
- package/src/components/Banner.tsx +29 -0
- package/src/components/BaseToolbar.tsx +2 -2
- package/src/components/Button.tsx +2 -2
- package/src/components/Checkbox.tsx +2 -2
- package/src/components/Chip.tsx +15 -14
- package/src/components/Collection.tsx +25 -2
- package/src/components/Combobox.tsx +22 -23
- package/src/components/ComboboxMenu.tsx +1 -1
- package/src/components/Dialog.tsx +2 -2
- package/src/components/DimensionInput.tsx +14 -12
- package/src/components/EditableText.tsx +3 -1
- package/src/components/FillInputField.tsx +2 -2
- package/src/components/Grid.tsx +131 -113
- package/src/components/GridView.tsx +36 -18
- package/src/components/InputField.tsx +116 -171
- package/src/components/Label.tsx +14 -73
- package/src/components/LabeledField.tsx +13 -2
- package/src/components/List.tsx +106 -47
- package/src/components/ListView.tsx +52 -31
- package/src/components/MediaThumbnail.tsx +14 -6
- package/src/components/Message.tsx +8 -8
- package/src/components/NoyaLogo.tsx +41 -0
- package/src/components/Popover.tsx +9 -6
- package/src/components/Section.tsx +172 -0
- package/src/components/SegmentedControl.tsx +1 -1
- package/src/components/SelectMenu.tsx +9 -4
- package/src/components/Slider.tsx +16 -7
- package/src/components/Sortable.tsx +186 -47
- package/src/components/UserPointer.tsx +1 -1
- package/src/components/ai-assistant/AIAssistantLayout.tsx +102 -0
- package/src/components/connected-users-menu/ConnectedUsersMenuLayout.tsx +71 -0
- package/src/components/file-explorer/FileExplorerLayout.tsx +71 -0
- package/src/components/internal/Menu.tsx +20 -10
- package/src/components/internal/SelectItem.tsx +3 -2
- package/src/components/pipeline/PipelineResultLayout.tsx +32 -0
- package/src/index.css +118 -112
- package/src/index.tsx +9 -0
- package/src/theme/index.ts +2 -0
- package/src/utils/classNames.ts +76 -3
- package/src/utils/inputs.ts +21 -0
- package/src/utils/moveTreeItem.ts +99 -0
- package/tailwind.config.ts +82 -75
|
@@ -19,17 +19,18 @@ import {
|
|
|
19
19
|
} from "@dnd-kit/sortable";
|
|
20
20
|
import { memoGeneric } from "@noya-app/react-utils";
|
|
21
21
|
import * as React from "react";
|
|
22
|
+
import { memo } from "react";
|
|
22
23
|
import { createPortal } from "react-dom";
|
|
23
24
|
|
|
24
25
|
export type RelativeDropPosition = "above" | "below" | "inside";
|
|
25
26
|
|
|
26
27
|
export type DropValidator = (
|
|
27
28
|
sourceIndex: number,
|
|
28
|
-
|
|
29
|
+
targetIndex: number,
|
|
29
30
|
position: RelativeDropPosition
|
|
30
31
|
) => boolean;
|
|
31
32
|
|
|
32
|
-
export const
|
|
33
|
+
export const normalizeListTargetIndex = (
|
|
33
34
|
index: number,
|
|
34
35
|
position: "above" | "below"
|
|
35
36
|
): number => {
|
|
@@ -38,12 +39,12 @@ export const normalizeListDestinationIndex = (
|
|
|
38
39
|
|
|
39
40
|
export const defaultAcceptsDrop: DropValidator = (
|
|
40
41
|
sourceIndex,
|
|
41
|
-
|
|
42
|
+
targetIndex,
|
|
42
43
|
position
|
|
43
44
|
) => {
|
|
44
45
|
if (position === "inside") return false;
|
|
45
46
|
|
|
46
|
-
const normalized =
|
|
47
|
+
const normalized = normalizeListTargetIndex(targetIndex, position);
|
|
47
48
|
|
|
48
49
|
if (sourceIndex === normalized || sourceIndex + 1 === normalized) {
|
|
49
50
|
return false;
|
|
@@ -52,18 +53,22 @@ export const defaultAcceptsDrop: DropValidator = (
|
|
|
52
53
|
return true;
|
|
53
54
|
};
|
|
54
55
|
|
|
55
|
-
|
|
56
|
+
export type SortableItemContextProps = {
|
|
56
57
|
keys: UniqueIdentifier[];
|
|
57
58
|
acceptsDrop: DropValidator;
|
|
58
59
|
axis: "x" | "y";
|
|
59
60
|
position: { x: number; y: number };
|
|
60
61
|
setActivatorEvent: (event: PointerEvent) => void;
|
|
61
|
-
|
|
62
|
+
getDropTargetParentIndex?: (index: number) => number | undefined;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const SortableItemContext = React.createContext<SortableItemContextProps>({
|
|
62
66
|
keys: [],
|
|
63
67
|
acceptsDrop: defaultAcceptsDrop,
|
|
64
68
|
axis: "y",
|
|
65
69
|
position: { x: 0, y: 0 },
|
|
66
70
|
setActivatorEvent: () => {},
|
|
71
|
+
getDropTargetParentIndex: undefined,
|
|
67
72
|
});
|
|
68
73
|
|
|
69
74
|
type ValidateDropIndicatorParams = {
|
|
@@ -92,42 +97,67 @@ export function validateDropIndicator({
|
|
|
92
97
|
|
|
93
98
|
const acceptsDropInside = acceptsDrop(activeIndex, overIndex, "inside");
|
|
94
99
|
|
|
95
|
-
//
|
|
96
|
-
// and dropping inside is allowed, show "inside" indicator
|
|
100
|
+
// Drop into the middle third if possible
|
|
97
101
|
if (
|
|
98
|
-
|
|
99
|
-
offsetStart <= elementStart + (elementSize * 2) / 3 &&
|
|
102
|
+
isContainedInMiddleThird(elementStart, elementSize, offsetStart) &&
|
|
100
103
|
acceptsDropInside
|
|
101
|
-
)
|
|
104
|
+
) {
|
|
102
105
|
return "inside";
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const containingHalf = getContainingHalf(
|
|
109
|
+
elementStart,
|
|
110
|
+
elementSize,
|
|
111
|
+
offsetStart
|
|
112
|
+
);
|
|
103
113
|
|
|
104
|
-
//
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
114
|
+
// Drop above or below if possible
|
|
115
|
+
const acceptedHalf = acceptsDrop(activeIndex, overIndex, containingHalf);
|
|
116
|
+
|
|
117
|
+
return acceptedHalf
|
|
118
|
+
? containingHalf
|
|
119
|
+
: // Lastly, check if inside but not middle third
|
|
120
|
+
acceptsDropInside
|
|
111
121
|
? "inside"
|
|
112
122
|
: undefined;
|
|
113
123
|
}
|
|
114
124
|
|
|
125
|
+
function isContainedInMiddleThird(
|
|
126
|
+
elementStart: number,
|
|
127
|
+
elementSize: number,
|
|
128
|
+
offsetStart: number
|
|
129
|
+
): boolean {
|
|
130
|
+
return (
|
|
131
|
+
offsetStart >= elementStart + elementSize / 3 &&
|
|
132
|
+
offsetStart <= elementStart + (elementSize * 2) / 3
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function getContainingHalf(
|
|
137
|
+
elementStart: number,
|
|
138
|
+
elementSize: number,
|
|
139
|
+
offsetStart: number
|
|
140
|
+
): "above" | "below" {
|
|
141
|
+
if (offsetStart < elementStart + elementSize / 2) return "above";
|
|
142
|
+
return "below";
|
|
143
|
+
}
|
|
144
|
+
|
|
115
145
|
/* ----------------------------------------------------------------------------
|
|
116
146
|
* Item
|
|
117
147
|
* ------------------------------------------------------------------------- */
|
|
118
148
|
|
|
119
149
|
type UseSortableReturnType = ReturnType<typeof useSortable>;
|
|
120
150
|
|
|
151
|
+
type ItemChildrenProps<T> = {
|
|
152
|
+
ref: React.Ref<T>;
|
|
153
|
+
relativeDropPosition?: RelativeDropPosition;
|
|
154
|
+
style?: React.CSSProperties;
|
|
155
|
+
} & UseSortableReturnType["attributes"];
|
|
156
|
+
|
|
121
157
|
interface ItemProps<T> {
|
|
122
158
|
id: UniqueIdentifier;
|
|
123
159
|
disabled?: boolean;
|
|
124
|
-
children: (
|
|
125
|
-
props: {
|
|
126
|
-
ref: React.Ref<T>;
|
|
127
|
-
relativeDropPosition?: RelativeDropPosition;
|
|
128
|
-
style?: React.CSSProperties;
|
|
129
|
-
} & UseSortableReturnType["attributes"]
|
|
130
|
-
) => JSX.Element;
|
|
160
|
+
children: (props: ItemChildrenProps<T>) => JSX.Element;
|
|
131
161
|
}
|
|
132
162
|
|
|
133
163
|
function SortableItem<T extends HTMLElement>({
|
|
@@ -135,8 +165,14 @@ function SortableItem<T extends HTMLElement>({
|
|
|
135
165
|
disabled,
|
|
136
166
|
children,
|
|
137
167
|
}: ItemProps<T>) {
|
|
138
|
-
const {
|
|
139
|
-
|
|
168
|
+
const {
|
|
169
|
+
keys,
|
|
170
|
+
position,
|
|
171
|
+
acceptsDrop,
|
|
172
|
+
setActivatorEvent,
|
|
173
|
+
axis,
|
|
174
|
+
getDropTargetParentIndex,
|
|
175
|
+
} = React.useContext(SortableItemContext);
|
|
140
176
|
const sortable = useSortable({ id, disabled });
|
|
141
177
|
const { activatorEvent } = useDndContext();
|
|
142
178
|
|
|
@@ -147,6 +183,7 @@ function SortableItem<T extends HTMLElement>({
|
|
|
147
183
|
setNodeRef,
|
|
148
184
|
isDragging,
|
|
149
185
|
index,
|
|
186
|
+
activeIndex,
|
|
150
187
|
overIndex,
|
|
151
188
|
over,
|
|
152
189
|
} = sortable;
|
|
@@ -161,23 +198,40 @@ function SortableItem<T extends HTMLElement>({
|
|
|
161
198
|
const offsetTop = eventY + position.y;
|
|
162
199
|
|
|
163
200
|
const ref = React.useCallback((node: T) => setNodeRef(node), [setNodeRef]);
|
|
201
|
+
const parentIndex =
|
|
202
|
+
overIndex === -1 ? undefined : getDropTargetParentIndex?.(overIndex);
|
|
203
|
+
const isValidParent =
|
|
204
|
+
parentIndex === undefined || parentIndex === -1
|
|
205
|
+
? false
|
|
206
|
+
: acceptsDrop(activeIndex, parentIndex, "inside");
|
|
207
|
+
const overIdAcceptsDrop =
|
|
208
|
+
overIndex === -1
|
|
209
|
+
? undefined
|
|
210
|
+
: acceptsDrop(activeIndex, overIndex, "inside");
|
|
211
|
+
|
|
212
|
+
const relativeDropPosition =
|
|
213
|
+
index >= 0 && index === overIndex && !isDragging && active && over
|
|
214
|
+
? validateDropIndicator({
|
|
215
|
+
acceptsDrop,
|
|
216
|
+
keys,
|
|
217
|
+
activeId: active.id,
|
|
218
|
+
overId: over.id,
|
|
219
|
+
offsetStart: axis === "x" ? offsetLeft : offsetTop,
|
|
220
|
+
elementStart: axis === "x" ? over.rect.left : over.rect.top,
|
|
221
|
+
elementSize: axis === "x" ? over.rect.width : over.rect.height,
|
|
222
|
+
})
|
|
223
|
+
: undefined;
|
|
224
|
+
|
|
225
|
+
const showDragInsideIndicatorOnParent =
|
|
226
|
+
!overIdAcceptsDrop && isValidParent && parentIndex === index;
|
|
164
227
|
|
|
165
228
|
return children({
|
|
166
229
|
ref,
|
|
167
230
|
...attributes,
|
|
168
231
|
...listeners,
|
|
169
232
|
relativeDropPosition:
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
acceptsDrop,
|
|
173
|
-
keys,
|
|
174
|
-
activeId: active.id,
|
|
175
|
-
overId: over.id,
|
|
176
|
-
offsetStart: axis === "x" ? offsetLeft : offsetTop,
|
|
177
|
-
elementStart: axis === "x" ? over.rect.left : over.rect.top,
|
|
178
|
-
elementSize: axis === "x" ? over.rect.width : over.rect.height,
|
|
179
|
-
})
|
|
180
|
-
: undefined,
|
|
233
|
+
relativeDropPosition ??
|
|
234
|
+
(showDragInsideIndicatorOnParent ? "inside" : undefined),
|
|
181
235
|
});
|
|
182
236
|
}
|
|
183
237
|
|
|
@@ -191,11 +245,12 @@ interface RootProps {
|
|
|
191
245
|
renderOverlay?: (index: number) => React.ReactNode;
|
|
192
246
|
onMoveItem?: (
|
|
193
247
|
sourceIndex: number,
|
|
194
|
-
|
|
248
|
+
targetIndex: number,
|
|
195
249
|
position: RelativeDropPosition
|
|
196
250
|
) => void;
|
|
197
|
-
acceptsDrop?:
|
|
198
|
-
axis?: "
|
|
251
|
+
acceptsDrop?: SortableItemContextProps["acceptsDrop"];
|
|
252
|
+
axis?: SortableItemContextProps["axis"];
|
|
253
|
+
getDropTargetParentIndex?: SortableItemContextProps["getDropTargetParentIndex"];
|
|
199
254
|
}
|
|
200
255
|
|
|
201
256
|
function SortableRoot({
|
|
@@ -205,6 +260,7 @@ function SortableRoot({
|
|
|
205
260
|
renderOverlay,
|
|
206
261
|
acceptsDrop = defaultAcceptsDrop,
|
|
207
262
|
axis = "y",
|
|
263
|
+
getDropTargetParentIndex,
|
|
208
264
|
}: RootProps) {
|
|
209
265
|
const sensors = useSensors(
|
|
210
266
|
useSensor(PointerSensor, {
|
|
@@ -242,7 +298,6 @@ function SortableRoot({
|
|
|
242
298
|
const { active, over } = event;
|
|
243
299
|
|
|
244
300
|
setActiveIndex(undefined);
|
|
245
|
-
|
|
246
301
|
if (over && active.id !== over.id) {
|
|
247
302
|
const oldIndex = keys.findIndex((id) => id === active.id);
|
|
248
303
|
const newIndex = keys.findIndex((id) => id === over.id);
|
|
@@ -281,14 +336,22 @@ function SortableRoot({
|
|
|
281
336
|
return (
|
|
282
337
|
<SortableItemContext.Provider
|
|
283
338
|
value={React.useMemo(
|
|
284
|
-
() => ({
|
|
339
|
+
(): SortableItemContextProps => ({
|
|
285
340
|
keys,
|
|
286
341
|
acceptsDrop,
|
|
287
342
|
position,
|
|
288
343
|
setActivatorEvent,
|
|
289
344
|
axis,
|
|
345
|
+
getDropTargetParentIndex,
|
|
290
346
|
}),
|
|
291
|
-
[
|
|
347
|
+
[
|
|
348
|
+
acceptsDrop,
|
|
349
|
+
axis,
|
|
350
|
+
getDropTargetParentIndex,
|
|
351
|
+
keys,
|
|
352
|
+
position,
|
|
353
|
+
setActivatorEvent,
|
|
354
|
+
]
|
|
292
355
|
)}
|
|
293
356
|
>
|
|
294
357
|
<DndContext
|
|
@@ -323,7 +386,83 @@ function SortableRoot({
|
|
|
323
386
|
);
|
|
324
387
|
}
|
|
325
388
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
389
|
+
// Create a map of all subcomponents
|
|
390
|
+
const SortableSubcomponents = {
|
|
391
|
+
Root: memo(SortableRoot),
|
|
392
|
+
Item: memoGeneric(SortableItem),
|
|
393
|
+
} as const;
|
|
394
|
+
|
|
395
|
+
interface SortableProps<T, TElement extends HTMLElement>
|
|
396
|
+
extends Omit<RootProps, "children" | "keys" | "renderOverlay"> {
|
|
397
|
+
items: T[];
|
|
398
|
+
keyExtractor: (item: T) => UniqueIdentifier;
|
|
399
|
+
shouldRenderOverlay?: boolean;
|
|
400
|
+
renderItem: (
|
|
401
|
+
item: T,
|
|
402
|
+
props: ItemChildrenProps<TElement>,
|
|
403
|
+
{ isOverlay }: { isOverlay: boolean }
|
|
404
|
+
) => JSX.Element;
|
|
329
405
|
}
|
|
406
|
+
|
|
407
|
+
const SortableComponent = memoGeneric(function Sortable<
|
|
408
|
+
TItem,
|
|
409
|
+
TElement extends HTMLElement,
|
|
410
|
+
>(props: SortableProps<TItem, TElement>) {
|
|
411
|
+
const {
|
|
412
|
+
renderItem,
|
|
413
|
+
items,
|
|
414
|
+
keyExtractor,
|
|
415
|
+
shouldRenderOverlay = true,
|
|
416
|
+
...rest
|
|
417
|
+
} = props;
|
|
418
|
+
|
|
419
|
+
const keys = React.useMemo(
|
|
420
|
+
() => items.map(keyExtractor),
|
|
421
|
+
[items, keyExtractor]
|
|
422
|
+
);
|
|
423
|
+
|
|
424
|
+
const renderOverlay = React.useCallback(
|
|
425
|
+
(index: number) => {
|
|
426
|
+
return renderItem(
|
|
427
|
+
items[index],
|
|
428
|
+
{
|
|
429
|
+
ref: () => {},
|
|
430
|
+
style: {},
|
|
431
|
+
tabIndex: -1,
|
|
432
|
+
"aria-disabled": true,
|
|
433
|
+
"aria-pressed": false,
|
|
434
|
+
"aria-roledescription": "Item being dragged",
|
|
435
|
+
"aria-describedby": "Item being dragged",
|
|
436
|
+
role: "button",
|
|
437
|
+
},
|
|
438
|
+
{ isOverlay: true }
|
|
439
|
+
);
|
|
440
|
+
},
|
|
441
|
+
[renderItem, items]
|
|
442
|
+
);
|
|
443
|
+
|
|
444
|
+
return (
|
|
445
|
+
<SortableSubcomponents.Root
|
|
446
|
+
{...rest}
|
|
447
|
+
keys={keys}
|
|
448
|
+
renderOverlay={shouldRenderOverlay ? renderOverlay : undefined}
|
|
449
|
+
>
|
|
450
|
+
{keys.map((key, index) => (
|
|
451
|
+
<SortableSubcomponents.Item key={key} id={key}>
|
|
452
|
+
{(props) => {
|
|
453
|
+
return renderItem(
|
|
454
|
+
items[index],
|
|
455
|
+
props as ItemChildrenProps<TElement>,
|
|
456
|
+
{ isOverlay: false }
|
|
457
|
+
);
|
|
458
|
+
}}
|
|
459
|
+
</SortableSubcomponents.Item>
|
|
460
|
+
))}
|
|
461
|
+
</SortableSubcomponents.Root>
|
|
462
|
+
);
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
// Export both the namespace and the component
|
|
466
|
+
const Sortable = Object.assign(SortableComponent, SortableSubcomponents);
|
|
467
|
+
|
|
468
|
+
export { Sortable };
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { IconButton } from "../IconButton";
|
|
2
|
+
|
|
3
|
+
import { cssVars } from "../../theme";
|
|
4
|
+
|
|
5
|
+
import { Avatar } from "../Avatar";
|
|
6
|
+
import { Logo } from "../NoyaLogo";
|
|
7
|
+
|
|
8
|
+
import React, { forwardRef } from "react";
|
|
9
|
+
import { INPUT_HEIGHT } from "../../theme";
|
|
10
|
+
import { cx } from "../../utils/classNames";
|
|
11
|
+
import { TextAreaRow } from "../TextArea";
|
|
12
|
+
|
|
13
|
+
export const AIAssistantLoadingIndicator = () => {
|
|
14
|
+
return (
|
|
15
|
+
<div className="flex gap-2">
|
|
16
|
+
<Avatar
|
|
17
|
+
name="Assistant"
|
|
18
|
+
size={INPUT_HEIGHT}
|
|
19
|
+
backgroundColor={cssVars.colors.primaryPastel}
|
|
20
|
+
>
|
|
21
|
+
<Logo style={{ width: 15 }} fill={cssVars.colors.primary} />
|
|
22
|
+
</Avatar>
|
|
23
|
+
<div className="animate-pulse">▋</div>
|
|
24
|
+
</div>
|
|
25
|
+
);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
type AIAssistantInputProps = {
|
|
29
|
+
value: string;
|
|
30
|
+
onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void;
|
|
31
|
+
onSubmitClick: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
|
32
|
+
onKeyDown: (e: React.KeyboardEvent<HTMLTextAreaElement>) => void;
|
|
33
|
+
disabled: boolean;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const AIAssistantInput = forwardRef<
|
|
37
|
+
HTMLDivElement,
|
|
38
|
+
AIAssistantInputProps
|
|
39
|
+
>(({ value, onChange, onSubmitClick, onKeyDown, disabled }, ref) => (
|
|
40
|
+
<TextAreaRow
|
|
41
|
+
autoResize
|
|
42
|
+
autoFocus
|
|
43
|
+
value={value}
|
|
44
|
+
onChange={onChange}
|
|
45
|
+
ref={ref}
|
|
46
|
+
textAreaClassName="py-2 pl-3 !pr-9 w-full mt-3"
|
|
47
|
+
placeholder="Tell me what you'd like to do..."
|
|
48
|
+
onKeyDown={onKeyDown}
|
|
49
|
+
disabled={disabled}
|
|
50
|
+
end={
|
|
51
|
+
<IconButton
|
|
52
|
+
iconName="ArrowUpIcon"
|
|
53
|
+
onClick={onSubmitClick}
|
|
54
|
+
disabled={!value.trim() || disabled}
|
|
55
|
+
className="h-8 w-8 z-20 rounded-full"
|
|
56
|
+
size={20}
|
|
57
|
+
color={cssVars.colors.text}
|
|
58
|
+
style={{
|
|
59
|
+
backgroundColor: cssVars.colors.chipDefaultBg,
|
|
60
|
+
}}
|
|
61
|
+
/>
|
|
62
|
+
}
|
|
63
|
+
/>
|
|
64
|
+
));
|
|
65
|
+
|
|
66
|
+
type AIAssistantLayoutProps = {
|
|
67
|
+
renderMessages: () => React.ReactNode;
|
|
68
|
+
renderInput: () => React.ReactNode;
|
|
69
|
+
isLoading: boolean;
|
|
70
|
+
renderLoadingIndicator?: () => React.ReactNode;
|
|
71
|
+
className?: string;
|
|
72
|
+
style?: React.CSSProperties;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export const AIAssistantLayout = forwardRef<
|
|
76
|
+
HTMLDivElement,
|
|
77
|
+
AIAssistantLayoutProps
|
|
78
|
+
>(
|
|
79
|
+
(
|
|
80
|
+
{
|
|
81
|
+
renderMessages,
|
|
82
|
+
renderInput,
|
|
83
|
+
isLoading,
|
|
84
|
+
renderLoadingIndicator,
|
|
85
|
+
className,
|
|
86
|
+
style,
|
|
87
|
+
}: AIAssistantLayoutProps,
|
|
88
|
+
ref
|
|
89
|
+
) => {
|
|
90
|
+
return (
|
|
91
|
+
<div className={cx("flex flex-col flex-1", className)} style={style}>
|
|
92
|
+
<div className="flex-1 min-h-0 overflow-auto" ref={ref}>
|
|
93
|
+
<div className="flex flex-col gap-2 pb-4 pt-1">
|
|
94
|
+
{renderMessages()}
|
|
95
|
+
{isLoading ? renderLoadingIndicator?.() : null}
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
<div className="border-t border-divider pt-1 px-1">{renderInput()}</div>
|
|
99
|
+
</div>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
);
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { cssVars } from "../../theme";
|
|
2
|
+
|
|
3
|
+
import { ChatBubbleWithDotsIcon } from "@noya-app/noya-icons";
|
|
4
|
+
import React from "react";
|
|
5
|
+
import { INPUT_HEIGHT } from "../../theme";
|
|
6
|
+
import { colorFromString } from "../../utils/colorFromString";
|
|
7
|
+
import { Avatar, AvatarProps, AvatarStack } from "../Avatar";
|
|
8
|
+
import Button from "../Button";
|
|
9
|
+
import { Spacer } from "../Spacer";
|
|
10
|
+
import { Small } from "../Text";
|
|
11
|
+
import { Tooltip } from "../Tooltip";
|
|
12
|
+
|
|
13
|
+
export const UserAvatar = ({ userId, name, image }: AvatarProps) => (
|
|
14
|
+
<Tooltip
|
|
15
|
+
key={userId}
|
|
16
|
+
content={
|
|
17
|
+
<div className="flex flex-col">
|
|
18
|
+
<Small>{name}</Small>
|
|
19
|
+
</div>
|
|
20
|
+
}
|
|
21
|
+
>
|
|
22
|
+
<Avatar size={INPUT_HEIGHT} userId={userId} name={name} image={image} />
|
|
23
|
+
</Tooltip>
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
type ConnectedUsersMenuLayoutProps = {
|
|
27
|
+
renderUsers: () => React.ReactNode;
|
|
28
|
+
currentUserId?: string;
|
|
29
|
+
launchAIAssistant?: () => void;
|
|
30
|
+
isAssistantOpen?: boolean;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const ConnectedUsersMenuLayout = ({
|
|
34
|
+
renderUsers,
|
|
35
|
+
currentUserId,
|
|
36
|
+
launchAIAssistant,
|
|
37
|
+
isAssistantOpen,
|
|
38
|
+
}: ConnectedUsersMenuLayoutProps) => {
|
|
39
|
+
return (
|
|
40
|
+
<div className="flex gap-1.5">
|
|
41
|
+
<AvatarStack size={INPUT_HEIGHT}>{renderUsers()}</AvatarStack>
|
|
42
|
+
|
|
43
|
+
{/* AI Assistant Avatar */}
|
|
44
|
+
{launchAIAssistant && (
|
|
45
|
+
<Tooltip
|
|
46
|
+
content={
|
|
47
|
+
<div className="flex flex-col">
|
|
48
|
+
<Small>AI Assistant</Small>
|
|
49
|
+
</div>
|
|
50
|
+
}
|
|
51
|
+
>
|
|
52
|
+
<Button
|
|
53
|
+
variant={isAssistantOpen ? "secondary" : undefined}
|
|
54
|
+
onClick={launchAIAssistant}
|
|
55
|
+
style={{
|
|
56
|
+
backgroundColor: isAssistantOpen
|
|
57
|
+
? colorFromString(currentUserId ?? "")
|
|
58
|
+
: undefined,
|
|
59
|
+
}}
|
|
60
|
+
>
|
|
61
|
+
AI
|
|
62
|
+
<Spacer.Horizontal size={8} />
|
|
63
|
+
<ChatBubbleWithDotsIcon
|
|
64
|
+
color={isAssistantOpen ? undefined : cssVars.colors.icon}
|
|
65
|
+
/>
|
|
66
|
+
</Button>
|
|
67
|
+
</Tooltip>
|
|
68
|
+
)}
|
|
69
|
+
</div>
|
|
70
|
+
);
|
|
71
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Banner,
|
|
3
|
+
BannerProps,
|
|
4
|
+
IconButton,
|
|
5
|
+
Section,
|
|
6
|
+
SectionProps,
|
|
7
|
+
} from "@noya-app/noya-designsystem";
|
|
8
|
+
import { forwardRefGeneric } from "@noya-app/react-utils";
|
|
9
|
+
import React from "react";
|
|
10
|
+
import { cx } from "../../utils/classNames";
|
|
11
|
+
import { Collection, CollectionProps, CollectionRef } from "../Collection";
|
|
12
|
+
|
|
13
|
+
export const FileExplorerLayout = ({
|
|
14
|
+
children,
|
|
15
|
+
className,
|
|
16
|
+
...props
|
|
17
|
+
}: SectionProps) => (
|
|
18
|
+
<Section className={cx(className, "px-3 flex-1")} {...props}>
|
|
19
|
+
{children}
|
|
20
|
+
</Section>
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
export const FileExplorerUploadButton = ({
|
|
24
|
+
showUploadButton,
|
|
25
|
+
onUpload,
|
|
26
|
+
children,
|
|
27
|
+
}: {
|
|
28
|
+
showUploadButton?: boolean;
|
|
29
|
+
onUpload?: () => void;
|
|
30
|
+
children?: React.ReactNode;
|
|
31
|
+
}) => (
|
|
32
|
+
<div className="flex items-center gap-2">
|
|
33
|
+
{showUploadButton && (
|
|
34
|
+
<IconButton
|
|
35
|
+
iconName="UploadIcon"
|
|
36
|
+
onClick={onUpload}
|
|
37
|
+
aria-label="Upload media"
|
|
38
|
+
tooltip="Upload media"
|
|
39
|
+
/>
|
|
40
|
+
)}
|
|
41
|
+
{children}
|
|
42
|
+
</div>
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
export const FileExplorerCollection = forwardRefGeneric(
|
|
46
|
+
function FileExplorerCollection<T, M extends string = string>(
|
|
47
|
+
{ ...props }: CollectionProps<T, M>,
|
|
48
|
+
ref: React.ForwardedRef<CollectionRef>
|
|
49
|
+
) {
|
|
50
|
+
return <Collection<T, M> ref={ref} className="-mx-3 flex-1" {...props} />;
|
|
51
|
+
}
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
export const FileExplorerDetail = ({
|
|
55
|
+
selected,
|
|
56
|
+
children,
|
|
57
|
+
}: {
|
|
58
|
+
selected: boolean;
|
|
59
|
+
children: React.ReactNode;
|
|
60
|
+
}) => (
|
|
61
|
+
<span
|
|
62
|
+
className={cx("text-sm", selected ? "text-primary" : "text-text-muted")}
|
|
63
|
+
>
|
|
64
|
+
{children}
|
|
65
|
+
</span>
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
export const FileExplorerEmptyState = ({
|
|
69
|
+
label = "No files",
|
|
70
|
+
...props
|
|
71
|
+
}: BannerProps) => <Banner label={label} className="mx-3" {...props} />;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getShortcutDisplayParts } from "@noya-app/noya-keymap";
|
|
2
2
|
import React, { memo, ReactNode } from "react";
|
|
3
3
|
import { useDesignSystemConfiguration } from "../../contexts/DesignSystemConfiguration";
|
|
4
|
-
import { cx } from "../../utils/classNames";
|
|
4
|
+
import { cx, mergeConflictingClassNames } from "../../utils/classNames";
|
|
5
5
|
import { IScoredItem } from "../../utils/fuzzyScorer";
|
|
6
6
|
import withSeparatorElements from "../../utils/withSeparatorElements";
|
|
7
7
|
import { IconName } from "../Icons";
|
|
@@ -157,12 +157,12 @@ export const getMenuItemKey = <T extends string>(
|
|
|
157
157
|
}
|
|
158
158
|
};
|
|
159
159
|
|
|
160
|
-
export const CHECKBOX_WIDTH =
|
|
160
|
+
export const CHECKBOX_WIDTH = 15;
|
|
161
161
|
export const CHECKBOX_RIGHT_INSET = 8;
|
|
162
162
|
export const CHECKBOX_INDENT_WIDTH = 6;
|
|
163
163
|
|
|
164
164
|
export const styles = {
|
|
165
|
-
separatorStyle: "h-px bg-divider mx-
|
|
165
|
+
separatorStyle: "h-px bg-divider mx-3 my-1",
|
|
166
166
|
selectedItemStyle:
|
|
167
167
|
"focus:outline-none focus:text-white focus:bg-primary focus:kbd:text-white",
|
|
168
168
|
testSelectedItemStyle: "outline-none text-white bg-primary kbd:text-white",
|
|
@@ -175,7 +175,12 @@ export const styles = {
|
|
|
175
175
|
font-sans text-button font-medium
|
|
176
176
|
${disabled ? "text-text-disabled" : ""}
|
|
177
177
|
`,
|
|
178
|
-
|
|
178
|
+
itemIndicator: {
|
|
179
|
+
className: "flex items-center relative -left-2.5",
|
|
180
|
+
style: {
|
|
181
|
+
marginRight: -CHECKBOX_RIGHT_INSET,
|
|
182
|
+
},
|
|
183
|
+
},
|
|
179
184
|
contentStyle:
|
|
180
185
|
"rounded bg-popover-background text-text shadow-[0_2px_4px_rgba(0,0,0,0.2),_0_0_12px_rgba(0,0,0,0.1)] z-menu py-1",
|
|
181
186
|
};
|
|
@@ -265,12 +270,17 @@ export const SectionHeader = memo(function SectionHeader({
|
|
|
265
270
|
return (
|
|
266
271
|
<span
|
|
267
272
|
id={id}
|
|
268
|
-
className={
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
273
|
+
className={mergeConflictingClassNames(
|
|
274
|
+
[
|
|
275
|
+
variant === "label"
|
|
276
|
+
? `text-label ${textStyles.label} font-bold text-text-disabled`
|
|
277
|
+
: "font-sans text-heading5 font-normal",
|
|
278
|
+
"bg-listview-raised-background flex items-center py-1.5 px-3",
|
|
279
|
+
isFirst && "-mt-1",
|
|
280
|
+
],
|
|
281
|
+
{
|
|
282
|
+
categories: ["font"],
|
|
283
|
+
}
|
|
274
284
|
)}
|
|
275
285
|
>
|
|
276
286
|
{indented && (
|