boltz-ui 0.1.0

High-level reusable GPUI UI components (Label, Button, Input, etc.).
Documentation
use crate::prelude::*;
use gpui::{AnyElement, AnyView, ClickEvent, Hsla, IntoElement, ParentElement, Styled};

/// Chips provide a container for an informative label.
///
/// # Usage Example
///
/// ```
/// use ui::Chip;
///
/// let chip = Chip::new("This Chip");
/// ```
#[derive(IntoElement, RegisterComponent)]
pub struct Chip {
    label: SharedString,
    label_color: Color,
    label_size: LabelSize,
    icon: Option<IconName>,
    icon_color: Color,
    bg_color: Option<Hsla>,
    border_color: Option<Hsla>,
    height: Option<Pixels>,
    truncate: bool,
    pill: bool,
    on_dismiss: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
    tooltip: Option<Box<dyn Fn(&mut Window, &mut App) -> AnyView + 'static>>,
}

impl Chip {
    /// Creates a new `Chip` component with the specified label.
    pub fn new(label: impl Into<SharedString>) -> Self {
        Self {
            label: label.into(),
            label_color: Color::Default,
            label_size: LabelSize::XSmall,
            icon: None,
            icon_color: Color::Default,
            bg_color: None,
            border_color: None,
            height: None,
            truncate: false,
            pill: false,
            on_dismiss: None,
            tooltip: None,
        }
    }

    /// Sets the color of the label.
    pub fn label_color(mut self, color: Color) -> Self {
        self.label_color = color;
        self
    }

    /// Sets the size of the label.
    pub fn label_size(mut self, size: LabelSize) -> Self {
        self.label_size = size;
        self
    }

    /// Sets an icon to display before the label.
    pub fn icon(mut self, icon: IconName) -> Self {
        self.icon = Some(icon);
        self
    }

    /// Sets the color of the icon.
    pub fn icon_color(mut self, color: Color) -> Self {
        self.icon_color = color;
        self
    }

    /// Sets a custom background color for the callout content.
    pub fn bg_color(mut self, color: Hsla) -> Self {
        self.bg_color = Some(color);
        self
    }

    /// Sets a custom border color for the chip.
    pub fn border_color(mut self, color: Hsla) -> Self {
        self.border_color = Some(color);
        self
    }

    /// Sets a custom height for the chip.
    pub fn height(mut self, height: Pixels) -> Self {
        self.height = Some(height);
        self
    }

    /// Allows the chip to shrink and truncate its label when space is limited.
    pub fn truncate(mut self) -> Self {
        self.truncate = true;
        self
    }

    /// Uses fully-rounded (`rounded_full`) corners instead of `rounded_md`.
    pub fn pill(mut self, pill: bool) -> Self {
        self.pill = pill;
        self
    }

    /// Adds a dismiss ("x") affordance; clicking it invokes `on_dismiss`.
    pub fn dismissible(
        mut self,
        on_dismiss: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
    ) -> Self {
        self.on_dismiss = Some(Box::new(on_dismiss));
        self
    }

    pub fn tooltip(mut self, tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static) -> Self {
        self.tooltip = Some(Box::new(tooltip));
        self
    }
}

impl RenderOnce for Chip {
    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
        let bg_color = self.bg_color.unwrap_or_else(|| semantic::surface(cx));
        let border_color = self.border_color.unwrap_or_else(|| semantic::border(cx));
        let pill = self.pill;

        h_flex()
            .when_some(self.height, |this, h| this.h(h))
            .when(self.truncate, |this| this.min_w_0())
            .when(!self.truncate, |this| this.flex_none())
            .gap_1()
            .px_2()
            .py_0p5()
            .border_1()
            .when(pill, |this| this.rounded_full())
            .when(!pill, |this| this.rounded_md())
            .border_color(border_color)
            .bg(bg_color)
            .overflow_hidden()
            .when_some(self.icon, |this, icon| {
                this.child(
                    Icon::new(icon)
                        .size(IconSize::XSmall)
                        .color(self.icon_color),
                )
            })
            .child(
                Label::new(self.label.clone())
                    .size(self.label_size)
                    .color(self.label_color)
                    .buffer_font(cx)
                    .truncate(),
            )
            .id(self.label.clone())
            .when_some(self.on_dismiss, |this, on_dismiss| {
                this.child(
                    div()
                        .id("dismiss")
                        .cursor_pointer()
                        .child(
                            Icon::new(IconName::XMark)
                                .size(IconSize::XSmall)
                                .color(Color::Muted),
                        )
                        .on_click(move |event, window, cx| {
                            cx.stop_propagation();
                            on_dismiss(event, window, cx)
                        }),
                )
            })
            .when_some(self.tooltip, |this, tooltip| this.tooltip(tooltip))
    }
}

impl Component for Chip {
    fn scope() -> ComponentScope {
        ComponentScope::DataDisplay
    }

    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
        let chip_examples = vec![
            single_example("Default", Chip::new("Chip Example").into_any_element()),
            single_example(
                "Pill",
                Chip::new("Chip Example").pill(true).into_any_element(),
            ),
            single_example(
                "Customized Label Color",
                Chip::new("Chip Example")
                    .label_color(Color::Accent)
                    .into_any_element(),
            ),
            single_example(
                "Customized Label Size",
                Chip::new("Chip Example")
                    .label_size(LabelSize::Large)
                    .label_color(Color::Accent)
                    .into_any_element(),
            ),
            single_example(
                "With Icon",
                Chip::new("Chip Example")
                    .icon(IconName::Check)
                    .into_any_element(),
            ),
            single_example(
                "Dismissible",
                Chip::new("Chip Example")
                    .dismissible(|_, _, _| {})
                    .into_any_element(),
            ),
        ];

        Some(example_group(chip_examples).vertical().into_any_element())
    }
}