use gpui::{FontWeight, Hsla, white};
use crate::prelude::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum BadgeVariant {
#[default]
Soft,
Solid,
Outline,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum BadgeColor {
#[default]
Neutral,
Primary,
Success,
Warning,
Danger,
}
impl BadgeColor {
fn shade(self, step: u16) -> Hsla {
match self {
BadgeColor::Neutral => palette::neutral(step),
BadgeColor::Primary => palette::primary(step),
BadgeColor::Success => palette::success(step),
BadgeColor::Warning => palette::warning(step),
BadgeColor::Danger => palette::danger(step),
}
}
}
#[derive(IntoElement, RegisterComponent)]
pub struct Badge {
label: SharedString,
variant: BadgeVariant,
color: BadgeColor,
dot: bool,
}
impl Badge {
pub fn new(label: impl Into<SharedString>) -> Self {
Self {
label: label.into(),
variant: BadgeVariant::default(),
color: BadgeColor::default(),
dot: false,
}
}
pub fn variant(mut self, variant: BadgeVariant) -> Self {
self.variant = variant;
self
}
pub fn color(mut self, color: BadgeColor) -> Self {
self.color = color;
self
}
pub fn dot(mut self, dot: bool) -> Self {
self.dot = dot;
self
}
}
impl RenderOnce for Badge {
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
let mut base = h_flex()
.items_center()
.gap_1()
.px_2()
.py_0p5()
.rounded_full();
base = match self.variant {
BadgeVariant::Soft => base
.bg(self.color.shade(100))
.text_color(self.color.shade(800)),
BadgeVariant::Solid => base.bg(self.color.shade(600)).text_color(white()),
BadgeVariant::Outline => base
.border_1()
.border_color(self.color.shade(300))
.text_color(self.color.shade(700)),
};
base.when(self.dot, |this| {
this.child(div().size_1p5().rounded_full().bg(self.color.shade(500)))
})
.child(
Label::new(self.label)
.size(LabelSize::XSmall)
.weight(FontWeight::MEDIUM)
.color(Color::Custom(match self.variant {
BadgeVariant::Solid => white(),
BadgeVariant::Soft => self.color.shade(800),
BadgeVariant::Outline => self.color.shade(700),
})),
)
}
}
impl Component for Badge {
fn scope() -> ComponentScope {
ComponentScope::Status
}
fn description() -> Option<&'static str> {
Some("A small pill label conveying status or category.")
}
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
let colors = [
("Neutral", BadgeColor::Neutral),
("Primary", BadgeColor::Primary),
("Success", BadgeColor::Success),
("Warning", BadgeColor::Warning),
("Danger", BadgeColor::Danger),
];
let row = |variant: BadgeVariant, dot: bool| {
let mut r = h_flex().gap_2();
for (name, color) in colors {
r = r.child(Badge::new(name).variant(variant).color(color).dot(dot));
}
r
};
let status_row = || {
h_flex()
.gap_2()
.child(
Badge::new("Active")
.variant(BadgeVariant::Soft)
.color(BadgeColor::Success)
.dot(true),
)
.child(
Badge::new("Pending")
.variant(BadgeVariant::Soft)
.color(BadgeColor::Warning)
.dot(true),
)
.child(
Badge::new("Failed")
.variant(BadgeVariant::Solid)
.color(BadgeColor::Danger),
)
.child(
Badge::new("Draft")
.variant(BadgeVariant::Outline)
.color(BadgeColor::Neutral),
)
};
Some(
v_flex()
.gap_3()
.child(row(BadgeVariant::Soft, true))
.child(row(BadgeVariant::Solid, true))
.child(row(BadgeVariant::Outline, true))
.child(row(BadgeVariant::Soft, false))
.child(row(BadgeVariant::Solid, false))
.child(row(BadgeVariant::Outline, false))
.child(status_row())
.into_any_element(),
)
}
}