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