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
33 changes: 17 additions & 16 deletions crates/ui/src/composer/components/pickers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -679,15 +679,10 @@ fn render_model_pane(
cx: &mut Context<PopoverState>|
-> AnyElement {
let composer = composer.clone();
crate::material::accessible_clickable(div(), id, Role::Tab, label.clone(), cx)
.aria_selected(active)
crate::material::tab(id, label.clone(), active, cx)
.flex_none()
.size(px(if compact { 44. } else { 28. }))
.flex()
.items_center()
.justify_center()
.rounded(px(6.))
.cursor_pointer()
.when(active, |s| s.bg(cx.theme().muted))
.hover(|s| s.bg(cx.theme().muted))
.tooltip(move |window, cx| {
Expand All @@ -707,14 +702,22 @@ fn render_model_pane(
.into_any_element()
};

let mut rail_col = v_flex().w_full().py_2().px_1p5().gap_1().child(rail_icon(
"rail-fav".into(),
crate::tr!("composer.favorites").into_owned().into(),
Icon::new(IconName::Star),
rail == PickerRail::Favorites,
PickerRail::Favorites,
cx,
));
let mut rail_col = gpui_base::Tabs::new("model-provider-rail-tabs")
.flex()
.flex_col()
.aria_label(crate::tr!("composer.model_sources"))
.w_full()
.py_2()
.px_1p5()
.gap_1()
.child(rail_icon(
"rail-fav".into(),
crate::tr!("composer.favorites").into_owned().into(),
Icon::new(IconName::Star),
rail == PickerRail::Favorites,
PickerRail::Favorites,
cx,
));
// One entry per *enabled* native profile: every built-in plus any
// user-created profiles whose switch is on. Each is its own rail.
let profile_ids: Vec<String> = {
Expand Down Expand Up @@ -754,8 +757,6 @@ fn render_model_pane(
}
let rail = div()
.id("model-provider-rail")
.role(Role::TabList)
.aria_label(crate::tr!("composer.model_sources"))
.flex_none()
.w(px(if compact { 56. } else { 44. }))
.h_full()
Expand Down
13 changes: 5 additions & 8 deletions crates/ui/src/diff/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -695,15 +695,12 @@ impl DiffPanel {
label: gpui::SharedString,
is_active: bool,
cx: &mut Context<Self>|
-> gpui::Stateful<gpui::Div> {
material::accessible_clickable(h_flex(), id, Role::Tab, label.clone(), cx)
.aria_selected(is_active)
-> gpui_base::Tab {
material::tab(id, label.clone(), is_active, cx)
.h(px(28.))
.px_2p5()
.gap_1p5()
.items_center()
.rounded(material::radius_button())
.cursor_pointer()
.text_size(px(13.))
.font_medium()
.when(is_active, |s| s.bg(tab_active))
Expand All @@ -714,10 +711,10 @@ impl DiffPanel {
.child(label)
};

h_flex()
.id("right-panel-tabs")
gpui_base::Tabs::new("right-panel-tabs")
.flex()
.items_center()
.debug_selector(|| "right-panel-tabs".into())
.role(Role::TabList)
.aria_label(crate::tr!("diff.panel_tabs"))
.flex_none()
.h(px(if hosts_caption {
Expand Down
19 changes: 18 additions & 1 deletion crates/ui/src/material.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use gpui::{
Pixels, Rgba, Role, SharedString, Stateful, StatefulInteractiveElement as _, Styled as _, div,
linear_color_stop, linear_gradient, px,
};
use gpui_base::{StyledExt as _, Toggle, ToggleGroup, v_flex};
use gpui_base::{StyledExt as _, Tab, Toggle, ToggleGroup, v_flex};

/// Height reserved beneath chat messages for hover-revealed actions.
pub(crate) const CHAT_ACTION_ROW_HEIGHT: f32 = 24.;
Expand Down Expand Up @@ -453,6 +453,23 @@ pub fn accessible_clickable<E: gpui::Element + gpui::InteractiveElement>(
.aria_label(label)
}

/// A gpui-base tab that can take keyboard focus. Callers style it and put
/// it in a [`gpui_base::Tabs`] list.
pub fn tab(
id: impl Into<ElementId>,
label: impl Into<SharedString>,
selected: bool,
cx: &App,
) -> Tab {
let ring = focus_ring(cx);
Tab::new(id)
.selected(selected)
.accessibility_label(label)
.tab_index(0)
.focus_visible(move |style| style.shadow(vec![ring]))
.cursor_pointer()
}

/// The keyboard focus ring of a clickable that is not a `Button`.
fn focus_ring(cx: &App) -> BoxShadow {
let ring = cx.theme().ring.opacity(if cx.theme().mode.is_dark() {
Expand Down
62 changes: 27 additions & 35 deletions crates/ui/src/settings_page.rs
Original file line number Diff line number Diff line change
Expand Up @@ -831,38 +831,30 @@ impl SettingsPage {
} else {
cx.theme().muted_foreground
};
crate::material::accessible_clickable(
gpui_base::h_flex(),
section.id(),
Role::Tab,
label.clone(),
cx,
)
.aria_selected(active)
.debug_selector(move || section.id().into())
// Keep the hitbox, stable element id, and hover style on the
// same element. Splitting them across an outer clickable and
// an anonymous inner row leaves GPUI tracking two overlapping
// interaction regions, which makes hover paint stale or skip
// as the pointer crosses adjacent tabs.
.h(px(30.))
.items_center()
.gap_2()
.px_2()
.rounded(px(6.))
.cursor_pointer()
.when(active, |s| s.bg(cx.theme().list_active))
.when(!active, |s| s.hover(|s| s.bg(cx.theme().sidebar_accent)))
.child(Icon::new(section.icon()).size_4().text_color(fg))
.child(
div()
.text_size(px(13.))
.when(active, |d| d.font_medium())
.text_color(fg)
.child(label),
)
.on_click(cx.listener(move |this, _, _, cx| this.select_section(section, cx)))
.into_any_element()
crate::material::tab(section.id(), label.clone(), active, cx)
.debug_selector(move || section.id().into())
// Keep the hitbox, stable element id, and hover style on the
// same element. Splitting them across an outer clickable and
// an anonymous inner row leaves GPUI tracking two overlapping
// interaction regions, which makes hover paint stale or skip
// as the pointer crosses adjacent tabs.
.h(px(30.))
.justify_start()
.gap_2()
.px_2()
.rounded(px(6.))
.when(active, |s| s.bg(cx.theme().list_active))
.when(!active, |s| s.hover(|s| s.bg(cx.theme().sidebar_accent)))
.child(Icon::new(section.icon()).size_4().text_color(fg))
.child(
div()
.text_size(px(13.))
.when(active, |d| d.font_medium())
.text_color(fg)
.child(label),
)
.on_click(cx.listener(move |this, _, _, cx| this.select_section(section, cx)))
.into_any_element()
}

fn group_caption(&self, group: SectionGroup, cx: &Context<Self>) -> AnyElement {
Expand Down Expand Up @@ -980,9 +972,9 @@ impl SettingsPage {
}

fn render_nav(&self, window: &mut Window, cx: &mut Context<Self>) -> AnyElement {
let mut tabs = v_flex()
.id("settings-nav-tabs")
.role(Role::TabList)
let mut tabs = gpui_base::Tabs::new("settings-nav-tabs")
.flex()
.flex_col()
.aria_label(crate::tr!("settings.title"))
.flex_1()
.min_h_0()
Expand Down
103 changes: 48 additions & 55 deletions crates/ui/src/terminal_drawer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1444,9 +1444,9 @@ impl Render for TerminalDrawer {
self.marked_text = None;
}

let mut tab_strip = h_flex()
.id("terminal-tab-list")
.role(Role::TabList)
let mut tab_strip = gpui_base::Tabs::new("terminal-tab-list")
.flex()
.items_center()
.aria_label(crate::tr!("terminal.tabs"))
.min_w_0()
.gap(px(2.))
Expand All @@ -1457,61 +1457,54 @@ impl Render for TerminalDrawer {
let close_id = id;
let tab_label = crate::tr!("terminal.tab", label = label.clone()).into_owned();
tab_strip = tab_strip.child(
crate::material::accessible_clickable(
h_flex(),
("terminal-tab", id),
Role::Tab,
tab_label,
cx,
)
.aria_selected(selected)
.h(px(25.))
.gap(px(2.))
.px_2()
.rounded(material::radius_button())
.cursor_pointer()
.bg(if selected {
cx.theme().list_active
} else {
cx.theme().background.opacity(0.)
})
.on_click(cx.listener(move |this, _, _, cx| {
this.workspace_store
.update(cx, |store, _cx| store.activate_terminal(id));
}))
.child(
div()
.max_w(px(92.))
.overflow_hidden()
.text_ellipsis()
.text_size(px(11.))
.text_color(if *exited || !selected {
cx.theme().muted_foreground
} else {
cx.theme().foreground
})
.child(label.clone()),
)
.when(*bell, |this| {
this.child(
crate::material::tab(("terminal-tab", id), tab_label, selected, cx)
.justify_start()
.h(px(25.))
.gap(px(2.))
.px_2()
.rounded(material::radius_button())
.bg(if selected {
cx.theme().list_active
} else {
cx.theme().background.opacity(0.)
})
.on_click(cx.listener(move |this, _, _, cx| {
this.workspace_store
.update(cx, |store, _cx| store.activate_terminal(id));
}))
.child(
div()
.max_w(px(92.))
.overflow_hidden()
.text_ellipsis()
.text_size(px(11.))
.text_color(cx.theme().warning)
.child("●"),
.text_color(if *exited || !selected {
cx.theme().muted_foreground
} else {
cx.theme().foreground
})
.child(label.clone()),
)
})
.child(
Button::new(("terminal-tab-close", close_id))
.ghost()
.compact()
.xsmall()
.icon(IconName::Close)
.tooltip(crate::tr!("terminal.close_tab"))
.on_click(cx.listener(move |this, _, _, cx| {
this.workspace_store
.update(cx, |store, cx| store.close_terminal(close_id, cx));
})),
),
.when(*bell, |this| {
this.child(
div()
.text_size(px(11.))
.text_color(cx.theme().warning)
.child("●"),
)
})
.child(
Button::new(("terminal-tab-close", close_id))
.ghost()
.compact()
.xsmall()
.icon(IconName::Close)
.tooltip(crate::tr!("terminal.close_tab"))
.on_click(cx.listener(move |this, _, _, cx| {
this.workspace_store
.update(cx, |store, cx| store.close_terminal(close_id, cx));
})),
),
);
}

Expand Down
Loading