rustyle_css/components/
textarea.rs1use super::{ComponentStyle, Size, State, Variant};
6use crate::css::{Color, Radius, Spacing};
7use crate::tokens::{BorderTokens, ColorTokens, SpacingTokens};
8
9#[derive(Clone, Debug)]
11pub struct TextareaStyle {
12 pub variant: Variant,
13 pub size: Size,
14 pub state: State,
15 pub tokens: Option<TextareaTokens>,
16}
17
18#[derive(Clone, Debug)]
20pub struct TextareaTokens {
21 pub colors: ColorTokens,
22 pub spacing: SpacingTokens,
23 pub borders: BorderTokens,
24}
25
26impl TextareaStyle {
27 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 pub fn state(mut self, state: State) -> Self {
39 self.state = state;
40 self
41 }
42
43 pub fn tokens(mut self, tokens: TextareaTokens) -> 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::all(spacing.sm.clone()),
88 Size::Medium => Spacing::all(spacing.md.clone()),
89 Size::Large => Spacing::all(spacing.lg.clone()),
90 Size::ExtraLarge => Spacing::all(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 TextareaStyle {
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("resize: vertical; ");
129 css.push_str("font-family: inherit; ");
130 css.push_str("font-size: inherit; ");
131 css.push_str("line-height: 1.5; ");
132 css.push_str("transition: all 0.2s ease; ");
133
134 if self.state == State::Disabled {
135 css.push_str("opacity: 0.6; ");
136 css.push_str("cursor: not-allowed; ");
137 }
138
139 if matches!(self.state, State::Focus | State::FocusVisible) {
140 css.push_str("outline: 2px solid transparent; ");
141 css.push_str("outline-offset: 2px; ");
142 }
143
144 css
145 }
146
147 fn class_name(&self) -> &str {
148 "rustyle-textarea"
149 }
150}
151
152impl Default for TextareaStyle {
153 fn default() -> Self {
154 Self::new(Variant::Primary, Size::Medium)
155 }
156}