rustyle_css/components/
select.rs

1//! Select component styles
2//!
3//! Provides type-safe select/dropdown 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/// Select style configuration
10#[derive(Clone, Debug)]
11pub struct SelectStyle {
12    pub variant: Variant,
13    pub size: Size,
14    pub state: State,
15    pub tokens: Option<SelectTokens>,
16}
17
18/// Select design tokens
19#[derive(Clone, Debug)]
20pub struct SelectTokens {
21    pub colors: ColorTokens,
22    pub spacing: SpacingTokens,
23    pub borders: BorderTokens,
24}
25
26impl SelectStyle {
27    /// Create a new select 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: SelectTokens) -> Self {
45        self.tokens = Some(tokens);
46        self
47    }
48
49    fn background_color(&self) -> Color {
50        let default_colors = ColorTokens::default();
51        let colors = self
52            .tokens
53            .as_ref()
54            .map(|t| &t.colors)
55            .unwrap_or(&default_colors);
56
57        match &self.state {
58            State::Disabled => colors.secondary.c200.clone(),
59            State::Focus | State::FocusVisible => colors.primary.c50.clone(),
60            _ => colors.background.base.clone(),
61        }
62    }
63
64    fn border_color(&self) -> Color {
65        let default_colors = ColorTokens::default();
66        let colors = self
67            .tokens
68            .as_ref()
69            .map(|t| &t.colors)
70            .unwrap_or(&default_colors);
71
72        match &self.state {
73            State::Focus | State::FocusVisible => colors.primary.c500.clone(),
74            _ => colors.secondary.c300.clone(),
75        }
76    }
77
78    fn padding(&self) -> Spacing {
79        let default_spacing = SpacingTokens::default();
80        let spacing = self
81            .tokens
82            .as_ref()
83            .map(|t| &t.spacing)
84            .unwrap_or(&default_spacing);
85
86        match &self.size {
87            Size::Small => Spacing::vh(spacing.xs.clone(), spacing.sm.clone()),
88            Size::Medium => Spacing::vh(spacing.sm.clone(), spacing.md.clone()),
89            Size::Large => Spacing::vh(spacing.md.clone(), spacing.lg.clone()),
90            Size::ExtraLarge => Spacing::vh(spacing.lg.clone(), spacing.xl.clone()),
91        }
92    }
93
94    fn border_radius(&self) -> Radius {
95        let default_borders = BorderTokens::default();
96        let borders = self
97            .tokens
98            .as_ref()
99            .map(|t| &t.borders)
100            .unwrap_or(&default_borders);
101
102        match &self.size {
103            Size::Small => Radius::all(borders.radius.sm.clone()),
104            Size::Medium => Radius::all(borders.radius.base.clone()),
105            Size::Large => Radius::all(borders.radius.lg.clone()),
106            Size::ExtraLarge => Radius::all(borders.radius.xl.clone()),
107        }
108    }
109}
110
111impl ComponentStyle for SelectStyle {
112    fn to_css(&self) -> String {
113        let mut css = String::new();
114
115        css.push_str(&format!(
116            "background-color: {}; ",
117            self.background_color().to_css()
118        ));
119        css.push_str(&format!(
120            "border: 1px solid {}; ",
121            self.border_color().to_css()
122        ));
123        css.push_str(&format!(
124            "border-radius: {}; ",
125            self.border_radius().to_css()
126        ));
127        css.push_str(&format!("padding: {}; ", self.padding().to_css()));
128        css.push_str("cursor: pointer; ");
129        css.push_str("font-size: inherit; ");
130        css.push_str("transition: all 0.2s ease; ");
131
132        if self.state == State::Disabled {
133            css.push_str("opacity: 0.6; ");
134            css.push_str("cursor: not-allowed; ");
135        }
136
137        if matches!(self.state, State::Focus | State::FocusVisible) {
138            css.push_str("outline: 2px solid transparent; ");
139            css.push_str("outline-offset: 2px; ");
140        }
141
142        css
143    }
144
145    fn class_name(&self) -> &str {
146        "rustyle-select"
147    }
148}
149
150impl Default for SelectStyle {
151    fn default() -> Self {
152        Self::new(Variant::Primary, Size::Medium)
153    }
154}