@noya-app/noya-designsystem 0.1.63 → 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/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +13 -0
- package/dist/index.css +1 -1
- package/dist/index.d.mts +169 -127
- package/dist/index.d.ts +169 -127
- package/dist/index.js +3131 -2642
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2941 -2459
- 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 +9 -3
- 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 +2 -0
- package/src/components/Divider.tsx +10 -6
- package/src/components/DraggableMenuButton.tsx +2 -0
- package/src/components/Drawer.tsx +11 -3
- package/src/components/DropdownMenu.tsx +3 -1
- 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 +2 -0
- package/src/components/Icons.tsx +1 -1
- package/src/components/InputField.tsx +2 -0
- package/src/components/Label.tsx +1 -1
- package/src/components/LabeledElementView.tsx +6 -2
- package/src/components/LabeledField.tsx +2 -0
- 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 +3 -1
- 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 +42 -16
- package/src/components/SelectMenu.tsx +3 -1
- package/src/components/SelectionToolbar.tsx +3 -1
- package/src/components/Slider.tsx +3 -1
- package/src/components/Switch.tsx +3 -1
- package/src/components/Tabs.tsx +3 -1
- 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 +8 -2
- package/src/components/ToolbarDrawer.tsx +29 -0
- package/src/components/Tooltip.tsx +1 -1
- 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 +2 -0
- package/src/components/internal/Checkmark.tsx +24 -0
- package/src/components/internal/Menu.tsx +2 -1
- package/src/components/internal/MenuViewport.tsx +4 -2
- 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 +107 -26
- package/src/components/workspace/PanelWorkspaceLayout.tsx +98 -65
- package/src/components/workspace/VerticalTabMenu.tsx +36 -0
- package/src/components/workspace/WorkspaceLayout.tsx +46 -42
- package/src/components/workspace/constants.ts +4 -0
- package/src/components/workspace/panelStorage.ts +216 -0
- package/src/components/workspace/types.ts +11 -4
- 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 +2 -0
- 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 +19 -0
- package/src/index.tsx +5 -2
- package/src/utils/classNames.ts +4 -3
- package/src/utils/combobox.ts +2 -0
- package/src/utils/formatByteSize.ts +8 -0
- package/src/utils/withSeparatorElements.ts +1 -1
- package/src/hooks/usePreservePanelSize.tsx +0 -183
- package/src/mediaQuery.ts +0 -13
|
@@ -1,5 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
clientStorage,
|
|
5
|
+
memoGeneric,
|
|
6
|
+
usePersistentStateObject,
|
|
7
|
+
} from "@noya-app/react-utils";
|
|
8
|
+
import React, { useMemo, useRef } from "react";
|
|
3
9
|
import {
|
|
4
10
|
ImperativePanelGroupHandle,
|
|
5
11
|
ImperativePanelHandle,
|
|
@@ -7,19 +13,25 @@ import {
|
|
|
7
13
|
PanelGroup,
|
|
8
14
|
PanelResizeHandle,
|
|
9
15
|
} from "react-resizable-panels";
|
|
16
|
+
import { DividerVertical } from "../Divider";
|
|
10
17
|
import {
|
|
11
18
|
CONTENT_AREA_ID,
|
|
12
19
|
EDITOR_PANEL_GROUP_ID,
|
|
13
20
|
LEFT_SIDEBAR_ID,
|
|
14
21
|
RIGHT_SIDEBAR_ID,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
} from "./constants";
|
|
23
|
+
import {
|
|
24
|
+
getInitialLayout,
|
|
25
|
+
getLayoutCollapsedState,
|
|
26
|
+
getLayoutIds,
|
|
27
|
+
getPropertyKey,
|
|
28
|
+
getStorageKey,
|
|
29
|
+
panelStorage,
|
|
30
|
+
setInitialLayout,
|
|
31
|
+
toPixelSize,
|
|
32
|
+
} from "./panelStorage";
|
|
33
|
+
import { LayoutProps, WorkspaceLayoutState } from "./types";
|
|
34
|
+
import { VerticalTabMenu } from "./VerticalTabMenu";
|
|
23
35
|
|
|
24
36
|
export const PanelWorkspaceLayout = memoGeneric(function PanelWorkspaceLayout<
|
|
25
37
|
LeftTab extends string,
|
|
@@ -32,7 +44,6 @@ export const PanelWorkspaceLayout = memoGeneric(function PanelWorkspaceLayout<
|
|
|
32
44
|
rightSidebarOptions,
|
|
33
45
|
centerPanelPercentage,
|
|
34
46
|
autoSavePrefix,
|
|
35
|
-
internalLayoutState,
|
|
36
47
|
leftSidebarRef,
|
|
37
48
|
rightSidebarRef,
|
|
38
49
|
leftTabItems,
|
|
@@ -41,11 +52,59 @@ export const PanelWorkspaceLayout = memoGeneric(function PanelWorkspaceLayout<
|
|
|
41
52
|
rightTabValue,
|
|
42
53
|
onChangeLeftTab,
|
|
43
54
|
onChangeRightTab,
|
|
44
|
-
handleChangeLayoutState,
|
|
45
55
|
}: LayoutProps<LeftTab, RightTab>) {
|
|
46
56
|
const panelGroupRef = useRef<ImperativePanelGroupHandle>(null);
|
|
47
57
|
|
|
48
|
-
|
|
58
|
+
const hasLeftPanel = !!leftPanel;
|
|
59
|
+
const hasRightPanel = !!rightPanel;
|
|
60
|
+
const hasCenterPanel = !!centerPanel;
|
|
61
|
+
|
|
62
|
+
const autoSaveId = useMemo(() => {
|
|
63
|
+
return autoSavePrefix
|
|
64
|
+
? `${autoSavePrefix}--v2--${EDITOR_PANEL_GROUP_ID}`
|
|
65
|
+
: Math.random().toString(36).substring(2, 15);
|
|
66
|
+
}, [autoSavePrefix]);
|
|
67
|
+
|
|
68
|
+
const layoutIds = getLayoutIds({
|
|
69
|
+
hasLeftPanel,
|
|
70
|
+
hasRightPanel,
|
|
71
|
+
hasCenterPanel,
|
|
72
|
+
});
|
|
73
|
+
const storageKey = getStorageKey(autoSaveId);
|
|
74
|
+
const propertyKey = getPropertyKey(layoutIds);
|
|
75
|
+
|
|
76
|
+
setInitialLayout({
|
|
77
|
+
storageKey,
|
|
78
|
+
propertyKey,
|
|
79
|
+
calculateLayout: () => {
|
|
80
|
+
return getInitialLayout({
|
|
81
|
+
layoutIds,
|
|
82
|
+
leftSidebarOptions,
|
|
83
|
+
rightSidebarOptions,
|
|
84
|
+
centerPanelPercentage,
|
|
85
|
+
hasLeftPanel,
|
|
86
|
+
hasRightPanel,
|
|
87
|
+
hasCenterPanel,
|
|
88
|
+
});
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const [data, setData] = usePersistentStateObject<
|
|
93
|
+
WorkspaceLayoutState,
|
|
94
|
+
string
|
|
95
|
+
>(clientStorage, storageKey, {});
|
|
96
|
+
|
|
97
|
+
const layoutGroup = data?.[propertyKey];
|
|
98
|
+
|
|
99
|
+
const { leftSidebarCollapsed, rightSidebarCollapsed } = useMemo(() => {
|
|
100
|
+
return getLayoutCollapsedState({
|
|
101
|
+
layoutGroup,
|
|
102
|
+
propertyKey,
|
|
103
|
+
layoutIds,
|
|
104
|
+
hasLeftPanel,
|
|
105
|
+
hasRightPanel,
|
|
106
|
+
});
|
|
107
|
+
}, [layoutGroup, propertyKey, layoutIds, hasLeftPanel, hasRightPanel]);
|
|
49
108
|
|
|
50
109
|
return (
|
|
51
110
|
<PanelGroup
|
|
@@ -53,22 +112,29 @@ export const PanelWorkspaceLayout = memoGeneric(function PanelWorkspaceLayout<
|
|
|
53
112
|
id={EDITOR_PANEL_GROUP_ID}
|
|
54
113
|
className="flex-1"
|
|
55
114
|
direction="horizontal"
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
115
|
+
storage={panelStorage}
|
|
116
|
+
autoSaveId={autoSaveId}
|
|
117
|
+
onLayout={(layout) => {
|
|
118
|
+
setData((prev) => {
|
|
119
|
+
const newData = {
|
|
120
|
+
...prev,
|
|
121
|
+
[propertyKey]: {
|
|
122
|
+
layout: layout.map((size) => toPixelSize(size)),
|
|
123
|
+
expandToSizes: prev?.[propertyKey]?.expandToSizes ?? {},
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
return newData;
|
|
127
|
+
});
|
|
128
|
+
}}
|
|
61
129
|
>
|
|
62
130
|
{leftPanel && (
|
|
63
131
|
<>
|
|
64
|
-
{leftTabItems && (
|
|
132
|
+
{leftSidebarOptions.showTabs && leftTabItems && (
|
|
65
133
|
<VerticalTabMenu
|
|
66
134
|
tooltipSide="right"
|
|
67
135
|
items={leftTabItems}
|
|
68
136
|
activeValue={leftTabValue}
|
|
69
|
-
isSidebarCollapsed={
|
|
70
|
-
internalLayoutState?.leftSidebarCollapsed ?? false
|
|
71
|
-
}
|
|
137
|
+
isSidebarCollapsed={leftSidebarCollapsed}
|
|
72
138
|
onChange={(value) => {
|
|
73
139
|
if (value === leftTabValue) {
|
|
74
140
|
if (leftSidebarRef.current?.isExpanded()) {
|
|
@@ -85,9 +151,9 @@ export const PanelWorkspaceLayout = memoGeneric(function PanelWorkspaceLayout<
|
|
|
85
151
|
}}
|
|
86
152
|
/>
|
|
87
153
|
)}
|
|
88
|
-
{
|
|
89
|
-
|
|
90
|
-
|
|
154
|
+
{leftSidebarOptions.showTabs &&
|
|
155
|
+
leftTabItems &&
|
|
156
|
+
!leftSidebarCollapsed && <DividerVertical className="h-full" />}
|
|
91
157
|
<Panel
|
|
92
158
|
id={LEFT_SIDEBAR_ID}
|
|
93
159
|
className="relative"
|
|
@@ -103,7 +169,7 @@ export const PanelWorkspaceLayout = memoGeneric(function PanelWorkspaceLayout<
|
|
|
103
169
|
}
|
|
104
170
|
collapsible
|
|
105
171
|
>
|
|
106
|
-
{
|
|
172
|
+
{leftSidebarCollapsed ? null : leftPanel}
|
|
107
173
|
</Panel>
|
|
108
174
|
<PanelResizeHandle className="cursor-col-resize w-px h-full bg-divider" />
|
|
109
175
|
</>
|
|
@@ -135,19 +201,17 @@ export const PanelWorkspaceLayout = memoGeneric(function PanelWorkspaceLayout<
|
|
|
135
201
|
}
|
|
136
202
|
collapsible
|
|
137
203
|
>
|
|
138
|
-
{
|
|
204
|
+
{rightSidebarCollapsed ? null : rightPanel}
|
|
139
205
|
</Panel>
|
|
140
|
-
{
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
{rightTabItems && (
|
|
206
|
+
{rightSidebarOptions.showTabs &&
|
|
207
|
+
rightTabItems &&
|
|
208
|
+
!rightSidebarCollapsed && <DividerVertical className="h-full" />}
|
|
209
|
+
{rightSidebarOptions.showTabs && rightTabItems && (
|
|
144
210
|
<VerticalTabMenu
|
|
145
211
|
tooltipSide="left"
|
|
146
212
|
items={rightTabItems}
|
|
147
213
|
activeValue={rightTabValue}
|
|
148
|
-
isSidebarCollapsed={
|
|
149
|
-
internalLayoutState?.rightSidebarCollapsed ?? false
|
|
150
|
-
}
|
|
214
|
+
isSidebarCollapsed={rightSidebarCollapsed}
|
|
151
215
|
onChange={(value) => {
|
|
152
216
|
if (value === rightTabValue) {
|
|
153
217
|
if (rightSidebarRef.current?.isExpanded()) {
|
|
@@ -169,34 +233,3 @@ export const PanelWorkspaceLayout = memoGeneric(function PanelWorkspaceLayout<
|
|
|
169
233
|
</PanelGroup>
|
|
170
234
|
);
|
|
171
235
|
});
|
|
172
|
-
|
|
173
|
-
function VerticalTabMenu<TabValue extends string>({
|
|
174
|
-
items,
|
|
175
|
-
activeValue,
|
|
176
|
-
isSidebarCollapsed,
|
|
177
|
-
onChange,
|
|
178
|
-
tooltipSide,
|
|
179
|
-
}: {
|
|
180
|
-
items: MenuItem<TabValue>[];
|
|
181
|
-
activeValue: TabValue | undefined;
|
|
182
|
-
isSidebarCollapsed: boolean;
|
|
183
|
-
onChange: (value: TabValue) => void;
|
|
184
|
-
tooltipSide?: ComponentProps<typeof Tooltip>["side"];
|
|
185
|
-
}) {
|
|
186
|
-
return (
|
|
187
|
-
<div
|
|
188
|
-
className="w-[47px] h-full bg-sidebar-background flex flex-col items-center py-3 gap-3"
|
|
189
|
-
style={{
|
|
190
|
-
[cssVarNames.colors.inputBackground]: "transparent",
|
|
191
|
-
[cssVarNames.colors.buttonBackground]: "transparent",
|
|
192
|
-
}}
|
|
193
|
-
>
|
|
194
|
-
<ToolbarMenu
|
|
195
|
-
items={items}
|
|
196
|
-
activeValue={isSidebarCollapsed ? undefined : activeValue}
|
|
197
|
-
onSelectMenuItem={onChange}
|
|
198
|
-
tooltipSide={tooltipSide}
|
|
199
|
-
/>
|
|
200
|
-
</div>
|
|
201
|
-
);
|
|
202
|
-
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React, { ComponentProps } from "react";
|
|
2
|
+
import { cssVarNames } from "../../theme";
|
|
3
|
+
import { MenuItem } from "../internal/Menu";
|
|
4
|
+
import { ToolbarMenu } from "../Toolbar";
|
|
5
|
+
import { Tooltip } from "../Tooltip";
|
|
6
|
+
|
|
7
|
+
export function VerticalTabMenu<TabValue extends string>({
|
|
8
|
+
items,
|
|
9
|
+
activeValue,
|
|
10
|
+
isSidebarCollapsed,
|
|
11
|
+
onChange,
|
|
12
|
+
tooltipSide,
|
|
13
|
+
}: {
|
|
14
|
+
items: MenuItem<TabValue>[];
|
|
15
|
+
activeValue: TabValue | undefined;
|
|
16
|
+
isSidebarCollapsed: boolean;
|
|
17
|
+
onChange: (value: TabValue) => void;
|
|
18
|
+
tooltipSide?: ComponentProps<typeof Tooltip>["side"];
|
|
19
|
+
}) {
|
|
20
|
+
return (
|
|
21
|
+
<div
|
|
22
|
+
className="w-[47px] h-full bg-sidebar-background flex flex-col items-center py-3 gap-3"
|
|
23
|
+
style={{
|
|
24
|
+
[cssVarNames.colors.inputBackground]: "transparent",
|
|
25
|
+
[cssVarNames.colors.buttonBackground]: "transparent",
|
|
26
|
+
}}
|
|
27
|
+
>
|
|
28
|
+
<ToolbarMenu
|
|
29
|
+
items={items}
|
|
30
|
+
activeValue={isSidebarCollapsed ? undefined : activeValue}
|
|
31
|
+
onSelectMenuItem={onChange}
|
|
32
|
+
tooltipSide={tooltipSide}
|
|
33
|
+
/>
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
1
3
|
import { useKeyboardShortcuts } from "@noya-app/noya-keymap";
|
|
2
4
|
import {
|
|
3
5
|
forwardRefGeneric,
|
|
@@ -5,13 +7,7 @@ import {
|
|
|
5
7
|
useSize,
|
|
6
8
|
useWindowSize,
|
|
7
9
|
} from "@noya-app/react-utils";
|
|
8
|
-
import React, {
|
|
9
|
-
memo,
|
|
10
|
-
useCallback,
|
|
11
|
-
useImperativeHandle,
|
|
12
|
-
useRef,
|
|
13
|
-
useState,
|
|
14
|
-
} from "react";
|
|
10
|
+
import React, { memo, useImperativeHandle, useMemo, useRef } from "react";
|
|
15
11
|
import { cx } from "../../utils/classNames";
|
|
16
12
|
import { MenuItem } from "../internal/Menu";
|
|
17
13
|
import { DrawerWorkspaceLayout } from "./DrawerWorkspaceLayout";
|
|
@@ -19,7 +15,6 @@ import { PanelWorkspaceLayout } from "./PanelWorkspaceLayout";
|
|
|
19
15
|
import { renderPanelChildren } from "./renderPanelChildren";
|
|
20
16
|
import {
|
|
21
17
|
LayoutProps,
|
|
22
|
-
PanelLayoutState,
|
|
23
18
|
PercentageSidebarOptions,
|
|
24
19
|
RenderPanel,
|
|
25
20
|
SidebarRef,
|
|
@@ -55,7 +50,6 @@ export interface WorkspaceLayoutProps<
|
|
|
55
50
|
sideType?: SideType;
|
|
56
51
|
sideTypeBreakpoint?: number;
|
|
57
52
|
detectSize?: DetectSizeType;
|
|
58
|
-
onChangeLayoutState?: (layoutState: PanelLayoutState) => void;
|
|
59
53
|
}
|
|
60
54
|
|
|
61
55
|
export type WorkspaceLayoutRef = {
|
|
@@ -78,23 +72,24 @@ export type SideOptions = {
|
|
|
78
72
|
minSize?: number | string;
|
|
79
73
|
maxSize?: number | string;
|
|
80
74
|
defaultCollapsed?: boolean;
|
|
75
|
+
showTabs?: boolean;
|
|
81
76
|
};
|
|
82
77
|
|
|
83
78
|
const defaultLeftTabItems: MenuItem<string>[] = [
|
|
84
79
|
{
|
|
85
80
|
title: "",
|
|
86
|
-
value: "
|
|
81
|
+
value: "defaultLeft",
|
|
87
82
|
icon: "BorderLeftIcon",
|
|
88
|
-
tooltip: "Sidebar",
|
|
83
|
+
tooltip: "Left Sidebar",
|
|
89
84
|
},
|
|
90
85
|
];
|
|
91
86
|
|
|
92
87
|
const defaultRightTabItems: MenuItem<string>[] = [
|
|
93
88
|
{
|
|
94
89
|
title: "",
|
|
95
|
-
value: "
|
|
90
|
+
value: "defaultRight",
|
|
96
91
|
icon: "BorderRightIcon",
|
|
97
|
-
tooltip: "Sidebar",
|
|
92
|
+
tooltip: "Right Sidebar",
|
|
98
93
|
},
|
|
99
94
|
];
|
|
100
95
|
|
|
@@ -117,7 +112,6 @@ export const WorkspaceLayout = forwardRefGeneric(function WorkspaceLayout<
|
|
|
117
112
|
style,
|
|
118
113
|
toolbar,
|
|
119
114
|
children,
|
|
120
|
-
onChangeLayoutState,
|
|
121
115
|
sideType = "auto",
|
|
122
116
|
sideTypeBreakpoint = 800,
|
|
123
117
|
detectSize = "container",
|
|
@@ -129,6 +123,7 @@ export const WorkspaceLayout = forwardRefGeneric(function WorkspaceLayout<
|
|
|
129
123
|
style: leftStyle,
|
|
130
124
|
className: leftClassName,
|
|
131
125
|
defaultCollapsed: leftDefaultCollapsed = false,
|
|
126
|
+
showTabs: leftShowTabs = true,
|
|
132
127
|
} = {},
|
|
133
128
|
rightOptions: {
|
|
134
129
|
minSize: rightMinSize = 200,
|
|
@@ -138,6 +133,7 @@ export const WorkspaceLayout = forwardRefGeneric(function WorkspaceLayout<
|
|
|
138
133
|
style: rightStyle,
|
|
139
134
|
className: rightClassName,
|
|
140
135
|
defaultCollapsed: rightDefaultCollapsed = false,
|
|
136
|
+
showTabs: rightShowTabs = true,
|
|
141
137
|
} = {},
|
|
142
138
|
theme,
|
|
143
139
|
}: WorkspaceLayoutProps<LeftTab, RightTab>,
|
|
@@ -213,20 +209,6 @@ export const WorkspaceLayout = forwardRefGeneric(function WorkspaceLayout<
|
|
|
213
209
|
defaultValue: getFirstTabValue(rightTabItems, "" as RightTab),
|
|
214
210
|
});
|
|
215
211
|
|
|
216
|
-
const [internalLayoutState, setInternalLayoutState] =
|
|
217
|
-
useState<PanelLayoutState | null>({
|
|
218
|
-
leftSidebarCollapsed: leftDefaultCollapsed,
|
|
219
|
-
rightSidebarCollapsed: rightDefaultCollapsed,
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
const handleChangeLayoutState = useCallback(
|
|
223
|
-
(state: PanelLayoutState) => {
|
|
224
|
-
setInternalLayoutState(state);
|
|
225
|
-
onChangeLayoutState?.(state);
|
|
226
|
-
},
|
|
227
|
-
[onChangeLayoutState]
|
|
228
|
-
);
|
|
229
|
-
|
|
230
212
|
useImperativeHandle(forwardedRef, () => ({
|
|
231
213
|
setLeftExpanded: (expanded: boolean) => {
|
|
232
214
|
if (expanded) {
|
|
@@ -316,19 +298,43 @@ export const WorkspaceLayout = forwardRefGeneric(function WorkspaceLayout<
|
|
|
316
298
|
<PanelInner className="bg-canvas-background">{children}</PanelInner>
|
|
317
299
|
);
|
|
318
300
|
|
|
319
|
-
const leftSidebarOptions: PercentageSidebarOptions =
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
301
|
+
const leftSidebarOptions: PercentageSidebarOptions = useMemo(
|
|
302
|
+
() => ({
|
|
303
|
+
minSize: leftSidebarMinPercentage,
|
|
304
|
+
maxSize: leftSidebarMaxPercentage,
|
|
305
|
+
initialSize: leftSidebarPercentage,
|
|
306
|
+
resizable: leftResizable,
|
|
307
|
+
defaultCollapsed: leftDefaultCollapsed,
|
|
308
|
+
showTabs: leftShowTabs,
|
|
309
|
+
}),
|
|
310
|
+
[
|
|
311
|
+
leftSidebarMinPercentage,
|
|
312
|
+
leftSidebarMaxPercentage,
|
|
313
|
+
leftSidebarPercentage,
|
|
314
|
+
leftResizable,
|
|
315
|
+
leftDefaultCollapsed,
|
|
316
|
+
leftShowTabs,
|
|
317
|
+
]
|
|
318
|
+
);
|
|
325
319
|
|
|
326
|
-
const rightSidebarOptions: PercentageSidebarOptions =
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
320
|
+
const rightSidebarOptions: PercentageSidebarOptions = useMemo(
|
|
321
|
+
() => ({
|
|
322
|
+
minSize: rightSidebarMinPercentage,
|
|
323
|
+
maxSize: rightSidebarMaxPercentage,
|
|
324
|
+
initialSize: rightSidebarPercentage,
|
|
325
|
+
resizable: rightResizable,
|
|
326
|
+
defaultCollapsed: rightDefaultCollapsed,
|
|
327
|
+
showTabs: rightShowTabs,
|
|
328
|
+
}),
|
|
329
|
+
[
|
|
330
|
+
rightSidebarMinPercentage,
|
|
331
|
+
rightSidebarMaxPercentage,
|
|
332
|
+
rightSidebarPercentage,
|
|
333
|
+
rightResizable,
|
|
334
|
+
rightDefaultCollapsed,
|
|
335
|
+
rightShowTabs,
|
|
336
|
+
]
|
|
337
|
+
);
|
|
332
338
|
|
|
333
339
|
const LayoutComponent =
|
|
334
340
|
sideType === "panel"
|
|
@@ -374,10 +380,8 @@ export const WorkspaceLayout = forwardRefGeneric(function WorkspaceLayout<
|
|
|
374
380
|
rightSidebarOptions={rightSidebarOptions}
|
|
375
381
|
centerPanelPercentage={centerPanelPercentage}
|
|
376
382
|
autoSavePrefix={autoSavePrefix}
|
|
377
|
-
internalLayoutState={internalLayoutState}
|
|
378
383
|
leftSidebarRef={leftSidebarRef}
|
|
379
384
|
rightSidebarRef={rightSidebarRef}
|
|
380
|
-
handleChangeLayoutState={handleChangeLayoutState}
|
|
381
385
|
/>
|
|
382
386
|
)}
|
|
383
387
|
</div>
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { clientStorage, IClientStorage } from "@noya-app/react-utils";
|
|
2
|
+
import { PanelGroupStorage } from "react-resizable-panels";
|
|
3
|
+
import {
|
|
4
|
+
CONTENT_AREA_ID,
|
|
5
|
+
LEFT_SIDEBAR_ID,
|
|
6
|
+
RIGHT_SIDEBAR_ID,
|
|
7
|
+
} from "./constants";
|
|
8
|
+
import {
|
|
9
|
+
PercentageSidebarOptions,
|
|
10
|
+
WorkspaceLayoutGroup,
|
|
11
|
+
WorkspaceLayoutState,
|
|
12
|
+
} from "./types";
|
|
13
|
+
|
|
14
|
+
// Workspace sizes are stored as percentages (e.g. layout: [78.7224911972, 21.2775088028])
|
|
15
|
+
// we need to convert them to pixels relative to the window width before storing
|
|
16
|
+
// and convert them back to percentages when getting the item
|
|
17
|
+
export const panelStorage: PanelGroupStorage = {
|
|
18
|
+
getItem: (name) => {
|
|
19
|
+
const item = clientStorage.getItem(name);
|
|
20
|
+
|
|
21
|
+
if (item) {
|
|
22
|
+
const parsed = JSON.parse(item);
|
|
23
|
+
const percentageSize = mapWorkspaceLayoutState(parsed, (size) =>
|
|
24
|
+
toPercentageSize(size)
|
|
25
|
+
);
|
|
26
|
+
const stringified = JSON.stringify(percentageSize);
|
|
27
|
+
// console.log("getItem", name, stringified);
|
|
28
|
+
return stringified;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return item;
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
setItem: (name, value) => {
|
|
35
|
+
const parsed = JSON.parse(value);
|
|
36
|
+
const pixelSize = mapWorkspaceLayoutState(parsed, (size) =>
|
|
37
|
+
toPixelSize(size)
|
|
38
|
+
);
|
|
39
|
+
// console.log("setItem", name, pixelSize);
|
|
40
|
+
clientStorage.setItem(name, JSON.stringify(pixelSize));
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const toPixelSize = (size: number, windowWidth = window.innerWidth) =>
|
|
45
|
+
Math.round((size * windowWidth) / 100);
|
|
46
|
+
export const toPercentageSize = (
|
|
47
|
+
size: number,
|
|
48
|
+
windowWidth = window.innerWidth
|
|
49
|
+
) => (size * 100) / windowWidth;
|
|
50
|
+
|
|
51
|
+
export function mapWorkspaceLayoutState(
|
|
52
|
+
layout: WorkspaceLayoutState,
|
|
53
|
+
f: (size: number) => number
|
|
54
|
+
): WorkspaceLayoutState {
|
|
55
|
+
return Object.fromEntries(
|
|
56
|
+
Object.entries(layout).map(
|
|
57
|
+
([key, value]): [string, WorkspaceLayoutGroup] => {
|
|
58
|
+
const expandToSizes = Object.fromEntries(
|
|
59
|
+
Object.entries(value.expandToSizes).map(([key, value]) => [
|
|
60
|
+
key,
|
|
61
|
+
f(value),
|
|
62
|
+
])
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
return [
|
|
66
|
+
key,
|
|
67
|
+
{
|
|
68
|
+
expandToSizes,
|
|
69
|
+
layout: value.layout.map(f),
|
|
70
|
+
},
|
|
71
|
+
];
|
|
72
|
+
}
|
|
73
|
+
)
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function getLayoutIds({
|
|
78
|
+
hasLeftPanel,
|
|
79
|
+
hasRightPanel,
|
|
80
|
+
hasCenterPanel,
|
|
81
|
+
}: {
|
|
82
|
+
hasLeftPanel: boolean;
|
|
83
|
+
hasRightPanel: boolean;
|
|
84
|
+
hasCenterPanel: boolean;
|
|
85
|
+
}) {
|
|
86
|
+
return [
|
|
87
|
+
hasLeftPanel && LEFT_SIDEBAR_ID,
|
|
88
|
+
hasCenterPanel && CONTENT_AREA_ID,
|
|
89
|
+
hasRightPanel && RIGHT_SIDEBAR_ID,
|
|
90
|
+
].flatMap((id) => (id ? [id] : []));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function getPropertyKey(layoutIds: string[]) {
|
|
94
|
+
return layoutIds.sort().join(",");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function getStorageKey(autoSaveId: string) {
|
|
98
|
+
return `react-resizable-panels:${autoSaveId}`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function getInitialLayout({
|
|
102
|
+
layoutIds,
|
|
103
|
+
leftSidebarOptions,
|
|
104
|
+
rightSidebarOptions,
|
|
105
|
+
centerPanelPercentage,
|
|
106
|
+
hasLeftPanel,
|
|
107
|
+
hasRightPanel,
|
|
108
|
+
hasCenterPanel,
|
|
109
|
+
windowWidth = window.innerWidth,
|
|
110
|
+
}: {
|
|
111
|
+
layoutIds: string[];
|
|
112
|
+
leftSidebarOptions: PercentageSidebarOptions;
|
|
113
|
+
rightSidebarOptions: PercentageSidebarOptions;
|
|
114
|
+
centerPanelPercentage: number;
|
|
115
|
+
hasLeftPanel: boolean;
|
|
116
|
+
hasRightPanel: boolean;
|
|
117
|
+
hasCenterPanel: boolean;
|
|
118
|
+
windowWidth?: number;
|
|
119
|
+
}) {
|
|
120
|
+
return layoutIds.map((id) => {
|
|
121
|
+
if (id === LEFT_SIDEBAR_ID && hasLeftPanel) {
|
|
122
|
+
return leftSidebarOptions.defaultCollapsed
|
|
123
|
+
? 0
|
|
124
|
+
: toPixelSize(leftSidebarOptions.initialSize ?? 0, windowWidth);
|
|
125
|
+
} else if (id === RIGHT_SIDEBAR_ID && hasRightPanel) {
|
|
126
|
+
return rightSidebarOptions.defaultCollapsed
|
|
127
|
+
? 0
|
|
128
|
+
: toPixelSize(rightSidebarOptions.initialSize ?? 0, windowWidth);
|
|
129
|
+
} else if (id === CONTENT_AREA_ID && hasCenterPanel) {
|
|
130
|
+
const shouldShowLeftSidebar =
|
|
131
|
+
hasLeftPanel && !leftSidebarOptions.defaultCollapsed;
|
|
132
|
+
const shouldShowRightSidebar =
|
|
133
|
+
hasRightPanel && !rightSidebarOptions.defaultCollapsed;
|
|
134
|
+
|
|
135
|
+
if (!shouldShowLeftSidebar && !shouldShowRightSidebar) {
|
|
136
|
+
return toPixelSize(100, windowWidth);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return toPixelSize(centerPanelPercentage, windowWidth);
|
|
140
|
+
}
|
|
141
|
+
return 0;
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function setInitialLayout({
|
|
146
|
+
storageKey,
|
|
147
|
+
propertyKey,
|
|
148
|
+
calculateLayout,
|
|
149
|
+
storage = clientStorage,
|
|
150
|
+
}: {
|
|
151
|
+
storageKey: string;
|
|
152
|
+
propertyKey: string;
|
|
153
|
+
calculateLayout: () => number[];
|
|
154
|
+
storage?: IClientStorage;
|
|
155
|
+
}) {
|
|
156
|
+
const savedLayout = storage.getItem(storageKey);
|
|
157
|
+
|
|
158
|
+
if (!savedLayout) {
|
|
159
|
+
const layoutObject: WorkspaceLayoutState = {
|
|
160
|
+
[propertyKey]: {
|
|
161
|
+
layout: calculateLayout(),
|
|
162
|
+
expandToSizes: {},
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
storage.setItem(storageKey, JSON.stringify(layoutObject));
|
|
167
|
+
} else {
|
|
168
|
+
let parsedLayout: WorkspaceLayoutState;
|
|
169
|
+
|
|
170
|
+
try {
|
|
171
|
+
parsedLayout = JSON.parse(savedLayout);
|
|
172
|
+
} catch (error) {
|
|
173
|
+
console.error(error);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (!(propertyKey in parsedLayout)) {
|
|
178
|
+
const layoutObject: WorkspaceLayoutState = {
|
|
179
|
+
...parsedLayout,
|
|
180
|
+
[propertyKey]: {
|
|
181
|
+
layout: calculateLayout(),
|
|
182
|
+
expandToSizes: {},
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
storage.setItem(storageKey, JSON.stringify(layoutObject));
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function getLayoutCollapsedState({
|
|
192
|
+
layoutGroup,
|
|
193
|
+
layoutIds,
|
|
194
|
+
hasLeftPanel,
|
|
195
|
+
hasRightPanel,
|
|
196
|
+
}: {
|
|
197
|
+
layoutGroup: WorkspaceLayoutGroup;
|
|
198
|
+
propertyKey: string;
|
|
199
|
+
layoutIds: string[];
|
|
200
|
+
hasLeftPanel: boolean;
|
|
201
|
+
hasRightPanel: boolean;
|
|
202
|
+
}) {
|
|
203
|
+
const propertySize = Object.fromEntries(
|
|
204
|
+
layoutIds.map((id, index) => [id, layoutGroup.layout?.[index]])
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
const leftSize = propertySize?.[LEFT_SIDEBAR_ID];
|
|
208
|
+
const leftSidebarCollapsed = hasLeftPanel && leftSize === 0 ? true : false;
|
|
209
|
+
const rightSize = propertySize?.[RIGHT_SIDEBAR_ID];
|
|
210
|
+
const rightSidebarCollapsed = hasRightPanel && rightSize === 0 ? true : false;
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
leftSidebarCollapsed,
|
|
214
|
+
rightSidebarCollapsed,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
@@ -12,6 +12,8 @@ export type PercentageSidebarOptions = {
|
|
|
12
12
|
maxSize?: number;
|
|
13
13
|
initialSize?: number;
|
|
14
14
|
resizable?: boolean;
|
|
15
|
+
defaultCollapsed?: boolean;
|
|
16
|
+
showTabs?: boolean;
|
|
15
17
|
};
|
|
16
18
|
|
|
17
19
|
export type PanelLayoutState = {
|
|
@@ -40,8 +42,13 @@ export type LayoutProps<
|
|
|
40
42
|
rightSidebarOptions: PercentageSidebarOptions;
|
|
41
43
|
centerPanelPercentage: number;
|
|
42
44
|
autoSavePrefix?: string;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
rightSidebarRef: React.RefObject<SidebarRef>;
|
|
46
|
-
handleChangeLayoutState: (state: PanelLayoutState) => void;
|
|
45
|
+
leftSidebarRef: React.RefObject<SidebarRef | null>;
|
|
46
|
+
rightSidebarRef: React.RefObject<SidebarRef | null>;
|
|
47
47
|
};
|
|
48
|
+
|
|
49
|
+
export type WorkspaceLayoutGroup = {
|
|
50
|
+
expandToSizes: Record<string, number>;
|
|
51
|
+
layout: number[];
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export type WorkspaceLayoutState = Record<string, WorkspaceLayoutGroup>;
|