ui/components/
breadcrumb.rs1use gpui::{AnyElement, ClickEvent};
2use smallvec::SmallVec;
3
4use crate::prelude::*;
5
6pub struct BreadcrumbItem {
12 id: ElementId,
13 label: SharedString,
14 on_click: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
15}
16
17impl BreadcrumbItem {
18 pub fn new(id: impl Into<ElementId>, label: impl Into<SharedString>) -> Self {
19 Self {
20 id: id.into(),
21 label: label.into(),
22 on_click: None,
23 }
24 }
25
26 pub fn on_click(
27 mut self,
28 handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
29 ) -> Self {
30 self.on_click = Some(Box::new(handler));
31 self
32 }
33}
34
35#[derive(IntoElement, RegisterComponent)]
37pub struct Breadcrumb {
38 items: SmallVec<[BreadcrumbItem; 4]>,
39}
40
41impl Breadcrumb {
42 pub fn new() -> Self {
43 Self {
44 items: SmallVec::new(),
45 }
46 }
47
48 pub fn item(mut self, item: BreadcrumbItem) -> Self {
49 self.items.push(item);
50 self
51 }
52
53 pub fn items(mut self, items: impl IntoIterator<Item = BreadcrumbItem>) -> Self {
54 self.items.extend(items);
55 self
56 }
57}
58
59impl Default for Breadcrumb {
60 fn default() -> Self {
61 Self::new()
62 }
63}
64
65impl RenderOnce for Breadcrumb {
66 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
67 let last_index = self.items.len().saturating_sub(1);
68 let hover_color = semantic::text(cx);
69
70 h_flex()
71 .items_center()
72 .gap_1()
73 .children(self.items.into_iter().enumerate().map(|(index, item)| {
74 let is_current = index == last_index;
75
76 let crumb = if is_current {
77 div()
78 .text_color(semantic::text(cx))
79 .child(Label::new(item.label))
80 .into_any_element()
81 } else {
82 h_flex()
83 .id(item.id)
84 .cursor_pointer()
85 .text_color(semantic::text_muted(cx))
86 .hover(move |this| this.text_color(hover_color))
87 .child(Label::new(item.label))
88 .when_some(item.on_click, |this, handler| this.on_click(handler))
89 .into_any_element()
90 };
91
92 h_flex()
93 .items_center()
94 .gap_1()
95 .when(index > 0, |this| {
96 this.child(
97 Icon::new(IconName::ChevronRight)
98 .size(IconSize::XSmall)
99 .color(Color::Muted),
100 )
101 })
102 .child(crumb)
103 .into_any_element()
104 }))
105 }
106}
107
108impl Component for Breadcrumb {
109 fn scope() -> ComponentScope {
110 ComponentScope::Navigation
111 }
112
113 fn description() -> Option<&'static str> {
114 Some("A horizontal trail of links showing the user's location within a hierarchy.")
115 }
116
117 fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
118 Some(
119 v_flex()
120 .gap_6()
121 .child(example_group_with_title(
122 "Basic Usage",
123 vec![
124 single_example(
125 "Three Levels",
126 Breadcrumb::new()
127 .item(BreadcrumbItem::new("crumb-home", "Home"))
128 .item(BreadcrumbItem::new("crumb-projects", "Projects"))
129 .item(BreadcrumbItem::new("crumb-current", "Current Page"))
130 .into_any_element(),
131 ),
132 single_example(
133 "Deep Hierarchy",
134 Breadcrumb::new()
135 .item(BreadcrumbItem::new("crumb-org", "Acme Corp"))
136 .item(BreadcrumbItem::new("crumb-team", "Engineering"))
137 .item(BreadcrumbItem::new("crumb-repo", "rust-dex"))
138 .item(BreadcrumbItem::new("crumb-file", "gallery_app.rs"))
139 .into_any_element(),
140 ),
141 single_example(
142 "Two Levels",
143 Breadcrumb::new()
144 .item(BreadcrumbItem::new("crumb-root", "Dashboard"))
145 .item(BreadcrumbItem::new("crumb-leaf", "Settings"))
146 .into_any_element(),
147 ),
148 ],
149 ))
150 .into_any_element(),
151 )
152 }
153}