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