@noya-app/noya-designsystem 0.1.50 → 0.1.52
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 -13
- package/CHANGELOG.md +19 -0
- package/dist/index.css +1 -1
- package/dist/index.d.mts +314 -203
- package/dist/index.d.ts +314 -203
- package/dist/index.js +6473 -12656
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +8399 -14605
- package/dist/index.mjs.map +1 -1
- package/package.json +15 -12
- package/postcss.config.js +7 -0
- package/src/components/ActionMenu.tsx +12 -5
- package/src/components/Banner.tsx +2 -2
- package/src/components/BaseToolbar.tsx +6 -2
- package/src/components/Breadcrumbs.tsx +32 -2
- package/src/components/Button.tsx +30 -19
- package/src/components/Chip.tsx +3 -0
- package/src/components/Collection.tsx +12 -3
- package/src/components/ColorSwatch.tsx +42 -0
- package/src/components/ColorSwatchControl.tsx +92 -0
- package/src/components/ComboboxMenu.tsx +10 -1
- package/src/components/Dialog.tsx +1 -1
- package/src/components/FillInputField.tsx +1 -1
- package/src/components/Grid.tsx +22 -12
- package/src/components/GridView.tsx +1 -1
- package/src/components/LabeledField.tsx +4 -1
- package/src/components/List.tsx +37 -26
- package/src/components/ListView.tsx +7 -4
- package/src/components/MediaThumbnail.tsx +63 -15
- package/src/components/Popover.tsx +1 -1
- package/src/components/SearchCompletionMenu.tsx +12 -12
- package/src/components/SegmentedControl.tsx +2 -2
- package/src/components/SelectionToolbar.tsx +70 -0
- package/src/components/Switch.tsx +1 -1
- package/src/components/Text.tsx +2 -2
- package/src/components/Toast.tsx +13 -7
- package/src/components/Toolbar.tsx +33 -11
- package/src/components/Tooltip.tsx +4 -2
- package/src/components/catppuccin/fileIcons.ts +2430 -0
- package/src/components/connected-users-menu/ConnectedUsersMenuLayout.tsx +3 -1
- package/src/components/file-explorer/FileExplorerLayout.tsx +12 -2
- package/src/components/internal/Menu.tsx +5 -4
- package/src/components/internal/MenuViewport.tsx +2 -0
- package/src/components/internal/TextInput.tsx +7 -2
- package/src/components/pipeline/PipelineResultLayout.tsx +18 -0
- package/src/contexts/DialogContext.tsx +20 -5
- package/src/index.css +103 -34
- package/src/index.tsx +3 -0
- package/src/theme/index.ts +7 -2
- package/src/theme/themeUtils.ts +10 -3
- package/src/utils/classNames.ts +4 -3
- package/src/utils/combobox.ts +18 -13
- package/src/utils/moveTreeItem.ts +4 -4
- package/tailwind.config.ts +2 -239
- package/tailwind.d.ts +1 -1
- package/tsup.config.ts +1 -4
|
@@ -29,11 +29,13 @@ export const LabeledField = memo(function LabeledField({
|
|
|
29
29
|
minWidth = 100,
|
|
30
30
|
className,
|
|
31
31
|
style,
|
|
32
|
+
labelStyle: labelStyleProp,
|
|
32
33
|
...props
|
|
33
34
|
}: {
|
|
34
35
|
children: React.ReactNode;
|
|
35
36
|
label: React.ReactNode;
|
|
36
37
|
labelPosition?: LabelPosition;
|
|
38
|
+
labelStyle?: React.CSSProperties;
|
|
37
39
|
/** Used for both the `id` of the field and the `htmlFor` of the label, unless `id` and `htmlFor` are explicitly provided */
|
|
38
40
|
fieldId?: string;
|
|
39
41
|
className?: string;
|
|
@@ -79,8 +81,9 @@ export const LabeledField = memo(function LabeledField({
|
|
|
79
81
|
const labelStyle = useMemo(
|
|
80
82
|
() => ({
|
|
81
83
|
minWidth: `calc(${labelWidth}px - ${indentLevel * 8}px)`,
|
|
84
|
+
...labelStyleProp,
|
|
82
85
|
}),
|
|
83
|
-
[labelWidth, indentLevel]
|
|
86
|
+
[labelWidth, indentLevel, labelStyleProp]
|
|
84
87
|
);
|
|
85
88
|
|
|
86
89
|
return (
|
package/src/components/List.tsx
CHANGED
|
@@ -8,13 +8,16 @@ import { cssVars } from "../theme";
|
|
|
8
8
|
import { cx } from "../utils/classNames";
|
|
9
9
|
import { updateSelection } from "../utils/selection";
|
|
10
10
|
import { ActionMenu } from "./ActionMenu";
|
|
11
|
-
import {
|
|
12
|
-
|
|
11
|
+
import {
|
|
12
|
+
CollectionItemSize,
|
|
13
|
+
CollectionProps,
|
|
14
|
+
CollectionRef,
|
|
15
|
+
} from "./Collection";
|
|
13
16
|
import { ListView, ListViewItemInfo } from "./ListView";
|
|
14
17
|
import { TreeView } from "./TreeView";
|
|
15
18
|
|
|
16
19
|
const cssVarOverrides = {
|
|
17
|
-
"--n-icon-selected": cssVars.colors.
|
|
20
|
+
"--n-icon-selected": cssVars.colors.selectedListItemText,
|
|
18
21
|
};
|
|
19
22
|
|
|
20
23
|
const emptyArray: string[] = [];
|
|
@@ -114,7 +117,7 @@ export const List = memoGeneric(
|
|
|
114
117
|
variant="bare"
|
|
115
118
|
className={cx(
|
|
116
119
|
className,
|
|
117
|
-
isDropTargetActive && "bg-
|
|
120
|
+
isDropTargetActive && "bg-selected-list-item-background",
|
|
118
121
|
scrollable && "basis-0 min-h-0"
|
|
119
122
|
)}
|
|
120
123
|
containerRef={fileDropTargetRef}
|
|
@@ -149,6 +152,7 @@ export const List = memoGeneric(
|
|
|
149
152
|
const thumbnail = renderThumbnail?.({
|
|
150
153
|
item,
|
|
151
154
|
selected: isSelected,
|
|
155
|
+
size,
|
|
152
156
|
});
|
|
153
157
|
const onOpenChange = (open: boolean) => {
|
|
154
158
|
setDropdownOpenId(open ? id : undefined);
|
|
@@ -169,6 +173,15 @@ export const List = memoGeneric(
|
|
|
169
173
|
onSelect={(action) => handleMenuAction(action, item)}
|
|
170
174
|
selected={isSelected}
|
|
171
175
|
onOpenChange={onOpenChange}
|
|
176
|
+
variant="bare"
|
|
177
|
+
style={{
|
|
178
|
+
backgroundColor: isSelected
|
|
179
|
+
? cssVars.colors.selectedListItemBackground
|
|
180
|
+
: "transparent",
|
|
181
|
+
color: isSelected
|
|
182
|
+
? cssVars.colors.selectedListItemText
|
|
183
|
+
: undefined,
|
|
184
|
+
}}
|
|
172
185
|
/>
|
|
173
186
|
));
|
|
174
187
|
|
|
@@ -188,9 +201,6 @@ export const List = memoGeneric(
|
|
|
188
201
|
aria-label={getName(item)}
|
|
189
202
|
aria-selected={isSelected}
|
|
190
203
|
className={cx("cursor-pointer rounded px-1", pyMap[size])}
|
|
191
|
-
backgroundColor={
|
|
192
|
-
isSelected ? cssVars.colors.primaryPastel : undefined
|
|
193
|
-
}
|
|
194
204
|
testRelativeDropPosition={
|
|
195
205
|
testShowDropIndicatorId === id ? "inside" : undefined
|
|
196
206
|
}
|
|
@@ -238,9 +248,10 @@ export const List = memoGeneric(
|
|
|
238
248
|
{thumbnail && (
|
|
239
249
|
<div
|
|
240
250
|
className={cx(
|
|
241
|
-
"
|
|
251
|
+
"overflow-hidden flex-none flex items-center justify-center",
|
|
242
252
|
thumbnailSize === "auto" && thumbnailSizeMap[size],
|
|
243
|
-
isSelected && "text-
|
|
253
|
+
isSelected && "text-selected-list-item-text",
|
|
254
|
+
size === "small" ? "rounded-sm" : "rounded"
|
|
244
255
|
)}
|
|
245
256
|
tabIndex={-1}
|
|
246
257
|
>
|
|
@@ -268,7 +279,7 @@ export const List = memoGeneric(
|
|
|
268
279
|
<ViewComponent.RowTitle
|
|
269
280
|
className={cx(
|
|
270
281
|
fontStyleMap[size],
|
|
271
|
-
isSelected && "text-
|
|
282
|
+
isSelected && "text-selected-list-item-text"
|
|
272
283
|
)}
|
|
273
284
|
>
|
|
274
285
|
{getName(item)}
|
|
@@ -316,37 +327,37 @@ const DetailContainer = memo(function DetailContainer({
|
|
|
316
327
|
);
|
|
317
328
|
});
|
|
318
329
|
|
|
319
|
-
const pyMap: Record<
|
|
320
|
-
xxs: "py-
|
|
321
|
-
xs: "py-
|
|
322
|
-
small: "py-
|
|
330
|
+
const pyMap: Record<CollectionItemSize, string> = {
|
|
331
|
+
xxs: "py-0.5",
|
|
332
|
+
xs: "py-0.5",
|
|
333
|
+
small: "py-0.5",
|
|
323
334
|
medium: "py-1",
|
|
324
335
|
large: "py-2",
|
|
325
336
|
xl: "py-3",
|
|
326
337
|
};
|
|
327
338
|
|
|
328
|
-
const thumbnailSizeMap: Record<
|
|
329
|
-
xxs: "w-
|
|
330
|
-
xs: "w-
|
|
331
|
-
small: "w-
|
|
339
|
+
const thumbnailSizeMap: Record<CollectionItemSize, string> = {
|
|
340
|
+
xxs: "w-5 h-5",
|
|
341
|
+
xs: "w-5 h-5",
|
|
342
|
+
small: "w-5 h-5",
|
|
332
343
|
medium: "w-8 h-8",
|
|
333
344
|
large: "w-16 h-16",
|
|
334
345
|
xl: "w-24 h-24",
|
|
335
346
|
};
|
|
336
347
|
|
|
337
|
-
const rowGapMap: Record<
|
|
338
|
-
xxs: "gap-
|
|
339
|
-
xs: "gap-
|
|
340
|
-
small: "gap-
|
|
348
|
+
const rowGapMap: Record<CollectionItemSize, string> = {
|
|
349
|
+
xxs: "gap-2",
|
|
350
|
+
xs: "gap-2",
|
|
351
|
+
small: "gap-2",
|
|
341
352
|
medium: "gap-3",
|
|
342
353
|
large: "gap-3",
|
|
343
354
|
xl: "gap-4",
|
|
344
355
|
};
|
|
345
356
|
|
|
346
|
-
const fontStyleMap: Record<
|
|
347
|
-
xxs: "font-medium",
|
|
348
|
-
xs: "font-medium",
|
|
349
|
-
small: "font-medium",
|
|
357
|
+
const fontStyleMap: Record<CollectionItemSize, string> = {
|
|
358
|
+
xxs: "font-medium text-list-small",
|
|
359
|
+
xs: "font-medium text-list-small",
|
|
360
|
+
small: "font-medium text-list-small",
|
|
350
361
|
medium: "font-medium",
|
|
351
362
|
large: "font-medium",
|
|
352
363
|
xl: "font-medium text-heading4 text-text",
|
|
@@ -232,7 +232,7 @@ const RowContainer = forwardRef<
|
|
|
232
232
|
: "font-sans text-heading5 font-normal",
|
|
233
233
|
!$isSectionHeader &&
|
|
234
234
|
$selected &&
|
|
235
|
-
"text-
|
|
235
|
+
"text-selected-list-item-text active:bg-selected-list-item-background",
|
|
236
236
|
!$isSectionHeader && $disabled && "text-text-disabled",
|
|
237
237
|
$clickable ? "cursor-pointer" : "cursor-default",
|
|
238
238
|
$isSectionHeader && "bg-listview-raised-background",
|
|
@@ -254,7 +254,10 @@ const RowContainer = forwardRef<
|
|
|
254
254
|
}),
|
|
255
255
|
...($selected &&
|
|
256
256
|
!$isSectionHeader && {
|
|
257
|
-
backgroundColor:
|
|
257
|
+
backgroundColor:
|
|
258
|
+
$colorScheme === "primary"
|
|
259
|
+
? cssVars.colors["selectedListItemBackground"]
|
|
260
|
+
: cssVars.colors[$colorScheme],
|
|
258
261
|
}),
|
|
259
262
|
...($selected &&
|
|
260
263
|
!$isSectionHeader &&
|
|
@@ -279,7 +282,7 @@ const RowContainer = forwardRef<
|
|
|
279
282
|
backgroundColor: $selected
|
|
280
283
|
? $colorScheme === "secondary"
|
|
281
284
|
? cssVars.colors.secondaryLight
|
|
282
|
-
: cssVars.colors.
|
|
285
|
+
: cssVars.colors.selectedListItemBackground
|
|
283
286
|
: cssVars.colors.activeBackground,
|
|
284
287
|
},
|
|
285
288
|
}),
|
|
@@ -358,7 +361,7 @@ const ListViewDragIndicatorElement = forwardRef<
|
|
|
358
361
|
backgroundColor:
|
|
359
362
|
$colorScheme === "secondary"
|
|
360
363
|
? cssVars.colors.secondary
|
|
361
|
-
: cssVars.colors.
|
|
364
|
+
: cssVars.colors.selectedListItemBackground,
|
|
362
365
|
border: `2px solid white`,
|
|
363
366
|
boxShadow: "0 0 2px rgba(0,0,0,0.5)",
|
|
364
367
|
}),
|
|
@@ -1,8 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
FileIcon,
|
|
3
|
+
ImageIcon,
|
|
4
|
+
SpeakerLoudIcon,
|
|
5
|
+
VideoIcon,
|
|
6
|
+
} from "@noya-app/noya-icons";
|
|
2
7
|
import React, { memo, useMemo } from "react";
|
|
3
8
|
import { cssVars } from "../theme";
|
|
4
9
|
import { cx } from "../utils/classNames";
|
|
10
|
+
import { CollectionItemSize } from "./Collection";
|
|
5
11
|
import { IconName, Icons } from "./Icons";
|
|
12
|
+
import { fileExtensions, fileNames } from "./catppuccin/fileIcons";
|
|
6
13
|
|
|
7
14
|
type ColorScheme = "primary" | "secondary" | "icon" | "warning";
|
|
8
15
|
|
|
@@ -29,6 +36,9 @@ type MediaThumbnailProps = {
|
|
|
29
36
|
* If provided, use this icon instead of the default one for the asset type
|
|
30
37
|
*/
|
|
31
38
|
iconName?: IconName;
|
|
39
|
+
size?: CollectionItemSize;
|
|
40
|
+
|
|
41
|
+
fileName?: string;
|
|
32
42
|
};
|
|
33
43
|
|
|
34
44
|
export const getThumbnailColors = (
|
|
@@ -37,8 +47,8 @@ export const getThumbnailColors = (
|
|
|
37
47
|
) => {
|
|
38
48
|
if (selected) {
|
|
39
49
|
return {
|
|
40
|
-
icon: cssVars.colors.
|
|
41
|
-
background: cssVars.colors.
|
|
50
|
+
icon: cssVars.colors.selectedListItemText,
|
|
51
|
+
background: cssVars.colors.selectedListItemIconBackground,
|
|
42
52
|
};
|
|
43
53
|
}
|
|
44
54
|
|
|
@@ -67,29 +77,34 @@ export const getThumbnailColors = (
|
|
|
67
77
|
}
|
|
68
78
|
};
|
|
69
79
|
|
|
70
|
-
const iconStyles = {
|
|
71
|
-
width: "max(15px, 20%)",
|
|
72
|
-
height: "max(15px, 20%)",
|
|
73
|
-
};
|
|
74
|
-
|
|
75
80
|
export const MediaThumbnail = memo(function MediaThumbnail({
|
|
76
81
|
contentType: contentTypeProp,
|
|
77
82
|
selected = false,
|
|
78
83
|
className,
|
|
79
84
|
colorScheme = "icon",
|
|
80
85
|
url,
|
|
81
|
-
iconName
|
|
86
|
+
iconName,
|
|
87
|
+
size = "medium",
|
|
88
|
+
fileName,
|
|
82
89
|
}: MediaThumbnailProps) {
|
|
83
90
|
const { icon: iconColor, background: backgroundColor } = getThumbnailColors(
|
|
84
91
|
colorScheme,
|
|
85
92
|
selected
|
|
86
93
|
);
|
|
87
94
|
const contentType = getAssetType(contentTypeProp);
|
|
95
|
+
const iconStyles = useMemo(
|
|
96
|
+
() => ({
|
|
97
|
+
width: iconSizeMap[size],
|
|
98
|
+
height: iconSizeMap[size],
|
|
99
|
+
}),
|
|
100
|
+
[size]
|
|
101
|
+
);
|
|
88
102
|
|
|
89
103
|
const content = useMemo(() => {
|
|
90
104
|
switch (contentType) {
|
|
91
105
|
case "image":
|
|
92
106
|
if (!url) return <ImageIcon color={iconColor} style={iconStyles} />;
|
|
107
|
+
|
|
93
108
|
return (
|
|
94
109
|
<img
|
|
95
110
|
src={url}
|
|
@@ -98,18 +113,32 @@ export const MediaThumbnail = memo(function MediaThumbnail({
|
|
|
98
113
|
tabIndex={-1}
|
|
99
114
|
/>
|
|
100
115
|
);
|
|
101
|
-
|
|
102
116
|
case "video":
|
|
103
117
|
return <VideoIcon color={iconColor} style={iconStyles} />;
|
|
104
|
-
|
|
105
118
|
case "audio":
|
|
106
119
|
return <SpeakerLoudIcon color={iconColor} style={iconStyles} />;
|
|
107
|
-
|
|
108
120
|
default:
|
|
109
|
-
|
|
110
|
-
|
|
121
|
+
if (iconName) {
|
|
122
|
+
const Icon = Icons[iconName];
|
|
123
|
+
return <Icon color={iconColor} style={iconStyles} />;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (fileName) {
|
|
127
|
+
const fileIcon = findFileIcon(fileName);
|
|
128
|
+
if (fileIcon) {
|
|
129
|
+
return (
|
|
130
|
+
<img
|
|
131
|
+
src={`https://api.iconify.design/catppuccin/${fileIcon}.svg?height=none&box=1`}
|
|
132
|
+
alt=""
|
|
133
|
+
className="w-4 h-4"
|
|
134
|
+
/>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return <FileIcon color={iconColor} style={iconStyles} />;
|
|
111
140
|
}
|
|
112
|
-
}, [contentType, url, iconColor, iconName]);
|
|
141
|
+
}, [contentType, url, iconColor, iconStyles, iconName, fileName]);
|
|
113
142
|
|
|
114
143
|
return (
|
|
115
144
|
<div
|
|
@@ -123,3 +152,22 @@ export const MediaThumbnail = memo(function MediaThumbnail({
|
|
|
123
152
|
</div>
|
|
124
153
|
);
|
|
125
154
|
});
|
|
155
|
+
|
|
156
|
+
const iconSizeMap: Record<CollectionItemSize, number | string> = {
|
|
157
|
+
xxs: 12,
|
|
158
|
+
xs: 12,
|
|
159
|
+
small: 12,
|
|
160
|
+
medium: "max(15px, 20%)",
|
|
161
|
+
large: "max(15px, 20%)",
|
|
162
|
+
xl: "max(15px, 20%)",
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
function findFileIcon(fileName: string) {
|
|
166
|
+
const fileIcon = fileNames[fileName];
|
|
167
|
+
if (fileIcon) return fileIcon;
|
|
168
|
+
const fileExtensionIndex = fileName.lastIndexOf(".");
|
|
169
|
+
if (fileExtensionIndex === -1) return undefined;
|
|
170
|
+
const fileExtension = fileName.slice(fileExtensionIndex + 1);
|
|
171
|
+
const fileExtensionIcon = fileExtensions[fileExtension];
|
|
172
|
+
return fileExtensionIcon;
|
|
173
|
+
}
|
|
@@ -33,7 +33,7 @@ interface Props
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
export const popoverStyle = {
|
|
36
|
-
base: "rounded font-[14px] bg-popover-background shadow-
|
|
36
|
+
base: "rounded font-[14px] bg-popover-background shadow-popover max-h-[600px] overflow-y-auto text-text-muted z-[1000]",
|
|
37
37
|
large: "w-[680px]",
|
|
38
38
|
normal: "w-[240px]",
|
|
39
39
|
};
|
|
@@ -119,21 +119,21 @@ export const SearchCompletionMenu = forwardRef(function SearchCompletionMenu(
|
|
|
119
119
|
(event: React.KeyboardEvent<HTMLInputElement>) => {
|
|
120
120
|
handleKeyboardEvent(event.nativeEvent, getCurrentPlatform(navigator), {
|
|
121
121
|
ArrowUp: () => {
|
|
122
|
-
const nextIndex = getNextIndex(
|
|
123
|
-
results,
|
|
124
|
-
index,
|
|
125
|
-
"previous",
|
|
126
|
-
(item) => item.disabled ?? false
|
|
127
|
-
);
|
|
122
|
+
const nextIndex = getNextIndex({
|
|
123
|
+
items: results,
|
|
124
|
+
currentIndex: index,
|
|
125
|
+
direction: "previous",
|
|
126
|
+
isDisabled: (item) => item.disabled ?? false,
|
|
127
|
+
});
|
|
128
128
|
setIndex(nextIndex);
|
|
129
129
|
},
|
|
130
130
|
ArrowDown: () => {
|
|
131
|
-
const nextIndex = getNextIndex(
|
|
132
|
-
results,
|
|
133
|
-
index,
|
|
134
|
-
"next",
|
|
135
|
-
(item) => item.disabled ?? false
|
|
136
|
-
);
|
|
131
|
+
const nextIndex = getNextIndex({
|
|
132
|
+
items: results,
|
|
133
|
+
currentIndex: index,
|
|
134
|
+
direction: "next",
|
|
135
|
+
isDisabled: (item) => item.disabled ?? false,
|
|
136
|
+
});
|
|
137
137
|
setIndex(nextIndex);
|
|
138
138
|
},
|
|
139
139
|
Tab: () => selectAndClose(results[index]!),
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { memoGeneric } from "@noya-app/react-utils";
|
|
1
2
|
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group";
|
|
2
3
|
import React, {
|
|
3
4
|
createContext,
|
|
@@ -7,7 +8,6 @@ import React, {
|
|
|
7
8
|
useContext,
|
|
8
9
|
useMemo,
|
|
9
10
|
} from "react";
|
|
10
|
-
import { memoGeneric } from "../../../noya-react-utils/src/utils/reactGenerics";
|
|
11
11
|
import { useLabel } from "../hooks/useLabel";
|
|
12
12
|
import { cx } from "../utils/classNames";
|
|
13
13
|
import { renderIcon } from "./Icons";
|
|
@@ -80,7 +80,7 @@ const SegmentedControlItem = forwardRef(function SegmentedControlItem<
|
|
|
80
80
|
? "aria-checked:bg-primary aria-checked:text-white"
|
|
81
81
|
: "aria-checked:bg-background aria-checked:text-text",
|
|
82
82
|
"focus:z-interactable",
|
|
83
|
-
"aria-checked:shadow-
|
|
83
|
+
"aria-checked:shadow-segment",
|
|
84
84
|
"whitespace-pre",
|
|
85
85
|
className
|
|
86
86
|
)}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { MenuItem, ToolbarMenu } from "@noya-app/noya-designsystem";
|
|
2
|
+
import { Rect } from "@noya-app/noya-geometry";
|
|
3
|
+
import { memoGeneric, useSize } from "@noya-app/react-utils";
|
|
4
|
+
import * as PopperPrimitive from "@radix-ui/react-popper";
|
|
5
|
+
import type { MeasurableElement } from "@radix-ui/utils";
|
|
6
|
+
import React, { CSSProperties, useMemo } from "react";
|
|
7
|
+
|
|
8
|
+
// Create a Measurable from a Rect
|
|
9
|
+
const createVirtualElement = (rect: Rect): MeasurableElement => ({
|
|
10
|
+
getBoundingClientRect: () => ({
|
|
11
|
+
width: rect.width,
|
|
12
|
+
height: rect.height,
|
|
13
|
+
x: rect.x,
|
|
14
|
+
y: rect.y,
|
|
15
|
+
top: rect.y,
|
|
16
|
+
right: rect.x + rect.width,
|
|
17
|
+
bottom: rect.y + rect.height,
|
|
18
|
+
left: rect.x,
|
|
19
|
+
toJSON: () => null,
|
|
20
|
+
}),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
type SelectionToolbarProps<T extends string> = {
|
|
24
|
+
rect: Rect;
|
|
25
|
+
menuItems: MenuItem<T>[];
|
|
26
|
+
onSelectMenuItem: (item: T) => void;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const SelectionToolbar = memoGeneric(function SelectionToolbar<
|
|
30
|
+
T extends string,
|
|
31
|
+
>({ rect, menuItems, onSelectMenuItem }: SelectionToolbarProps<T>) {
|
|
32
|
+
const containerRef = React.useRef<HTMLDivElement>(null);
|
|
33
|
+
const size = useSize(containerRef, "width");
|
|
34
|
+
|
|
35
|
+
// Create a virtual reference element from the rect
|
|
36
|
+
const virtualRef = useMemo(
|
|
37
|
+
() => ({
|
|
38
|
+
current: createVirtualElement(rect),
|
|
39
|
+
}),
|
|
40
|
+
[rect]
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<PopperPrimitive.Root>
|
|
45
|
+
<PopperPrimitive.Anchor virtualRef={virtualRef} />
|
|
46
|
+
<PopperPrimitive.Content
|
|
47
|
+
side="top"
|
|
48
|
+
sideOffset={10}
|
|
49
|
+
align="center"
|
|
50
|
+
avoidCollisions
|
|
51
|
+
collisionPadding={10}
|
|
52
|
+
data-editor-overlay
|
|
53
|
+
contentEditable={false}
|
|
54
|
+
style={{
|
|
55
|
+
zIndex: 1000,
|
|
56
|
+
opacity: size ? 1 : 0,
|
|
57
|
+
transition: "opacity 0.2s ease-in-out",
|
|
58
|
+
["--n-input-background" as keyof CSSProperties]: "transparent",
|
|
59
|
+
}}
|
|
60
|
+
>
|
|
61
|
+
<div
|
|
62
|
+
ref={containerRef}
|
|
63
|
+
className="flex gap-1 bg-popover-background rounded shadow-popover p-1"
|
|
64
|
+
>
|
|
65
|
+
<ToolbarMenu items={menuItems} onSelectMenuItem={onSelectMenuItem} />
|
|
66
|
+
</div>
|
|
67
|
+
</PopperPrimitive.Content>
|
|
68
|
+
</PopperPrimitive.Root>
|
|
69
|
+
);
|
|
70
|
+
});
|
|
@@ -38,7 +38,7 @@ export const Switch = function Switch({
|
|
|
38
38
|
onChange(newValue);
|
|
39
39
|
}}
|
|
40
40
|
className={cx(
|
|
41
|
-
"all-unset w-8 h-[19px] bg-active-background rounded-full relative cursor-pointer ring-offset-background [webkit-tap-highlight-color:rgba(0,0,0,0)] focus:ring-2 focus:ring-primary focus:ring-offset-[1px] outline-none data-[state=checked]:bg-primary transition-all",
|
|
41
|
+
"all-unset w-8 h-[19px] bg-active-background rounded-full relative cursor-pointer ring-offset-background [-webkit-tap-highlight-color:rgba(0,0,0,0)] focus:ring-2 focus:ring-primary focus:ring-offset-[1px] outline-none data-[state=checked]:bg-primary transition-all",
|
|
42
42
|
colorScheme === "secondary" && "data-[state=checked]:bg-secondary",
|
|
43
43
|
"focus:z-interactable",
|
|
44
44
|
className
|
package/src/components/Text.tsx
CHANGED
|
@@ -33,8 +33,8 @@ const elements: Record<Variant, keyof ReactHTML> = {
|
|
|
33
33
|
};
|
|
34
34
|
|
|
35
35
|
export const textStyles: Record<Variant, string> = {
|
|
36
|
-
title: "font-sans text-title font-bold
|
|
37
|
-
subtitle: "font-sans text-subtitle font-medium
|
|
36
|
+
title: "font-sans text-title font-bold",
|
|
37
|
+
subtitle: "font-sans text-subtitle font-medium",
|
|
38
38
|
heading1: "font-sans text-heading1 font-semibold",
|
|
39
39
|
heading2: "font-sans text-heading2 font-medium",
|
|
40
40
|
heading3: "font-sans text-heading3 font-normal",
|
package/src/components/Toast.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import * as ToastPrimitive from
|
|
2
|
-
import React, { ReactNode } from
|
|
3
|
-
import { IconButton } from
|
|
4
|
-
import { textStyles } from
|
|
1
|
+
import * as ToastPrimitive from "@radix-ui/react-toast";
|
|
2
|
+
import React, { ReactNode } from "react";
|
|
3
|
+
import { IconButton } from "./IconButton";
|
|
4
|
+
import { textStyles } from "./Text";
|
|
5
5
|
|
|
6
6
|
export const Toast = ({
|
|
7
7
|
title,
|
|
@@ -15,15 +15,21 @@ export const Toast = ({
|
|
|
15
15
|
}) => {
|
|
16
16
|
return (
|
|
17
17
|
<ToastPrimitive.Root
|
|
18
|
-
className="px-2 py-2.5 grid grid-cols-[auto_max-content] gap-x-2.5 items-center rounded text-sm bg-popover-background shadow-
|
|
18
|
+
className="px-2 py-2.5 grid grid-cols-[auto_max-content] gap-x-2.5 items-center rounded text-sm bg-popover-background shadow-popover text-text-muted"
|
|
19
19
|
{...props}
|
|
20
20
|
>
|
|
21
21
|
{title && (
|
|
22
|
-
<ToastPrimitive.Title
|
|
22
|
+
<ToastPrimitive.Title
|
|
23
|
+
className={`mb-[5px] ${textStyles.label} font-bold text-text`}
|
|
24
|
+
>
|
|
23
25
|
{title}
|
|
24
26
|
</ToastPrimitive.Title>
|
|
25
27
|
)}
|
|
26
|
-
<ToastPrimitive.Description
|
|
28
|
+
<ToastPrimitive.Description
|
|
29
|
+
className={`${textStyles.small} text-text-muted`}
|
|
30
|
+
>
|
|
31
|
+
{content}
|
|
32
|
+
</ToastPrimitive.Description>
|
|
27
33
|
{children && (
|
|
28
34
|
<ToastPrimitive.Action asChild altText="">
|
|
29
35
|
{children}
|
|
@@ -2,8 +2,11 @@ import { DropdownChevronIcon } from "@noya-app/noya-icons";
|
|
|
2
2
|
import { useKeyboardShortcuts } from "@noya-app/noya-keymap";
|
|
3
3
|
import React from "react";
|
|
4
4
|
|
|
5
|
+
import { memoGeneric } from "@noya-app/react-utils";
|
|
6
|
+
import { cssVars } from "../theme";
|
|
5
7
|
import { BaseToolbar } from "./BaseToolbar";
|
|
6
8
|
import { Button } from "./Button";
|
|
9
|
+
import { DividerVertical } from "./Divider";
|
|
7
10
|
import { DropdownMenu } from "./DropdownMenu";
|
|
8
11
|
import { renderIcon } from "./Icons";
|
|
9
12
|
import {
|
|
@@ -12,6 +15,7 @@ import {
|
|
|
12
15
|
MenuItem,
|
|
13
16
|
} from "./internal/Menu";
|
|
14
17
|
import { Spacer } from "./Spacer";
|
|
18
|
+
import { Tooltip } from "./Tooltip";
|
|
15
19
|
|
|
16
20
|
export interface ToolbarProps<T extends string = string> {
|
|
17
21
|
children?: React.ReactNode;
|
|
@@ -76,7 +80,12 @@ export function Toolbar<T extends string = string>({
|
|
|
76
80
|
);
|
|
77
81
|
}
|
|
78
82
|
|
|
79
|
-
|
|
83
|
+
const activeButtonStyle = {
|
|
84
|
+
backgroundColor: cssVars.colors.primaryPastel,
|
|
85
|
+
color: cssVars.colors.primary,
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export const ToolbarMenu = memoGeneric(function ToolbarMenu<T extends string>({
|
|
80
89
|
items,
|
|
81
90
|
onSelectMenuItem,
|
|
82
91
|
}: {
|
|
@@ -86,14 +95,18 @@ export function ToolbarMenu<T extends string>({
|
|
|
86
95
|
return (
|
|
87
96
|
<>
|
|
88
97
|
{items.map((item, i) => {
|
|
89
|
-
if (item.type === "separator") return null;
|
|
90
98
|
if (item.type === "sectionHeader") return null;
|
|
91
99
|
|
|
100
|
+
if (item.type === "separator") {
|
|
101
|
+
return <DividerVertical key={i} overflow={4} />;
|
|
102
|
+
}
|
|
103
|
+
|
|
92
104
|
if (isSelectableMenuItem(item)) {
|
|
93
|
-
|
|
105
|
+
const content = (
|
|
94
106
|
<Button
|
|
95
107
|
key={i}
|
|
96
108
|
disabled={item.disabled}
|
|
109
|
+
style={item.checked ? activeButtonStyle : undefined}
|
|
97
110
|
onClick={() => {
|
|
98
111
|
if (onSelectMenuItem && item.value) {
|
|
99
112
|
onSelectMenuItem(item.value);
|
|
@@ -101,14 +114,18 @@ export function ToolbarMenu<T extends string>({
|
|
|
101
114
|
}}
|
|
102
115
|
>
|
|
103
116
|
{item.title}
|
|
104
|
-
{item.icon &&
|
|
105
|
-
|
|
106
|
-
<Spacer.Horizontal inline size={6} />
|
|
107
|
-
{renderIcon(item.icon)}
|
|
108
|
-
</>
|
|
109
|
-
)}
|
|
117
|
+
{item.title && item.icon && <Spacer.Horizontal inline size={6} />}
|
|
118
|
+
{item.icon && renderIcon(item.icon)}
|
|
110
119
|
</Button>
|
|
111
120
|
);
|
|
121
|
+
|
|
122
|
+
return item.tooltip ? (
|
|
123
|
+
<Tooltip sideOffset={10} content={item.tooltip} key={i}>
|
|
124
|
+
{content}
|
|
125
|
+
</Tooltip>
|
|
126
|
+
) : (
|
|
127
|
+
content
|
|
128
|
+
);
|
|
112
129
|
}
|
|
113
130
|
|
|
114
131
|
return (
|
|
@@ -121,8 +138,13 @@ export function ToolbarMenu<T extends string>({
|
|
|
121
138
|
}
|
|
122
139
|
}}
|
|
123
140
|
>
|
|
124
|
-
<Button
|
|
141
|
+
<Button
|
|
142
|
+
disabled={item.disabled}
|
|
143
|
+
style={item.checked ? activeButtonStyle : undefined}
|
|
144
|
+
>
|
|
125
145
|
{item.title}
|
|
146
|
+
{item.title && item.icon && <Spacer.Horizontal inline size={6} />}
|
|
147
|
+
{item.icon && renderIcon(item.icon)}
|
|
126
148
|
<Spacer.Horizontal size={6} />
|
|
127
149
|
<DropdownChevronIcon />
|
|
128
150
|
</Button>
|
|
@@ -131,4 +153,4 @@ export function ToolbarMenu<T extends string>({
|
|
|
131
153
|
})}
|
|
132
154
|
</>
|
|
133
155
|
);
|
|
134
|
-
}
|
|
156
|
+
});
|
|
@@ -9,11 +9,13 @@ import { textStyles } from "./Text";
|
|
|
9
9
|
interface Props {
|
|
10
10
|
children: React.ReactNode;
|
|
11
11
|
content: React.ReactNode;
|
|
12
|
+
sideOffset?: number;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
export const Tooltip = React.memo(function Tooltip({
|
|
15
16
|
children,
|
|
16
17
|
content,
|
|
18
|
+
sideOffset = 2,
|
|
17
19
|
}: Props) {
|
|
18
20
|
return (
|
|
19
21
|
<TooltipPrimitive.Provider>
|
|
@@ -23,9 +25,9 @@ export const Tooltip = React.memo(function Tooltip({
|
|
|
23
25
|
<TooltipPrimitive.Content
|
|
24
26
|
side="bottom"
|
|
25
27
|
align="center"
|
|
26
|
-
sideOffset={
|
|
28
|
+
sideOffset={sideOffset}
|
|
27
29
|
collisionPadding={8}
|
|
28
|
-
className={`${textStyles.small} text-text rounded-[3px] py-small px-medium bg-popover-background shadow-
|
|
30
|
+
className={`${textStyles.small} text-text rounded-[3px] py-small px-medium bg-popover-background shadow-popover border border-solid border-divider-strong z-menu`}
|
|
29
31
|
>
|
|
30
32
|
{content}
|
|
31
33
|
{/* <Arrow /> */}
|