Add hidden-on-hover sidebar mode

Domain ships HIDDEN_SIDEBAR_WIDTH_PX = 8 alongside the existing
collapsed/default tiers. ElyShell tracks sidebar_hover_expanded with
expand_hidden_sidebar / collapse_hidden_sidebar helpers; switching
back to a non-hidden width via the layout cards or core API resets
the flag automatically so the sidebar can never be both hidden and
expanded after a mode change.

Renderer:
- render_sidebar takes a sidebar_hidden flag and routes to a thin
  8 px clickable rail (hover bg + click expands) when the active
  space's width is at HIDDEN.
- While the rail is expanded, render_browser overlays a transparent
  backdrop + the full default-width sidebar absolutely positioned in
  the shell inset, so the main pane content never reflows.
- collapsed_sidebar_active still drives the COLLAPSED-tier compact
  sidebar; the hidden tier is opted out of that path.

Appearance form:
- Layout cards section gains the design's third "Hidden on hover"
  card with a 6 px sliver preview that mutates the active space to
  HIDDEN_SIDEBAR_WIDTH_PX. LayoutMode now derives id/width/preview
  from a small enum so adding a fourth mode would be one match arm.

The hover-expanded state never persists into the domain; once the
user switches modes or clicks the backdrop, it collapses cleanly.
This commit is contained in:
2026-05-09 20:01:31 -04:00
parent c98ca18613
commit 9a2f9f2d74
6 changed files with 145 additions and 28 deletions
+64 -5
View File
@@ -1,9 +1,9 @@
use ely_browser_core::BrowserSnapshot;
use ely_design_system::{colors, spacing};
use ely_domain::BrowserTab;
use ely_domain::{BrowserTab, DEFAULT_SIDEBAR_WIDTH_PX, HIDDEN_SIDEBAR_WIDTH_PX};
use gpui::{
AnyElement, Context, InteractiveElement, IntoElement, ParentElement, Render, Styled, Window,
div, px, rgb, rgba,
AnyElement, Context, InteractiveElement, IntoElement, ParentElement, Render, SharedString,
StatefulInteractiveElement, Styled, Window, div, prelude::FluentBuilder, px, rgb, rgba,
};
use super::chrome::{
@@ -36,7 +36,9 @@ impl ElyShell {
Ok(sidebar_width) => sidebar_width,
Err(message) => return render_error(message),
};
let sidebar_collapsed = collapsed_sidebar_active(sidebar_width);
let sidebar_hidden = sidebar_width <= f32::from(HIDDEN_SIDEBAR_WIDTH_PX);
let sidebar_collapsed = collapsed_sidebar_active(sidebar_width) && !sidebar_hidden;
let hover_expanded = sidebar_hidden && self.sidebar_hover_expanded;
div()
.size_full()
@@ -72,13 +74,52 @@ impl ElyShell {
.p(px(spacing::SHELL_INSET))
.gap(px(spacing::SIDEBAR_MAIN_GAP))
.flex()
.child(self.render_sidebar(&snapshot, sidebar_width, sidebar_collapsed, cx))
.child(self.render_sidebar(
&snapshot,
sidebar_width,
sidebar_collapsed,
sidebar_hidden,
cx,
))
.child(self.render_main_pane(&snapshot, &active_tab, sidebar_collapsed, cx)),
)
.when(hover_expanded, |el| {
el.child(self.render_hidden_sidebar_overlay(&snapshot, cx))
})
.children(render_command_overlay(&snapshot, cx))
.into_any_element()
}
fn render_hidden_sidebar_overlay(
&mut self,
snapshot: &BrowserSnapshot,
cx: &mut Context<Self>,
) -> AnyElement {
div()
.absolute()
.inset_0()
.child(
div()
.id(SharedString::from("hidden-sidebar-backdrop"))
.absolute()
.inset_0()
.on_click(cx.listener(|shell, _, _, cx| shell.collapse_hidden_sidebar(cx))),
)
.child(
div()
.absolute()
.top(px(spacing::SHELL_INSET))
.left(px(spacing::SHELL_INSET))
.bottom(px(spacing::SHELL_INSET))
.child(self.render_expanded_sidebar(
snapshot,
f32::from(DEFAULT_SIDEBAR_WIDTH_PX),
cx,
)),
)
.into_any_element()
}
fn render_main_pane(
&mut self,
snapshot: &BrowserSnapshot,
@@ -112,13 +153,31 @@ impl ElyShell {
snapshot: &BrowserSnapshot,
sidebar_width: f32,
sidebar_collapsed: bool,
sidebar_hidden: bool,
cx: &mut Context<Self>,
) -> AnyElement {
if sidebar_hidden {
return self.render_hidden_sidebar_rail(cx);
}
if sidebar_collapsed {
return self.render_compact_sidebar(snapshot, sidebar_width, cx);
}
self.render_expanded_sidebar(snapshot, sidebar_width, cx)
}
fn render_hidden_sidebar_rail(&mut self, cx: &mut Context<Self>) -> AnyElement {
div()
.id(SharedString::from("hidden-sidebar-rail"))
.w(px(f32::from(HIDDEN_SIDEBAR_WIDTH_PX)))
.h_full()
.rounded(px(spacing::RADIUS_PANE))
.bg(rgba(0xffffff8c))
.cursor_pointer()
.hover(|style| style.bg(rgba(0xffffffd9)))
.active(|style| style.opacity(0.78))
.on_click(cx.listener(|shell, _, _, cx| shell.expand_hidden_sidebar(cx)))
.into_any_element()
}
}
fn render_error(message: String) -> AnyElement {