Skip to content

Commit f63b5ca

Browse files
authored
feat: Resizable left-side panels like navigator (#5503)
## Description 1. What is this PR about (link the issue and add a short description) ## Steps for reproduction 1. click button 2. expect xyz ## Code Review - [ ] hi @kof, I need you to do - conceptual review (architecture, feature-correctness) - detailed review (read every line) - test it on preview ## Before requesting a review - [ ] made a self-review - [ ] added inline comments where things may be not obvious (the "why", not "what") ## Before merging - [ ] tested locally and on preview environment (preview dev login: 0000) - [ ] updated [test cases](https://github.com/webstudio-is/webstudio/blob/main/apps/builder/docs/test-cases.md) document - [ ] added tests - [ ] if any new env variables are added, added them to `.env` file
1 parent b18315e commit f63b5ca

53 files changed

Lines changed: 920 additions & 670 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/builder/app/builder/builder.tsx

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,21 @@ import { TooltipProvider } from "@radix-ui/react-tooltip";
44
import { usePublish, $publisher } from "~/shared/pubsub";
55
import type { Build } from "@webstudio-is/project-build";
66
import type { Project } from "@webstudio-is/project";
7-
import { theme, Box, type CSS, Flex, Grid } from "@webstudio-is/design-system";
7+
import {
8+
theme,
9+
Box,
10+
type CSS,
11+
Flex,
12+
Grid,
13+
rawTheme,
14+
} from "@webstudio-is/design-system";
815
import type { AuthPermit } from "@webstudio-is/trpc-interface/index.server";
916
import { registerContainers, createObjectPool } from "~/shared/sync";
1017
import {
1118
ServerSyncStorage,
1219
startProjectSync,
1320
useSyncServer,
1421
} from "./shared/sync/sync-server";
15-
import { SidebarLeft } from "./sidebar-left";
1622
import { Inspector } from "./inspector";
1723
import { Topbar } from "./features/topbar";
1824
import { Footer } from "./features/footer";
@@ -48,7 +54,6 @@ import {
4854
$dataLoadingState,
4955
$isCloneDialogOpen,
5056
$loadingState,
51-
type SidebarPanelName,
5257
} from "./shared/nano-states";
5358
import { CloneProjectDialog } from "~/shared/clone-project";
5459
import type { TokenPermissions } from "@webstudio-is/authorization-token";
@@ -72,6 +77,8 @@ import { useInertHandlers } from "./shared/inert-handlers";
7277
import { TextToolbar } from "./features/workspace/canvas-tools/text-toolbar";
7378
import { SyncClient } from "~/shared/sync-client";
7479
import { RemoteDialog } from "./features/help/remote-dialog";
80+
import type { SidebarPanelName } from "./sidebar-left/types";
81+
import { SidebarLeft } from "./sidebar-left/sidebar-left";
7582

7683
registerContainers();
7784

@@ -143,10 +150,12 @@ const getChromeLayout = ({
143150
isPreviewMode,
144151
navigatorLayout,
145152
activeSidebarPanel,
153+
leftSidebarWidth,
146154
}: {
147155
isPreviewMode: boolean;
148156
navigatorLayout: Settings["navigatorLayout"];
149157
activeSidebarPanel?: SidebarPanelName;
158+
leftSidebarWidth: number;
150159
}) => {
151160
if (isPreviewMode) {
152161
return {
@@ -161,7 +170,7 @@ const getChromeLayout = ({
161170

162171
if (navigatorLayout === "undocked" && activeSidebarPanel !== "none") {
163172
return {
164-
gridTemplateColumns: `auto ${theme.sizes.sidebarWidth} 1fr ${theme.sizes.sidebarWidth}`,
173+
gridTemplateColumns: `auto ${leftSidebarWidth}px 1fr ${theme.sizes.sidebarWidth}`,
165174
gridTemplateAreas: `
166175
"header header header header"
167176
"sidebar navigator main inspector"
@@ -180,16 +189,26 @@ const getChromeLayout = ({
180189
};
181190
};
182191

192+
const defaultSidebarWidth = Number.parseFloat(rawTheme.spacing[30]);
193+
183194
const ChromeWrapper = ({
184195
children,
185196
isPreviewMode,
186197
navigatorLayout,
187198
}: ChromeWrapperProps) => {
188199
const activeSidebarPanel = useStore($activeSidebarPanel);
200+
const settings = useStore($settings);
201+
const leftSidebarWidth =
202+
activeSidebarPanel === "none"
203+
? defaultSidebarWidth
204+
: (settings.sidebarPanelWidths[activeSidebarPanel] ??
205+
defaultSidebarWidth);
206+
189207
const gridLayout = getChromeLayout({
190208
isPreviewMode,
191209
navigatorLayout,
192210
activeSidebarPanel,
211+
leftSidebarWidth,
193212
});
194213

195214
return (
@@ -366,6 +385,13 @@ export const Builder = ({
366385
isPreviewMode={isPreviewMode}
367386
navigatorLayout={navigatorLayout}
368387
>
388+
<Box
389+
data-dialog-boundary
390+
css={{
391+
gridArea: "sidebar / sidebar / main / inspector",
392+
pointerEvents: "none",
393+
}}
394+
/>
369395
<ProjectSettings />
370396
<Main>
371397
<Workspace>
@@ -378,15 +404,10 @@ export const Builder = ({
378404
)}
379405
</Workspace>
380406
</Main>
407+
<Main css={{ pointerEvents: "none" }}>
408+
<CanvasToolsContainer />
409+
</Main>
381410

382-
<SidePanel
383-
gridArea="sidebar"
384-
css={{
385-
order: navigatorLayout === "docked" ? 1 : undefined,
386-
}}
387-
>
388-
<SidebarLeft publish={publish} />
389-
</SidePanel>
390411
<SidePanel
391412
gridArea="inspector"
392413
isPreviewMode={isPreviewMode}
@@ -406,9 +427,14 @@ export const Builder = ({
406427
>
407428
<Inspector navigatorLayout={navigatorLayout} />
408429
</SidePanel>
409-
<Main css={{ pointerEvents: "none" }}>
410-
<CanvasToolsContainer />
411-
</Main>
430+
<SidePanel
431+
gridArea="sidebar"
432+
css={{
433+
order: navigatorLayout === "docked" ? 1 : undefined,
434+
}}
435+
>
436+
<SidebarLeft publish={publish} />
437+
</SidePanel>
412438
<Topbar
413439
project={project}
414440
hasProPlan={userPlanFeatures.hasProPlan}

apps/builder/app/builder/features/assets/assets.tsx

Lines changed: 3 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,10 @@
1-
import {
2-
Button,
3-
PanelTitle,
4-
Separator,
5-
Tooltip,
6-
} from "@webstudio-is/design-system";
7-
import { XIcon } from "@webstudio-is/icons";
1+
import { PanelTitle, Separator } from "@webstudio-is/design-system";
82
import { ImageManager } from "~/builder/shared/image-manager";
93

10-
export const AssetsPanel = ({ onClose }: { onClose: () => void }) => {
4+
export const AssetsPanel = (_props: { onClose: () => void }) => {
115
return (
126
<>
13-
<PanelTitle
14-
suffix={
15-
<Tooltip content="Close panel" side="bottom">
16-
<Button
17-
color="ghost"
18-
prefix={<XIcon />}
19-
aria-label="Close panel"
20-
onClick={onClose}
21-
/>
22-
</Tooltip>
23-
}
24-
>
25-
Assets
26-
</PanelTitle>
7+
<PanelTitle>Assets</PanelTitle>
278
<Separator />
289
<ImageManager />
2910
</>

apps/builder/app/builder/features/breakpoints/breakpoints-popover.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ import {
3838
minCanvasWidth,
3939
} from "~/shared/breakpoints";
4040
import { $scale } from "~/builder/shared/nano-states";
41-
import { setCanvasWidth } from "./use-set-initial-canvas-width";
4241
import { serverSyncStore } from "~/shared/sync";
42+
import { setCanvasWidth } from "~/builder/shared/calc-canvas-width";
4343

4444
export const BreakpointsPopover = () => {
4545
const view = useStore($breakpointsMenuView);

apps/builder/app/builder/features/breakpoints/breakpoints-selector.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
$selectedBreakpointId,
1717
} from "~/shared/nano-states";
1818
import { groupBreakpoints, isBaseBreakpoint } from "~/shared/breakpoints";
19-
import { setCanvasWidth } from "./use-set-initial-canvas-width";
19+
import { setCanvasWidth } from "../../shared/calc-canvas-width";
2020
import { $canvasWidth } from "~/builder/shared/nano-states";
2121
import { useDebouncedCallback } from "use-debounce";
2222

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
11
export * from "./breakpoints-popover";
22
export * from "./breakpoints-selector-container";
3-
export * from "./use-set-initial-canvas-width";

apps/builder/app/builder/features/breakpoints/use-set-initial-canvas-width.ts

Lines changed: 0 additions & 70 deletions
This file was deleted.

apps/builder/app/builder/features/command-panel/groups/breakpoints-group.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ import {
1313
$selectedBreakpoint,
1414
$selectedBreakpointId,
1515
} from "~/shared/nano-states";
16-
import { setCanvasWidth } from "~/builder/features/breakpoints";
1716
import { closeCommandPanel } from "../command-state";
1817
import type { BaseOption } from "../shared/types";
18+
import { setCanvasWidth } from "~/builder/shared/calc-canvas-width";
1919

2020
export type BreakpointOption = BaseOption & {
2121
type: "breakpoint";

apps/builder/app/builder/features/components/components.tsx

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { useState } from "react";
22
import { matchSorter } from "match-sorter";
33
import { computed } from "nanostores";
44
import { useStore } from "@nanostores/react";
5-
import { XIcon } from "@webstudio-is/icons";
65
import { isFeatureEnabled } from "@webstudio-is/feature-flags";
76
import {
87
type WsComponentMeta,
@@ -23,10 +22,7 @@ import {
2322
findNextListItemIndex,
2423
Text,
2524
Box,
26-
Grid,
2725
PanelTitle,
28-
Tooltip,
29-
Button,
3026
} from "@webstudio-is/design-system";
3127
import { CollapsibleSection } from "~/builder/shared/collapsible-section";
3228
import { dragItemAttribute, useDraggable } from "./use-draggable";
@@ -240,7 +236,7 @@ export const ComponentsPanel = ({
240236

241237
const { metasByCategory, availableComponents } = useStore($metas);
242238

243-
const { dragCard, draggableContainerRef } = useDraggable({
239+
const { dragCard, draggableContainerRef, isDragging } = useDraggable({
244240
publish,
245241
availableComponents,
246242
});
@@ -253,20 +249,7 @@ export const ComponentsPanel = ({
253249

254250
return (
255251
<>
256-
<PanelTitle
257-
suffix={
258-
<Tooltip content="Close panel" side="bottom">
259-
<Button
260-
color="ghost"
261-
prefix={<XIcon />}
262-
aria-label="Close panel"
263-
onClick={onClose}
264-
/>
265-
</Tooltip>
266-
}
267-
>
268-
Components
269-
</PanelTitle>
252+
<PanelTitle>Components</PanelTitle>
270253
<Separator />
271254

272255
<Box css={{ padding: theme.panel.padding }}>
@@ -286,9 +269,9 @@ export const ComponentsPanel = ({
286269
fullWidth
287270
>
288271
<List asChild>
289-
<Grid
272+
<Flex
290273
gap="1"
291-
columns="3"
274+
wrap="wrap"
292275
css={{
293276
paddingInline: theme.panel.paddingInline,
294277
overflow: "auto",
@@ -309,8 +292,11 @@ export const ComponentsPanel = ({
309292
>
310293
<ComponentCard
311294
{...{ [dragItemAttribute]: meta.name }}
295+
// Too hard to calculate, goal was to have 3 cards in one row on the smallest width and to fill it at the same ime
296+
css={{ width: 69 }}
312297
label={meta.label}
313298
description={meta.description}
299+
disableTooltip={isDragging}
314300
icon={
315301
<InstanceIcon
316302
size="auto"
@@ -328,7 +314,7 @@ export const ComponentsPanel = ({
328314
<Text>No matching component</Text>
329315
</Flex>
330316
)}
331-
</Grid>
317+
</Flex>
332318
</List>
333319
</CollapsibleSection>
334320
))}

apps/builder/app/builder/features/components/use-draggable.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ export const useDraggable = ({
9090
availableComponents: Set<string>;
9191
}) => {
9292
const [dragComponent, setDragComponent] = useState<Instance["component"]>();
93+
const [isDragging, setIsDragging] = useState(false);
9394
const [point, setPoint] = useState<Point>({ x: 0, y: 0 });
9495
const canvasRect = useStore($canvasRect);
9596
const scale = useStore($scale);
@@ -102,6 +103,7 @@ export const useDraggable = ({
102103
},
103104
onStart({ data: componentName }) {
104105
setDragComponent(componentName);
106+
setIsDragging(true);
105107
publish({
106108
type: "dragStart",
107109
payload: {
@@ -123,6 +125,7 @@ export const useDraggable = ({
123125
},
124126
onEnd({ isCanceled }) {
125127
setDragComponent(undefined);
128+
setIsDragging(false);
126129
publish({
127130
type: "dragEnd",
128131
payload: { isCanceled },
@@ -143,5 +146,6 @@ export const useDraggable = ({
143146
return {
144147
dragCard,
145148
draggableContainerRef: dragHandlers.rootRef,
149+
isDragging,
146150
};
147151
};

0 commit comments

Comments
 (0)