Add sidebar collapse shortcut

This commit is contained in:
2026-05-08 08:36:58 -04:00
parent c14f061d52
commit 43ab4be548
7 changed files with 260 additions and 18 deletions
+2
View File
@@ -28,6 +28,7 @@ actions!(
SplitRight,
ToggleFavoriteTab,
TogglePinnedTab,
ToggleSidebar,
]
);
@@ -52,6 +53,7 @@ fn main() {
MenuItem::action("Split Right", SplitRight),
MenuItem::separator(),
MenuItem::action("Command Mode", FocusCommandMode),
MenuItem::action("Toggle Sidebar", ToggleSidebar),
MenuItem::separator(),
MenuItem::action("Close Tab", CloseCurrentTab),
MenuItem::separator(),
+1
View File
@@ -6,6 +6,7 @@ mod notes;
mod plugins;
mod reading_list;
mod render;
mod sidebar;
mod site_permissions;
mod splits;
mod tab_groups;
+18 -16
View File
@@ -11,6 +11,7 @@ use gpui_component::{
input::Input,
};
use super::sidebar::{collapsed_sidebar_active, render_command_bar_identity};
use super::{ElyShell, ShellState};
impl Render for ElyShell {
@@ -36,6 +37,7 @@ impl ElyShell {
Ok(sidebar_width) => sidebar_width,
Err(message) => return render_error(message),
};
let sidebar_collapsed = collapsed_sidebar_active(sidebar_width);
div()
.size_full()
@@ -54,17 +56,24 @@ impl ElyShell {
.on_action(cx.listener(Self::on_split_right))
.on_action(cx.listener(Self::on_toggle_favorite_tab))
.on_action(cx.listener(Self::on_toggle_pinned_tab))
.on_action(cx.listener(Self::on_toggle_sidebar))
.bg(rgb(ELY_THEME.canvas))
.text_color(rgb(ELY_THEME.ink))
.flex()
.flex_col()
.child(self.render_command_bar(&snapshot, &active_tab, sidebar_width, cx))
.child(self.render_command_bar(
&snapshot,
&active_tab,
sidebar_width,
sidebar_collapsed,
cx,
))
.child(
div()
.flex()
.flex_1()
.overflow_hidden()
.child(self.render_sidebar(&snapshot, sidebar_width, cx))
.child(self.render_sidebar(&snapshot, sidebar_width, sidebar_collapsed, cx))
.child(self.render_content_area(&snapshot, &active_tab, cx)),
)
.into_any_element()
@@ -75,6 +84,7 @@ impl ElyShell {
snapshot: &BrowserSnapshot,
active_tab: &BrowserTab,
sidebar_width: f32,
sidebar_collapsed: bool,
cx: &mut Context<Self>,
) -> AnyElement {
let favorite_icon =
@@ -91,20 +101,7 @@ impl ElyShell {
.items_center()
.border_b_1()
.border_color(rgb(colors::HAIRLINE))
.child(
div()
.w(px(sidebar_width - spacing::XL))
.flex()
.items_center()
.gap_2()
.child(div().text_size(px(18.0)).font_semibold().child("ELY Browser"))
.child(
div()
.text_xs()
.text_color(rgb(colors::MUTED))
.child(snapshot.active_space_name.clone()),
),
)
.child(render_command_bar_identity(snapshot, sidebar_width, sidebar_collapsed))
.child(
div()
.flex_1()
@@ -149,8 +146,13 @@ impl ElyShell {
&mut self,
snapshot: &BrowserSnapshot,
sidebar_width: f32,
sidebar_collapsed: bool,
cx: &mut Context<Self>,
) -> AnyElement {
if sidebar_collapsed {
return self.render_compact_sidebar(snapshot, sidebar_width, cx);
}
div()
.w(px(sidebar_width))
.h_full()
+215
View File
@@ -0,0 +1,215 @@
use ely_browser_core::BrowserSnapshot;
use ely_design_system::{colors, spacing};
use ely_domain::{
ArchivedTab, BrowserTab, COLLAPSED_SIDEBAR_WIDTH_PX, DEFAULT_SIDEBAR_WIDTH_PX, Space,
};
use gpui::{AnyElement, Context, IntoElement, ParentElement, Styled, Window, div, px, rgb};
use gpui_component::{
IconName, Selectable, Sizable, StyledExt,
button::{Button, ButtonVariants},
};
use super::{ElyShell, ShellState};
use crate::ToggleSidebar;
impl ElyShell {
pub(super) fn on_toggle_sidebar(
&mut self,
_: &ToggleSidebar,
_: &mut Window,
cx: &mut Context<Self>,
) {
self.toggle_sidebar_width(cx);
}
pub(super) fn render_compact_sidebar(
&mut self,
snapshot: &BrowserSnapshot,
sidebar_width: f32,
cx: &mut Context<Self>,
) -> AnyElement {
div()
.w(px(sidebar_width))
.h_full()
.flex()
.flex_col()
.items_center()
.gap_2()
.p_2()
.border_r_1()
.border_color(rgb(colors::HAIRLINE))
.bg(rgb(colors::CANVAS))
.children(snapshot.favorites.iter().enumerate().map(|(index, tab)| {
self.render_compact_tab_button(
("compact-favorite", index),
tab,
tab.id() == &snapshot.active_tab_id,
IconName::Star,
cx,
)
}))
.children(snapshot.pinned_tabs.iter().enumerate().map(|(index, tab)| {
self.render_compact_tab_button(
("compact-pinned", index),
tab,
tab.id() == &snapshot.active_tab_id,
IconName::Asterisk,
cx,
)
}))
.children(snapshot.spaces.iter().enumerate().map(|(index, space)| {
self.render_compact_space_button(
index,
space,
space.id() == &snapshot.active_space_id,
cx,
)
}))
.children(snapshot.tabs.iter().enumerate().map(|(index, tab)| {
self.render_compact_tab_button(
("compact-tab", index),
tab,
tab.id() == &snapshot.active_tab_id,
IconName::Globe,
cx,
)
}))
.children(snapshot.archived_tabs.iter().rev().enumerate().map(
|(index, archived_tab)| {
self.render_compact_archived_button(index, archived_tab, cx)
},
))
.into_any_element()
}
fn toggle_sidebar_width(&mut self, cx: &mut Context<Self>) {
let ShellState::Ready(core) = &mut self.state else {
return;
};
let Ok(snapshot) = core.snapshot() else {
return;
};
let Some(active_space) =
snapshot.spaces.iter().find(|space| space.id() == &snapshot.active_space_id)
else {
return;
};
let next_width = if active_space.sidebar_width_px() <= COLLAPSED_SIDEBAR_WIDTH_PX {
DEFAULT_SIDEBAR_WIDTH_PX
} else {
COLLAPSED_SIDEBAR_WIDTH_PX
};
if core.set_space_sidebar_width(&snapshot.active_space_id, next_width).is_ok() {
cx.notify();
}
}
fn render_compact_space_button(
&mut self,
index: usize,
space: &Space,
active: bool,
cx: &mut Context<Self>,
) -> AnyElement {
let space_id = space.id().clone();
Button::new(("compact-space", index))
.ghost()
.small()
.selected(active)
.label(space.icon().to_string())
.tooltip(space.name().to_string())
.on_click(cx.listener(move |shell, _, window, cx| {
shell.select_space(&space_id, window, cx);
}))
.into_any_element()
}
fn render_compact_tab_button(
&mut self,
id: (&'static str, usize),
tab: &BrowserTab,
active: bool,
icon: IconName,
cx: &mut Context<Self>,
) -> AnyElement {
let tab_id = tab.id().clone();
Button::new(id)
.ghost()
.small()
.selected(active)
.icon(icon)
.tooltip(tab.title().to_string())
.on_click(cx.listener(move |shell, _, window, cx| {
shell.select_tab(&tab_id, window, cx);
}))
.into_any_element()
}
fn render_compact_archived_button(
&mut self,
index: usize,
archived_tab: &ArchivedTab,
cx: &mut Context<Self>,
) -> AnyElement {
let tab = archived_tab.tab();
let tab_id = tab.id().clone();
Button::new(("compact-archive", index))
.ghost()
.small()
.icon(IconName::Undo2)
.tooltip(tab.title().to_string())
.on_click(cx.listener(move |shell, _, window, cx| {
shell.restore_archived_tab(&tab_id, window, cx);
}))
.into_any_element()
}
}
pub(super) fn render_command_bar_identity(
snapshot: &BrowserSnapshot,
sidebar_width: f32,
sidebar_collapsed: bool,
) -> AnyElement {
let width = sidebar_width - spacing::XL;
if sidebar_collapsed {
return div()
.w(px(width))
.flex()
.items_center()
.justify_center()
.child(
div()
.size(px(28.0))
.rounded_md()
.bg(rgb(colors::PRIMARY))
.text_color(rgb(colors::CANVAS))
.text_xs()
.font_semibold()
.flex()
.items_center()
.justify_center()
.child("ELY"),
)
.into_any_element();
}
div()
.w(px(width))
.flex()
.items_center()
.gap_2()
.child(div().text_size(px(18.0)).font_semibold().child("ELY Browser"))
.child(
div()
.text_xs()
.text_color(rgb(colors::MUTED))
.child(snapshot.active_space_name.clone()),
)
.into_any_element()
}
pub(super) fn collapsed_sidebar_active(sidebar_width: f32) -> bool {
sidebar_width <= f32::from(COLLAPSED_SIDEBAR_WIDTH_PX)
}
+22 -1
View File
@@ -5,7 +5,7 @@ use gpui::{App, KeyBinding};
use crate::{
CloseCurrentTab, FocusAddressBar, FocusCommandMode, OpenDownloads, OpenHistory, OpenNewTab,
OpenSettings, OpenTaskManager, Quit, RestoreClosedTab, SelectNextTab, SelectPreviousTab,
SplitRight, ToggleFavoriteTab,
SplitRight, ToggleFavoriteTab, ToggleSidebar,
};
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
@@ -33,6 +33,7 @@ pub(crate) enum ShortcutAction {
SelectNextTab,
SelectPreviousTab,
SplitRight,
ToggleSidebar,
ToggleFavoriteTab,
OpenDownloads,
OpenHistory,
@@ -52,6 +53,7 @@ impl ShortcutAction {
Self::SelectNextTab => "Next Tab",
Self::SelectPreviousTab => "Previous Tab",
Self::SplitRight => "Split Right",
Self::ToggleSidebar => "Toggle Sidebar",
Self::ToggleFavoriteTab => "Toggle Favorite",
Self::OpenDownloads => "Open Downloads",
Self::OpenHistory => "Open History",
@@ -70,6 +72,7 @@ impl ShortcutAction {
| Self::SelectNextTab
| Self::SelectPreviousTab
| Self::SplitRight
| Self::ToggleSidebar
| Self::ToggleFavoriteTab => "Tabs",
Self::OpenDownloads | Self::OpenHistory => "Library",
Self::OpenSettings | Self::OpenTaskManager => "System",
@@ -87,6 +90,7 @@ impl ShortcutAction {
Self::SelectNextTab => None,
Self::SelectPreviousTab => None,
Self::SplitRight => Some(">split-right"),
Self::ToggleSidebar => None,
Self::ToggleFavoriteTab => Some(">favorite"),
Self::OpenDownloads => Some(">open-downloads"),
Self::OpenHistory => Some(">open-history"),
@@ -126,6 +130,7 @@ pub(crate) const SHORTCUT_ACTIONS: &[ShortcutAction] = &[
ShortcutAction::SelectNextTab,
ShortcutAction::SelectPreviousTab,
ShortcutAction::SplitRight,
ShortcutAction::ToggleSidebar,
ShortcutAction::ToggleFavoriteTab,
ShortcutAction::OpenDownloads,
ShortcutAction::OpenHistory,
@@ -139,6 +144,8 @@ pub(crate) const SHORTCUT_BINDINGS: &[ShortcutBinding] = &[
shortcut(ShortcutAction::OpenNewTab, ShortcutPlatform::WindowsLinux, "ctrl-t"),
shortcut(ShortcutAction::SplitRight, ShortcutPlatform::Macos, "cmd-\\"),
shortcut(ShortcutAction::SplitRight, ShortcutPlatform::WindowsLinux, "ctrl-\\"),
shortcut(ShortcutAction::ToggleSidebar, ShortcutPlatform::Macos, "cmd-b"),
shortcut(ShortcutAction::ToggleSidebar, ShortcutPlatform::WindowsLinux, "ctrl-b"),
shortcut(ShortcutAction::OpenDownloads, ShortcutPlatform::Macos, "cmd-shift-j"),
shortcut(ShortcutAction::OpenDownloads, ShortcutPlatform::WindowsLinux, "ctrl-shift-j"),
shortcut(ShortcutAction::OpenHistory, ShortcutPlatform::Macos, "cmd-y"),
@@ -240,6 +247,7 @@ impl ShortcutBinding {
ShortcutAction::ToggleFavoriteTab => {
KeyBinding::new(self.keystroke, ToggleFavoriteTab, None)
}
ShortcutAction::ToggleSidebar => KeyBinding::new(self.keystroke, ToggleSidebar, None),
}
}
}
@@ -289,6 +297,19 @@ mod tests {
assert_eq!(bindings, vec!["Cmd + ,".to_string(), "Ctrl + ,".to_string()]);
}
#[test]
fn toggle_sidebar_shortcut_has_platform_bindings() {
let bindings = bindings_for_action(ShortcutAction::ToggleSidebar, ShortcutPlatform::Macos)
.chain(bindings_for_action(
ShortcutAction::ToggleSidebar,
ShortcutPlatform::WindowsLinux,
))
.map(|binding| binding.display_keystroke())
.collect::<Vec<_>>();
assert_eq!(bindings, vec!["Cmd + B".to_string(), "Ctrl + B".to_string()]);
}
#[test]
fn every_declared_action_has_a_binding() {
for action in SHORTCUT_ACTIONS {