breadcrumbs #4

This commit is contained in:
Oleg Isonen committed 2022-03-23 13:02:25 +00:00
1 parent 8c5252173b
commit de1bf2fcaa
11 files changed
+155 -65

No files matched your search

+58
View File
@@ -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 (
<Flex align="center">
<Button ghost css={{ color: "$loContrast", px: 0 }} onClick={onClick}>
{component}
</Button>{" "}
<ChevronRightIcon />
</Flex>
);
};
const EmptyState = () => (
<Text variant="loContrast" size="1">
No instance selected
</Text>
);
type BreadcrumbsProps = {
publish: Publish;
};
export const Breadcrumbs = ({ publish }: BreadcrumbsProps) => {
const selectedInstancePath = useSelectedInstancePath();
return (
<Flex
as="footer"
align="center"
css={{ height: "$5", background: "$hiContrast", padding: "$1" }}
>
{selectedInstancePath.length === 0 ? (
<EmptyState />
) : (
selectedInstancePath.map((instance) => (
<Breadcrumb
key={instance.id}
component={instance.component}
onClick={() => {
publish<"focusElement", Instance["id"]>({
type: "focusElement",
payload: instance.id,
});
}}
/>
))
)}
</Flex>
);
};
+1
View File
@@ -0,0 +1 @@
export * from "./Breadcrumbs";
+8 -6
View File
@@ -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<HTMLIFrameElement, CanvasIframeProps>(
({ pointerEvents = "all", ...rest }, ref) => {
({ pointerEvents = "all", css, ...rest }, ref) => {
return (
<iframe {...rest} ref={ref} className={iframeStyle({ pointerEvents })} />
<iframe
{...rest}
ref={ref}
className={iframeStyle({ pointerEvents, css })}
/>
);
}
);
+48 -31
View File
@@ -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<SelectedInstanceData>();
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 (
<Box
as="aside"
css={{
gridArea,
display: "flex",
@@ -69,6 +73,18 @@ const SidePanel = ({
);
};
const Main = ({ children }: { children: Array<JSX.Element> }) => (
<Flex
as="main"
direction="column"
css={{
gridArea: "main",
}}
>
{children}
</Flex>
);
type ChromeWrapperProps = {
children: Array<JSX.Element>;
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<boolean>();
const selectedInstanceData = useSelectedInstanceData();
const [publish, iframeRef] = usePublish();
const [isPreviewMode] = useIsPreviewMode();
useSync({ config, project });
useSubscribeRootInstance();
useSubscribeSelectedInstanceData();
const [isDragging, setIsDragging] = useState<boolean>();
const [publish, iframeRef] = usePublish();
const [isPreviewMode] = useIsPreviewMode();
return (
<DndProvider backend={HTML5Backend}>
<ChromeWrapper isPreviewMode={isPreviewMode}>
@@ -126,7 +143,6 @@ export const Designer = ({ config, project }: DesignerProps) => {
iframeRef={iframeRef}
onDragChange={setIsDragging}
publish={publish}
selectedInstanceData={selectedInstanceData}
/>
</SidePanel>
<Topbar
@@ -135,21 +151,22 @@ export const Designer = ({ config, project }: DesignerProps) => {
project={project}
publish={publish}
/>
<CanvasIframe
ref={iframeRef}
src={`${config.canvasPath}/${project.id}`}
pointerEvents={isDragging ? "none" : "all"}
title={project.title}
/>
<Main>
<CanvasIframe
ref={iframeRef}
src={`${config.canvasPath}/${project.id}`}
pointerEvents={isDragging ? "none" : "all"}
title={project.title}
css={{ height: "100%" }}
/>
<Breadcrumbs publish={publish} />
</Main>
<SidePanel
gridArea="inspector"
isPreviewMode={isPreviewMode}
css={{ overflow: "hidden" }}
>
<SidebarRight
publish={publish}
selectedInstanceData={selectedInstanceData}
/>
<SidebarRight publish={publish} />
</SidePanel>
</ChromeWrapper>
</DndProvider>
+5 -6
View File
@@ -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 (
<Box css={{ p: "$2" }}>
@@ -36,6 +34,7 @@ export const SidebarRight = ({
</Box>
);
}
return (
<Tabs defaultValue="style" css={{ width: "100%", gap: "$2" }}>
<TabsList>
@@ -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<Instance> => {
const [rootInstance] = useRootInstance();
const [selectedInstanceData] = useSelectedInstanceData();
return useMemo(
() =>
selectedInstanceData !== undefined && rootInstance !== undefined
? getInstancePath(rootInstance, selectedInstanceData.id)
: [],
[selectedInstanceData, rootInstance]
);
};
+7
View File
@@ -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<Instance | undefined>();
export const useRootInstance = () => useValue(rootInstanceContainer);
const selectedInstanceDataContainer = createValueContainer<
SelectedInstanceData | undefined
>();
export const useSelectedInstanceData = () =>
useValue(selectedInstanceDataContainer);
const isPreviewModeContainer = createValueContainer<boolean>(false);
export const useIsPreviewMode = () => useValue(isPreviewModeContainer);
+6 -16
View File
@@ -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<Instance["id"]>;
selectedInstancePath: Array<Instance>;
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;
+2 -6
View File
@@ -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<HTMLIFrameElement | null>;
publish: Publish;
selectedInstanceData?: SelectedInstanceData;
};
export const SidebarLeft = ({
onDragChange,
iframeRef,
publish,
selectedInstanceData,
}: SidebarLeftProps) => {
const [selectedInstanceData] = useSelectedInstanceData();
const [activeTab, setActiveTab] = useState<TabName>("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 = ({
}}
>
<TabContent
rootInstance={rootInstance}
selectedInstanceData={selectedInstanceData}
publish={publish}
iframeRef={iframeRef}
+1
View File
@@ -18,6 +18,7 @@ type TopbarProps = {
export const Topbar = ({ config, css, project, publish }: TopbarProps) => {
return (
<Flex
as="header"
align="center"
justify="between"
css={{
@@ -98,6 +98,9 @@ export const Text = styled("div", {
contrast: {
color: "$hiContrast",
},
loContrast: {
color: "$loContrast",
},
},
gradient: {
true: {