ui/components/
vertical_nav.rs1use gpui::{AnyElement, ClickEvent};
2use smallvec::SmallVec;
3
4use crate::prelude::*;
5
6#[derive(IntoElement, RegisterComponent)]
14pub struct VerticalNav {
15 children: SmallVec<[AnyElement; 4]>,
16}
17
18impl VerticalNav {
19 pub fn new() -> Self {
20 Self {
21 children: SmallVec::new(),
22 }
23 }
24}
25
26impl Default for VerticalNav {
27 fn default() -> Self {
28 Self::new()
29 }
30}
31
32impl ParentElement for VerticalNav {
33 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
34 self.children.extend(elements);
35 }
36}
37
38impl RenderOnce for VerticalNav {
39 fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
40 v_flex().w(px(220.)).gap_1().children(self.children)
41 }
42}
43
44impl Component for VerticalNav {
45 fn scope() -> ComponentScope {
46 ComponentScope::Navigation
47 }
48
49 fn description() -> Option<&'static str> {
50 Some(
51 "A flat list of navigation links for in-page sub-navigation; distinct from the collapsible Sidebar.",
52 )
53 }
54
55 fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
56 Some(
57 v_flex()
58 .gap_6()
59 .child(example_group_with_title(
60 "Basic Usage",
61 vec![single_example(
62 "Default",
63 VerticalNav::new()
64 .child(
65 VerticalNavItem::new("vnav-general", "General")
66 .icon(IconName::Check)
67 .active(true),
68 )
69 .child(VerticalNavItem::new("vnav-security", "Security"))
70 .child(VerticalNavItem::new("vnav-billing", "Billing"))
71 .into_any_element(),
72 )],
73 ))
74 .into_any_element(),
75 )
76 }
77}
78
79#[derive(IntoElement)]
81pub struct VerticalNavItem {
82 id: ElementId,
83 label: SharedString,
84 icon: Option<IconName>,
85 active: bool,
86 on_click: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
87}
88
89impl VerticalNavItem {
90 pub fn new(id: impl Into<ElementId>, label: impl Into<SharedString>) -> Self {
91 Self {
92 id: id.into(),
93 label: label.into(),
94 icon: None,
95 active: false,
96 on_click: None,
97 }
98 }
99
100 pub fn icon(mut self, icon: IconName) -> Self {
101 self.icon = Some(icon);
102 self
103 }
104
105 pub fn active(mut self, active: bool) -> Self {
106 self.active = active;
107 self
108 }
109
110 pub fn on_click(
111 mut self,
112 handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
113 ) -> Self {
114 self.on_click = Some(Box::new(handler));
115 self
116 }
117}
118
119impl RenderOnce for VerticalNavItem {
120 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
121 let text_color = if self.active {
122 semantic::text(cx)
123 } else {
124 semantic::text_muted(cx)
125 };
126 let hover = semantic::hover_bg(cx);
127
128 h_flex()
129 .id(self.id)
130 .items_center()
131 .gap_2()
132 .px_4()
133 .py_2()
134 .rounded_md()
135 .cursor_pointer()
136 .text_color(text_color)
137 .when(self.active, |this| this.bg(semantic::elevated_surface(cx)))
138 .hover(move |this| this.bg(hover))
139 .when_some(self.icon, |this, icon| {
140 this.child(Icon::new(icon).size(IconSize::Small))
141 })
142 .child(Label::new(self.label))
143 .when_some(self.on_click, |this, handler| this.on_click(handler))
144 }
145}