rustyle_css/components/
input.rs

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