diff --git a/apps/web/client/src/app/project/[id]/_components/canvas/index.tsx b/apps/web/client/src/app/project/[id]/_components/canvas/index.tsx index fa90d54f0b..23539ec1b0 100644 --- a/apps/web/client/src/app/project/[id]/_components/canvas/index.tsx +++ b/apps/web/client/src/app/project/[id]/_components/canvas/index.tsx @@ -2,7 +2,18 @@ import { useEditorEngine } from '@/components/store/editor'; import { EditorAttributes } from '@onlook/constants'; -import { EditorMode } from '@onlook/models'; +import { EditorMode, LeftPanelTabValue } from '@onlook/models'; +import { + ContextMenu, + ContextMenuContent, + ContextMenuItem, + ContextMenuSeparator, + ContextMenuSub, + ContextMenuSubContent, + ContextMenuSubTrigger, + ContextMenuTrigger, +} from '@onlook/ui/context-menu'; +import { Icons } from '@onlook/ui/icons'; import { throttle } from 'lodash'; import { observer } from 'mobx-react-lite'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; @@ -23,7 +34,11 @@ const MAX_Y = 10000; const MIN_X = -5000; const MIN_Y = -5000; -export const Canvas = observer(() => { +interface CanvasProps { + onSidebarClick?: (tab: LeftPanelTabValue) => void; +} + +export const Canvas = observer(({ onSidebarClick }: CanvasProps) => { const editorEngine = useEditorEngine(); const containerRef = useRef(null); const scale = editorEngine.canvas.scale; @@ -272,41 +287,127 @@ export const Canvas = observer(() => { return ( -
{ - // Only terminate drag if no mouse button is pressed - // Note: The global mouseup listener will handle the actual cleanup - // This is just an additional safety check for when mouse leaves without buttons pressed - if (e.buttons === 0 && isDragSelecting) { - setIsDragSelecting(false); - setFramesInSelection(new Set()); - editorEngine.state.isDragSelecting = false; - } - }} - > -
- -
- - - - - clampPosition(position, scale) - } - /> -
+ + +
{ + // Only terminate drag if no mouse button is pressed + // Note: The global mouseup listener will handle the actual cleanup + // This is just an additional safety check for when mouse leaves without buttons pressed + if (e.buttons === 0 && isDragSelecting) { + setIsDragSelecting(false); + setFramesInSelection(new Set()); + editorEngine.state.isDragSelecting = false; + } + }} + > +
+ +
+ + + + + clampPosition(position, scale) + } + /> +
+
+ + { + // Add Element action + }} + > + + Add Element + + { + // Add Component action + }} + > + + Add Component + + + + + + Panels + + + { + onSidebarClick?.(LeftPanelTabValue.LAYERS); + }} + > + Layers + + { + onSidebarClick?.(LeftPanelTabValue.BRAND); + }} + > + Brand + + { + onSidebarClick?.(LeftPanelTabValue.PAGES); + }} + > + Pages + + { + onSidebarClick?.(LeftPanelTabValue.IMAGES); + }} + > + Images + + + + + { + onSidebarClick?.(LeftPanelTabValue.BRANCHES); + }} + > + Branches + + + { + editorEngine.copy.copy(); + }} + > + + Copy + ⌘ C + + { + editorEngine.copy.paste(); + }} + > + + Paste + ⌘ V + + +
); }); diff --git a/apps/web/client/src/app/project/[id]/_components/left-panel/design-panel/index.tsx b/apps/web/client/src/app/project/[id]/_components/left-panel/design-panel/index.tsx index 2ca8c8973c..f43b63d196 100644 --- a/apps/web/client/src/app/project/[id]/_components/left-panel/design-panel/index.tsx +++ b/apps/web/client/src/app/project/[id]/_components/left-panel/design-panel/index.tsx @@ -1,6 +1,7 @@ import { useEditorEngine } from '@/components/store/editor'; import { transKeys } from '@/i18n/keys'; import { LeftPanelTabValue } from '@onlook/models'; +import { Button } from '@onlook/ui/button'; import { Icons } from '@onlook/ui/icons'; import { cn } from '@onlook/ui/utils'; import { observer } from 'mobx-react-lite'; @@ -47,11 +48,16 @@ const tabs: { }, ]; -export const DesignPanel = observer(() => { +interface DesignPanelProps { + onClose?: () => void; + activeSection?: string | null; +} + +export const DesignPanel = observer(({ onClose, activeSection }: DesignPanelProps) => { const editorEngine = useEditorEngine(); const t = useTranslations(); const isLocked = editorEngine.state.leftPanelLocked; - const selectedTab = editorEngine.state.leftPanelTab; + const selectedTab = (activeSection as LeftPanelTabValue) || editorEngine.state.leftPanelTab; const handleMouseEnter = (tab: LeftPanelTabValue) => { if (isLocked) { @@ -102,8 +108,8 @@ export const DesignPanel = observer(() => { className="flex h-full overflow-auto" onMouseLeave={handleMouseLeave} > - {/* Left sidebar with tabs */} -
+ {/* Left sidebar with tabs - HIDDEN */} + {/*
{tabs.map((tab) => (
-
+ */} {/* Content panel */} - {editorEngine.state.leftPanelTab && ( + {selectedTab && ( <>
-
- {selectedTab === LeftPanelTabValue.LAYERS && } - {selectedTab === LeftPanelTabValue.BRAND && } - {selectedTab === LeftPanelTabValue.PAGES && } - {selectedTab === LeftPanelTabValue.IMAGES && } - {selectedTab === LeftPanelTabValue.BRANCHES && } +
+ {/* Panel header with close button */} +
+
+ {selectedTab === LeftPanelTabValue.LAYERS && } + {selectedTab === LeftPanelTabValue.BRAND && } + {selectedTab === LeftPanelTabValue.PAGES && } + {selectedTab === LeftPanelTabValue.IMAGES && } + {selectedTab === LeftPanelTabValue.BRANCHES && } + + {selectedTab === LeftPanelTabValue.LAYERS && t(transKeys.editor.panels.layers.tabs.layers)} + {selectedTab === LeftPanelTabValue.BRAND && t(transKeys.editor.panels.layers.tabs.brand)} + {selectedTab === LeftPanelTabValue.PAGES && t(transKeys.editor.panels.layers.tabs.pages)} + {selectedTab === LeftPanelTabValue.IMAGES && t(transKeys.editor.panels.layers.tabs.images)} + {selectedTab === LeftPanelTabValue.BRANCHES && t(transKeys.editor.panels.layers.tabs.branches)} + +
+ {onClose && ( + + )} +
+
+ {selectedTab === LeftPanelTabValue.LAYERS && } + {selectedTab === LeftPanelTabValue.BRAND && } + {selectedTab === LeftPanelTabValue.PAGES && } + {selectedTab === LeftPanelTabValue.IMAGES && } + {selectedTab === LeftPanelTabValue.BRANCHES && } +
diff --git a/apps/web/client/src/app/project/[id]/_components/left-panel/index.tsx b/apps/web/client/src/app/project/[id]/_components/left-panel/index.tsx index dc8fc0e5b6..98435074ac 100644 --- a/apps/web/client/src/app/project/[id]/_components/left-panel/index.tsx +++ b/apps/web/client/src/app/project/[id]/_components/left-panel/index.tsx @@ -5,11 +5,16 @@ import { observer } from "mobx-react-lite"; import { CodePanel } from "./code-panel"; import { DesignPanel } from "./design-panel"; -export const LeftPanel = observer(() => { +interface LeftPanelProps { + onClose?: () => void; + activeSection?: string | null; +} + +export const LeftPanel = observer(({ onClose, activeSection }: LeftPanelProps) => { const editorEngine = useEditorEngine(); return <>
- +
diff --git a/apps/web/client/src/app/project/[id]/_components/main.tsx b/apps/web/client/src/app/project/[id]/_components/main.tsx index 4109496d6f..f00d7b5650 100644 --- a/apps/web/client/src/app/project/[id]/_components/main.tsx +++ b/apps/web/client/src/app/project/[id]/_components/main.tsx @@ -4,14 +4,14 @@ import { useEditorEngine } from '@/components/store/editor'; import { SubscriptionModal } from '@/components/ui/pricing-modal'; import { SettingsModalWithProjects } from '@/components/ui/settings-modal/with-project'; import { EditorAttributes } from '@onlook/constants'; -import { EditorMode } from '@onlook/models'; +import { EditorMode, LeftPanelTabValue } from '@onlook/models'; import { Button } from '@onlook/ui/button'; import { Icons } from '@onlook/ui/icons'; import { TooltipProvider } from '@onlook/ui/tooltip'; import { cn } from '@onlook/ui/utils'; import { observer } from 'mobx-react-lite'; import { useRouter } from 'next/navigation'; -import { useEffect, useRef } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { usePanelMeasurements } from '../_hooks/use-panel-measure'; import { useStartProject } from '../_hooks/use-start-project'; import { BottomBar } from './bottom-bar'; @@ -31,6 +31,19 @@ export const Main = observer(() => { leftPanelRef, rightPanelRef, ); + const [activeSection, setActiveSection] = useState(null); + + const handleSidebarClick = (tab: LeftPanelTabValue) => { + editorEngine.state.leftPanelTab = tab; + editorEngine.state.leftPanelLocked = true; + setActiveSection(tab); + }; + + const handleClosePanel = () => { + editorEngine.state.leftPanelTab = null; + editorEngine.state.leftPanelLocked = false; + setActiveSection(null); + }; useEffect(() => { function handleGlobalWheel(event: WheelEvent) { @@ -82,7 +95,7 @@ export const Main = observer(() => { return (
- +
@@ -91,9 +104,9 @@ export const Main = observer(() => { {/* Left Panel */}
- +
{/* EditorBar anchored between panels */}