@noya-app/noya-designsystem 0.1.62 → 0.1.64
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/.eslintrc.js +1 -5
- package/.turbo/turbo-build.log +10 -10
- package/.turbo/turbo-lint.log +0 -1
- package/CHANGELOG.md +23 -0
- package/dist/index.css +1 -1
- package/dist/index.d.mts +299 -179
- package/dist/index.d.ts +299 -179
- package/dist/index.js +3772 -2999
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3485 -2720
- package/dist/index.mjs.map +1 -1
- package/package.json +10 -22
- package/src/__tests__/validateDropIndicator.test.ts +26 -41
- package/src/__tests__/workspaceLayout.test.ts +281 -0
- package/src/components/ActionMenu.tsx +2 -0
- package/src/components/ActivityIndicator.tsx +5 -3
- package/src/components/AnimatePresence.tsx +6 -4
- package/src/components/Avatar.tsx +4 -2
- package/src/components/BaseToolbar.tsx +45 -34
- package/src/components/Breadcrumbs.tsx +2 -0
- package/src/components/Button.tsx +35 -21
- package/src/components/Chip.tsx +2 -0
- package/src/components/Collection.tsx +4 -1
- package/src/components/ColorSwatch.tsx +42 -22
- package/src/components/ColorSwatchControl.tsx +5 -3
- package/src/components/Combobox.tsx +2 -0
- package/src/components/CommandPalette.tsx +2 -0
- package/src/components/ContextMenu.tsx +3 -1
- package/src/components/Dialog.tsx +3 -1
- package/src/components/DimensionInput.tsx +3 -1
- package/src/components/Divider.tsx +10 -6
- package/src/components/DraggableMenuButton.tsx +22 -4
- package/src/components/Drawer.tsx +11 -3
- package/src/components/DropdownMenu.tsx +34 -6
- package/src/components/EditableText.tsx +2 -0
- package/src/components/FileUploadIndicator.tsx +108 -0
- package/src/components/FillPreviewBackground.tsx +2 -0
- package/src/components/FloatingWindow.tsx +2 -0
- package/src/components/GradientPicker.tsx +2 -0
- package/src/components/Grid.tsx +47 -38
- package/src/components/GridView.tsx +33 -11
- package/src/components/IconButton.tsx +4 -2
- package/src/components/Icons.tsx +1 -1
- package/src/components/InputField.tsx +7 -5
- package/src/components/Label.tsx +1 -3
- package/src/components/LabeledElementView.tsx +6 -2
- package/src/components/LabeledField.tsx +38 -34
- package/src/components/List.tsx +10 -2
- package/src/components/ListView.tsx +27 -14
- package/src/components/MediaThumbnail.tsx +31 -2
- package/src/components/Message.tsx +8 -9
- package/src/components/Popover.tsx +15 -4
- package/src/components/Progress.tsx +20 -7
- package/src/components/ScrollArea.tsx +3 -1
- package/src/components/SearchCompletionMenu.tsx +2 -0
- package/src/components/Section.tsx +2 -0
- package/src/components/SegmentedControl.tsx +43 -17
- package/src/components/SelectMenu.tsx +6 -4
- package/src/components/SelectionToolbar.tsx +31 -8
- package/src/components/Slider.tsx +4 -2
- package/src/components/Spacer.tsx +0 -1
- package/src/components/Switch.tsx +3 -1
- package/src/components/Tabs.tsx +48 -0
- package/src/components/Text.tsx +6 -5
- package/src/components/TextArea.tsx +2 -0
- package/src/components/Toast.tsx +1 -1
- package/src/components/Toolbar.tsx +113 -12
- package/src/components/ToolbarDrawer.tsx +29 -0
- package/src/components/Tooltip.tsx +9 -9
- package/src/components/TreeView.tsx +2 -0
- package/src/components/UserPointer.tsx +2 -0
- package/src/components/ai-assistant/AIAssistantLayout.tsx +2 -0
- package/src/components/blocks/ImageBlockComponent.tsx +156 -0
- package/src/components/connected-users-menu/ConnectedUsersMenuLayout.tsx +2 -0
- package/src/components/file-explorer/FileExplorerLayout.tsx +14 -6
- package/src/components/internal/Checkmark.tsx +24 -0
- package/src/components/internal/Menu.tsx +21 -3
- package/src/components/internal/MenuViewport.tsx +8 -5
- package/src/components/internal/SelectItem.tsx +4 -23
- package/src/components/internal/TextInput.tsx +2 -0
- package/src/components/sorting/DragRegistration.tsx +8 -4
- package/src/components/sorting/SharedDragProvider.tsx +43 -18
- package/src/components/sorting/Sortable.tsx +19 -10
- package/src/components/sorting/createSharedDrag.tsx +7 -6
- package/src/components/sorting/sorting.ts +6 -9
- package/src/components/workspace/DrawerWorkspaceLayout.tsx +113 -28
- package/src/components/workspace/PanelWorkspaceLayout.tsx +149 -16
- package/src/components/workspace/VerticalTabMenu.tsx +36 -0
- package/src/components/workspace/WorkspaceLayout.tsx +132 -47
- package/src/components/workspace/constants.ts +4 -0
- package/src/components/workspace/panelStorage.ts +216 -0
- package/src/components/workspace/renderPanelChildren.tsx +12 -0
- package/src/components/workspace/types.ts +30 -7
- package/src/contexts/DesignSystemConfiguration.tsx +12 -5
- package/src/contexts/DialogContext.tsx +2 -0
- package/src/contexts/FloatingWindowContext.tsx +2 -0
- package/src/contexts/GlobalInputBlurContext.tsx +2 -0
- package/src/contexts/ImageDataContext.tsx +2 -0
- package/src/contexts/LinkComponentContext.tsx +17 -0
- package/src/contexts/OpenPortalsContext.tsx +2 -0
- package/src/contexts/PortalScopeContext.tsx +2 -0
- package/src/hooks/mergeEventHandlers.ts +1 -1
- package/src/hooks/useHover.ts +30 -17
- package/src/hooks/useIndent.ts +2 -0
- package/src/hooks/useLabel.ts +12 -10
- package/src/hooks/useObjectURL.ts +5 -3
- package/src/hooks/usePlatform.ts +5 -3
- package/src/hooks/useTheme.ts +3 -1
- package/src/index.css +25 -4
- package/src/index.tsx +7 -2
- package/src/utils/classNames.ts +4 -3
- package/src/utils/combobox.ts +2 -0
- package/src/utils/editableBlockStyles.ts +10 -0
- package/src/utils/formatByteSize.ts +8 -0
- package/src/utils/withSeparatorElements.ts +1 -1
- package/src/hooks/usePreservePanelSize.tsx +0 -183
- package/src/hooks/useWindowSize.tsx +0 -26
- package/src/mediaQuery.ts +0 -13
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { useControlledOrUncontrolled } from "@noya-app/react-utils";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { DetailedHTMLProps, HTMLAttributes } from "react";
|
|
4
|
+
import { cx } from "../utils/classNames";
|
|
5
|
+
import { Divider } from "./Divider";
|
|
6
|
+
import { isSelectableMenuItem, MenuItem } from "./internal/Menu";
|
|
7
|
+
import { SegmentedControl } from "./SegmentedControl";
|
|
8
|
+
|
|
9
|
+
export type TabsProps<T extends string = string> = {
|
|
10
|
+
value?: T;
|
|
11
|
+
onValueChange?: (value: T) => void;
|
|
12
|
+
items: MenuItem<T>[];
|
|
13
|
+
renderContent: ({ value }: { value: T }) => React.ReactNode;
|
|
14
|
+
variant?: "default" | "tabs" | "buttons";
|
|
15
|
+
} & DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
|
|
16
|
+
|
|
17
|
+
export function Tabs<T extends string = string>({
|
|
18
|
+
value,
|
|
19
|
+
onValueChange,
|
|
20
|
+
items,
|
|
21
|
+
renderContent,
|
|
22
|
+
variant = "tabs",
|
|
23
|
+
...props
|
|
24
|
+
}: TabsProps<T>) {
|
|
25
|
+
const selectableItems = items.filter(isSelectableMenuItem);
|
|
26
|
+
|
|
27
|
+
const [activeTab, setActiveTab] = useControlledOrUncontrolled({
|
|
28
|
+
defaultValue: selectableItems.at(0)?.value ?? ("" as T),
|
|
29
|
+
value,
|
|
30
|
+
onChange: onValueChange,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div {...props} className={cx("flex flex-col", props.className)}>
|
|
35
|
+
<div className="flex flex-row px-3">
|
|
36
|
+
<SegmentedControl
|
|
37
|
+
className="flex-1"
|
|
38
|
+
value={activeTab}
|
|
39
|
+
onValueChange={setActiveTab}
|
|
40
|
+
variant={variant}
|
|
41
|
+
items={selectableItems}
|
|
42
|
+
/>
|
|
43
|
+
</div>
|
|
44
|
+
<Divider />
|
|
45
|
+
{renderContent({ value: activeTab })}
|
|
46
|
+
</div>
|
|
47
|
+
);
|
|
48
|
+
}
|
package/src/components/Text.tsx
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React, { ForwardedRef, forwardRef, ReactNode } from "react";
|
|
2
4
|
import { ThemeColor } from "../theme";
|
|
3
5
|
import { camelToKebabCase, KebabToCamelCase } from "../theme/themeUtils";
|
|
4
6
|
import { cx } from "../utils/classNames";
|
|
@@ -17,7 +19,7 @@ type Variant =
|
|
|
17
19
|
| "code"
|
|
18
20
|
| "label";
|
|
19
21
|
|
|
20
|
-
const elements: Record<Variant,
|
|
22
|
+
const elements: Record<Variant, React.HTMLElementType> = {
|
|
21
23
|
title: "h1",
|
|
22
24
|
subtitle: "h1",
|
|
23
25
|
heading1: "h1",
|
|
@@ -48,7 +50,7 @@ export const textStyles: Record<Variant, string> = {
|
|
|
48
50
|
} as const;
|
|
49
51
|
|
|
50
52
|
export type TextProps = {
|
|
51
|
-
as?:
|
|
53
|
+
as?: React.HTMLElementType;
|
|
52
54
|
href?: string;
|
|
53
55
|
className?: string;
|
|
54
56
|
style?: React.CSSProperties;
|
|
@@ -78,11 +80,10 @@ export const Text = forwardRef(function Text(
|
|
|
78
80
|
}: TextProps,
|
|
79
81
|
forwardedRef: ForwardedRef<HTMLElement>
|
|
80
82
|
) {
|
|
81
|
-
const Component = as ?? elements[variant] ?? "span";
|
|
83
|
+
const Component = (as ?? elements[variant] ?? "span") as "span";
|
|
82
84
|
|
|
83
85
|
return (
|
|
84
86
|
<Component
|
|
85
|
-
// @ts-expect-error expected to be many semantic html elements
|
|
86
87
|
ref={forwardedRef}
|
|
87
88
|
className={cx(
|
|
88
89
|
`${textStyles[variant]} text-${camelToKebabCase(String(color)) ?? "text"} `,
|
package/src/components/Toast.tsx
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
1
3
|
import { useKeyboardShortcuts } from "@noya-app/noya-keymap";
|
|
2
|
-
import React, { ComponentProps } from "react";
|
|
4
|
+
import React, { ComponentProps, ReactNode } from "react";
|
|
3
5
|
|
|
4
6
|
import { memoGeneric } from "@noya-app/react-utils";
|
|
5
7
|
import { BaseToolbar } from "./BaseToolbar";
|
|
@@ -9,17 +11,18 @@ import { DropdownMenu } from "./DropdownMenu";
|
|
|
9
11
|
import {
|
|
10
12
|
getKeyboardShortcutsForMenuItems,
|
|
11
13
|
isSelectableMenuItem,
|
|
14
|
+
KeyboardShortcut,
|
|
12
15
|
MenuItem,
|
|
16
|
+
PopoverMenuItem,
|
|
13
17
|
SelectableMenuItem,
|
|
14
18
|
SubMenuItem,
|
|
15
19
|
} from "./internal/Menu";
|
|
20
|
+
import { Popover } from "./Popover";
|
|
21
|
+
import { Spacer } from "./Spacer";
|
|
22
|
+
import { ToolbarDrawer } from "./ToolbarDrawer";
|
|
16
23
|
import { Tooltip } from "./Tooltip";
|
|
17
24
|
|
|
18
|
-
const
|
|
19
|
-
height: 27,
|
|
20
|
-
width: 27,
|
|
21
|
-
minWidth: 27,
|
|
22
|
-
};
|
|
25
|
+
const SIDE_OFFSET = 6;
|
|
23
26
|
|
|
24
27
|
export const ToolbarMenuDropdown = memoGeneric(function ToolbarMenuDropdown<
|
|
25
28
|
T extends string,
|
|
@@ -55,24 +58,102 @@ export const ToolbarMenuDropdown = memoGeneric(function ToolbarMenuDropdown<
|
|
|
55
58
|
);
|
|
56
59
|
});
|
|
57
60
|
|
|
61
|
+
export const ToolbarShortcut = ({
|
|
62
|
+
shortcut,
|
|
63
|
+
label,
|
|
64
|
+
}: {
|
|
65
|
+
shortcut: string;
|
|
66
|
+
label?: ReactNode;
|
|
67
|
+
}) => {
|
|
68
|
+
return (
|
|
69
|
+
<div className="flex items-center">
|
|
70
|
+
{label}
|
|
71
|
+
<Spacer.Horizontal size={6} />
|
|
72
|
+
<KeyboardShortcut shortcut={shortcut} />
|
|
73
|
+
</div>
|
|
74
|
+
);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export const ToolbarMenuPopover = memoGeneric(function ToolbarMenuPopover({
|
|
78
|
+
item,
|
|
79
|
+
portalContainer,
|
|
80
|
+
}: {
|
|
81
|
+
item: PopoverMenuItem;
|
|
82
|
+
portalContainer?: HTMLElement | null;
|
|
83
|
+
}) {
|
|
84
|
+
const {
|
|
85
|
+
disabled,
|
|
86
|
+
icon,
|
|
87
|
+
title,
|
|
88
|
+
tooltip,
|
|
89
|
+
content: itemContent,
|
|
90
|
+
checked,
|
|
91
|
+
} = item;
|
|
92
|
+
const [open, setOpen] = React.useState(false);
|
|
93
|
+
|
|
94
|
+
const content = (
|
|
95
|
+
<span>
|
|
96
|
+
<Popover
|
|
97
|
+
open={open}
|
|
98
|
+
onOpenChange={setOpen}
|
|
99
|
+
side="top"
|
|
100
|
+
showArrow={false}
|
|
101
|
+
sideOffset={SIDE_OFFSET}
|
|
102
|
+
portalContainer={portalContainer}
|
|
103
|
+
trigger={
|
|
104
|
+
<Button
|
|
105
|
+
disabled={disabled}
|
|
106
|
+
active={checked || open}
|
|
107
|
+
icon={icon}
|
|
108
|
+
iconRight="DropdownChevronIcon"
|
|
109
|
+
>
|
|
110
|
+
{title}
|
|
111
|
+
</Button>
|
|
112
|
+
}
|
|
113
|
+
>
|
|
114
|
+
{typeof itemContent === "function"
|
|
115
|
+
? itemContent({ close: () => setOpen(false) })
|
|
116
|
+
: itemContent}
|
|
117
|
+
</Popover>
|
|
118
|
+
</span>
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
return tooltip && !open ? (
|
|
122
|
+
<Tooltip
|
|
123
|
+
sideOffset={SIDE_OFFSET}
|
|
124
|
+
side="top"
|
|
125
|
+
content={tooltip}
|
|
126
|
+
portalContainer={portalContainer}
|
|
127
|
+
>
|
|
128
|
+
{content}
|
|
129
|
+
</Tooltip>
|
|
130
|
+
) : (
|
|
131
|
+
content
|
|
132
|
+
);
|
|
133
|
+
});
|
|
134
|
+
|
|
58
135
|
export const ToolbarMenuButton = memoGeneric(function ToolbarMenuButton<
|
|
59
136
|
T extends string,
|
|
60
137
|
>({
|
|
61
138
|
item,
|
|
62
139
|
onSelectMenuItem,
|
|
63
140
|
as,
|
|
141
|
+
activeValue,
|
|
142
|
+
tooltipSide,
|
|
64
143
|
}: {
|
|
65
144
|
item: SelectableMenuItem<T>;
|
|
66
145
|
onSelectMenuItem?: (value: T) => void;
|
|
67
146
|
as?: ComponentProps<typeof Button>["as"];
|
|
147
|
+
activeValue?: T;
|
|
148
|
+
tooltipSide?: ComponentProps<typeof Tooltip>["side"];
|
|
68
149
|
}) {
|
|
150
|
+
const isActive = item.checked || activeValue === item.value;
|
|
69
151
|
const content = (
|
|
70
152
|
<Button
|
|
71
153
|
as={as}
|
|
72
154
|
disabled={item.disabled}
|
|
73
|
-
active={
|
|
155
|
+
active={isActive}
|
|
74
156
|
icon={item.icon}
|
|
75
|
-
style={item.icon && !item.title ? iconButtonStyle : undefined}
|
|
76
157
|
{...(as === "a" && { href: item.value })}
|
|
77
158
|
onClick={() => {
|
|
78
159
|
if (onSelectMenuItem && item.value) {
|
|
@@ -84,8 +165,10 @@ export const ToolbarMenuButton = memoGeneric(function ToolbarMenuButton<
|
|
|
84
165
|
</Button>
|
|
85
166
|
);
|
|
86
167
|
|
|
87
|
-
return item.
|
|
88
|
-
<
|
|
168
|
+
return item.drawer && isActive ? (
|
|
169
|
+
<ToolbarDrawer trigger={content}>{item.drawer}</ToolbarDrawer>
|
|
170
|
+
) : item.tooltip ? (
|
|
171
|
+
<Tooltip sideOffset={SIDE_OFFSET} content={item.tooltip} side={tooltipSide}>
|
|
89
172
|
{content}
|
|
90
173
|
</Tooltip>
|
|
91
174
|
) : (
|
|
@@ -99,23 +182,32 @@ export const ToolbarMenuItem = memoGeneric(function ToolbarMenuItem<
|
|
|
99
182
|
item,
|
|
100
183
|
onSelectMenuItem,
|
|
101
184
|
buttonAs,
|
|
185
|
+
activeValue,
|
|
186
|
+
tooltipSide,
|
|
187
|
+
portalContainer,
|
|
102
188
|
}: {
|
|
103
189
|
item: MenuItem<T>;
|
|
104
190
|
onSelectMenuItem?: (value: T) => void;
|
|
105
191
|
buttonAs?: ComponentProps<typeof Button>["as"];
|
|
192
|
+
activeValue?: T;
|
|
193
|
+
tooltipSide?: ComponentProps<typeof Tooltip>["side"];
|
|
194
|
+
portalContainer?: HTMLElement | null;
|
|
106
195
|
}) {
|
|
107
196
|
if (item.type === "sectionHeader") return null;
|
|
108
|
-
|
|
109
197
|
if (item.type === "separator") {
|
|
110
198
|
return <DividerVertical overflow={4} />;
|
|
111
199
|
}
|
|
112
|
-
|
|
200
|
+
if (item.type === "popover") {
|
|
201
|
+
return <ToolbarMenuPopover item={item} portalContainer={portalContainer} />;
|
|
202
|
+
}
|
|
113
203
|
if (isSelectableMenuItem(item)) {
|
|
114
204
|
return (
|
|
115
205
|
<ToolbarMenuButton
|
|
116
206
|
item={item}
|
|
117
207
|
onSelectMenuItem={onSelectMenuItem}
|
|
118
208
|
as={buttonAs}
|
|
209
|
+
activeValue={activeValue}
|
|
210
|
+
tooltipSide={tooltipSide}
|
|
119
211
|
/>
|
|
120
212
|
);
|
|
121
213
|
}
|
|
@@ -129,10 +221,16 @@ export const ToolbarMenu = memoGeneric(function ToolbarMenu<T extends string>({
|
|
|
129
221
|
items,
|
|
130
222
|
onSelectMenuItem,
|
|
131
223
|
buttonAs,
|
|
224
|
+
activeValue,
|
|
225
|
+
tooltipSide,
|
|
226
|
+
portalContainer,
|
|
132
227
|
}: {
|
|
133
228
|
items: MenuItem<T>[];
|
|
134
229
|
onSelectMenuItem?: (value: T) => void;
|
|
135
230
|
buttonAs?: ComponentProps<typeof Button>["as"];
|
|
231
|
+
activeValue?: T;
|
|
232
|
+
tooltipSide?: ComponentProps<typeof Tooltip>["side"];
|
|
233
|
+
portalContainer?: HTMLElement | null;
|
|
136
234
|
}) {
|
|
137
235
|
return (
|
|
138
236
|
<>
|
|
@@ -143,6 +241,9 @@ export const ToolbarMenu = memoGeneric(function ToolbarMenu<T extends string>({
|
|
|
143
241
|
item={item}
|
|
144
242
|
onSelectMenuItem={onSelectMenuItem}
|
|
145
243
|
buttonAs={buttonAs}
|
|
244
|
+
activeValue={activeValue}
|
|
245
|
+
tooltipSide={tooltipSide}
|
|
246
|
+
portalContainer={portalContainer}
|
|
146
247
|
/>
|
|
147
248
|
);
|
|
148
249
|
})}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import React, { ForwardedRef, forwardRef, memo, ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
type ToolbarDrawerProps = {
|
|
4
|
+
children: ReactNode;
|
|
5
|
+
trigger: ReactNode;
|
|
6
|
+
sideOffset?: number;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const ToolbarDrawer = memo(
|
|
10
|
+
forwardRef(function ToolbarDrawer(
|
|
11
|
+
{ children, trigger, sideOffset = 8 }: ToolbarDrawerProps,
|
|
12
|
+
ref: ForwardedRef<HTMLDivElement>
|
|
13
|
+
) {
|
|
14
|
+
return (
|
|
15
|
+
<div className="relative" ref={ref}>
|
|
16
|
+
<div
|
|
17
|
+
className="absolute bottom-full left-1/2 -translate-x-1/2 rounded-t-lg border border-toolbar-drawer-border border-b-transparent bg-toolbar-drawer-background -z-10"
|
|
18
|
+
style={{
|
|
19
|
+
marginBottom: `${sideOffset}px`,
|
|
20
|
+
boxShadow: "0px -2px 12px 0px var(--n-toolbar-drawer-shadow)",
|
|
21
|
+
}}
|
|
22
|
+
>
|
|
23
|
+
{children}
|
|
24
|
+
</div>
|
|
25
|
+
{trigger}
|
|
26
|
+
</div>
|
|
27
|
+
);
|
|
28
|
+
})
|
|
29
|
+
);
|
|
@@ -1,25 +1,26 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { Tooltip as TooltipPrimitive } from "radix-ui";
|
|
2
2
|
import * as React from "react";
|
|
3
|
+
import { ComponentProps } from "react";
|
|
3
4
|
import {
|
|
4
5
|
portalScopeProps,
|
|
5
6
|
usePortalScopeId,
|
|
6
7
|
} from "../contexts/PortalScopeContext";
|
|
7
8
|
import { textStyles } from "./Text";
|
|
8
9
|
|
|
9
|
-
// const Arrow = styled(TooltipPrimitive.Arrow)(({ theme }) => ({
|
|
10
|
-
// fill: theme.colors.popover.background,
|
|
11
|
-
// }));
|
|
12
|
-
|
|
13
10
|
interface Props {
|
|
14
11
|
children: React.ReactNode;
|
|
15
12
|
content: React.ReactNode;
|
|
16
13
|
sideOffset?: number;
|
|
14
|
+
side?: ComponentProps<typeof TooltipPrimitive.Content>["side"];
|
|
15
|
+
portalContainer?: HTMLElement | null;
|
|
17
16
|
}
|
|
18
17
|
|
|
19
18
|
export const Tooltip = React.memo(function Tooltip({
|
|
20
19
|
children,
|
|
21
20
|
content,
|
|
22
21
|
sideOffset = 2,
|
|
22
|
+
side = "bottom",
|
|
23
|
+
portalContainer,
|
|
23
24
|
}: Props) {
|
|
24
25
|
const portalScopeId = usePortalScopeId();
|
|
25
26
|
|
|
@@ -27,17 +28,16 @@ export const Tooltip = React.memo(function Tooltip({
|
|
|
27
28
|
<TooltipPrimitive.Provider>
|
|
28
29
|
<TooltipPrimitive.Root>
|
|
29
30
|
<TooltipPrimitive.Trigger asChild>{children}</TooltipPrimitive.Trigger>
|
|
30
|
-
<TooltipPrimitive.Portal>
|
|
31
|
+
<TooltipPrimitive.Portal container={portalContainer}>
|
|
31
32
|
<TooltipPrimitive.Content
|
|
32
33
|
{...portalScopeProps(portalScopeId)}
|
|
33
|
-
side=
|
|
34
|
+
side={side}
|
|
34
35
|
align="center"
|
|
35
36
|
sideOffset={sideOffset}
|
|
36
37
|
collisionPadding={8}
|
|
37
|
-
className={`${textStyles.small} text-text rounded-
|
|
38
|
+
className={`${textStyles.small} text-text rounded-sm p-2 bg-popover-background shadow-tooltip z-menu`}
|
|
38
39
|
>
|
|
39
40
|
{content}
|
|
40
|
-
{/* <Arrow /> */}
|
|
41
41
|
</TooltipPrimitive.Content>
|
|
42
42
|
</TooltipPrimitive.Portal>
|
|
43
43
|
</TooltipPrimitive.Root>
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { ActivityIndicator, cx } from "@noya-app/noya-designsystem";
|
|
2
|
+
import { ImageIcon } from "@noya-app/noya-icons";
|
|
3
|
+
import React, { ForwardedRef, forwardRef } from "react";
|
|
4
|
+
|
|
5
|
+
type ImageState = "loading" | "error" | "loaded";
|
|
6
|
+
|
|
7
|
+
const placeholderClassName =
|
|
8
|
+
"cursor-pointer flex items-center justify-center p-4 border border-dashed border-indigo-100 rounded-md bg-indigo-50";
|
|
9
|
+
|
|
10
|
+
export function decodeImageSrc(src?: string): string | undefined {
|
|
11
|
+
if (!src) return undefined;
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
return decodeURIComponent(src);
|
|
15
|
+
} catch (error) {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const ImageLoadingComponent = forwardRef(function ImageLoadingComponent(
|
|
21
|
+
props: React.HTMLAttributes<HTMLSpanElement>,
|
|
22
|
+
forwardedRef: ForwardedRef<HTMLSpanElement>
|
|
23
|
+
) {
|
|
24
|
+
return (
|
|
25
|
+
<span
|
|
26
|
+
{...props}
|
|
27
|
+
ref={forwardedRef}
|
|
28
|
+
contentEditable={false}
|
|
29
|
+
className={cx(placeholderClassName, props.className)}
|
|
30
|
+
>
|
|
31
|
+
<ActivityIndicator />
|
|
32
|
+
</span>
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const ImagePlaceholderComponent = forwardRef(function ImagePlaceholderComponent(
|
|
37
|
+
props: React.HTMLAttributes<HTMLSpanElement>,
|
|
38
|
+
forwardedRef: ForwardedRef<HTMLSpanElement>
|
|
39
|
+
) {
|
|
40
|
+
return (
|
|
41
|
+
<span
|
|
42
|
+
{...props}
|
|
43
|
+
ref={forwardedRef}
|
|
44
|
+
contentEditable={false}
|
|
45
|
+
className={cx(placeholderClassName, props.className)}
|
|
46
|
+
>
|
|
47
|
+
<ImageIcon className="w-[30px] h-[30px] text-indigo-900 pointer-events-none" />
|
|
48
|
+
</span>
|
|
49
|
+
);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const ImageErrorComponent = forwardRef(function ImageErrorComponent(
|
|
53
|
+
{ src, ...props }: React.HTMLAttributes<HTMLSpanElement> & { src?: string },
|
|
54
|
+
forwardedRef: ForwardedRef<HTMLSpanElement>
|
|
55
|
+
) {
|
|
56
|
+
return (
|
|
57
|
+
<span
|
|
58
|
+
{...props}
|
|
59
|
+
ref={forwardedRef}
|
|
60
|
+
contentEditable={false}
|
|
61
|
+
className={cx(placeholderClassName, props.className)}
|
|
62
|
+
>
|
|
63
|
+
<span className="text-cm-error font-mono text-sm">
|
|
64
|
+
Invalid image URL: {src ?? "No URL provided"}
|
|
65
|
+
</span>
|
|
66
|
+
</span>
|
|
67
|
+
);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const ImageDisplayComponent = forwardRef(function ImageDisplayComponent(
|
|
71
|
+
{
|
|
72
|
+
src,
|
|
73
|
+
alt,
|
|
74
|
+
title,
|
|
75
|
+
...props
|
|
76
|
+
}: React.DetailedHTMLProps<
|
|
77
|
+
React.ImgHTMLAttributes<HTMLImageElement>,
|
|
78
|
+
HTMLImageElement
|
|
79
|
+
>,
|
|
80
|
+
forwardedRef: ForwardedRef<HTMLImageElement>
|
|
81
|
+
) {
|
|
82
|
+
return (
|
|
83
|
+
<img
|
|
84
|
+
{...props}
|
|
85
|
+
ref={forwardedRef}
|
|
86
|
+
src={src}
|
|
87
|
+
alt={alt ?? ""}
|
|
88
|
+
title={title ?? undefined}
|
|
89
|
+
className={cx(
|
|
90
|
+
"cursor-pointer max-w-full max-h-[720px] object-contain mx-auto my-8",
|
|
91
|
+
props.className
|
|
92
|
+
)}
|
|
93
|
+
/>
|
|
94
|
+
);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const ImageBlockComponent_ = forwardRef(function ImageBlockComponent(
|
|
98
|
+
{
|
|
99
|
+
src,
|
|
100
|
+
alt,
|
|
101
|
+
title,
|
|
102
|
+
imageState,
|
|
103
|
+
...props
|
|
104
|
+
}: React.DetailedHTMLProps<
|
|
105
|
+
React.ImgHTMLAttributes<HTMLImageElement>,
|
|
106
|
+
HTMLImageElement
|
|
107
|
+
> & { imageState?: ImageState },
|
|
108
|
+
forwardedRef: ForwardedRef<HTMLImageElement>
|
|
109
|
+
) {
|
|
110
|
+
if (imageState === "loading") {
|
|
111
|
+
return (
|
|
112
|
+
<ImageLoadingComponent
|
|
113
|
+
{...props}
|
|
114
|
+
ref={forwardedRef as ForwardedRef<HTMLSpanElement>}
|
|
115
|
+
/>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!src) {
|
|
120
|
+
return (
|
|
121
|
+
<ImagePlaceholderComponent
|
|
122
|
+
{...props}
|
|
123
|
+
ref={forwardedRef as ForwardedRef<HTMLSpanElement>}
|
|
124
|
+
/>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const decodedSrc = decodeImageSrc(src.toString());
|
|
129
|
+
|
|
130
|
+
if (!decodedSrc || imageState === "error") {
|
|
131
|
+
return (
|
|
132
|
+
<ImageErrorComponent
|
|
133
|
+
{...props}
|
|
134
|
+
src={src.toString()}
|
|
135
|
+
ref={forwardedRef as ForwardedRef<HTMLSpanElement>}
|
|
136
|
+
/>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return (
|
|
141
|
+
<ImageDisplayComponent
|
|
142
|
+
{...props}
|
|
143
|
+
ref={forwardedRef}
|
|
144
|
+
src={src}
|
|
145
|
+
alt={alt}
|
|
146
|
+
title={title}
|
|
147
|
+
/>
|
|
148
|
+
);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
export const ImageBlockComponent = Object.assign(ImageBlockComponent_, {
|
|
152
|
+
Loading: ImageLoadingComponent,
|
|
153
|
+
Placeholder: ImagePlaceholderComponent,
|
|
154
|
+
Error: ImageErrorComponent,
|
|
155
|
+
Display: ImageDisplayComponent,
|
|
156
|
+
});
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
1
3
|
import {
|
|
4
|
+
ActivityIndicator,
|
|
2
5
|
Banner,
|
|
3
6
|
BannerProps,
|
|
4
|
-
|
|
7
|
+
Button,
|
|
5
8
|
Section,
|
|
6
9
|
SectionProps,
|
|
7
10
|
} from "@noya-app/noya-designsystem";
|
|
@@ -28,20 +31,25 @@ export const FileExplorerLayout = ({
|
|
|
28
31
|
export const FileExplorerUploadButton = ({
|
|
29
32
|
showUploadButton,
|
|
30
33
|
onUpload,
|
|
34
|
+
isUploading,
|
|
31
35
|
children,
|
|
32
36
|
}: {
|
|
33
37
|
showUploadButton?: boolean;
|
|
34
38
|
onUpload?: () => void;
|
|
39
|
+
isUploading?: boolean;
|
|
35
40
|
children?: React.ReactNode;
|
|
36
41
|
}) => (
|
|
37
42
|
<div className="flex items-center gap-2">
|
|
38
43
|
{showUploadButton && (
|
|
39
|
-
<
|
|
40
|
-
|
|
44
|
+
<Button
|
|
45
|
+
icon={isUploading ? <ActivityIndicator size={15} /> : "UploadIcon"}
|
|
41
46
|
onClick={onUpload}
|
|
42
|
-
aria-label="Upload
|
|
43
|
-
tooltip="Upload
|
|
44
|
-
|
|
47
|
+
aria-label="Upload file"
|
|
48
|
+
tooltip="Upload file"
|
|
49
|
+
disabled={isUploading}
|
|
50
|
+
>
|
|
51
|
+
Upload
|
|
52
|
+
</Button>
|
|
45
53
|
)}
|
|
46
54
|
{children}
|
|
47
55
|
</div>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
export function Checkmark(props: React.SVGProps<SVGSVGElement>) {
|
|
4
|
+
return (
|
|
5
|
+
<svg
|
|
6
|
+
viewBox="0 0 15 15"
|
|
7
|
+
fill="none"
|
|
8
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
9
|
+
aria-hidden="true"
|
|
10
|
+
width={15}
|
|
11
|
+
height={15}
|
|
12
|
+
{...props}
|
|
13
|
+
>
|
|
14
|
+
<path
|
|
15
|
+
className="scale-90 origin-center"
|
|
16
|
+
d="M3 8L6 11L12 5"
|
|
17
|
+
strokeWidth="1.1"
|
|
18
|
+
strokeLinecap="round"
|
|
19
|
+
strokeLinejoin="round"
|
|
20
|
+
stroke="currentColor"
|
|
21
|
+
/>
|
|
22
|
+
</svg>
|
|
23
|
+
);
|
|
24
|
+
}
|