use gpui_kit::base::StyledExt as _;
use gpui_kit::{
AnyElement, App, ElementId, FontWeight, Hsla, InteractiveElement as _, IntoElement,
ParentElement, RenderOnce, SharedString, StyleRefinement, Styled, TestSupportExt as _, Window,
div, prelude::FluentBuilder as _,
};
use crate::{ActiveTheme as _, Icon, theme::ThemeTokens};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[non_exhaustive]
pub enum TagVariant {
#[default]
Primary,
Secondary,
Danger,
Success,
Warning,
Info,
}
impl TagVariant {
fn colors(self, outline: bool, theme: &ThemeTokens) -> (Hsla, Hsla, Hsla) {
let clear = gpui_kit::transparent_black();
let status = match self {
Self::Primary | Self::Secondary => None,
Self::Danger => Some((theme.destructive(), theme.destructive_tint)),
Self::Success => Some((theme.success, theme.success_tint)),
Self::Warning => Some((theme.warning, theme.warning_tint)),
Self::Info => Some((theme.info, theme.info_tint)),
};
match (status, outline) {
(Some((color, _)), true) => (clear, color, color),
(Some((color, tint)), false) => (tint, color, clear),
(None, true) => (clear, theme.foreground(), theme.input()),
(None, false) if self == Self::Primary => {
(theme.primary(), theme.primary_foreground(), clear)
}
(None, false) => (theme.selected, theme.foreground(), clear),
}
}
}
#[derive(IntoElement)]
pub struct Tag {
id: ElementId,
style: StyleRefinement,
variant: TagVariant,
outline: bool,
icon: Option<Icon>,
label: Option<SharedString>,
children: Vec<AnyElement>,
}
impl Tag {
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
id: id.into(),
style: StyleRefinement::default(),
variant: TagVariant::default(),
outline: false,
icon: None,
label: None,
children: Vec::new(),
}
}
pub fn label(mut self, label: impl Into<SharedString>) -> Self {
self.label = Some(label.into());
self
}
pub fn icon(mut self, icon: impl Into<Icon>) -> Self {
self.icon = Some(icon.into());
self
}
pub fn variant(mut self, variant: TagVariant) -> Self {
self.variant = variant;
self
}
pub fn secondary(self) -> Self {
self.variant(TagVariant::Secondary)
}
pub fn danger(self) -> Self {
self.variant(TagVariant::Danger)
}
pub fn success(self) -> Self {
self.variant(TagVariant::Success)
}
pub fn warning(self) -> Self {
self.variant(TagVariant::Warning)
}
pub fn info(self) -> Self {
self.variant(TagVariant::Info)
}
pub fn outline(mut self) -> Self {
self.outline = true;
self
}
}
impl Styled for Tag {
fn style(&mut self) -> &mut StyleRefinement {
&mut self.style
}
}
impl ParentElement for Tag {
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
self.children.extend(elements);
}
}
impl RenderOnce for Tag {
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
let theme = cx.theme();
let (fill, ink, border) = self.variant.colors(self.outline, theme);
let (height, icon_size) = (theme.metrics.tag_height, theme.metrics.tag_icon);
let text = theme.base.typography.xs;
div()
.id(self.id)
.test_support()
.flex_none()
.flex()
.items_center()
.justify_center()
.gap_1()
.h(height)
.px_2()
.overflow_hidden()
.whitespace_nowrap()
.rounded_full()
.border_1()
.border_color(border)
.bg(fill)
.text_color(ink)
.text_size(text.size)
.line_height(text.line_height)
.font_weight(FontWeight::MEDIUM)
.when_some(self.icon, |this, icon| this.child(icon.size(icon_size)))
.when_some(self.label, |this, label| this.child(label))
.children(self.children)
.refine_style(&self.style)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::theme::{ThemeConfig, to_hex};
use gpui_kit::base::ThemeAppearance;
#[test]
fn variants_paint_from_the_theme() {
let dark = crate::theme::test_tokens(&ThemeConfig::dark(), ThemeAppearance::Dark);
let light = crate::theme::test_tokens(&ThemeConfig::light(), ThemeAppearance::Light);
let hex =
|(fill, ink, border): (Hsla, Hsla, Hsla)| [to_hex(fill), to_hex(ink), to_hex(border)];
assert_eq!(
hex(TagVariant::Primary.colors(false, &dark)),
["#dfdfdf", "#2d2d2d", "#00000000"].map(String::from),
);
assert_eq!(
hex(TagVariant::Secondary.colors(false, &light)),
["#e8e8e8", "#1a1c1f", "#00000000"].map(String::from),
);
assert_eq!(
hex(TagVariant::Success.colors(false, &dark)),
["#25372a", "#40c977", "#00000000"].map(String::from),
);
assert_eq!(
hex(TagVariant::Info.colors(true, &light)),
["#00000000", "#339cff", "#339cff"].map(String::from),
);
assert_eq!(
hex(TagVariant::Primary.colors(true, &light)),
["#00000000", "#1a1c1f", "#e5e5e6"].map(String::from),
);
}
}