Skip to main content

guise/
chip.rs

1//! `Chip` — a selectable pill (controlled).
2
3use gpui::prelude::*;
4use gpui::{div, px, App, ClickEvent, ElementId, FontWeight, IntoElement, SharedString, Window};
5
6use crate::input::ClickHandler;
7use crate::style::ColorValue;
8use crate::theme::{theme, Size};
9
10/// A selectable chip. The Mantine `Chip`. Controlled: pass `checked` and a
11/// change handler via `cx.listener`.
12#[derive(IntoElement)]
13pub struct Chip {
14    id: ElementId,
15    label: SharedString,
16    checked: bool,
17    color: ColorValue,
18    size: Size,
19    on_change: Option<ClickHandler>,
20}
21
22impl Chip {
23    pub fn new(id: impl Into<ElementId>, label: impl Into<SharedString>) -> Self {
24        Chip {
25            id: id.into(),
26            label: label.into(),
27            checked: false,
28            color: ColorValue::default(),
29            size: Size::Md,
30            on_change: None,
31        }
32    }
33
34    pub fn checked(mut self, checked: bool) -> Self {
35        self.checked = checked;
36        self
37    }
38
39    pub fn color(mut self, color: impl Into<ColorValue>) -> Self {
40        self.color = color.into();
41        self
42    }
43
44    pub fn size(mut self, size: Size) -> Self {
45        self.size = size;
46        self
47    }
48
49    pub fn on_change(
50        mut self,
51        handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
52    ) -> Self {
53        self.on_change = Some(Box::new(handler));
54        self
55    }
56
57    fn metrics(&self) -> (f32, f32, f32) {
58        match self.size {
59            Size::Xs => (24.0, 10.0, 11.0),
60            Size::Sm => (28.0, 12.0, 12.0),
61            Size::Md => (32.0, 16.0, 14.0),
62            Size::Lg => (38.0, 20.0, 16.0),
63            Size::Xl => (44.0, 24.0, 18.0),
64        }
65    }
66}
67
68impl RenderOnce for Chip {
69    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
70        let t = theme(cx);
71        let (height, pad_x, font) = self.metrics();
72        let accent = self.color.accent(t);
73
74        let (bg, fg, border) = if self.checked {
75            (self.color.soft(t), accent, accent)
76        } else {
77            (t.surface().hsla(), t.text().hsla(), t.border().hsla())
78        };
79        let hover_bg = t.surface_hover().hsla();
80
81        let mut el = div()
82            .id(self.id)
83            .flex()
84            .items_center()
85            .gap(px(6.0))
86            .h(px(height))
87            .px(px(pad_x))
88            .rounded(px(height))
89            .border_1()
90            .border_color(border)
91            .bg(bg)
92            .text_color(fg)
93            .text_size(px(font))
94            .font_weight(FontWeight::MEDIUM);
95        if self.checked {
96            el = el.child(SharedString::new_static("\u{2713}"));
97        } else {
98            el = el.hover(move |s| s.bg(hover_bg));
99        }
100        el = el.child(self.label);
101        if let Some(handler) = self.on_change {
102            el = el.on_click(handler);
103        }
104        el
105    }
106}