1use crate::{prelude::*, Icon, IconButton, Tab};
2use gpui::prelude::*;
3use gpui::Div;
4use gpui::Stateful;
5
6#[derive(RenderOnce)]
7pub struct TabBar {
8 id: ElementId,
9 /// Backwards, Forwards
10 can_navigate: (bool, bool),
11 tabs: Vec<Tab>,
12}
13
14impl<V: 'static> Component<V> for TabBar {
15 type Rendered = Stateful<V, Div<V>>;
16
17 fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
18 let (can_navigate_back, can_navigate_forward) = self.can_navigate;
19
20 div()
21 .group("tab_bar")
22 .id(self.id.clone())
23 .w_full()
24 .flex()
25 .bg(cx.theme().colors().tab_bar_background)
26 // Left Side
27 .child(
28 div()
29 .relative()
30 .px_1()
31 .flex()
32 .flex_none()
33 .gap_2()
34 // Nav Buttons
35 .child(
36 div()
37 .right_0()
38 .flex()
39 .items_center()
40 .gap_px()
41 .child(
42 IconButton::new("arrow_left", Icon::ArrowLeft)
43 .state(InteractionState::Enabled.if_enabled(can_navigate_back)),
44 )
45 .child(
46 IconButton::new("arrow_right", Icon::ArrowRight).state(
47 InteractionState::Enabled.if_enabled(can_navigate_forward),
48 ),
49 ),
50 ),
51 )
52 .child(
53 div().w_0().flex_1().h_full().child(
54 div()
55 .id("tabs")
56 .flex()
57 .overflow_x_scroll()
58 .children(self.tabs.clone()),
59 ),
60 )
61 // Right Side
62 .child(
63 div()
64 // We only use absolute here since we don't
65 // have opacity or `hidden()` yet
66 .absolute()
67 .neg_top_7()
68 .px_1()
69 .flex()
70 .flex_none()
71 .gap_2()
72 .group_hover("tab_bar", |this| this.top_0())
73 // Nav Buttons
74 .child(
75 div()
76 .flex()
77 .items_center()
78 .gap_px()
79 .child(IconButton::new("plus", Icon::Plus))
80 .child(IconButton::new("split", Icon::Split)),
81 ),
82 )
83 }
84}
85
86impl TabBar {
87 pub fn new(id: impl Into<ElementId>, tabs: Vec<Tab>) -> Self {
88 Self {
89 id: id.into(),
90 can_navigate: (false, false),
91 tabs,
92 }
93 }
94
95 pub fn can_navigate(mut self, can_navigate: (bool, bool)) -> Self {
96 self.can_navigate = can_navigate;
97 self
98 }
99}
100
101use gpui::ElementId;
102#[cfg(feature = "stories")]
103pub use stories::*;
104
105#[cfg(feature = "stories")]
106mod stories {
107 use super::*;
108 use crate::Story;
109 use gpui::{Div, Render};
110
111 pub struct TabBarStory;
112
113 impl Render<Self> for TabBarStory {
114 type Element = Div<Self>;
115
116 fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
117 Story::container(cx)
118 .child(Story::title_for::<_, TabBar>(cx))
119 .child(Story::label(cx, "Default"))
120 .child(TabBar::new(
121 "tab-bar",
122 vec![
123 Tab::new(1)
124 .title("Cargo.toml".to_string())
125 .current(false)
126 .git_status(GitStatus::Modified),
127 Tab::new(2)
128 .title("Channels Panel".to_string())
129 .current(false),
130 Tab::new(3)
131 .title("channels_panel.rs".to_string())
132 .current(true)
133 .git_status(GitStatus::Modified),
134 Tab::new(4)
135 .title("workspace.rs".to_string())
136 .current(false)
137 .git_status(GitStatus::Modified),
138 Tab::new(5)
139 .title("icon_button.rs".to_string())
140 .current(false),
141 Tab::new(6)
142 .title("storybook.rs".to_string())
143 .current(false)
144 .git_status(GitStatus::Created),
145 Tab::new(7).title("theme.rs".to_string()).current(false),
146 Tab::new(8)
147 .title("theme_registry.rs".to_string())
148 .current(false),
149 Tab::new(9)
150 .title("styleable_helpers.rs".to_string())
151 .current(false),
152 ],
153 ))
154 }
155 }
156}