@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
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import React, { memo, useLayoutEffect, useRef, useState } from "react";
|
|
2
|
+
import {
|
|
3
|
+
EDITOR_PANEL_GROUP_ID,
|
|
4
|
+
LEFT_SIDEBAR_ID,
|
|
5
|
+
RIGHT_SIDEBAR_ID,
|
|
6
|
+
} from "../../hooks/usePreservePanelSize";
|
|
7
|
+
import { Drawer } from "../Drawer";
|
|
8
|
+
import { LayoutProps } from "./types";
|
|
9
|
+
|
|
10
|
+
export const DrawerWorkspaceLayout = memo(function DrawerWorkspaceLayout({
|
|
11
|
+
leftPanel,
|
|
12
|
+
leftSidebarRef,
|
|
13
|
+
rightPanel,
|
|
14
|
+
rightSidebarRef,
|
|
15
|
+
centerPanel,
|
|
16
|
+
internalLayoutState,
|
|
17
|
+
handleChangeLayoutState,
|
|
18
|
+
}: LayoutProps) {
|
|
19
|
+
const portalContainer = useRef<HTMLDivElement>(null);
|
|
20
|
+
const [mounted, setMounted] = useState(false);
|
|
21
|
+
|
|
22
|
+
useLayoutEffect(() => {
|
|
23
|
+
if (mounted) return;
|
|
24
|
+
|
|
25
|
+
setMounted(true);
|
|
26
|
+
handleChangeLayoutState({
|
|
27
|
+
leftSidebarCollapsed: true,
|
|
28
|
+
rightSidebarCollapsed: true,
|
|
29
|
+
});
|
|
30
|
+
}, [handleChangeLayoutState, mounted]);
|
|
31
|
+
|
|
32
|
+
const leftSidebarCollapsed =
|
|
33
|
+
internalLayoutState?.leftSidebarCollapsed ?? false;
|
|
34
|
+
const rightSidebarCollapsed =
|
|
35
|
+
internalLayoutState?.rightSidebarCollapsed ?? false;
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<div
|
|
39
|
+
ref={portalContainer}
|
|
40
|
+
id={EDITOR_PANEL_GROUP_ID}
|
|
41
|
+
className="flex flex-1 relative focus:outline-none"
|
|
42
|
+
>
|
|
43
|
+
{leftPanel && (
|
|
44
|
+
<Drawer
|
|
45
|
+
id={LEFT_SIDEBAR_ID}
|
|
46
|
+
ref={leftSidebarRef}
|
|
47
|
+
className="flex-1"
|
|
48
|
+
open={!leftSidebarCollapsed}
|
|
49
|
+
positioning="absolute"
|
|
50
|
+
side="left"
|
|
51
|
+
trigger={null}
|
|
52
|
+
portalContainer={portalContainer.current}
|
|
53
|
+
onOpenChange={(open) => {
|
|
54
|
+
handleChangeLayoutState({
|
|
55
|
+
rightSidebarCollapsed: rightSidebarCollapsed,
|
|
56
|
+
leftSidebarCollapsed: !open,
|
|
57
|
+
});
|
|
58
|
+
}}
|
|
59
|
+
>
|
|
60
|
+
{leftPanel}
|
|
61
|
+
</Drawer>
|
|
62
|
+
)}
|
|
63
|
+
{centerPanel}
|
|
64
|
+
{rightPanel && (
|
|
65
|
+
<Drawer
|
|
66
|
+
id={RIGHT_SIDEBAR_ID}
|
|
67
|
+
ref={rightSidebarRef}
|
|
68
|
+
className="flex-1"
|
|
69
|
+
open={!rightSidebarCollapsed}
|
|
70
|
+
positioning="absolute"
|
|
71
|
+
side="right"
|
|
72
|
+
trigger={null}
|
|
73
|
+
portalContainer={portalContainer.current}
|
|
74
|
+
onOpenChange={(open) => {
|
|
75
|
+
handleChangeLayoutState({
|
|
76
|
+
leftSidebarCollapsed: leftSidebarCollapsed,
|
|
77
|
+
rightSidebarCollapsed: !open,
|
|
78
|
+
});
|
|
79
|
+
}}
|
|
80
|
+
>
|
|
81
|
+
{rightPanel}
|
|
82
|
+
</Drawer>
|
|
83
|
+
)}
|
|
84
|
+
</div>
|
|
85
|
+
);
|
|
86
|
+
});
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import React, { memo, useRef } from "react";
|
|
2
|
+
import {
|
|
3
|
+
ImperativePanelGroupHandle,
|
|
4
|
+
ImperativePanelHandle,
|
|
5
|
+
Panel,
|
|
6
|
+
PanelGroup,
|
|
7
|
+
PanelResizeHandle,
|
|
8
|
+
} from "react-resizable-panels";
|
|
9
|
+
import {
|
|
10
|
+
CONTENT_AREA_ID,
|
|
11
|
+
EDITOR_PANEL_GROUP_ID,
|
|
12
|
+
LEFT_SIDEBAR_ID,
|
|
13
|
+
RIGHT_SIDEBAR_ID,
|
|
14
|
+
usePreservePanelSize,
|
|
15
|
+
} from "../../hooks/usePreservePanelSize";
|
|
16
|
+
import { LayoutProps } from "./types";
|
|
17
|
+
|
|
18
|
+
export const PanelWorkspaceLayout = memo(function PanelWorkspaceLayout({
|
|
19
|
+
leftPanel,
|
|
20
|
+
rightPanel,
|
|
21
|
+
centerPanel,
|
|
22
|
+
leftSidebarOptions,
|
|
23
|
+
rightSidebarOptions,
|
|
24
|
+
centerPanelPercentage,
|
|
25
|
+
autoSavePrefix,
|
|
26
|
+
internalLayoutState,
|
|
27
|
+
leftSidebarRef,
|
|
28
|
+
rightSidebarRef,
|
|
29
|
+
handleChangeLayoutState,
|
|
30
|
+
}: LayoutProps) {
|
|
31
|
+
const panelGroupRef = useRef<ImperativePanelGroupHandle>(null);
|
|
32
|
+
|
|
33
|
+
usePreservePanelSize(panelGroupRef, handleChangeLayoutState);
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<PanelGroup
|
|
37
|
+
ref={panelGroupRef}
|
|
38
|
+
id={EDITOR_PANEL_GROUP_ID}
|
|
39
|
+
className="flex-1"
|
|
40
|
+
direction="horizontal"
|
|
41
|
+
autoSaveId={
|
|
42
|
+
autoSavePrefix
|
|
43
|
+
? `${autoSavePrefix}--${EDITOR_PANEL_GROUP_ID}`
|
|
44
|
+
: undefined
|
|
45
|
+
}
|
|
46
|
+
>
|
|
47
|
+
{leftPanel && (
|
|
48
|
+
<>
|
|
49
|
+
<Panel
|
|
50
|
+
id={LEFT_SIDEBAR_ID}
|
|
51
|
+
className="relative"
|
|
52
|
+
order={1}
|
|
53
|
+
ref={leftSidebarRef as React.RefObject<ImperativePanelHandle>}
|
|
54
|
+
defaultSize={leftSidebarOptions.initialSize}
|
|
55
|
+
minSize={leftSidebarOptions.minSize}
|
|
56
|
+
maxSize={
|
|
57
|
+
leftSidebarOptions.maxSize ??
|
|
58
|
+
(!leftSidebarOptions.resizable
|
|
59
|
+
? leftSidebarOptions.initialSize
|
|
60
|
+
: undefined)
|
|
61
|
+
}
|
|
62
|
+
collapsible
|
|
63
|
+
>
|
|
64
|
+
{internalLayoutState?.leftSidebarCollapsed ? null : leftPanel}
|
|
65
|
+
</Panel>
|
|
66
|
+
<PanelResizeHandle className="cursor-col-resize w-px h-full bg-divider" />
|
|
67
|
+
</>
|
|
68
|
+
)}
|
|
69
|
+
<Panel
|
|
70
|
+
id={CONTENT_AREA_ID}
|
|
71
|
+
order={2}
|
|
72
|
+
defaultSize={centerPanelPercentage}
|
|
73
|
+
minSize={10}
|
|
74
|
+
className="flex relative"
|
|
75
|
+
>
|
|
76
|
+
{centerPanel}
|
|
77
|
+
</Panel>
|
|
78
|
+
{rightPanel && (
|
|
79
|
+
<>
|
|
80
|
+
<PanelResizeHandle className="cursor-col-resize w-px h-full bg-divider" />
|
|
81
|
+
<Panel
|
|
82
|
+
id={RIGHT_SIDEBAR_ID}
|
|
83
|
+
className="relative"
|
|
84
|
+
order={3}
|
|
85
|
+
ref={rightSidebarRef as React.RefObject<ImperativePanelHandle>}
|
|
86
|
+
minSize={rightSidebarOptions.minSize}
|
|
87
|
+
defaultSize={rightSidebarOptions.initialSize}
|
|
88
|
+
maxSize={
|
|
89
|
+
rightSidebarOptions.maxSize ??
|
|
90
|
+
(!rightSidebarOptions.resizable
|
|
91
|
+
? rightSidebarOptions.initialSize
|
|
92
|
+
: undefined)
|
|
93
|
+
}
|
|
94
|
+
collapsible
|
|
95
|
+
>
|
|
96
|
+
{internalLayoutState?.rightSidebarCollapsed ? null : rightPanel}
|
|
97
|
+
</Panel>
|
|
98
|
+
</>
|
|
99
|
+
)}
|
|
100
|
+
</PanelGroup>
|
|
101
|
+
);
|
|
102
|
+
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useKeyboardShortcuts } from "@noya-app/noya-keymap";
|
|
2
|
+
import { useSize } from "@noya-app/react-utils";
|
|
2
3
|
import React, {
|
|
3
4
|
forwardRef,
|
|
4
5
|
memo,
|
|
@@ -7,23 +8,19 @@ import React, {
|
|
|
7
8
|
useRef,
|
|
8
9
|
useState,
|
|
9
10
|
} from "react";
|
|
11
|
+
import { useWindowSize } from "../../hooks/useWindowSize";
|
|
12
|
+
import { cx } from "../../utils/classNames";
|
|
13
|
+
import { DrawerWorkspaceLayout } from "./DrawerWorkspaceLayout";
|
|
14
|
+
import { PanelWorkspaceLayout } from "./PanelWorkspaceLayout";
|
|
10
15
|
import {
|
|
11
|
-
ImperativePanelGroupHandle,
|
|
12
|
-
ImperativePanelHandle,
|
|
13
|
-
Panel,
|
|
14
|
-
PanelGroup,
|
|
15
|
-
PanelResizeHandle,
|
|
16
|
-
} from "react-resizable-panels";
|
|
17
|
-
import {
|
|
18
|
-
CONTENT_AREA_ID,
|
|
19
|
-
EDITOR_PANEL_GROUP_ID,
|
|
20
|
-
LEFT_SIDEBAR_ID,
|
|
21
16
|
PanelLayoutState,
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
} from "
|
|
25
|
-
|
|
26
|
-
|
|
17
|
+
PercentageSidebarOptions,
|
|
18
|
+
SidebarRef,
|
|
19
|
+
} from "./types";
|
|
20
|
+
|
|
21
|
+
export type SideType = "auto" | "panel" | "drawer";
|
|
22
|
+
|
|
23
|
+
export type DetectSizeType = "container" | "window";
|
|
27
24
|
|
|
28
25
|
export interface WorkspaceLayoutProps {
|
|
29
26
|
id?: string;
|
|
@@ -37,10 +34,13 @@ export interface WorkspaceLayoutProps {
|
|
|
37
34
|
right?: React.ReactNode;
|
|
38
35
|
rightOptions?: SideOptions;
|
|
39
36
|
toolbar?: React.ReactNode;
|
|
37
|
+
sideType?: SideType;
|
|
38
|
+
sideTypeBreakpoint?: number;
|
|
39
|
+
detectSize?: DetectSizeType;
|
|
40
40
|
onChangeLayoutState?: (layoutState: PanelLayoutState) => void;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
export type
|
|
43
|
+
export type WorkspaceLayoutRef = {
|
|
44
44
|
setLeftExpanded: (expanded: boolean) => void;
|
|
45
45
|
setRightExpanded: (expanded: boolean) => void;
|
|
46
46
|
toggleSides: () => void;
|
|
@@ -72,6 +72,9 @@ export const WorkspaceLayout = forwardRef(function WorkspaceLayout(
|
|
|
72
72
|
toolbar,
|
|
73
73
|
children,
|
|
74
74
|
onChangeLayoutState,
|
|
75
|
+
sideType = "auto",
|
|
76
|
+
sideTypeBreakpoint = 800,
|
|
77
|
+
detectSize = "container",
|
|
75
78
|
leftOptions: {
|
|
76
79
|
minSize: leftMinSize = 200,
|
|
77
80
|
maxSize: leftMaxSize = 500,
|
|
@@ -90,13 +93,25 @@ export const WorkspaceLayout = forwardRef(function WorkspaceLayout(
|
|
|
90
93
|
} = {},
|
|
91
94
|
theme,
|
|
92
95
|
}: WorkspaceLayoutProps,
|
|
93
|
-
forwardedRef: React.ForwardedRef<
|
|
96
|
+
forwardedRef: React.ForwardedRef<WorkspaceLayoutRef>
|
|
94
97
|
) {
|
|
98
|
+
const containerRef = React.useRef<HTMLDivElement | null>(null);
|
|
99
|
+
const containerSize = useSize(containerRef);
|
|
95
100
|
const windowSize = useWindowSize();
|
|
101
|
+
|
|
102
|
+
// If we're in embedded (iframed) mode, use the window size.
|
|
103
|
+
// Otherwise, use the container size (if it's available).
|
|
104
|
+
const parentSize =
|
|
105
|
+
detectSize === "window"
|
|
106
|
+
? windowSize
|
|
107
|
+
: containerSize && containerSize.width > 0
|
|
108
|
+
? containerSize
|
|
109
|
+
: windowSize;
|
|
110
|
+
|
|
96
111
|
function getPercentage(size: number | string) {
|
|
97
112
|
return typeof size === "string"
|
|
98
113
|
? parseFloat(size)
|
|
99
|
-
: (size /
|
|
114
|
+
: (size / parentSize.width) * 100;
|
|
100
115
|
}
|
|
101
116
|
|
|
102
117
|
function clampPercentage(
|
|
@@ -134,12 +149,12 @@ export const WorkspaceLayout = forwardRef(function WorkspaceLayout(
|
|
|
134
149
|
rightSidebarMaxPercentage
|
|
135
150
|
);
|
|
136
151
|
|
|
137
|
-
const
|
|
138
|
-
const
|
|
139
|
-
const rightSidebarRef = useRef<ImperativePanelHandle>(null);
|
|
152
|
+
const leftSidebarRef = useRef<SidebarRef>(null);
|
|
153
|
+
const rightSidebarRef = useRef<SidebarRef>(null);
|
|
140
154
|
|
|
141
155
|
const [internalLayoutState, setInternalLayoutState] =
|
|
142
156
|
useState<PanelLayoutState | null>(null);
|
|
157
|
+
|
|
143
158
|
const handleChangeLayoutState = useCallback(
|
|
144
159
|
(state: PanelLayoutState) => {
|
|
145
160
|
setInternalLayoutState(state);
|
|
@@ -148,8 +163,6 @@ export const WorkspaceLayout = forwardRef(function WorkspaceLayout(
|
|
|
148
163
|
[onChangeLayoutState]
|
|
149
164
|
);
|
|
150
165
|
|
|
151
|
-
usePreservePanelSize(panelGroupRef, handleChangeLayoutState);
|
|
152
|
-
|
|
153
166
|
useImperativeHandle(forwardedRef, () => ({
|
|
154
167
|
setLeftExpanded: (expanded: boolean) => {
|
|
155
168
|
if (expanded) {
|
|
@@ -217,96 +230,86 @@ export const WorkspaceLayout = forwardRef(function WorkspaceLayout(
|
|
|
217
230
|
(left ? leftSidebarPercentage : 0) -
|
|
218
231
|
(right ? rightSidebarPercentage : 0);
|
|
219
232
|
|
|
233
|
+
const leftPanel = left ? (
|
|
234
|
+
<PanelInner
|
|
235
|
+
style={leftStyle}
|
|
236
|
+
className={cx("bg-sidebar-background flex-col", leftClassName)}
|
|
237
|
+
>
|
|
238
|
+
{left}
|
|
239
|
+
</PanelInner>
|
|
240
|
+
) : null;
|
|
241
|
+
|
|
242
|
+
const rightPanel = right ? (
|
|
243
|
+
<PanelInner
|
|
244
|
+
style={rightStyle}
|
|
245
|
+
className={cx("bg-sidebar-background flex-col", rightClassName)}
|
|
246
|
+
>
|
|
247
|
+
{right}
|
|
248
|
+
</PanelInner>
|
|
249
|
+
) : null;
|
|
250
|
+
|
|
251
|
+
const centerPanel = (
|
|
252
|
+
<PanelInner className="bg-canvas-background">{children}</PanelInner>
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
const leftSidebarOptions: PercentageSidebarOptions = {
|
|
256
|
+
minSize: leftSidebarMinPercentage,
|
|
257
|
+
maxSize: leftSidebarMaxPercentage,
|
|
258
|
+
initialSize: leftSidebarPercentage,
|
|
259
|
+
resizable: leftResizable,
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
const rightSidebarOptions: PercentageSidebarOptions = {
|
|
263
|
+
minSize: rightSidebarMinPercentage,
|
|
264
|
+
maxSize: rightSidebarMaxPercentage,
|
|
265
|
+
initialSize: rightSidebarPercentage,
|
|
266
|
+
resizable: rightResizable,
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
const LayoutComponent =
|
|
270
|
+
sideType === "panel"
|
|
271
|
+
? PanelWorkspaceLayout
|
|
272
|
+
: sideType === "drawer"
|
|
273
|
+
? DrawerWorkspaceLayout
|
|
274
|
+
: parentSize.width > sideTypeBreakpoint
|
|
275
|
+
? PanelWorkspaceLayout
|
|
276
|
+
: DrawerWorkspaceLayout;
|
|
277
|
+
|
|
278
|
+
// Wait until the container size is available to render the layout.
|
|
279
|
+
// This is to avoid flickering when the layout is initially rendered.
|
|
280
|
+
// If we're in embedded (iframed) mode, we don't need to wait for the container size,
|
|
281
|
+
// because the window size is guaranteed to be available.
|
|
282
|
+
//
|
|
283
|
+
// We could always wait for the container size, but this lets us render
|
|
284
|
+
// sooner in the embedded case.
|
|
285
|
+
const readyToRender =
|
|
286
|
+
detectSize === "window" || (containerSize && containerSize.width > 0);
|
|
287
|
+
|
|
220
288
|
return (
|
|
221
289
|
<div
|
|
290
|
+
ref={containerRef}
|
|
222
291
|
id={id}
|
|
223
|
-
className={cx("flex flex-col bg-canvas-background", className)}
|
|
292
|
+
className={cx("flex flex-col bg-canvas-background relative", className)}
|
|
224
293
|
style={style}
|
|
225
294
|
data-theme={theme}
|
|
226
295
|
>
|
|
227
296
|
{toolbar}
|
|
228
|
-
<div className="flex flex-row flex-1">
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
className="relative"
|
|
245
|
-
order={1}
|
|
246
|
-
ref={leftSidebarRef}
|
|
247
|
-
defaultSize={leftSidebarPercentage}
|
|
248
|
-
minSize={leftSidebarMinPercentage ?? leftSidebarPercentage}
|
|
249
|
-
maxSize={
|
|
250
|
-
leftSidebarMaxPercentage ??
|
|
251
|
-
(!leftResizable ? leftSidebarPercentage : undefined)
|
|
252
|
-
}
|
|
253
|
-
collapsible
|
|
254
|
-
>
|
|
255
|
-
{internalLayoutState?.leftSidebarCollapsed ? null : (
|
|
256
|
-
<PanelInner
|
|
257
|
-
style={leftStyle}
|
|
258
|
-
className={cx(
|
|
259
|
-
"bg-sidebar-background flex-col",
|
|
260
|
-
leftClassName
|
|
261
|
-
)}
|
|
262
|
-
>
|
|
263
|
-
{left}
|
|
264
|
-
</PanelInner>
|
|
265
|
-
)}
|
|
266
|
-
</Panel>
|
|
267
|
-
<PanelResizeHandle className="cursor-col-resize w-px h-full bg-divider" />
|
|
268
|
-
</>
|
|
269
|
-
)}
|
|
270
|
-
<Panel
|
|
271
|
-
id={CONTENT_AREA_ID}
|
|
272
|
-
order={2}
|
|
273
|
-
defaultSize={centerPanelPercentage}
|
|
274
|
-
minSize={10}
|
|
275
|
-
className="flex relative"
|
|
276
|
-
>
|
|
277
|
-
<PanelInner className="bg-canvas-background">{children}</PanelInner>
|
|
278
|
-
</Panel>
|
|
279
|
-
{right && (
|
|
280
|
-
<>
|
|
281
|
-
<PanelResizeHandle className="cursor-col-resize w-px h-full bg-divider" />
|
|
282
|
-
<Panel
|
|
283
|
-
id={RIGHT_SIDEBAR_ID}
|
|
284
|
-
className="relative"
|
|
285
|
-
order={3}
|
|
286
|
-
ref={rightSidebarRef}
|
|
287
|
-
minSize={rightSidebarMinPercentage ?? rightSidebarPercentage}
|
|
288
|
-
defaultSize={rightSidebarPercentage}
|
|
289
|
-
maxSize={
|
|
290
|
-
rightSidebarMaxPercentage ??
|
|
291
|
-
(!rightResizable ? rightSidebarPercentage : undefined)
|
|
292
|
-
}
|
|
293
|
-
collapsible
|
|
294
|
-
>
|
|
295
|
-
{internalLayoutState?.rightSidebarCollapsed ? null : (
|
|
296
|
-
<PanelInner
|
|
297
|
-
style={rightStyle}
|
|
298
|
-
className={cx(
|
|
299
|
-
"bg-sidebar-background flex-col",
|
|
300
|
-
rightClassName
|
|
301
|
-
)}
|
|
302
|
-
>
|
|
303
|
-
{right}
|
|
304
|
-
</PanelInner>
|
|
305
|
-
)}
|
|
306
|
-
</Panel>
|
|
307
|
-
</>
|
|
308
|
-
)}
|
|
309
|
-
</PanelGroup>
|
|
297
|
+
<div className="flex flex-row flex-1 relative">
|
|
298
|
+
{readyToRender && (
|
|
299
|
+
<LayoutComponent
|
|
300
|
+
leftPanel={leftPanel}
|
|
301
|
+
rightPanel={rightPanel}
|
|
302
|
+
centerPanel={centerPanel}
|
|
303
|
+
leftSidebarOptions={leftSidebarOptions}
|
|
304
|
+
rightSidebarOptions={rightSidebarOptions}
|
|
305
|
+
centerPanelPercentage={centerPanelPercentage}
|
|
306
|
+
autoSavePrefix={autoSavePrefix}
|
|
307
|
+
internalLayoutState={internalLayoutState}
|
|
308
|
+
leftSidebarRef={leftSidebarRef}
|
|
309
|
+
rightSidebarRef={rightSidebarRef}
|
|
310
|
+
handleChangeLayoutState={handleChangeLayoutState}
|
|
311
|
+
/>
|
|
312
|
+
)}
|
|
310
313
|
</div>
|
|
311
314
|
</div>
|
|
312
315
|
);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export type SidebarRef = {
|
|
2
|
+
expand: () => void;
|
|
3
|
+
collapse: () => void;
|
|
4
|
+
isExpanded: () => boolean;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export type PercentageSidebarOptions = {
|
|
8
|
+
minSize?: number;
|
|
9
|
+
maxSize?: number;
|
|
10
|
+
initialSize?: number;
|
|
11
|
+
resizable?: boolean;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type PanelLayoutState = {
|
|
15
|
+
leftSidebarCollapsed: boolean;
|
|
16
|
+
rightSidebarCollapsed: boolean;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type LayoutProps = {
|
|
20
|
+
leftPanel: React.ReactNode;
|
|
21
|
+
rightPanel: React.ReactNode;
|
|
22
|
+
centerPanel: React.ReactNode;
|
|
23
|
+
leftSidebarOptions: PercentageSidebarOptions;
|
|
24
|
+
rightSidebarOptions: PercentageSidebarOptions;
|
|
25
|
+
centerPanelPercentage: number;
|
|
26
|
+
autoSavePrefix?: string;
|
|
27
|
+
internalLayoutState: PanelLayoutState | null;
|
|
28
|
+
leftSidebarRef: React.RefObject<SidebarRef>;
|
|
29
|
+
rightSidebarRef: React.RefObject<SidebarRef>;
|
|
30
|
+
handleChangeLayoutState: (state: PanelLayoutState) => void;
|
|
31
|
+
};
|
|
@@ -24,31 +24,26 @@ function createDeferredPromise<T>() {
|
|
|
24
24
|
return { promise, resolve, reject };
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
type RenderableProps = { close: () => void };
|
|
28
|
+
|
|
27
29
|
export type DialogContextValue = {
|
|
28
|
-
openDialog(
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
openDialog(
|
|
31
|
+
options: BaseDialogContents & {
|
|
32
|
+
children: ReactNode | ((props: RenderableProps) => ReactNode);
|
|
33
|
+
}
|
|
34
|
+
): Promise<void>;
|
|
33
35
|
|
|
34
36
|
openInputDialog(
|
|
35
37
|
options:
|
|
36
38
|
| string
|
|
37
|
-
| {
|
|
38
|
-
title: string;
|
|
39
|
-
description?: ReactNode;
|
|
39
|
+
| (BaseDialogContents & {
|
|
40
40
|
placeholder?: string;
|
|
41
41
|
initialValue?: string;
|
|
42
|
-
}
|
|
42
|
+
})
|
|
43
43
|
): Promise<string | undefined>;
|
|
44
44
|
|
|
45
45
|
openConfirmationDialog(
|
|
46
|
-
options:
|
|
47
|
-
| string
|
|
48
|
-
| {
|
|
49
|
-
title: string;
|
|
50
|
-
description?: ReactNode;
|
|
51
|
-
}
|
|
46
|
+
options: string | BaseDialogContents
|
|
52
47
|
): Promise<boolean>;
|
|
53
48
|
|
|
54
49
|
containsElement(element: HTMLElement): boolean;
|
|
@@ -56,26 +51,27 @@ export type DialogContextValue = {
|
|
|
56
51
|
|
|
57
52
|
const DialogContext = createContext<DialogContextValue | undefined>(undefined);
|
|
58
53
|
|
|
59
|
-
type
|
|
60
|
-
|
|
54
|
+
type BaseDialogContents = {
|
|
55
|
+
style?: React.CSSProperties;
|
|
56
|
+
className?: string;
|
|
61
57
|
title: string;
|
|
62
58
|
description?: ReactNode;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
type InputDialogContents = BaseDialogContents & {
|
|
62
|
+
type: "input";
|
|
63
63
|
placeholder?: string;
|
|
64
64
|
inputValue: string;
|
|
65
65
|
resolve: (value: string | undefined) => void;
|
|
66
66
|
};
|
|
67
67
|
|
|
68
|
-
type ConfirmationDialogContents = {
|
|
68
|
+
type ConfirmationDialogContents = BaseDialogContents & {
|
|
69
69
|
type: "confirmation";
|
|
70
|
-
title: string;
|
|
71
|
-
description?: ReactNode;
|
|
72
70
|
resolve: (value: boolean) => void;
|
|
73
71
|
};
|
|
74
72
|
|
|
75
|
-
type CustomDialogContents = {
|
|
73
|
+
type CustomDialogContents = BaseDialogContents & {
|
|
76
74
|
type: "custom";
|
|
77
|
-
title: string;
|
|
78
|
-
description?: ReactNode;
|
|
79
75
|
children: ReactNode;
|
|
80
76
|
resolve: () => void;
|
|
81
77
|
};
|
|
@@ -95,16 +91,20 @@ export const DialogProvider = function DialogProvider({
|
|
|
95
91
|
const isOpen = !!contents;
|
|
96
92
|
|
|
97
93
|
const close = useCallback(() => {
|
|
98
|
-
if (
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
94
|
+
if (contents) {
|
|
95
|
+
if (contents.type === "input") {
|
|
96
|
+
contents.resolve(undefined);
|
|
97
|
+
} else {
|
|
98
|
+
contents.resolve(false);
|
|
99
|
+
}
|
|
104
100
|
}
|
|
101
|
+
|
|
105
102
|
setContents(undefined);
|
|
106
103
|
}, [contents]);
|
|
107
104
|
|
|
105
|
+
const closeRef = useRef(close);
|
|
106
|
+
closeRef.current = close;
|
|
107
|
+
|
|
108
108
|
const submit = useCallback(() => {
|
|
109
109
|
if (!contents || contents.type !== "input") return;
|
|
110
110
|
|
|
@@ -115,7 +115,7 @@ export const DialogProvider = function DialogProvider({
|
|
|
115
115
|
const open: DialogContextValue["openInputDialog"] = useCallback((options) => {
|
|
116
116
|
const { promise, resolve } = createDeferredPromise<string | undefined>();
|
|
117
117
|
|
|
118
|
-
const { title, description, initialValue, placeholder } =
|
|
118
|
+
const { title, description, initialValue, placeholder, style, className } =
|
|
119
119
|
typeof options === "string" ? { title: options } : options;
|
|
120
120
|
|
|
121
121
|
setContents({
|
|
@@ -124,6 +124,8 @@ export const DialogProvider = function DialogProvider({
|
|
|
124
124
|
description,
|
|
125
125
|
inputValue: initialValue ?? "",
|
|
126
126
|
placeholder,
|
|
127
|
+
style,
|
|
128
|
+
className,
|
|
127
129
|
resolve: (value: string | undefined) => {
|
|
128
130
|
resolve(value);
|
|
129
131
|
setContents(undefined);
|
|
@@ -137,13 +139,15 @@ export const DialogProvider = function DialogProvider({
|
|
|
137
139
|
useCallback((options) => {
|
|
138
140
|
const { promise, resolve } = createDeferredPromise<boolean>();
|
|
139
141
|
|
|
140
|
-
const { title, description } =
|
|
142
|
+
const { title, description, style, className } =
|
|
141
143
|
typeof options === "string" ? { title: options } : options;
|
|
142
144
|
|
|
143
145
|
setContents({
|
|
144
146
|
type: "confirmation",
|
|
145
147
|
title,
|
|
146
148
|
description,
|
|
149
|
+
style,
|
|
150
|
+
className,
|
|
147
151
|
resolve: (value: boolean) => {
|
|
148
152
|
resolve(value);
|
|
149
153
|
setContents(undefined);
|
|
@@ -157,11 +161,20 @@ export const DialogProvider = function DialogProvider({
|
|
|
157
161
|
(options) => {
|
|
158
162
|
const { promise, resolve } = createDeferredPromise<void>();
|
|
159
163
|
|
|
164
|
+
const { title, description, style, className } = options;
|
|
165
|
+
|
|
166
|
+
const children =
|
|
167
|
+
typeof options.children === "function"
|
|
168
|
+
? options.children({ close: closeRef.current })
|
|
169
|
+
: options.children;
|
|
170
|
+
|
|
160
171
|
setContents({
|
|
161
172
|
type: "custom",
|
|
162
|
-
title
|
|
163
|
-
description
|
|
164
|
-
|
|
173
|
+
title,
|
|
174
|
+
description,
|
|
175
|
+
style,
|
|
176
|
+
className,
|
|
177
|
+
children,
|
|
165
178
|
resolve: () => {
|
|
166
179
|
resolve();
|
|
167
180
|
setContents(undefined);
|
|
@@ -214,6 +227,8 @@ export const DialogProvider = function DialogProvider({
|
|
|
214
227
|
title={contents?.title}
|
|
215
228
|
description={contents?.description}
|
|
216
229
|
open={isOpen}
|
|
230
|
+
style={contents?.style}
|
|
231
|
+
className={contents?.className}
|
|
217
232
|
onOpenChange={useCallback(
|
|
218
233
|
(isOpen: boolean) => {
|
|
219
234
|
if (!isOpen) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { useLayoutEffect, useRef } from "react";
|
|
2
2
|
import { ImperativePanelGroupHandle } from "react-resizable-panels";
|
|
3
|
+
import { PanelLayoutState } from "../components/workspace/types";
|
|
3
4
|
import { useWindowSize } from "./useWindowSize";
|
|
4
5
|
|
|
5
6
|
export const EDITOR_PANEL_GROUP_ID = "editor-panel-group";
|
|
@@ -7,11 +8,6 @@ export const LEFT_SIDEBAR_ID = "editor-left-sidebar";
|
|
|
7
8
|
export const RIGHT_SIDEBAR_ID = "editor-right-sidebar";
|
|
8
9
|
export const CONTENT_AREA_ID = "editor-content-area";
|
|
9
10
|
|
|
10
|
-
export type PanelLayoutState = {
|
|
11
|
-
leftSidebarCollapsed: boolean;
|
|
12
|
-
rightSidebarCollapsed: boolean;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
11
|
export function usePreservePanelSize(
|
|
16
12
|
panelGroupRef: React.RefObject<ImperativePanelGroupHandle | null>,
|
|
17
13
|
setPanelLayoutState?: (state: PanelLayoutState) => void
|