1use gpui::{AnyElement, Div, IntoElement, Stateful, transparent_black};
2use smallvec::SmallVec;
3
4use crate::TabBarStyle;
5use crate::prelude::*;
6
7#[derive(IntoElement, RegisterComponent)]
12pub struct Tab {
13 div: Stateful<Div>,
14 selected: bool,
15 style: TabBarStyle,
16 start_slot: Option<AnyElement>,
17 end_slot: Option<AnyElement>,
18 children: SmallVec<[AnyElement; 2]>,
19}
20
21impl Tab {
22 pub fn new(id: impl Into<ElementId>) -> Self {
23 let id = id.into();
24 Self {
25 div: div()
26 .id(id.clone())
27 .debug_selector(|| format!("TAB-{}", id)),
28 selected: false,
29 style: TabBarStyle::default(),
30 start_slot: None,
31 end_slot: None,
32 children: SmallVec::new(),
33 }
34 }
35
36 pub fn style(mut self, style: TabBarStyle) -> Self {
38 self.style = style;
39 self
40 }
41
42 pub fn start_slot<E: IntoElement>(mut self, element: impl Into<Option<E>>) -> Self {
43 self.start_slot = element.into().map(IntoElement::into_any_element);
44 self
45 }
46
47 pub fn end_slot<E: IntoElement>(mut self, element: impl Into<Option<E>>) -> Self {
48 self.end_slot = element.into().map(IntoElement::into_any_element);
49 self
50 }
51
52 pub fn content_height(cx: &App) -> Pixels {
53 DynamicSpacing::Base32.px(cx) - px(1.)
54 }
55
56 pub fn container_height(cx: &App) -> Pixels {
57 DynamicSpacing::Base32.px(cx)
58 }
59}
60
61impl InteractiveElement for Tab {
62 fn interactivity(&mut self) -> &mut gpui::Interactivity {
63 self.div.interactivity()
64 }
65}
66
67impl StatefulInteractiveElement for Tab {}
68
69impl Toggleable for Tab {
70 fn toggle_state(mut self, selected: bool) -> Self {
71 self.selected = selected;
72 self
73 }
74}
75
76impl ParentElement for Tab {
77 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
78 self.children.extend(elements)
79 }
80}
81
82impl RenderOnce for Tab {
83 #[allow(refining_impl_trait)]
84 fn render(self, _: &mut Window, cx: &mut App) -> Stateful<Div> {
85 let content = h_flex()
86 .items_center()
87 .gap_2()
88 .children(self.start_slot)
89 .children(self.children)
90 .children(self.end_slot);
91
92 match self.style {
93 TabBarStyle::Underline => {
94 let (text_color, border_color, hover_color) = if self.selected {
95 (
96 palette::primary(600),
97 palette::primary(600),
98 palette::primary(600),
99 )
100 } else {
101 (
102 semantic::text_muted(cx),
103 transparent_black(),
104 semantic::text(cx),
105 )
106 };
107
108 self.div
113 .h_full()
114 .flex()
115 .items_center()
116 .cursor_pointer()
117 .px_2()
118 .border_b_2()
119 .border_color(border_color)
120 .text_color(text_color)
121 .hover(move |this| this.text_color(hover_color))
122 .child(content)
123 }
124 TabBarStyle::Pills => {
125 let (text_color, bg) = if self.selected {
126 (semantic::text(cx), semantic::surface(cx))
127 } else {
128 (semantic::text_muted(cx), transparent_black())
129 };
130 let hover_bg = semantic::hover_bg(cx);
131
132 self.div
133 .cursor_pointer()
134 .px_3()
135 .py_1p5()
136 .rounded_md()
137 .bg(bg)
138 .text_color(text_color)
139 .when(self.selected, |this| this.shadow_level(Shadow::Sm))
140 .when(!self.selected, |this| {
141 this.hover(move |this| this.bg(hover_bg))
142 })
143 .child(content)
144 }
145 }
146 }
147}
148
149impl Component for Tab {
150 fn scope() -> ComponentScope {
151 ComponentScope::Navigation
152 }
153
154 fn description() -> Option<&'static str> {
155 Some(
156 "A tab component that can be used in a tabbed interface, supporting underline and pills styles.",
157 )
158 }
159
160 fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
161 Some(
162 v_flex()
163 .gap_6()
164 .children(vec![
165 example_group_with_title(
166 "Underline",
167 vec![
168 single_example(
169 "Default",
170 Tab::new("underline_default")
171 .child("Default Tab")
172 .into_any_element(),
173 ),
174 single_example(
175 "Selected",
176 Tab::new("underline_selected")
177 .toggle_state(true)
178 .child("Selected Tab")
179 .into_any_element(),
180 ),
181 ],
182 ),
183 example_group_with_title(
184 "Pills",
185 vec![
186 single_example(
187 "Default",
188 Tab::new("pills_default")
189 .style(TabBarStyle::Pills)
190 .child("Default Tab")
191 .into_any_element(),
192 ),
193 single_example(
194 "Selected",
195 Tab::new("pills_selected")
196 .style(TabBarStyle::Pills)
197 .toggle_state(true)
198 .child("Selected Tab")
199 .into_any_element(),
200 ),
201 ],
202 ),
203 ])
204 .into_any_element(),
205 )
206 }
207}