@noya-app/noya-designsystem 0.1.46 → 0.1.48
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 +16 -0
- package/dist/index.css +1 -1
- package/dist/index.d.mts +208 -98
- package/dist/index.d.ts +208 -98
- package/dist/index.js +2148 -1604
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2153 -1618
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -7
- package/src/__tests__/validateDropIndicator.test.ts +263 -0
- package/src/components/ActionMenu.tsx +40 -0
- package/src/components/ActivityIndicator.tsx +7 -1
- package/src/components/Collection.tsx +12 -2
- package/src/components/Combobox.tsx +14 -1
- package/src/components/ComboboxMenu.tsx +14 -5
- package/src/components/Dialog.tsx +28 -31
- package/src/components/Drawer.tsx +98 -0
- package/src/components/Grid.tsx +57 -27
- package/src/components/GridView.tsx +39 -25
- package/src/components/InputField.tsx +87 -92
- package/src/components/Label.tsx +7 -7
- package/src/components/List.tsx +42 -16
- package/src/components/ListView.tsx +21 -17
- package/src/components/MediaThumbnail.tsx +117 -0
- package/src/components/SearchCompletionMenu.tsx +31 -37
- package/src/components/SelectMenu.tsx +19 -17
- package/src/components/Sortable.tsx +70 -44
- package/src/components/Switch.tsx +1 -1
- package/src/components/internal/Menu.tsx +38 -23
- package/src/components/internal/MenuViewport.tsx +7 -1
- package/src/components/internal/SelectItem.tsx +51 -27
- 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 +91 -30
- package/src/hooks/usePreservePanelSize.tsx +1 -5
- package/src/index.css +3 -1
- package/src/index.tsx +4 -1
- package/tailwind.config.ts +1 -0
|
@@ -3,8 +3,10 @@ import { renderIcon } from "../Icons";
|
|
|
3
3
|
import { CheckIcon } from "@noya-app/noya-icons";
|
|
4
4
|
import * as Select from "@radix-ui/react-select";
|
|
5
5
|
import React, { useCallback } from "react";
|
|
6
|
+
import { cx } from "../../utils/classNames";
|
|
6
7
|
import { Spacer } from "../Spacer";
|
|
7
8
|
import {
|
|
9
|
+
CHECKBOX_INDENT_WIDTH,
|
|
8
10
|
CHECKBOX_RIGHT_INSET,
|
|
9
11
|
CHECKBOX_WIDTH,
|
|
10
12
|
KeyboardShortcut,
|
|
@@ -24,6 +26,7 @@ export const SelectItem = React.forwardRef(
|
|
|
24
26
|
onSelect,
|
|
25
27
|
shortcut,
|
|
26
28
|
indented,
|
|
29
|
+
testSelected,
|
|
27
30
|
...props
|
|
28
31
|
}: Select.SelectItemProps & {
|
|
29
32
|
icon?: React.ReactNode;
|
|
@@ -32,6 +35,7 @@ export const SelectItem = React.forwardRef(
|
|
|
32
35
|
onSelect?: (value: any) => void;
|
|
33
36
|
shortcut?: string;
|
|
34
37
|
indented?: boolean;
|
|
38
|
+
testSelected?: boolean;
|
|
35
39
|
},
|
|
36
40
|
forwardedRef: React.ForwardedRef<HTMLDivElement>
|
|
37
41
|
) => {
|
|
@@ -40,48 +44,68 @@ export const SelectItem = React.forwardRef(
|
|
|
40
44
|
onSelect?.(value);
|
|
41
45
|
}, [onSelect, value]);
|
|
42
46
|
|
|
47
|
+
// Prevent the click from propagating outside the menu
|
|
48
|
+
const cancelClickPropagation = useCallback(
|
|
49
|
+
(event: React.MouseEvent<HTMLDivElement>) => {
|
|
50
|
+
event.stopPropagation();
|
|
51
|
+
},
|
|
52
|
+
[]
|
|
53
|
+
);
|
|
54
|
+
|
|
43
55
|
if (checked && Components.CheckboxItem) {
|
|
44
56
|
return (
|
|
45
|
-
<
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
57
|
+
<div className="px-1">
|
|
58
|
+
<Components.CheckboxItem
|
|
59
|
+
checked={checked}
|
|
60
|
+
disabled={disabled}
|
|
61
|
+
onSelect={handleSelectItem}
|
|
62
|
+
className={cx(
|
|
63
|
+
styles.itemStyle({ disabled }),
|
|
64
|
+
styles.selectedItemStyle,
|
|
65
|
+
testSelected && styles.testSelectedItemStyle
|
|
66
|
+
)}
|
|
67
|
+
>
|
|
68
|
+
{Components.ItemIndicator && (
|
|
69
|
+
<Components.ItemIndicator className={styles.itemIndicatorStyle}>
|
|
70
|
+
<CheckIcon />
|
|
71
|
+
</Components.ItemIndicator>
|
|
72
|
+
)}
|
|
73
|
+
{icon && (
|
|
74
|
+
<>
|
|
75
|
+
{renderIcon(icon)}
|
|
76
|
+
<Spacer.Horizontal size={8} />
|
|
77
|
+
</>
|
|
78
|
+
)}
|
|
79
|
+
{children}
|
|
80
|
+
{shortcut && (
|
|
81
|
+
<>
|
|
82
|
+
<Spacer.Horizontal />
|
|
83
|
+
<Spacer.Horizontal size={24} />
|
|
84
|
+
<KeyboardShortcut shortcut={shortcut} />
|
|
85
|
+
</>
|
|
86
|
+
)}
|
|
87
|
+
</Components.CheckboxItem>
|
|
88
|
+
</div>
|
|
71
89
|
);
|
|
72
90
|
}
|
|
73
91
|
|
|
74
92
|
return (
|
|
75
93
|
<div className="px-1">
|
|
76
94
|
<Components.Item
|
|
77
|
-
className={
|
|
95
|
+
className={cx(
|
|
96
|
+
styles.itemStyle({ disabled }),
|
|
97
|
+
styles.selectedItemStyle,
|
|
98
|
+
testSelected && styles.testSelectedItemStyle
|
|
99
|
+
)}
|
|
78
100
|
disabled={disabled}
|
|
79
101
|
value={value}
|
|
80
102
|
ref={forwardedRef}
|
|
103
|
+
onClick={cancelClickPropagation}
|
|
81
104
|
onSelect={handleSelectItem}
|
|
82
105
|
{...props}
|
|
83
106
|
>
|
|
84
107
|
<div className="flex flex-1 items-center">
|
|
108
|
+
{indented && <Spacer.Horizontal size={CHECKBOX_INDENT_WIDTH} />}
|
|
85
109
|
{checked ? (
|
|
86
110
|
<div className={styles.itemIndicatorStyle}>
|
|
87
111
|
<CheckIcon />
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { styles } from "../Menu";
|
|
3
|
+
|
|
4
|
+
describe("Menu", () => {
|
|
5
|
+
test("styles.selectedItemStyle should have the same styles as styles.testSelectedItemStyle without the focus: prefix", () => {
|
|
6
|
+
const selectedWithoutFocus = styles.selectedItemStyle.replace(
|
|
7
|
+
/focus:/g,
|
|
8
|
+
""
|
|
9
|
+
);
|
|
10
|
+
expect(selectedWithoutFocus).toEqual(styles.testSelectedItemStyle);
|
|
11
|
+
});
|
|
12
|
+
});
|
|
@@ -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
|
+
};
|