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_8())
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()
132 .relative()
133 .flex_1()
134 .h_full()
135 .child(
136 div()
137 .absolute()
138 .top_0()
139 .left_0()
140 .size_full()
141 .border_b_1()
142 .border_color(semantic::border_muted(cx)),
143 )
144 .child(tabs_row)
145 .into_any_element(),
146 TabBarStyle::Pills => div()
147 .flex_1()
148 .p_1()
149 .rounded_lg()
150 .bg(semantic::elevated_surface(cx))
151 .child(tabs_row)
152 .into_any_element(),
153 };
154
155 div()
156 .id(self.id)
157 .group("tab_bar")
158 .flex()
159 .flex_none()
160 .w_full()
161 .h(Tab::container_height(cx))
162 .bg(semantic::surface(cx))
163 .when(!self.start_children.is_empty(), |this| {
164 this.child(
165 h_flex()
166 .flex_none()
167 .gap(DynamicSpacing::Base04.rems(cx))
168 .px(DynamicSpacing::Base06.rems(cx))
169 .border_b_1()
170 .border_r_1()
171 .border_color(semantic::border_muted(cx))
172 .children(self.start_children),
173 )
174 })
175 .child(middle)
176 .when(!self.end_children.is_empty(), |this| {
177 this.child(
178 h_flex()
179 .flex_none()
180 .gap(DynamicSpacing::Base04.rems(cx))
181 .px(DynamicSpacing::Base06.rems(cx))
182 .border_color(semantic::border_muted(cx))
183 .border_b_1()
184 .border_l_1()
185 .children(self.end_children),
186 )
187 })
188 }
189}
190
191impl Component for TabBar {
192 fn scope() -> ComponentScope {
193 ComponentScope::Navigation
194 }
195
196 fn name() -> &'static str {
197 "TabBar"
198 }
199
200 fn description() -> Option<&'static str> {
201 Some("A horizontal bar containing tabs for navigation between different views or sections.")
202 }
203
204 fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
205 Some(
206 v_flex()
207 .gap_6()
208 .children(vec![
209 example_group_with_title(
210 "Underline (default)",
211 vec![single_example(
212 "With Tabs",
213 TabBar::new("underline_tab_bar")
214 .child(Tab::new("u_tab1").toggle_state(true).child("Overview"))
215 .child(Tab::new("u_tab2").child("Activity"))
216 .child(Tab::new("u_tab3").child("Settings"))
217 .into_any_element(),
218 )],
219 ),
220 example_group_with_title(
221 "Pills",
222 vec![single_example(
223 "With Tabs",
224 TabBar::new("pills_tab_bar")
225 .style(TabBarStyle::Pills)
226 .child(
227 Tab::new("p_tab1")
228 .style(TabBarStyle::Pills)
229 .toggle_state(true)
230 .child("Overview"),
231 )
232 .child(
233 Tab::new("p_tab2")
234 .style(TabBarStyle::Pills)
235 .child("Activity"),
236 )
237 .child(
238 Tab::new("p_tab3")
239 .style(TabBarStyle::Pills)
240 .child("Settings"),
241 )
242 .into_any_element(),
243 )],
244 ),
245 example_group_with_title(
246 "With Start and End Children",
247 vec![single_example(
248 "Full TabBar",
249 TabBar::new("full_tab_bar")
250 .start_child(Button::new("start_button", "Start"))
251 .child(Tab::new("tab1"))
252 .child(Tab::new("tab2"))
253 .child(Tab::new("tab3"))
254 .end_child(Button::new("end_button", "End"))
255 .into_any_element(),
256 )],
257 ),
258 ])
259 .into_any_element(),
260 )
261 }
262}