Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions packages/editor/src/components/editor/editor-layout-v2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -89,7 +94,7 @@ function LeftColumn({
}
setActivePanel(id)
},
[isCollapsed, width, activePanel, setIsCollapsed, setWidth, setActivePanel],
[tabs, isCollapsed, width, activePanel, setIsCollapsed, setWidth, setActivePanel],
)

useEffect(() => {
Expand Down Expand Up @@ -126,7 +131,7 @@ function LeftColumn({
onIconClick={handleRailClick}
tabs={tabs}
/>
{!isCollapsed && (
{!isCollapsed && !tabs.find((t) => t.id === activePanel)?.noPanel && (
<div
className="relative flex h-full flex-col"
style={{
Expand Down Expand Up @@ -239,7 +244,7 @@ export function EditorLayoutV2({
overlays={overlays}
renderTabContent={renderTabContent}
sidebarOverlay={sidebarOverlay}
sidebarTabs={sidebarTabs}
sidebarTabs={sidebarTabs.filter((t) => !t.noPanel)}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mobile clears noPanel selection

Medium Severity

Filtering out noPanel tabs before EditorLayoutMobile makes a persisted activeSidebarPanel invalid, so the mobile validity effect rewrites it to the first remaining tab. Crossing the mobile breakpoint drops the host’s stage-driving selection, and renderTabContent can still mount that tab’s component for a frame before the reset.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 0f3857f. Configure here.

viewerContent={viewerContent}
viewerToolbarLeft={viewerToolbarLeft}
viewerToolbarRight={viewerToolbarRight}
Expand Down
3 changes: 2 additions & 1 deletion packages/editor/src/components/editor/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
8 changes: 7 additions & 1 deletion packages/editor/src/components/ui/sidebar/tab-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 (
<Tooltip key={tab.id}>
<TooltipTrigger asChild>
Expand Down
Loading