1use gpui::{AnyElement, ClickEvent, ElementId};
2use smallvec::SmallVec;
3
4use crate::prelude::*;
5
6#[derive(IntoElement, RegisterComponent)]
8pub struct Sidebar {
9 children: SmallVec<[AnyElement; 2]>,
10}
11
12impl Sidebar {
13 pub fn new() -> Self {
14 Self {
15 children: SmallVec::new(),
16 }
17 }
18}
19
20impl Default for Sidebar {
21 fn default() -> Self {
22 Self::new()
23 }
24}
25
26impl ParentElement for Sidebar {
27 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
28 self.children.extend(elements);
29 }
30}
31
32impl RenderOnce for Sidebar {
33 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
34 v_flex()
35 .w(px(256.))
36 .h_full()
37 .flex_shrink_0()
38 .gap_1()
39 .p_2()
40 .bg(semantic::surface(cx))
41 .border_r_1()
42 .border_color(semantic::border(cx))
43 .children(self.children)
44 }
45}
46
47impl Component for Sidebar {
48 fn scope() -> ComponentScope {
49 ComponentScope::Navigation
50 }
51
52 fn description() -> Option<&'static str> {
53 Some("A vertical navigation rail holding nav items.")
54 }
55
56 fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
57 Some(
58 Sidebar::new()
59 .child(
60 SidebarItem::new("nav-home", "Home")
61 .icon(IconName::Box)
62 .active(true),
63 )
64 .child(SidebarItem::new("nav-projects", "Projects").icon(IconName::Folder))
65 .child(SidebarItem::new("nav-team", "Team").icon(IconName::Person))
66 .child(SidebarItem::new("nav-settings", "Settings").icon(IconName::Settings))
67 .into_any_element(),
68 )
69 }
70}
71
72#[derive(IntoElement)]
74pub struct SidebarItem {
75 id: ElementId,
76 label: SharedString,
77 icon: Option<IconName>,
78 active: bool,
79 on_click: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
80}
81
82impl SidebarItem {
83 pub fn new(id: impl Into<ElementId>, label: impl Into<SharedString>) -> Self {
84 Self {
85 id: id.into(),
86 label: label.into(),
87 icon: None,
88 active: false,
89 on_click: None,
90 }
91 }
92
93 pub fn icon(mut self, icon: IconName) -> Self {
94 self.icon = Some(icon);
95 self
96 }
97
98 pub fn active(mut self, active: bool) -> Self {
99 self.active = active;
100 self
101 }
102
103 pub fn on_click(
104 mut self,
105 handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
106 ) -> Self {
107 self.on_click = Some(Box::new(handler));
108 self
109 }
110}
111
112impl RenderOnce for SidebarItem {
113 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
114 let text_color = if self.active {
115 semantic::text(cx)
116 } else {
117 semantic::text_muted(cx)
118 };
119 let hover = semantic::hover_bg(cx);
120
121 h_flex()
122 .id(self.id)
123 .items_center()
124 .gap_2()
125 .px_3()
126 .py_2()
127 .rounded_md()
128 .cursor_pointer()
129 .text_color(text_color)
130 .when(self.active, |this| this.bg(hover))
131 .hover(|this| this.bg(hover))
132 .when_some(self.icon, |this, icon| {
133 this.child(Icon::new(icon).size(IconSize::Small))
134 })
135 .child(Label::new(self.label))
136 .when_some(self.on_click, |this, handler| this.on_click(handler))
137 }
138}