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, bg) = if self.selected {
98 (semantic::text(cx), semantic::active_bg(cx))
99 } else {
100 (semantic::text_muted(cx), transparent_black())
101 };
102 let hover_color = semantic::text(cx);
103
104 self.div
109 .h_full()
110 .flex()
111 .items_center()
112 .cursor_pointer()
113 .px_2()
114 .rounded_md()
115 .bg(bg)
116 .text_color(text_color)
117 .hover(move |this| this.text_color(hover_color))
118 .child(content)
119 }
120 TabBarStyle::Pills => {
121 let (text_color, bg) = if self.selected {
122 (semantic::text(cx), semantic::surface(cx))
123 } else {
124 (semantic::text_muted(cx), transparent_black())
125 };
126 let hover_bg = semantic::hover_bg(cx);
127
128 self.div
129 .cursor_pointer()
130 .px_3()
131 .py_1p5()
132 .rounded_md()
133 .bg(bg)
134 .text_color(text_color)
135 .when(self.selected, |this| this.shadow_level(Shadow::Sm))
136 .when(!self.selected, |this| {
137 this.hover(move |this| this.bg(hover_bg))
138 })
139 .child(content)
140 }
141 }
142 }
143}
144
145impl Component for Tab {
146 fn scope() -> ComponentScope {
147 ComponentScope::Navigation
148 }
149
150 fn description() -> Option<&'static str> {
151 Some(
152 "A tab component that can be used in a tabbed interface, supporting underline and pills styles.",
153 )
154 }
155
156 fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
157 Some(
158 v_flex()
159 .gap_6()
160 .children(vec![
161 example_group_with_title(
162 "Underline",
163 vec![
164 single_example(
165 "Default",
166 Tab::new("underline_default")
167 .child("Default Tab")
168 .into_any_element(),
169 ),
170 single_example(
171 "Selected",
172 Tab::new("underline_selected")
173 .toggle_state(true)
174 .child("Selected Tab")
175 .into_any_element(),
176 ),
177 ],
178 ),
179 example_group_with_title(
180 "Pills",
181 vec![
182 single_example(
183 "Default",
184 Tab::new("pills_default")
185 .style(TabBarStyle::Pills)
186 .child("Default Tab")
187 .into_any_element(),
188 ),
189 single_example(
190 "Selected",
191 Tab::new("pills_selected")
192 .style(TabBarStyle::Pills)
193 .toggle_state(true)
194 .child("Selected Tab")
195 .into_any_element(),
196 ),
197 ],
198 ),
199 ])
200 .into_any_element(),
201 )
202 }
203}