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