diff --git a/crates/story/src/tabs_story.rs b/crates/story/src/tabs_story.rs index 2a364b32..a7046fd4 100644 --- a/crates/story/src/tabs_story.rs +++ b/crates/story/src/tabs_story.rs @@ -100,7 +100,7 @@ impl Render for TabsStory { ) .child( section("Tabs", cx).child( - TabBar::new() + TabBar::new("tabs") .w_full() .with_size(self.size) .selected_index(self.active_tab_ix) @@ -123,12 +123,14 @@ impl Render for TabsStory { .icon(IconName::ArrowRight), ), ) - .child(Tab::new("tab-account", "Account")) - .child(Tab::new("tab-profile", "Profile").disabled(true)) - .child(Tab::new("tab-documents", "Documents")) - .child(Tab::new("tab-mail", "Mail")) - .child(Tab::new("tab-appearance", "Appearance")) - .child(Tab::new("tab-settings", "Settings")) + .child(Tab::new("Account")) + .child(Tab::new("Profile").disabled(true)) + .child(Tab::new("Documents")) + .child(Tab::new("Mail")) + .child(Tab::new("Appearance")) + .child(Tab::new("Settings")) + .child(Tab::new("About")) + .child(Tab::new("License")) .suffix( h_flex() .mx_1() @@ -144,7 +146,7 @@ impl Render for TabsStory { ) .child( section("Pill Tabs", cx).child( - TabBar::new() + TabBar::new("pill") .w_full() .pill() .with_size(self.size) @@ -152,17 +154,19 @@ impl Render for TabsStory { .on_click(cx.listener(|this, ix: &usize, window, cx| { this.set_active_tab(*ix, window, cx); })) - .child(Tab::new("tab-account", "Account")) - .child(Tab::new("tab-profile", "Profile").disabled(true)) - .child(Tab::new("tab-documents", "Documents")) - .child(Tab::new("tab-mail", "Mail")) - .child(Tab::new("tab-appearance", "Appearance")) - .child(Tab::new("tab-settings", "Settings")), + .child(Tab::new("Account")) + .child(Tab::new("Profile").disabled(true)) + .child(Tab::new("Documents")) + .child(Tab::new("Mail")) + .child(Tab::new("Appearance")) + .child(Tab::new("Settings")) + .child(Tab::new("About")) + .child(Tab::new("License")), ), ) .child( section("Segmented Tabs", cx).child( - TabBar::new() + TabBar::new("segmented") .w_full() .segmented() .with_size(self.size) @@ -177,13 +181,17 @@ impl Render for TabsStory { "Mail", "Appearance", "Settings", + "About", + "License", ]), ), ) .child( section("Underline Tabs", cx).child( - TabBar::new() + TabBar::new("underline") .w_full() + .px_2() + .mx_3() .underline() .with_size(self.size) .selected_index(self.active_tab_ix) @@ -195,7 +203,9 @@ impl Render for TabsStory { .child("Documents") .child("Mail") .child("Appearance") - .child("Settings"), + .child("Settings") + .child("About") + .child("License"), ), ) } diff --git a/crates/ui/src/dock/tab_panel.rs b/crates/ui/src/dock/tab_panel.rs index a56c4cfd..6c24a4c1 100644 --- a/crates/ui/src/dock/tab_panel.rs +++ b/crates/ui/src/dock/tab_panel.rs @@ -650,7 +650,7 @@ impl TabPanel { let tabs_count = self.panels.len(); - TabBar::new() + TabBar::new("tab-bar") .mt(-px(1.)) .track_scroll(self.tab_bar_scroll_handle.clone()) .when( @@ -673,9 +673,6 @@ impl TabPanel { ) }, ) - .on_click(cx.listener(move |view, ix: &usize, window, cx| { - view.set_active_ix(*ix, window, cx); - })) .children(self.panels.iter().enumerate().filter_map(|(ix, panel)| { let mut active = state.active_panel.as_ref() == Some(panel); let disabled = self.collapsed; @@ -690,12 +687,16 @@ impl TabPanel { } Some( - Tab::new(("tab", ix), panel.title(window, cx)) + Tab::new("") + .child(panel.title(window, cx)) .py_2() .selected(active) .disabled(disabled) .when(!disabled, |this| { - this.when(state.draggable, |this| { + this.on_click(cx.listener(move |view, _, window, cx| { + view.set_active_ix(ix, window, cx); + })) + .when(state.draggable, |this| { this.on_drag( DragPanel::new(panel.clone(), view.clone()), |drag, _, _, cx| { diff --git a/crates/ui/src/tab/tab.rs b/crates/ui/src/tab/tab.rs index 366aad18..6e1a0217 100644 --- a/crates/ui/src/tab/tab.rs +++ b/crates/ui/src/tab/tab.rs @@ -4,8 +4,8 @@ use crate::{ActiveTheme, Selectable, Sizable, Size, StyledExt}; use gpui::prelude::FluentBuilder as _; use gpui::{ div, px, AnyElement, App, ClickEvent, Div, Edges, ElementId, Hsla, InteractiveElement, - IntoElement, ParentElement as _, Pixels, RenderOnce, SharedString, Stateful, - StatefulInteractiveElement, Styled, Window, + IntoElement, ParentElement, Pixels, RenderOnce, SharedString, StatefulInteractiveElement, + Styled, Window, }; #[derive(Debug, Clone, Default, Copy, PartialEq, Eq, Hash)] @@ -45,6 +45,23 @@ impl Default for TabStyle { } impl TabVariant { + fn height(&self, size: Size) -> Pixels { + match size { + Size::Small | Size::XSmall => match self { + TabVariant::Tab => px(24.), + TabVariant::Pill => px(24.), + TabVariant::Segmented => px(24.), + TabVariant::Underline => px(30.), + }, + _ => match self { + TabVariant::Tab => px(30.), + TabVariant::Pill => px(31.), + TabVariant::Segmented => px(30.), + TabVariant::Underline => px(36.), + }, + } + } + fn inner_height(&self, size: Size) -> Pixels { match size { Size::Small | Size::XSmall => match self { @@ -57,7 +74,7 @@ impl TabVariant { TabVariant::Tab => px(30.), TabVariant::Pill => px(31.), TabVariant::Segmented => px(30.), - TabVariant::Underline => px(25.), + TabVariant::Underline => px(24.), }, } } @@ -122,7 +139,8 @@ impl TabVariant { }, _ => match self { TabVariant::Underline => Edges { - bottom: px(4.), + top: px(5.), + bottom: px(3.), ..Default::default() }, _ => Edges::all(px(0.)), @@ -233,7 +251,7 @@ impl TabVariant { ..Default::default() }, TabVariant::Pill => TabStyle { - fg: cx.theme().accent_foreground, + fg: cx.theme().primary, bg: cx.theme().transparent, borders: Edges::all(px(1.)), border_color: cx.theme().primary, @@ -322,10 +340,11 @@ impl TabVariant { #[derive(IntoElement)] pub struct Tab { id: ElementId, - base: Stateful
, - label: AnyElement, + base: Div, + label: SharedString, prefix: Option, suffix: Option, + children: Vec, variant: TabVariant, size: Size, disabled: bool, @@ -336,31 +355,30 @@ pub struct Tab { impl From<&'static str> for Tab { fn from(label: &'static str) -> Self { let label = SharedString::from(label); - Self::new(label.clone(), label) + Self::new(label) } } impl From for Tab { fn from(label: String) -> Self { let label = SharedString::from(label); - Self::new(label.clone(), label) + Self::new(label) } } impl From for Tab { fn from(label: SharedString) -> Self { - let label = SharedString::from(label); - Self::new(label.clone(), label) + Self::new(label) } } impl Tab { - pub fn new(id: impl Into, label: impl IntoElement) -> Self { - let id: ElementId = id.into(); + pub fn new(label: impl Into) -> Self { Self { - id: id.clone(), - base: div().id(id).gap_1(), - label: label.into_any_element(), + id: ElementId::Integer(0), + base: div().gap_1(), + label: label.into(), + children: Vec::new(), disabled: false, selected: false, prefix: None, @@ -371,6 +389,12 @@ impl Tab { } } + /// Set id to the tab. + pub fn id(mut self, id: impl Into) -> Self { + self.id = id.into(); + self + } + /// Set Tab Variant. pub fn variant(mut self, variant: TabVariant) -> Self { self.variant = variant; @@ -423,6 +447,12 @@ impl Tab { } } +impl ParentElement for Tab { + fn extend(&mut self, elements: impl IntoIterator) { + self.children.extend(elements); + } +} + impl Selectable for Tab { fn element_id(&self) -> &ElementId { &self.id @@ -470,13 +500,11 @@ impl RenderOnce for Tab { let inner_paddings = self.variant.inner_paddings(self.size); let inner_margins = self.variant.inner_margins(self.size); let inner_height = self.variant.inner_height(self.size); - - let height = match self.size { - Size::Small | Size::XSmall => px(24.), - _ => px(31.), - }; + let height = self.variant.height(self.size); + let has_label = !self.label.is_empty(); self.base + .id(self.id) .flex() .items_center() .flex_shrink_0() @@ -516,7 +544,8 @@ impl RenderOnce for Tab { .margins(inner_margins) .text_ellipsis() .flex_shrink_0() - .child(self.label) + .when(has_label, |this| this.child(self.label)) + .when(!has_label, |this| this.children(self.children)) .bg(tab_style.inner_bg) .rounded(tab_style.inner_radius) .hover(|this| { diff --git a/crates/ui/src/tab/tab_bar.rs b/crates/ui/src/tab/tab_bar.rs index e6aee863..40c582e3 100644 --- a/crates/ui/src/tab/tab_bar.rs +++ b/crates/ui/src/tab/tab_bar.rs @@ -3,8 +3,8 @@ use std::sync::Arc; use crate::{h_flex, ActiveTheme, Selectable, Sizable, Size, StyledExt}; use gpui::prelude::FluentBuilder as _; use gpui::{ - div, rems, AbsoluteLength, AnyElement, App, Div, Edges, IntoElement, ParentElement, RenderOnce, - ScrollHandle, StatefulInteractiveElement as _, Styled, Window, + div, AnyElement, App, Div, Edges, ElementId, IntoElement, ParentElement, RenderOnce, + ScrollHandle, Stateful, StatefulInteractiveElement as _, Styled, Window, }; use gpui::{px, InteractiveElement}; use smallvec::SmallVec; @@ -13,12 +13,12 @@ use super::{Tab, TabVariant}; #[derive(IntoElement)] pub struct TabBar { - base: Div, - scroll_handle: ScrollHandle, + base: Stateful
, + scroll_handle: Option, prefix: Option, suffix: Option, children: SmallVec<[Tab; 2]>, - last_empty_space: Option, + last_empty_space: AnyElement, selected_index: Option, variant: TabVariant, size: Size, @@ -27,16 +27,16 @@ pub struct TabBar { impl TabBar { /// Create a new TabBar. - pub fn new() -> Self { + pub fn new(id: impl Into) -> Self { Self { - base: div().px(px(-1.)), + base: div().id(id).px(px(-1.)), children: SmallVec::new(), - scroll_handle: ScrollHandle::new(), + scroll_handle: None, prefix: None, suffix: None, variant: TabVariant::default(), size: Size::default(), - last_empty_space: None, + last_empty_space: div().w_3().into_any_element(), selected_index: None, on_click: None, } @@ -68,7 +68,7 @@ impl TabBar { /// Track the scroll of the TabBar pub fn track_scroll(mut self, scroll_handle: ScrollHandle) -> Self { - self.scroll_handle = scroll_handle; + self.scroll_handle = Some(scroll_handle); self } @@ -105,7 +105,7 @@ impl TabBar { /// Set the last empty space element of the TabBar pub fn last_empty_space(mut self, last_empty_space: impl IntoElement) -> Self { - self.last_empty_space = Some(last_empty_space.into_any_element()); + self.last_empty_space = last_empty_space.into_any_element(); self } @@ -139,31 +139,32 @@ impl RenderOnce for TabBar { }; let (bg, paddings, gap) = match self.variant { TabVariant::Tab => { - let padding = Edges::all(AbsoluteLength::Pixels(px(0.))); + let padding = Edges::all(px(0.)); (cx.theme().tab_bar, padding, px(0.)) } TabVariant::Pill => { - let padding = Edges::all(AbsoluteLength::Rems(rems(0.25))); + let padding = Edges::all(px(0.)); (cx.theme().transparent, padding, default_gap) } TabVariant::Segmented => { - let padding = Edges::all(AbsoluteLength::Rems(rems(0.25))); + let padding = Edges::all(px(4.)); (cx.theme().accent, padding, default_gap / 2.) } TabVariant::Underline => { - let padding = Edges::all(AbsoluteLength::Pixels(px(0.))); + let padding = Edges::all(px(0.)); (cx.theme().transparent, padding, default_gap / 2.) } }; self.base .group("tab-bar") + .w_full() .relative() .flex() .flex_none() + .flex_nowrap() .items_center() .bg(bg) - .paddings(paddings) .text_color(cx.theme().tab_foreground) .when( self.variant == TabVariant::Underline || self.variant == TabVariant::Tab, @@ -172,6 +173,7 @@ impl RenderOnce for TabBar { div() .id("border-b") .absolute() + .left_0() .bottom_0() .size_full() .border_b_1() @@ -187,16 +189,20 @@ impl RenderOnce for TabBar { .child( h_flex() .id("tabs") - .flex_grow() + .flex_1() .overflow_x_scroll() - .track_scroll(&self.scroll_handle) + .when_some(self.scroll_handle, |this, scroll_handle| { + this.track_scroll(&scroll_handle) + }) .gap(gap) + .paddings(paddings) .children( self.children .into_iter() .enumerate() .map(move |(ix, child)| { child + .id(ix) .variant(self.variant) .with_size(self.size) .when_some(self.selected_index, |this, selected_ix| { @@ -209,7 +215,7 @@ impl RenderOnce for TabBar { }) }), ) - .children(self.last_empty_space), + .child(self.last_empty_space), ) .when_some(self.suffix, |this, suffix| this.child(suffix)) }