Skip to main content

ui/components/
chip.rs

1use crate::prelude::*;
2use gpui::{AnyElement, AnyView, ClickEvent, Hsla, IntoElement, ParentElement, Styled};
3
4/// Chips provide a container for an informative label.
5///
6/// # Usage Example
7///
8/// ```
9/// use ui::Chip;
10///
11/// let chip = Chip::new("This Chip");
12/// ```
13#[derive(IntoElement, RegisterComponent)]
14pub struct Chip {
15    label: SharedString,
16    label_color: Color,
17    label_size: LabelSize,
18    icon: Option<IconName>,
19    icon_color: Color,
20    bg_color: Option<Hsla>,
21    border_color: Option<Hsla>,
22    height: Option<Pixels>,
23    truncate: bool,
24    pill: bool,
25    on_dismiss: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
26    tooltip: Option<Box<dyn Fn(&mut Window, &mut App) -> AnyView + 'static>>,
27}
28
29impl Chip {
30    /// Creates a new `Chip` component with the specified label.
31    pub fn new(label: impl Into<SharedString>) -> Self {
32        Self {
33            label: label.into(),
34            label_color: Color::Default,
35            label_size: LabelSize::XSmall,
36            icon: None,
37            icon_color: Color::Default,
38            bg_color: None,
39            border_color: None,
40            height: None,
41            truncate: false,
42            pill: false,
43            on_dismiss: None,
44            tooltip: None,
45        }
46    }
47
48    /// Sets the color of the label.
49    pub fn label_color(mut self, color: Color) -> Self {
50        self.label_color = color;
51        self
52    }
53
54    /// Sets the size of the label.
55    pub fn label_size(mut self, size: LabelSize) -> Self {
56        self.label_size = size;
57        self
58    }
59
60    /// Sets an icon to display before the label.
61    pub fn icon(mut self, icon: IconName) -> Self {
62        self.icon = Some(icon);
63        self
64    }
65
66    /// Sets the color of the icon.
67    pub fn icon_color(mut self, color: Color) -> Self {
68        self.icon_color = color;
69        self
70    }
71
72    /// Sets a custom background color for the callout content.
73    pub fn bg_color(mut self, color: Hsla) -> Self {
74        self.bg_color = Some(color);
75        self
76    }
77
78    /// Sets a custom border color for the chip.
79    pub fn border_color(mut self, color: Hsla) -> Self {
80        self.border_color = Some(color);
81        self
82    }
83
84    /// Sets a custom height for the chip.
85    pub fn height(mut self, height: Pixels) -> Self {
86        self.height = Some(height);
87        self
88    }
89
90    /// Allows the chip to shrink and truncate its label when space is limited.
91    pub fn truncate(mut self) -> Self {
92        self.truncate = true;
93        self
94    }
95
96    /// Uses fully-rounded (`rounded_full`) corners instead of `rounded_md`.
97    pub fn pill(mut self, pill: bool) -> Self {
98        self.pill = pill;
99        self
100    }
101
102    /// Adds a dismiss ("x") affordance; clicking it invokes `on_dismiss`.
103    pub fn dismissible(
104        mut self,
105        on_dismiss: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
106    ) -> Self {
107        self.on_dismiss = Some(Box::new(on_dismiss));
108        self
109    }
110
111    pub fn tooltip(mut self, tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static) -> Self {
112        self.tooltip = Some(Box::new(tooltip));
113        self
114    }
115}
116
117impl RenderOnce for Chip {
118    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
119        let bg_color = self.bg_color.unwrap_or_else(|| semantic::surface(cx));
120        let border_color = self.border_color.unwrap_or_else(|| semantic::border(cx));
121        let pill = self.pill;
122
123        h_flex()
124            .when_some(self.height, |this, h| this.h(h))
125            .when(self.truncate, |this| this.min_w_0())
126            .when(!self.truncate, |this| this.flex_none())
127            .gap_1()
128            .px_2()
129            .py_0p5()
130            .border_1()
131            .when(pill, |this| this.rounded_full())
132            .when(!pill, |this| this.rounded_md())
133            .border_color(border_color)
134            .bg(bg_color)
135            .overflow_hidden()
136            .when_some(self.icon, |this, icon| {
137                this.child(
138                    Icon::new(icon)
139                        .size(IconSize::XSmall)
140                        .color(self.icon_color),
141                )
142            })
143            .child(
144                Label::new(self.label.clone())
145                    .size(self.label_size)
146                    .color(self.label_color)
147                    .buffer_font(cx)
148                    .truncate(),
149            )
150            .id(self.label.clone())
151            .when_some(self.on_dismiss, |this, on_dismiss| {
152                this.child(
153                    div()
154                        .id("dismiss")
155                        .cursor_pointer()
156                        .child(
157                            Icon::new(IconName::XMark)
158                                .size(IconSize::XSmall)
159                                .color(Color::Muted),
160                        )
161                        .on_click(move |event, window, cx| {
162                            cx.stop_propagation();
163                            on_dismiss(event, window, cx)
164                        }),
165                )
166            })
167            .when_some(self.tooltip, |this, tooltip| this.tooltip(tooltip))
168    }
169}
170
171impl Component for Chip {
172    fn scope() -> ComponentScope {
173        ComponentScope::DataDisplay
174    }
175
176    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
177        let chip_examples = vec![
178            single_example("Default", Chip::new("Chip Example").into_any_element()),
179            single_example(
180                "Pill",
181                Chip::new("Chip Example").pill(true).into_any_element(),
182            ),
183            single_example(
184                "Customized Label Color",
185                Chip::new("Chip Example")
186                    .label_color(Color::Accent)
187                    .into_any_element(),
188            ),
189            single_example(
190                "Customized Label Size",
191                Chip::new("Chip Example")
192                    .label_size(LabelSize::Large)
193                    .label_color(Color::Accent)
194                    .into_any_element(),
195            ),
196            single_example(
197                "With Icon",
198                Chip::new("Chip Example")
199                    .icon(IconName::Check)
200                    .into_any_element(),
201            ),
202            single_example(
203                "Dismissible",
204                Chip::new("Chip Example")
205                    .dismissible(|_, _, _| {})
206                    .into_any_element(),
207            ),
208        ];
209
210        Some(example_group(chip_examples).vertical().into_any_element())
211    }
212}