tag: Add color to Tag to support more colors. (#698)
- Add `ColorName` enum. <img width="588" alt="image" src="https://github.com/user-attachments/assets/20670946-1a4f-45ff-b5ac-81e7eb760242" /> <img width="586" alt="image" src="https://github.com/user-attachments/assets/7ce4a87f-fa6b-4794-83c5-3f18e6d09dcb" />
This commit is contained in:
parent
4c9c7980ee
commit
3e4a3ed046
5 changed files with 210 additions and 10 deletions
|
|
@ -19,7 +19,12 @@ impl Example {
|
|||
|
||||
impl Render for Example {
|
||||
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||
div().p_4().size_full().child(self.root.clone())
|
||||
div()
|
||||
.p_4()
|
||||
.id("example")
|
||||
.overflow_y_scroll()
|
||||
.size_full()
|
||||
.child(self.root.clone())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use gpui_component::{
|
|||
link::Link,
|
||||
red_500,
|
||||
tag::Tag,
|
||||
v_flex, yellow_500, yellow_800, IconName, Kbd, Sizable, StyledExt,
|
||||
v_flex, yellow_500, yellow_800, ColorName, IconName, Kbd, Sizable, StyledExt,
|
||||
};
|
||||
|
||||
use crate::section;
|
||||
|
|
@ -218,6 +218,18 @@ impl Render for TextStory {
|
|||
),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
section("Tag::color", cx).child(
|
||||
v_flex().gap_4().child(
|
||||
h_flex().gap_2().flex_wrap().children(
|
||||
ColorName::all()
|
||||
.into_iter()
|
||||
.filter(|color| *color != ColorName::Gray)
|
||||
.map(|color| Tag::color(color).child(color.to_string())),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
section("Kbd", cx).child(
|
||||
h_flex()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use std::collections::HashMap;
|
||||
use std::{collections::HashMap, fmt::Display};
|
||||
|
||||
use gpui::Hsla;
|
||||
use gpui::{Hsla, SharedString};
|
||||
use serde::{de::Error, Deserialize, Deserializer};
|
||||
|
||||
use crate::hsl;
|
||||
|
|
@ -181,6 +181,126 @@ mod color_scales {
|
|||
}
|
||||
}
|
||||
|
||||
/// Enum representing the available color names.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum ColorName {
|
||||
Gray,
|
||||
Red,
|
||||
Orange,
|
||||
Amber,
|
||||
Yellow,
|
||||
Lime,
|
||||
Green,
|
||||
Emerald,
|
||||
Teal,
|
||||
Cyan,
|
||||
Sky,
|
||||
Blue,
|
||||
Indigo,
|
||||
Violet,
|
||||
Purple,
|
||||
Fuchsia,
|
||||
Pink,
|
||||
Rose,
|
||||
}
|
||||
|
||||
impl Display for ColorName {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for ColorName {
|
||||
fn from(value: &str) -> Self {
|
||||
match value.to_lowercase().as_str() {
|
||||
"gray" => ColorName::Gray,
|
||||
"red" => ColorName::Red,
|
||||
"orange" => ColorName::Orange,
|
||||
"amber" => ColorName::Amber,
|
||||
"yellow" => ColorName::Yellow,
|
||||
"lime" => ColorName::Lime,
|
||||
"green" => ColorName::Green,
|
||||
"emerald" => ColorName::Emerald,
|
||||
"teal" => ColorName::Teal,
|
||||
"cyan" => ColorName::Cyan,
|
||||
"sky" => ColorName::Sky,
|
||||
"blue" => ColorName::Blue,
|
||||
"indigo" => ColorName::Indigo,
|
||||
"violet" => ColorName::Violet,
|
||||
"purple" => ColorName::Purple,
|
||||
"fuchsia" => ColorName::Fuchsia,
|
||||
"pink" => ColorName::Pink,
|
||||
"rose" => ColorName::Rose,
|
||||
_ => ColorName::Gray,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SharedString> for ColorName {
|
||||
fn from(value: SharedString) -> Self {
|
||||
value.as_ref().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl ColorName {
|
||||
/// Returns all available color names.
|
||||
pub fn all() -> [Self; 18] {
|
||||
[
|
||||
ColorName::Gray,
|
||||
ColorName::Red,
|
||||
ColorName::Orange,
|
||||
ColorName::Amber,
|
||||
ColorName::Yellow,
|
||||
ColorName::Lime,
|
||||
ColorName::Green,
|
||||
ColorName::Emerald,
|
||||
ColorName::Teal,
|
||||
ColorName::Cyan,
|
||||
ColorName::Sky,
|
||||
ColorName::Blue,
|
||||
ColorName::Indigo,
|
||||
ColorName::Violet,
|
||||
ColorName::Purple,
|
||||
ColorName::Fuchsia,
|
||||
ColorName::Pink,
|
||||
ColorName::Rose,
|
||||
]
|
||||
}
|
||||
|
||||
/// Returns the color for the given scale.
|
||||
///
|
||||
/// The `scale` is any of `[50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950]`
|
||||
/// falls back to 500 if out of range.
|
||||
pub fn scale(&self, scale: usize) -> Hsla {
|
||||
let colors = match self {
|
||||
ColorName::Gray => &DEFAULT_COLOR.gray,
|
||||
ColorName::Red => &DEFAULT_COLOR.red,
|
||||
ColorName::Orange => &DEFAULT_COLOR.orange,
|
||||
ColorName::Amber => &DEFAULT_COLOR.amber,
|
||||
ColorName::Yellow => &DEFAULT_COLOR.yellow,
|
||||
ColorName::Lime => &DEFAULT_COLOR.lime,
|
||||
ColorName::Green => &DEFAULT_COLOR.green,
|
||||
ColorName::Emerald => &DEFAULT_COLOR.emerald,
|
||||
ColorName::Teal => &DEFAULT_COLOR.teal,
|
||||
ColorName::Cyan => &DEFAULT_COLOR.cyan,
|
||||
ColorName::Sky => &DEFAULT_COLOR.sky,
|
||||
ColorName::Blue => &DEFAULT_COLOR.blue,
|
||||
ColorName::Indigo => &DEFAULT_COLOR.indigo,
|
||||
ColorName::Violet => &DEFAULT_COLOR.violet,
|
||||
ColorName::Purple => &DEFAULT_COLOR.purple,
|
||||
ColorName::Fuchsia => &DEFAULT_COLOR.fuchsia,
|
||||
ColorName::Pink => &DEFAULT_COLOR.pink,
|
||||
ColorName::Rose => &DEFAULT_COLOR.rose,
|
||||
};
|
||||
|
||||
if let Some(color) = colors.get(&scale) {
|
||||
color.hsla
|
||||
} else {
|
||||
colors.get(&500).unwrap().hsla
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize)]
|
||||
pub(crate) struct ShadcnColors {
|
||||
pub(crate) black: ShadcnColor,
|
||||
|
|
@ -426,4 +546,20 @@ mod tests {
|
|||
assert_eq!(green.mix(red, 0.5).to_hex(), "#FFFF00");
|
||||
assert_eq!(blue.mix(yellow, 0.2).to_hex(), "#0098FF");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_color_name() {
|
||||
assert_eq!(ColorName::Purple.to_string(), "Purple");
|
||||
assert_eq!(format!("{}", ColorName::Green), "Green");
|
||||
assert_eq!(format!("{:?}", ColorName::Yellow), "Yellow");
|
||||
|
||||
let color = ColorName::Green;
|
||||
assert_eq!(color.scale(500).to_hex(), "#21C55E");
|
||||
assert_eq!(color.scale(1500).to_hex(), "#21C55E");
|
||||
|
||||
for name in ColorName::all().iter() {
|
||||
let name1: ColorName = name.to_string().as_str().into();
|
||||
assert_eq!(name1, *name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{theme::ActiveTheme as _, Sizable, Size};
|
||||
use crate::{theme::ActiveTheme as _, ColorName, Sizable, Size};
|
||||
use gpui::{
|
||||
div, prelude::FluentBuilder as _, relative, transparent_black, AnyElement, App, Div, Hsla,
|
||||
InteractiveElement as _, IntoElement, ParentElement, RenderOnce, Styled, Window,
|
||||
|
|
@ -7,16 +7,18 @@ use gpui::{
|
|||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub enum TagVariant {
|
||||
#[default]
|
||||
Outline,
|
||||
Primary,
|
||||
Secondary,
|
||||
Outline,
|
||||
Danger,
|
||||
Color(ColorName),
|
||||
Custom {
|
||||
color: Hsla,
|
||||
foreground: Hsla,
|
||||
border: Hsla,
|
||||
},
|
||||
}
|
||||
|
||||
impl TagVariant {
|
||||
fn bg(&self, cx: &App) -> Hsla {
|
||||
match self {
|
||||
|
|
@ -24,6 +26,13 @@ impl TagVariant {
|
|||
Self::Secondary => cx.theme().secondary,
|
||||
Self::Outline => transparent_black(),
|
||||
Self::Danger => cx.theme().danger,
|
||||
Self::Color(color) => {
|
||||
if cx.theme().is_dark() {
|
||||
color.scale(950).opacity(0.5)
|
||||
} else {
|
||||
color.scale(50)
|
||||
}
|
||||
}
|
||||
Self::Custom { color, .. } => *color,
|
||||
}
|
||||
}
|
||||
|
|
@ -34,6 +43,13 @@ impl TagVariant {
|
|||
Self::Secondary => cx.theme().secondary,
|
||||
Self::Outline => cx.theme().border,
|
||||
Self::Danger => cx.theme().danger,
|
||||
Self::Color(color) => {
|
||||
if cx.theme().is_dark() {
|
||||
color.scale(800).opacity(0.5)
|
||||
} else {
|
||||
color.scale(200)
|
||||
}
|
||||
}
|
||||
Self::Custom { border, .. } => *border,
|
||||
}
|
||||
}
|
||||
|
|
@ -44,6 +60,13 @@ impl TagVariant {
|
|||
Self::Secondary => cx.theme().secondary_foreground,
|
||||
Self::Outline => cx.theme().foreground,
|
||||
Self::Danger => cx.theme().danger_foreground,
|
||||
Self::Color(color) => {
|
||||
if cx.theme().is_dark() {
|
||||
color.scale(300)
|
||||
} else {
|
||||
color.scale(600)
|
||||
}
|
||||
}
|
||||
Self::Custom { foreground, .. } => *foreground,
|
||||
}
|
||||
}
|
||||
|
|
@ -72,22 +95,29 @@ impl Tag {
|
|||
self
|
||||
}
|
||||
|
||||
/// Create a new tag with default variant ([`TagVariant::Primary`]).
|
||||
pub fn primary() -> Self {
|
||||
Self::new().with_variant(TagVariant::Primary)
|
||||
}
|
||||
|
||||
/// Create a new tag with default variant ([`TagVariant::Secondary`]).
|
||||
pub fn secondary() -> Self {
|
||||
Self::new().with_variant(TagVariant::Secondary)
|
||||
}
|
||||
|
||||
/// Create a new tag with default variant ([`TagVariant::Outline`]).
|
||||
///
|
||||
/// See also [`Tag::new`].
|
||||
pub fn outline() -> Self {
|
||||
Self::new().with_variant(TagVariant::Outline)
|
||||
}
|
||||
|
||||
/// Create a new tag with default variant ([`TagVariant::Danger`]).
|
||||
pub fn danger() -> Self {
|
||||
Self::new().with_variant(TagVariant::Danger)
|
||||
}
|
||||
|
||||
/// Create a new tag with default variant ([`TagVariant::Custom`]).
|
||||
pub fn custom(color: Hsla, foreground: Hsla, border: Hsla) -> Self {
|
||||
Self::new().with_variant(TagVariant::Custom {
|
||||
color,
|
||||
|
|
@ -95,6 +125,11 @@ impl Tag {
|
|||
border,
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a new tag with default variant ([`TagVariant::Color`]).
|
||||
pub fn color(color: impl Into<ColorName>) -> Self {
|
||||
Self::new().with_variant(TagVariant::Color(color.into()))
|
||||
}
|
||||
}
|
||||
impl Sizable for Tag {
|
||||
fn with_size(mut self, size: impl Into<Size>) -> Self {
|
||||
|
|
@ -109,6 +144,10 @@ impl ParentElement for Tag {
|
|||
}
|
||||
impl RenderOnce for Tag {
|
||||
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let bg = self.variant.bg(cx);
|
||||
let fg = self.variant.fg(cx);
|
||||
let border = self.variant.border(cx);
|
||||
|
||||
self.base
|
||||
.line_height(relative(1.3))
|
||||
.text_xs()
|
||||
|
|
@ -116,9 +155,9 @@ impl RenderOnce for Tag {
|
|||
Size::XSmall | Size::Small => this.px_1p5().py_0().rounded(cx.theme().radius / 2.),
|
||||
_ => this.px_2p5().py_0p5().rounded(cx.theme().radius),
|
||||
})
|
||||
.bg(self.variant.bg(cx))
|
||||
.text_color(self.variant.fg(cx))
|
||||
.border_color(self.variant.border(cx))
|
||||
.bg(bg)
|
||||
.text_color(fg)
|
||||
.border_color(border)
|
||||
.hover(|this| this.opacity(0.9))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -411,15 +411,23 @@ impl Global for Theme {}
|
|||
|
||||
impl Theme {
|
||||
/// Returns the global theme reference
|
||||
#[inline(always)]
|
||||
pub fn global(cx: &App) -> &Theme {
|
||||
cx.global::<Theme>()
|
||||
}
|
||||
|
||||
/// Returns the global theme mutable reference
|
||||
#[inline(always)]
|
||||
pub fn global_mut(cx: &mut App) -> &mut Theme {
|
||||
cx.global_mut::<Theme>()
|
||||
}
|
||||
|
||||
/// Returns true if the theme is dark.
|
||||
#[inline(always)]
|
||||
pub fn is_dark(&self) -> bool {
|
||||
self.mode.is_dark()
|
||||
}
|
||||
|
||||
/// Apply a mask color to the theme.
|
||||
pub fn apply_color(&mut self, mask_color: Hsla) {
|
||||
self.title_bar = self.title_bar.apply(mask_color);
|
||||
|
|
@ -576,7 +584,7 @@ pub enum ThemeMode {
|
|||
}
|
||||
|
||||
impl ThemeMode {
|
||||
#[inline]
|
||||
#[inline(always)]
|
||||
pub fn is_dark(&self) -> bool {
|
||||
matches!(self, Self::Dark)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue