use crate::core::{BorderStyle, Color};
use super::Theme;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ComponentVariant {
#[default]
Default,
Primary,
Secondary,
Success,
Warning,
Error,
Info,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ComponentState {
#[default]
Rest,
Focused,
Selected,
Disabled,
Active,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct VariantStyle {
pub fg: Color,
pub bg: Color,
pub border: Color,
pub bold: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Density {
Compact,
#[default]
Comfortable,
Spacious,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SpacingTokens {
pub xs: u16,
pub sm: u16,
pub md: u16,
pub lg: u16,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DensityTokens {
pub mode: Density,
pub gap: u16,
pub padding: u16,
pub inline_padding: u16,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BorderTokens {
pub panel: BorderStyle,
pub dialog: BorderStyle,
pub control: BorderStyle,
pub focus: BorderStyle,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FocusTokens {
pub marker: &'static str,
pub bold: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct StateTokens {
pub selected_bold: bool,
pub disabled_dim: bool,
pub active_bold: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SymbolTokens {
pub selected: &'static str,
pub unselected: &'static str,
pub success: &'static str,
pub warning: &'static str,
pub error: &'static str,
pub info: &'static str,
pub disclosure_open: &'static str,
pub disclosure_closed: &'static str,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct DesignTokens {
pub spacing: SpacingTokens,
pub density: DensityTokens,
pub borders: BorderTokens,
pub focus: FocusTokens,
pub states: StateTokens,
pub symbols: SymbolTokens,
}
impl Default for SpacingTokens {
fn default() -> Self {
Self {
xs: 0,
sm: 1,
md: 1,
lg: 2,
}
}
}
impl DensityTokens {
pub fn for_density(mode: Density) -> Self {
match mode {
Density::Compact => Self {
mode,
gap: 1,
padding: 0,
inline_padding: 0,
},
Density::Comfortable => Self {
mode,
gap: 2,
padding: 1,
inline_padding: 1,
},
Density::Spacious => Self {
mode,
gap: 3,
padding: 2,
inline_padding: 2,
},
}
}
}
impl Default for DensityTokens {
fn default() -> Self {
Self::for_density(Density::Comfortable)
}
}
impl Default for BorderTokens {
fn default() -> Self {
Self {
panel: BorderStyle::Single,
dialog: BorderStyle::Round,
control: BorderStyle::Single,
focus: BorderStyle::Bold,
}
}
}
impl Default for FocusTokens {
fn default() -> Self {
Self {
marker: ">",
bold: true,
}
}
}
impl Default for StateTokens {
fn default() -> Self {
Self {
selected_bold: true,
disabled_dim: true,
active_bold: true,
}
}
}
impl Default for SymbolTokens {
fn default() -> Self {
Self {
selected: "✓",
unselected: "○",
success: "✓",
warning: "⚠",
error: "✗",
info: "ℹ",
disclosure_open: "▾",
disclosure_closed: "▸",
}
}
}
impl Theme {
pub fn design_tokens(&self) -> DesignTokens {
DesignTokens::default()
}
pub fn variant_style(&self, variant: ComponentVariant, state: ComponentState) -> VariantStyle {
let (fg, bg, border) = match variant {
ComponentVariant::Default => (
self.text.primary,
self.background.elevated,
self.border.default,
),
ComponentVariant::Primary => (
self.components.button.primary_text,
self.components.button.primary_bg,
self.primary,
),
ComponentVariant::Secondary => (
self.components.button.secondary_text,
self.components.button.secondary_bg,
self.secondary,
),
ComponentVariant::Success => (self.text.inverted, self.success, self.success),
ComponentVariant::Warning => (self.text.inverted, self.warning, self.warning),
ComponentVariant::Error => (
self.components.button.danger_text,
self.components.button.danger_bg,
self.error,
),
ComponentVariant::Info => (self.text.inverted, self.info, self.info),
};
match state {
ComponentState::Rest => VariantStyle {
fg,
bg,
border,
bold: false,
},
ComponentState::Focused => VariantStyle {
fg,
bg,
border: self.border.focused,
bold: self.design_tokens().focus.bold,
},
ComponentState::Selected => VariantStyle {
fg,
bg,
border,
bold: self.design_tokens().states.selected_bold,
},
ComponentState::Disabled => VariantStyle {
fg: self.text.disabled,
bg: self.background.disabled,
border: self.border.disabled,
bold: false,
},
ComponentState::Active => VariantStyle {
fg,
bg,
border,
bold: self.design_tokens().states.active_bold,
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_density_tokens_for_density() {
assert_eq!(DensityTokens::for_density(Density::Compact).padding, 0);
assert_eq!(DensityTokens::for_density(Density::Spacious).gap, 3);
}
#[test]
fn test_theme_design_tokens_are_derived_without_theme_fields() {
let theme = Theme::dark();
let tokens = theme.design_tokens();
assert_eq!(tokens.borders.dialog, BorderStyle::Round);
assert_eq!(tokens.focus.marker, ">");
}
#[test]
fn test_variant_style_uses_theme_colors() {
let theme = Theme::light();
let style = theme.variant_style(ComponentVariant::Primary, ComponentState::Focused);
assert_eq!(style.bg, theme.components.button.primary_bg);
assert_eq!(style.border, theme.border.focused);
assert!(style.bold);
}
}