1use gpui::{AnyElement, ScrollHandle};
2use smallvec::SmallVec;
3
4use crate::Tab;
5use crate::prelude::*;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
9pub enum TabBarStyle {
10 #[default]
13 Underline,
14 Pills,
16}
17
18#[derive(IntoElement, RegisterComponent)]
19pub struct TabBar {
20 id: ElementId,
21 style: TabBarStyle,
22 start_children: SmallVec<[AnyElement; 2]>,
23 children: SmallVec<[AnyElement; 2]>,
24 end_children: SmallVec<[AnyElement; 2]>,
25 scroll_handle: Option<ScrollHandle>,
26}
27
28impl TabBar {
29 pub fn new(id: impl Into<ElementId>) -> Self {
30 Self {
31 id: id.into(),
32 style: TabBarStyle::default(),
33 start_children: SmallVec::new(),
34 children: SmallVec::new(),
35 end_children: SmallVec::new(),
36 scroll_handle: None,
37 }
38 }
39
40 pub fn style(mut self, style: TabBarStyle) -> Self {
42 self.style = style;
43 self
44 }
45
46 pub fn track_scroll(mut self, scroll_handle: &ScrollHandle) -> Self {
47 self.scroll_handle = Some(scroll_handle.clone());
48 self
49 }
50
51 pub fn start_children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
52 &mut self.start_children
53 }
54
55 pub fn start_child(mut self, start_child: impl IntoElement) -> Self
56 where
57 Self: Sized,
58 {
59 self.start_children_mut()
60 .push(start_child.into_element().into_any());
61 self
62 }
63
64 pub fn start_children(
65 mut self,
66 start_children: impl IntoIterator<Item = impl IntoElement>,
67 ) -> Self
68 where
69 Self: Sized,
70 {
71 self.start_children_mut().extend(
72 start_children
73 .into_iter()
74 .map(|child| child.into_any_element()),
75 );
76 self
77 }
78
79 pub fn end_children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
80 &mut self.end_children
81 }
82
83 pub fn end_child(mut self, end_child: impl IntoElement) -> Self
84 where
85 Self: Sized,
86 {
87 self.end_children_mut()
88 .push(end_child.into_element().into_any());
89 self
90 }
91
92 pub fn end_children(mut self, end_children: impl IntoIterator<Item = impl IntoElement>) -> Self
93 where
94 Self: Sized,
95 {
96 self.end_children_mut().extend(
97 end_children
98 .into_iter()
99 .map(|child| child.into_any_element()),
100 );
101 self
102 }
103}
104
105impl ParentElement for TabBar {
106 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
107 self.children.extend(elements)
108 }
109}
110
111impl RenderOnce for TabBar {
112 fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
113 let style = self.style;
114
115 let tabs_row = h_flex()
116 .id("tabs")
117 .flex_grow()
118 .overflow_x_scroll()
119 .when(style == TabBarStyle::Underline, |this| this.gap_2())
120 .when(style == TabBarStyle::Pills, |this| this.gap_2())
121 .when_some(self.scroll_handle, |this, scroll_handle| {
122 this.track_scroll(&scroll_handle)
123 })
124 .children(self.children);
125
126 let middle = match style {
127 TabBarStyle::Underline => div()
134 .relative()
135 .flex_1()
136 .h_full()
137 .child(tabs_row)
138 .into_any_element(),
139 TabBarStyle::Pills => div()
140 .flex_1()
141 .p_1()
142 .rounded_lg()
143 .bg(semantic::elevated_surface(cx))
144 .child(tabs_row)
145 .into_any_element(),
146 };
147
148 div()
149 .id(self.id)
150 .group("tab_bar")
151 .flex()
152 .flex_none()
153 .w_full()
154 .h(Tab::container_height(cx))
155 .bg(semantic::surface(cx))
156 .when(!self.start_children.is_empty(), |this| {
157 this.child(
158 h_flex()
159 .flex_none()
160 .gap(DynamicSpacing::Base04.rems(cx))
161 .px(DynamicSpacing::Base06.rems(cx))
162 .children(self.start_children),
163 )
164 })
165 .child(middle)
166 .when(!self.end_children.is_empty(), |this| {
167 this.child(
168 h_flex()
169 .flex_none()
170 .gap(DynamicSpacing::Base04.rems(cx))
171 .px(DynamicSpacing::Base06.rems(cx))
172 .children(self.end_children),
173 )
174 })
175 }
176}
177
178impl Component for TabBar {
179 fn scope() -> ComponentScope {
180 ComponentScope::Navigation
181 }
182
183 fn name() -> &'static str {
184 "TabBar"
185 }
186
187 fn description() -> Option<&'static str> {
188 Some("A horizontal bar containing tabs for navigation between different views or sections.")
189 }
190
191 fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
192 Some(
193 v_flex()
194 .gap_6()
195 .children(vec![
196 example_group_with_title(
197 "Underline (default)",
198 vec![single_example(
199 "With Tabs",
200 TabBar::new("underline_tab_bar")
201 .child(Tab::new("u_tab1").toggle_state(true).child("Overview"))
202 .child(Tab::new("u_tab2").child("Activity"))
203 .child(Tab::new("u_tab3").child("Settings"))
204 .into_any_element(),
205 )],
206 ),
207 example_group_with_title(
208 "Pills",
209 vec![single_example(
210 "With Tabs",
211 TabBar::new("pills_tab_bar")
212 .style(TabBarStyle::Pills)
213 .child(
214 Tab::new("p_tab1")
215 .style(TabBarStyle::Pills)
216 .toggle_state(true)
217 .child("Overview"),
218 )
219 .child(
220 Tab::new("p_tab2")
221 .style(TabBarStyle::Pills)
222 .child("Activity"),
223 )
224 .child(
225 Tab::new("p_tab3")
226 .style(TabBarStyle::Pills)
227 .child("Settings"),
228 )
229 .into_any_element(),
230 )],
231 ),
232 example_group_with_title(
233 "With Start and End Children",
234 vec![single_example(
235 "Full TabBar",
236 TabBar::new("full_tab_bar")
237 .start_child(Button::new("start_button", "Start"))
238 .child(Tab::new("tab1"))
239 .child(Tab::new("tab2"))
240 .child(Tab::new("tab3"))
241 .end_child(Button::new("end_button", "End"))
242 .into_any_element(),
243 )],
244 ),
245 ])
246 .into_any_element(),
247 )
248 }
249}