rustyle_css/components/
button.rs

1//! Button component styles
2//!
3//! Provides type-safe button styling with variants, sizes, and states.
4
5use super::{ComponentStyle, Size, State, Variant};
6use crate::css::{Color, Radius, Spacing};
7use crate::tokens::{BorderTokens, ColorTokens, SpacingTokens};
8
9/// Button style configuration
10#[derive(Clone, Debug)]
11pub struct ButtonStyle {
12    pub variant: Variant,
13    pub size: Size,
14    pub state: State,
15    pub tokens: Option<ButtonTokens>,
16}
17
18/// Button design tokens
19#[derive(Clone, Debug)]
20pub struct ButtonTokens {
21    pub colors: ColorTokens,
22    pub spacing: SpacingTokens,
23    pub borders: BorderTokens,
24}
25
26impl ButtonStyle {
27    /// Create a new button style
28    pub fn new(variant: Variant, size: Size) -> Self {
29        Self {
30            variant,
31            size,
32            state: State::Default,
33            tokens: None,
34        }
35    }
36
37    /// Set state
38    pub fn state(mut self, state: State) -> Self {
39        self.state = state;
40        self
41    }
42
43    /// Set custom tokens
44    pub fn tokens(mut self, tokens: ButtonTokens) -> Self {
45        self.tokens = Some(tokens);
46        self
47    }
48
49    /// Get background color based on variant and state
50    fn background_color(&self) -> Color {
51        let default_colors = ColorTokens::default();
52        let colors = self
53            .tokens
54            .as_ref()
55            .map(|t| &t.colors)
56            .unwrap_or(&default_colors);
57
58        match (&self.variant, &self.state) {
59            (Variant::Primary, State::Default) => colors.primary.c500.clone(),
60            (Variant::Primary, State::Hover) => colors.primary.c600.clone(),
61            (Variant::Primary, State::Active) => colors.primary.c700.clone(),
62            (Variant::Primary, State::Disabled) => colors.secondary.c300.clone(),
63            (Variant::Secondary, State::Default) => colors.secondary.c500.clone(),
64            (Variant::Secondary, State::Hover) => colors.secondary.c600.clone(),
65            (Variant::Secondary, State::Active) => colors.secondary.c700.clone(),
66            (Variant::Secondary, State::Disabled) => colors.secondary.c300.clone(),
67            (Variant::Success, _) => colors.semantic.success.clone(),
68            (Variant::Error, _) => colors.semantic.error.clone(),
69            (Variant::Warning, _) => colors.semantic.warning.clone(),
70            (Variant::Info, _) => colors.semantic.info.clone(),
71            _ => colors.primary.c500.clone(),
72        }
73    }
74
75    /// Get text color based on variant
76    fn text_color(&self) -> Color {
77        let default_colors = ColorTokens::default();
78        let colors = self
79            .tokens
80            .as_ref()
81            .map(|t| &t.colors)
82            .unwrap_or(&default_colors);
83
84        match &self.variant {
85            Variant::Primary | Variant::Secondary => colors.text.inverse.clone(),
86            _ => colors.text.primary.clone(),
87        }
88    }
89
90    /// Get padding based on size
91    fn padding(&self) -> Spacing {
92        let default_spacing = SpacingTokens::default();
93        let spacing = self
94            .tokens
95            .as_ref()
96            .map(|t| &t.spacing)
97            .unwrap_or(&default_spacing);
98
99        match &self.size {
100            Size::Small => Spacing::vh(spacing.sm.clone(), spacing.md.clone()),
101            Size::Medium => Spacing::vh(spacing.md.clone(), spacing.lg.clone()),
102            Size::Large => Spacing::vh(spacing.lg.clone(), spacing.xl.clone()),
103            Size::ExtraLarge => Spacing::vh(spacing.xl.clone(), spacing.xl2.clone()),
104        }
105    }
106
107    /// Get border radius based on size
108    fn border_radius(&self) -> Radius {
109        let default_borders = BorderTokens::default();
110        let borders = self
111            .tokens
112            .as_ref()
113            .map(|t| &t.borders)
114            .unwrap_or(&default_borders);
115
116        match &self.size {
117            Size::Small => Radius::all(borders.radius.sm.clone()),
118            Size::Medium => Radius::all(borders.radius.base.clone()),
119            Size::Large => Radius::all(borders.radius.lg.clone()),
120            Size::ExtraLarge => Radius::all(borders.radius.xl.clone()),
121        }
122    }
123}
124
125impl ComponentStyle for ButtonStyle {
126    fn to_css(&self) -> String {
127        let mut css = String::new();
128
129        css.push_str(&format!(
130            "background-color: {}; ",
131            self.background_color().to_css()
132        ));
133        css.push_str(&format!("color: {}; ", self.text_color().to_css()));
134        css.push_str(&format!("padding: {}; ", self.padding().to_css()));
135        css.push_str(&format!(
136            "border-radius: {}; ",
137            self.border_radius().to_css()
138        ));
139        css.push_str("border: none; ");
140        css.push_str("cursor: pointer; ");
141        css.push_str("font-weight: 500; ");
142        css.push_str("transition: all 0.2s ease; ");
143
144        if self.state == State::Disabled {
145            css.push_str("opacity: 0.6; ");
146            css.push_str("cursor: not-allowed; ");
147        }
148
149        css
150    }
151
152    fn class_name(&self) -> &str {
153        "rustyle-button"
154    }
155}
156
157impl Default for ButtonStyle {
158    fn default() -> Self {
159        Self::new(Variant::Primary, Size::Medium)
160    }
161}