diff --git a/app/designer/breadcrumbs/Breadcrumbs.tsx b/app/designer/breadcrumbs/Breadcrumbs.tsx
new file mode 100644
index 0000000000..27fe569e3c
--- /dev/null
+++ b/app/designer/breadcrumbs/Breadcrumbs.tsx
@@ -0,0 +1,58 @@
+import { ChevronRightIcon } from "~/shared/icons";
+import { Button, Flex, Text } from "~/shared/design-system";
+import { useSelectedInstancePath } from "../instance/use-selected-instance-path";
+import { Instance } from "@webstudio-is/sdk";
+import { Publish } from "../canvas-iframe";
+
+type BreadcrumbProps = {
+ component: Instance["component"];
+ onClick: () => void;
+};
+
+const Breadcrumb = ({ component, onClick }: BreadcrumbProps) => {
+ return (
+
+ {" "}
+
+
+ );
+};
+
+const EmptyState = () => (
+
+ No instance selected
+
+);
+
+type BreadcrumbsProps = {
+ publish: Publish;
+};
+export const Breadcrumbs = ({ publish }: BreadcrumbsProps) => {
+ const selectedInstancePath = useSelectedInstancePath();
+ return (
+
+ {selectedInstancePath.length === 0 ? (
+
+ ) : (
+ selectedInstancePath.map((instance) => (
+ {
+ publish<"focusElement", Instance["id"]>({
+ type: "focusElement",
+ payload: instance.id,
+ });
+ }}
+ />
+ ))
+ )}
+
+ );
+};
diff --git a/app/designer/breadcrumbs/index.ts b/app/designer/breadcrumbs/index.ts
new file mode 100644
index 0000000000..c1874ecead
--- /dev/null
+++ b/app/designer/breadcrumbs/index.ts
@@ -0,0 +1 @@
+export * from "./Breadcrumbs";
diff --git a/app/designer/canvas-iframe/canvas-iframe.tsx b/app/designer/canvas-iframe/canvas-iframe.tsx
index e0b7fe46bb..273f2eec19 100644
--- a/app/designer/canvas-iframe/canvas-iframe.tsx
+++ b/app/designer/canvas-iframe/canvas-iframe.tsx
@@ -1,10 +1,7 @@
import { forwardRef } from "react";
-import { css } from "~/shared/design-system";
+import { CSS, css } from "~/shared/design-system";
const iframeStyle = css({
- gridArea: "canvas",
- width: "100%",
- height: "100%",
border: "none",
variants: {
pointerEvents: {
@@ -22,12 +19,17 @@ type CanvasIframeProps = {
pointerEvents: "all" | "none";
src: string;
title: string;
+ css: CSS;
};
export const CanvasIframe = forwardRef(
- ({ pointerEvents = "all", ...rest }, ref) => {
+ ({ pointerEvents = "all", css, ...rest }, ref) => {
return (
-
+
);
}
);
diff --git a/app/designer/designer.tsx b/app/designer/designer.tsx
index 5f6060425d..88813cb433 100644
--- a/app/designer/designer.tsx
+++ b/app/designer/designer.tsx
@@ -1,18 +1,23 @@
import { useState } from "react";
import { DndProvider } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";
+import type { Project } from "@webstudio-is/sdk";
import type { Config } from "~/config";
import type { SelectedInstanceData } from "~/shared/component";
-import type { Project } from "@webstudio-is/sdk";
-import { Box, Grid, type CSS } from "~/shared/design-system";
+import { Box, Flex, Grid, type CSS } from "~/shared/design-system";
+import interStyles from "~/shared/font-faces/inter.css";
import { SidebarLeft } from "./sidebar-left";
import { SidebarRight } from "./inspector";
import { CanvasIframe, useSubscribe, usePublish } from "./canvas-iframe";
-import { useIsPreviewMode, useRootInstance } from "./nano-values";
+import {
+ useIsPreviewMode,
+ useRootInstance,
+ useSelectedInstanceData,
+} from "./nano-values";
import { Topbar } from "./topbar";
import designerStyles from "./designer.css";
-import interStyles from "~/shared/font-faces/inter.css";
import { useSync } from "./sync";
+import { Breadcrumbs } from "./breadcrumbs";
export const links = () => {
return [
@@ -21,21 +26,19 @@ export const links = () => {
];
};
-const useSelectedInstanceData = (): SelectedInstanceData | undefined => {
- const [selectedInstanceData, setSelectedInstanceData] =
- useState();
- useSubscribe<"selectInstance", SelectedInstanceData>(
- "selectInstance",
- setSelectedInstanceData
- );
- return selectedInstanceData;
-};
-
const useSubscribeRootInstance = () => {
const [, setValue] = useRootInstance();
useSubscribe<"loadRootInstance">("loadRootInstance", setValue);
};
+const useSubscribeSelectedInstanceData = () => {
+ const [, setValue] = useSelectedInstanceData();
+ useSubscribe<"selectInstance", SelectedInstanceData>(
+ "selectInstance",
+ setValue
+ );
+};
+
type SidePanelProps = {
children: JSX.Element;
isPreviewMode: boolean;
@@ -52,6 +55,7 @@ const SidePanel = ({
if (isPreviewMode === true) return null;
return (
}) => (
+
+ {children}
+
+);
+
type ChromeWrapperProps = {
children: Array;
isPreviewMode: boolean;
@@ -81,7 +97,7 @@ const ChromeWrapper = ({ children, isPreviewMode }: ChromeWrapperProps) => {
gridTemplateRows: "auto 1fr",
gridTemplateAreas: `
"header header"
- "sidebar canvas"
+ "sidebar main"
`,
}
: {
@@ -89,7 +105,7 @@ const ChromeWrapper = ({ children, isPreviewMode }: ChromeWrapperProps) => {
gridTemplateRows: "auto 1fr",
gridTemplateAreas: `
"header header header"
- "sidebar canvas inspector"
+ "sidebar main inspector"
`,
};
return (
@@ -112,12 +128,13 @@ type DesignerProps = {
};
export const Designer = ({ config, project }: DesignerProps) => {
- const [isDragging, setIsDragging] = useState();
- const selectedInstanceData = useSelectedInstanceData();
- const [publish, iframeRef] = usePublish();
- const [isPreviewMode] = useIsPreviewMode();
useSync({ config, project });
useSubscribeRootInstance();
+ useSubscribeSelectedInstanceData();
+ const [isDragging, setIsDragging] = useState();
+ const [publish, iframeRef] = usePublish();
+ const [isPreviewMode] = useIsPreviewMode();
+
return (
@@ -126,7 +143,6 @@ export const Designer = ({ config, project }: DesignerProps) => {
iframeRef={iframeRef}
onDragChange={setIsDragging}
publish={publish}
- selectedInstanceData={selectedInstanceData}
/>
{
project={project}
publish={publish}
/>
-
+
+
+
+
-
+
diff --git a/app/designer/inspector/sidebar-right.tsx b/app/designer/inspector/sidebar-right.tsx
index 11eea57420..cda0f3eaf2 100644
--- a/app/designer/inspector/sidebar-right.tsx
+++ b/app/designer/inspector/sidebar-right.tsx
@@ -10,12 +10,11 @@ import {
import { StylePanel } from "~/designer/style-panel";
import { SettingsPanel } from "~/designer/settings-panel";
import type { Publish } from "~/designer/canvas-iframe";
-import type { SelectedInstanceData } from "~/shared/component";
import { BrushIcon, GearIcon } from "~/shared/icons";
+import { useSelectedInstanceData } from "../nano-values";
type SidebarRightProps = {
publish: Publish;
- selectedInstanceData?: SelectedInstanceData;
};
const contentStyle = {
@@ -23,10 +22,9 @@ const contentStyle = {
overflow: "auto",
};
-export const SidebarRight = ({
- publish,
- selectedInstanceData,
-}: SidebarRightProps) => {
+export const SidebarRight = ({ publish }: SidebarRightProps) => {
+ const [selectedInstanceData] = useSelectedInstanceData();
+
if (selectedInstanceData === undefined) {
return (
@@ -36,6 +34,7 @@ export const SidebarRight = ({
);
}
+
return (
diff --git a/app/designer/instance/use-selected-instance-path.ts b/app/designer/instance/use-selected-instance-path.ts
new file mode 100644
index 0000000000..ff5ef586f1
--- /dev/null
+++ b/app/designer/instance/use-selected-instance-path.ts
@@ -0,0 +1,16 @@
+import { Instance } from "@webstudio-is/sdk";
+import { useMemo } from "react";
+import { getInstancePath } from "~/shared/tree-utils";
+import { useRootInstance, useSelectedInstanceData } from "../nano-values";
+
+export const useSelectedInstancePath = (): Array => {
+ const [rootInstance] = useRootInstance();
+ const [selectedInstanceData] = useSelectedInstanceData();
+ return useMemo(
+ () =>
+ selectedInstanceData !== undefined && rootInstance !== undefined
+ ? getInstancePath(rootInstance, selectedInstanceData.id)
+ : [],
+ [selectedInstanceData, rootInstance]
+ );
+};
diff --git a/app/designer/nano-values/index.ts b/app/designer/nano-values/index.ts
index 0259475759..060e016823 100644
--- a/app/designer/nano-values/index.ts
+++ b/app/designer/nano-values/index.ts
@@ -1,9 +1,16 @@
import { createValueContainer, useValue } from "react-nano-state";
import { type Instance } from "@webstudio-is/sdk";
+import { SelectedInstanceData } from "~/shared/component";
const rootInstanceContainer = createValueContainer();
export const useRootInstance = () => useValue(rootInstanceContainer);
+const selectedInstanceDataContainer = createValueContainer<
+ SelectedInstanceData | undefined
+>();
+export const useSelectedInstanceData = () =>
+ useValue(selectedInstanceDataContainer);
+
const isPreviewModeContainer = createValueContainer(false);
export const useIsPreviewMode = () => useValue(isPreviewModeContainer);
diff --git a/app/designer/sidebar-left/panels/tree.tsx b/app/designer/sidebar-left/panels/tree.tsx
index 7b3b6f3351..6468db4035 100644
--- a/app/designer/sidebar-left/panels/tree.tsx
+++ b/app/designer/sidebar-left/panels/tree.tsx
@@ -11,8 +11,9 @@ import {
} from "~/shared/design-system";
import { TriangleRightIcon, TriangleDownIcon } from "~/shared/icons";
import { primitives, type SelectedInstanceData } from "~/shared/component";
-import { getInstancePath } from "~/shared/tree-utils";
import { type Publish } from "~/designer/canvas-iframe";
+import { useSelectedInstancePath } from "~/designer/instance/use-selected-instance-path";
+import { useRootInstance } from "~/designer/nano-values";
const openKeyframes = keyframes({
from: { height: 0 },
@@ -37,7 +38,7 @@ const CollapsibleContent = styled(Collapsible.Content, {
type TreeProps = {
instance: Instance;
selectedInstanceId?: Instance["id"];
- selectedInstancePath: Array;
+ selectedInstancePath: Array;
level?: number;
onSelect: (instance: Instance) => void;
};
@@ -49,9 +50,7 @@ const Tree = ({
onSelect,
selectedInstancePath,
}: TreeProps) => {
- const [isOpen, setIsOpen] = useState(
- selectedInstancePath.includes(instance.id)
- );
+ const [isOpen, setIsOpen] = useState(selectedInstancePath.includes(instance));
// Text nodes have only one child which is a string.
const showChildren =
@@ -122,25 +121,16 @@ const Tree = ({
};
type TabContentProps = {
- rootInstance?: Instance;
publish: Publish;
selectedInstanceData?: SelectedInstanceData;
};
export const TabContent = ({
- rootInstance,
publish,
selectedInstanceData,
}: TabContentProps) => {
- const selectedInstancePath = useMemo(
- () =>
- selectedInstanceData !== undefined && rootInstance !== undefined
- ? getInstancePath(rootInstance, selectedInstanceData.id).map(
- ({ id }) => id
- )
- : [],
- [selectedInstanceData, rootInstance]
- );
+ const [rootInstance] = useRootInstance();
+ const selectedInstancePath = useSelectedInstancePath();
if (rootInstance === undefined) return null;
diff --git a/app/designer/sidebar-left/sidebar-left.tsx b/app/designer/sidebar-left/sidebar-left.tsx
index d9c734c7b0..2c17e90b2c 100644
--- a/app/designer/sidebar-left/sidebar-left.tsx
+++ b/app/designer/sidebar-left/sidebar-left.tsx
@@ -7,8 +7,7 @@ import {
SidebarTabsTrigger,
} from "~/shared/design-system";
import { useSubscribe, type Publish } from "~/designer/canvas-iframe";
-import type { SelectedInstanceData } from "~/shared/component";
-import { useRootInstance } from "../nano-values";
+import { useSelectedInstanceData } from "../nano-values";
import * as panels from "./panels";
import type { TabName } from "./types";
@@ -27,18 +26,16 @@ type SidebarLeftProps = {
onDragChange: (isDragging: boolean) => void;
iframeRef: MutableRefObject;
publish: Publish;
- selectedInstanceData?: SelectedInstanceData;
};
export const SidebarLeft = ({
onDragChange,
iframeRef,
publish,
- selectedInstanceData,
}: SidebarLeftProps) => {
+ const [selectedInstanceData] = useSelectedInstanceData();
const [activeTab, setActiveTab] = useState("none");
const [isDragging, setIsDragging] = useState(false);
- const [rootInstance] = useRootInstance();
const { TabContent } = activeTab === "none" ? none : panels[activeTab];
useSubscribe<"clickCanvas">("clickCanvas", () => {
@@ -84,7 +81,6 @@ export const SidebarLeft = ({
}}
>
{
return (