From 0f3857ffa51414bcd52250c94be7f2a19043ffde Mon Sep 17 00:00:00 2001 From: Wassim SAMAD Date: Tue, 11 Aug 2026 12:05:47 +0200 Subject: [PATCH] feat(editor): support noPanel sidebar tabs for stage-driving rail entries A noPanel tab highlights in the rail and activates without opening the sidebar panel column, for host surfaces that swap the stage instead (e.g. the hosted item builder). Mobile layout skips noPanel tabs. Co-Authored-By: Claude Fable 5 --- .../editor/src/components/editor/editor-layout-v2.tsx | 11 ++++++++--- packages/editor/src/components/editor/index.tsx | 3 ++- packages/editor/src/components/ui/sidebar/tab-bar.tsx | 8 +++++++- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/packages/editor/src/components/editor/editor-layout-v2.tsx b/packages/editor/src/components/editor/editor-layout-v2.tsx index a30602a231..3adfdd0072 100644 --- a/packages/editor/src/components/editor/editor-layout-v2.tsx +++ b/packages/editor/src/components/editor/editor-layout-v2.tsx @@ -77,6 +77,11 @@ function LeftColumn({ // up to the minimum so the panel always returns to a usable size. const handleRailClick = useCallback( (id: string) => { + // noPanel tabs drive the stage, not the panel — leave collapse state alone. + if (tabs.find((t) => t.id === id)?.noPanel) { + setActivePanel(id) + return + } if (isCollapsed) { setIsCollapsed(false) if (width < SIDEBAR_MIN_WIDTH) setWidth(SIDEBAR_MIN_WIDTH) @@ -89,7 +94,7 @@ function LeftColumn({ } setActivePanel(id) }, - [isCollapsed, width, activePanel, setIsCollapsed, setWidth, setActivePanel], + [tabs, isCollapsed, width, activePanel, setIsCollapsed, setWidth, setActivePanel], ) useEffect(() => { @@ -126,7 +131,7 @@ function LeftColumn({ onIconClick={handleRailClick} tabs={tabs} /> - {!isCollapsed && ( + {!isCollapsed && !tabs.find((t) => t.id === activePanel)?.noPanel && (
!t.noPanel)} viewerContent={viewerContent} viewerToolbarLeft={viewerToolbarLeft} viewerToolbarRight={viewerToolbarRight} diff --git a/packages/editor/src/components/editor/index.tsx b/packages/editor/src/components/editor/index.tsx index 59f60bed43..1650377fd0 100644 --- a/packages/editor/src/components/editor/index.tsx +++ b/packages/editor/src/components/editor/index.tsx @@ -1393,12 +1393,13 @@ export default function Editor({ } const tabBarTabs = [ - ...(sidebarTabs?.map(({ id, label, mobileDefaultSnap, mobileIcon, icon }) => ({ + ...(sidebarTabs?.map(({ id, label, mobileDefaultSnap, mobileIcon, icon, noPanel }) => ({ id, label, mobileDefaultSnap, mobileIcon, icon, + noPanel, })) ?? []), // Host panels appear after the explicit tabs in the rail. The icon // doubles as the mobile icon; a half-height sheet is a sensible default. diff --git a/packages/editor/src/components/ui/sidebar/tab-bar.tsx b/packages/editor/src/components/ui/sidebar/tab-bar.tsx index 42bd61608e..8d86a1234b 100644 --- a/packages/editor/src/components/ui/sidebar/tab-bar.tsx +++ b/packages/editor/src/components/ui/sidebar/tab-bar.tsx @@ -13,6 +13,12 @@ export type SidebarTab = { mobileIcon?: ReactNode /** Desktop icon shown in the vertical rail (v2 layout). */ icon?: ReactNode + /** + * Rail entry that drives the stage instead of opening a sidebar panel: + * activating it hides the panel column (preserving its collapse state) and + * keeps the icon highlighted regardless of collapse. + */ + noPanel?: boolean } interface TabBarProps { @@ -75,7 +81,7 @@ export function IconRail({ tabs, activeTab, collapsed, onIconClick }: IconRailPr const pluginTabs = tabs.filter((tab) => pluginPanelIds.has(tab.id) || tab.id === 'plugins') const renderTab = (tab: SidebarTab) => { - const showActive = activeTab === tab.id && !collapsed + const showActive = activeTab === tab.id && (!collapsed || tab.noPanel === true) return (