diff --git a/apps/desktop/src/app/chat/sidebar/index.tsx b/apps/desktop/src/app/chat/sidebar/index.tsx index 23f97e4c1ef..9477cd568bf 100644 --- a/apps/desktop/src/app/chat/sidebar/index.tsx +++ b/apps/desktop/src/app/chat/sidebar/index.tsx @@ -248,8 +248,7 @@ export function ChatSidebar({ const { t } = useI18n() const s = t.sidebar const sidebarOpen = useStore($sidebarOpen) - // When collapsed but hover-revealed (floated over content), render the full - // sidebar — search field, pinned + recents — not just the nav rail. + // Collapsed-but-hover-revealed → render the full sidebar, not just the nav rail. const sidebarRevealed = useStore($sidebarRevealed) const contentVisible = sidebarOpen || sidebarRevealed const panesFlipped = useStore($panesFlipped) @@ -586,10 +585,7 @@ export function ChatSidebar({ sidebarOpen ? 'border-(--sidebar-edge-border) bg-(--ui-sidebar-surface-background) opacity-100' : 'pointer-events-none border-transparent bg-transparent opacity-0', - // Hover-reveal overlay: when collapsed, the PaneShell floats this - // sidebar over the content and marks the wrapper `data-pane-hover-reveal`. - // Force it fully visible + interactive while revealed, regardless of the - // collapsed (sidebarOpen=false) styling above. + // While floated by PaneShell's hover-reveal, force visible + interactive. 'in-data-[pane-hover-reveal=open]:pointer-events-auto in-data-[pane-hover-reveal=open]:border-(--sidebar-edge-border) in-data-[pane-hover-reveal=open]:bg-(--ui-sidebar-surface-background) in-data-[pane-hover-reveal=open]:opacity-100' )} collapsible="none" diff --git a/apps/desktop/src/components/pane-shell/pane-shell.tsx b/apps/desktop/src/components/pane-shell/pane-shell.tsx index f51626509fe..87d2de0f2e7 100644 --- a/apps/desktop/src/components/pane-shell/pane-shell.tsx +++ b/apps/desktop/src/components/pane-shell/pane-shell.tsx @@ -32,12 +32,7 @@ export interface PaneProps { defaultOpen?: boolean /** Forces the pane closed (track→0, aria-hidden) without writing to the store — for transient route gates. */ disabled?: boolean - /** - * When the pane is collapsed, reveal its contents as a fixed overlay on hover - * (or keyboard focus) instead of leaving it fully hidden. The overlay floats - * over the main content — it does not reserve grid space, so the collapsed - * track stays at 0px. - */ + /** When collapsed, float the contents over the main column on hover/focus instead of hiding them (track stays 0px). */ hoverReveal?: boolean /** Called with the reveal state whenever a collapsed hoverReveal pane floats in/out. */ onHoverRevealChange?: (revealed: boolean) => void @@ -234,28 +229,23 @@ export function Pane({ const side = slot?.side ?? 'left' // Collapsed + hoverReveal: float the pane contents over the main column on - // hover/focus instead of leaving them fully hidden. Honors any persisted - // resize width so the overlay matches the pane's expanded size. + // hover/focus instead of hiding them. Honors any persisted resize width. const overlayActive = !open && hoverReveal && !disabled + const override = resizable ? paneStates[id]?.widthOverride : undefined + const overlayWidth = override !== undefined ? `${override}px` : widthToCss(width, DEFAULT_WIDTH) + const revealed = overlayActive && hoverRevealed - const overlayWidth = - resizable && paneStates[id]?.widthOverride !== undefined - ? `${paneStates[id]?.widthOverride}px` - : widthToCss(width, DEFAULT_WIDTH) - - // Collapse the reveal whenever the pane reopens or gets disabled so it never - // sticks "open" underneath the now-expanded track. + // Reset stale reveal state when the track reopens/disables, and surface the + // effective state so consumers can render full content while floated. useEffect(() => { - if (!overlayActive && hoverRevealed) { + if (!overlayActive) { setHoverRevealed(false) } - }, [overlayActive, hoverRevealed]) + }, [overlayActive]) - // Surface the effective reveal state to consumers (e.g. so the sidebar can - // render its full content while floated, not just while the track is open). useEffect(() => { - onHoverRevealChange?.(overlayActive && hoverRevealed) - }, [onHoverRevealChange, overlayActive, hoverRevealed]) + onHoverRevealChange?.(revealed) + }, [onHoverRevealChange, revealed]) const startResize = useCallback( (event: ReactPointerEvent) => { @@ -312,54 +302,44 @@ export function Pane({ return null } - // Collapsed hover-reveal track: keep the grid cell at 0px (no reserved space) - // but don't clip — the trigger strip and the floating overlay both escape the - // zero-width box via absolute positioning, so the panel renders over the main - // content rather than pushing it. + // Collapsed hover-reveal track: grid cell stays 0px (no reserved space) but + // unclipped — the hot-zone and floating panel escape it via absolute + // positioning, rendering over the main content instead of pushing it. if (overlayActive) { - const revealEdge = slot.side === 'left' ? { left: 0 } : { right: 0 } + const left = slot.side === 'left' return (
- {/* Edge hot-zone: hovering it (or moving onto the revealed overlay) - keeps the panel open. Sits above main content but is invisible. */} + {/* Invisible edge hot-zone — hovering/focusing it floats the panel in. */}