mod tokens;
pub use tokens::*;
use crate::geometry::Color;
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Theme {
#[default]
Light,
Dark,
Auto,
}
#[derive(Debug, Clone)]
pub struct ThemeData {
pub variant: Theme,
pub is_dark: bool,
pub colors: ThemeColors,
pub spacing: SpacingScale,
pub typography: Typography,
pub radii: BorderRadii,
pub shadows: Shadows,
pub custom_vars: HashMap<String, String>,
}
impl ThemeData {
pub fn light() -> Self {
Self {
variant: Theme::Light,
is_dark: false,
colors: ThemeColors::light(),
spacing: SpacingScale::default(),
typography: Typography::default(),
radii: BorderRadii::default(),
shadows: Shadows::light(),
custom_vars: HashMap::new(),
}
}
pub fn dark() -> Self {
Self {
variant: Theme::Dark,
is_dark: true,
colors: ThemeColors::dark(),
spacing: SpacingScale::default(),
typography: Typography::default(),
radii: BorderRadii::default(),
shadows: Shadows::dark(),
custom_vars: HashMap::new(),
}
}
pub fn set_var(&mut self, name: impl Into<String>, value: impl Into<String>) {
self.custom_vars.insert(name.into(), value.into());
}
pub fn get_var(&self, name: &str) -> Option<&String> {
self.custom_vars.get(name)
}
pub fn resolve_var(&self, name: &str) -> Option<String> {
if let Some(value) = self.custom_vars.get(name) {
return Some(value.clone());
}
match name {
"--background" => Some(self.colors.background.to_css()),
"--foreground" => Some(self.colors.foreground.to_css()),
"--primary" => Some(self.colors.primary.to_css()),
"--primary-foreground" => Some(self.colors.primary_foreground.to_css()),
"--secondary" => Some(self.colors.secondary.to_css()),
"--secondary-foreground" => Some(self.colors.secondary_foreground.to_css()),
"--muted" => Some(self.colors.muted.to_css()),
"--muted-foreground" => Some(self.colors.muted_foreground.to_css()),
"--accent" => Some(self.colors.accent.to_css()),
"--accent-foreground" => Some(self.colors.accent_foreground.to_css()),
"--destructive" => Some(self.colors.destructive.to_css()),
"--destructive-foreground" => Some(self.colors.destructive_foreground.to_css()),
"--border" => Some(self.colors.border.to_css()),
"--input" => Some(self.colors.input.to_css()),
"--ring" => Some(self.colors.ring.to_css()),
"--card" => Some(self.colors.card.to_css()),
"--card-foreground" => Some(self.colors.card_foreground.to_css()),
"--popover" => Some(self.colors.popover.to_css()),
"--popover-foreground" => Some(self.colors.popover_foreground.to_css()),
"--radius" => Some(format!("{}rem", self.radii.default)),
"--radius-sm" => Some(format!("{}rem", self.radii.sm)),
"--radius-md" => Some(format!("{}rem", self.radii.md)),
"--radius-lg" => Some(format!("{}rem", self.radii.lg)),
"--radius-xl" => Some(format!("{}rem", self.radii.xl)),
"--radius-full" => Some("9999px".to_string()),
"--space-1" => Some(format!("{}rem", self.spacing.get(1))),
"--space-2" => Some(format!("{}rem", self.spacing.get(2))),
"--space-3" => Some(format!("{}rem", self.spacing.get(3))),
"--space-4" => Some(format!("{}rem", self.spacing.get(4))),
"--space-5" => Some(format!("{}rem", self.spacing.get(5))),
"--space-6" => Some(format!("{}rem", self.spacing.get(6))),
"--space-8" => Some(format!("{}rem", self.spacing.get(8))),
"--space-10" => Some(format!("{}rem", self.spacing.get(10))),
"--space-12" => Some(format!("{}rem", self.spacing.get(12))),
_ => None,
}
}
}
impl Default for ThemeData {
fn default() -> Self {
Self::light()
}
}
pub trait ColorExt {
fn to_css(&self) -> String;
}
impl ColorExt for Color {
fn to_css(&self) -> String {
let [r, g, b, a] = self.to_rgba8();
if a == 255 {
format!("rgb({}, {}, {})", r, g, b)
} else {
format!("rgba({}, {}, {}, {:.3})", r, g, b, self.a)
}
}
}
pub fn detect_system_theme() -> Theme {
Theme::Light
}