tailwind_rs_core/responsive/
responsive_builder.rs1use super::breakpoints::Breakpoint;
6use super::responsive_config::ResponsiveConfig;
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9
10#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
12pub struct ResponsiveBuilder {
13 classes: HashMap<Breakpoint, Vec<String>>,
15 config: ResponsiveConfig,
17}
18
19impl ResponsiveBuilder {
20 pub fn new() -> Self {
22 Self::default()
23 }
24
25 pub fn with_config(config: ResponsiveConfig) -> Self {
27 Self {
28 classes: HashMap::new(),
29 config,
30 }
31 }
32
33 pub fn add_class(&mut self, breakpoint: Breakpoint, class: impl Into<String>) -> &mut Self {
35 self.classes
36 .entry(breakpoint)
37 .or_insert_with(Vec::new)
38 .push(class.into());
39 self
40 }
41
42 pub fn add_classes(&mut self, breakpoint: Breakpoint, classes: Vec<String>) -> &mut Self {
44 self.classes
45 .entry(breakpoint)
46 .or_insert_with(Vec::new)
47 .extend(classes);
48 self
49 }
50
51 pub fn base(&mut self, class: impl Into<String>) -> &mut Self {
53 self.add_class(Breakpoint::Base, class)
54 }
55
56 pub fn sm(&mut self, class: impl Into<String>) -> &mut Self {
58 self.add_class(Breakpoint::Sm, class)
59 }
60
61 pub fn md(&mut self, class: impl Into<String>) -> &mut Self {
63 self.add_class(Breakpoint::Md, class)
64 }
65
66 pub fn lg(&mut self, class: impl Into<String>) -> &mut Self {
68 self.add_class(Breakpoint::Lg, class)
69 }
70
71 pub fn xl(&mut self, class: impl Into<String>) -> &mut Self {
73 self.add_class(Breakpoint::Xl, class)
74 }
75
76 pub fn xl2(&mut self, class: impl Into<String>) -> &mut Self {
78 self.add_class(Breakpoint::Xl2, class)
79 }
80
81 pub fn responsive(&mut self, base: impl Into<String>, sm: Option<String>, md: Option<String>, lg: Option<String>, xl: Option<String>, xl2: Option<String>) -> &mut Self {
83 self.base(base);
84
85 if let Some(sm_class) = sm {
86 self.sm(sm_class);
87 }
88 if let Some(md_class) = md {
89 self.md(md_class);
90 }
91 if let Some(lg_class) = lg {
92 self.lg(lg_class);
93 }
94 if let Some(xl_class) = xl {
95 self.xl(xl_class);
96 }
97 if let Some(xl2_class) = xl2 {
98 self.xl2(xl2_class);
99 }
100
101 self
102 }
103
104 pub fn get_classes(&self, breakpoint: Breakpoint) -> Vec<String> {
106 self.classes.get(&breakpoint).cloned().unwrap_or_default()
107 }
108
109 pub fn get_all_classes(&self) -> HashMap<Breakpoint, Vec<String>> {
111 self.classes.clone()
112 }
113
114 pub fn is_empty(&self) -> bool {
116 self.classes.is_empty() || self.classes.values().all(|classes| classes.is_empty())
117 }
118
119 pub fn len(&self) -> usize {
121 self.classes.len()
122 }
123
124 pub fn clear(&mut self) {
126 self.classes.clear();
127 }
128
129 pub fn remove_breakpoint(&mut self, breakpoint: Breakpoint) -> Vec<String> {
131 self.classes.remove(&breakpoint).unwrap_or_default()
132 }
133
134 pub fn build(&self) -> String {
136 let mut classes = Vec::new();
137
138 for breakpoint in Breakpoint::all() {
140 if let Some(breakpoint_classes) = self.classes.get(&breakpoint) {
141 if !breakpoint_classes.is_empty() {
142 let breakpoint_classes_str = breakpoint_classes.join(" ");
143 if breakpoint == Breakpoint::Base {
144 classes.push(breakpoint_classes_str);
145 } else {
146 classes.push(format!("{}{}", breakpoint.prefix(), breakpoint_classes_str));
147 }
148 }
149 }
150 }
151
152 classes.join(" ")
153 }
154
155 pub fn build_for_width(&self, screen_width: u32) -> String {
157 let mut classes = Vec::new();
158
159 let target_breakpoint = self.config.get_breakpoint_for_width(screen_width);
161
162 for breakpoint in Breakpoint::all() {
164 if breakpoint.min_width() <= target_breakpoint.min_width() {
165 if let Some(breakpoint_classes) = self.classes.get(&breakpoint) {
166 if !breakpoint_classes.is_empty() {
167 let breakpoint_classes_str = breakpoint_classes.join(" ");
168 if breakpoint == Breakpoint::Base {
169 classes.push(breakpoint_classes_str);
170 } else {
171 classes.push(format!("{}{}", breakpoint.prefix(), breakpoint_classes_str));
172 }
173 }
174 }
175 }
176 }
177
178 classes.join(" ")
179 }
180
181 pub fn get_config(&self) -> &ResponsiveConfig {
183 &self.config
184 }
185
186 pub fn update_config(&mut self, config: ResponsiveConfig) {
188 self.config = config;
189 }
190}
191
192impl Default for ResponsiveBuilder {
193 fn default() -> Self {
194 Self {
195 classes: HashMap::new(),
196 config: ResponsiveConfig::default(),
197 }
198 }
199}
200
201impl std::fmt::Display for ResponsiveBuilder {
202 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
203 write!(f, "{}", self.build())
204 }
205}
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210
211 #[test]
212 fn test_responsive_builder_new() {
213 let builder = ResponsiveBuilder::new();
214 assert!(builder.is_empty());
215 assert_eq!(builder.len(), 0);
216 }
217
218 #[test]
219 fn test_responsive_builder_add_class() {
220 let mut builder = ResponsiveBuilder::new();
221 builder.add_class(Breakpoint::Base, "text-sm");
222 builder.add_class(Breakpoint::Sm, "text-base");
223
224 assert!(!builder.is_empty());
225 assert_eq!(builder.len(), 2);
226 assert_eq!(builder.get_classes(Breakpoint::Base), vec!["text-sm"]);
227 assert_eq!(builder.get_classes(Breakpoint::Sm), vec!["text-base"]);
228 }
229
230 #[test]
231 fn test_responsive_builder_add_classes() {
232 let mut builder = ResponsiveBuilder::new();
233 builder.add_classes(Breakpoint::Base, vec!["text-sm".to_string(), "font-medium".to_string()]);
234
235 assert_eq!(builder.get_classes(Breakpoint::Base), vec!["text-sm", "font-medium"]);
236 }
237
238 #[test]
239 fn test_responsive_builder_breakpoint_methods() {
240 let mut builder = ResponsiveBuilder::new();
241 builder.base("text-sm");
242 builder.sm("text-base");
243 builder.md("text-lg");
244 builder.lg("text-xl");
245 builder.xl("text-2xl");
246 builder.xl2("text-3xl");
247
248 assert_eq!(builder.get_classes(Breakpoint::Base), vec!["text-sm"]);
249 assert_eq!(builder.get_classes(Breakpoint::Sm), vec!["text-base"]);
250 assert_eq!(builder.get_classes(Breakpoint::Md), vec!["text-lg"]);
251 assert_eq!(builder.get_classes(Breakpoint::Lg), vec!["text-xl"]);
252 assert_eq!(builder.get_classes(Breakpoint::Xl), vec!["text-2xl"]);
253 assert_eq!(builder.get_classes(Breakpoint::Xl2), vec!["text-3xl"]);
254 }
255
256 #[test]
257 fn test_responsive_builder_responsive() {
258 let mut builder = ResponsiveBuilder::new();
259 builder.responsive(
260 "text-sm",
261 Some("text-base".to_string()),
262 Some("text-lg".to_string()),
263 None,
264 None,
265 None,
266 );
267
268 assert_eq!(builder.get_classes(Breakpoint::Base), vec!["text-sm"]);
269 assert_eq!(builder.get_classes(Breakpoint::Sm), vec!["text-base"]);
270 assert_eq!(builder.get_classes(Breakpoint::Md), vec!["text-lg"]);
271 assert_eq!(builder.get_classes(Breakpoint::Lg), Vec::<String>::new());
272 }
273
274 #[test]
275 fn test_responsive_builder_build() {
276 let mut builder = ResponsiveBuilder::new();
277 builder.base("text-sm");
278 builder.sm("text-base");
279 builder.md("text-lg");
280
281 let result = builder.build();
282 assert!(result.contains("text-sm"));
283 assert!(result.contains("sm:text-base"));
284 assert!(result.contains("md:text-lg"));
285 }
286
287 #[test]
288 fn test_responsive_builder_build_for_width() {
289 let mut builder = ResponsiveBuilder::new();
290 builder.base("text-sm");
291 builder.sm("text-base");
292 builder.md("text-lg");
293
294 let result_0 = builder.build_for_width(0);
296 assert!(result_0.contains("text-sm"));
297 assert!(!result_0.contains("sm:"));
298 assert!(!result_0.contains("md:"));
299
300 let result_640 = builder.build_for_width(640);
302 assert!(result_640.contains("text-sm"));
303 assert!(result_640.contains("sm:text-base"));
304 assert!(!result_640.contains("md:"));
305
306 let result_768 = builder.build_for_width(768);
308 assert!(result_768.contains("text-sm"));
309 assert!(result_768.contains("sm:text-base"));
310 assert!(result_768.contains("md:text-lg"));
311 }
312
313 #[test]
314 fn test_responsive_builder_clear() {
315 let mut builder = ResponsiveBuilder::new();
316 builder.base("text-sm");
317 builder.sm("text-base");
318
319 assert!(!builder.is_empty());
320 builder.clear();
321 assert!(builder.is_empty());
322 }
323
324 #[test]
325 fn test_responsive_builder_remove_breakpoint() {
326 let mut builder = ResponsiveBuilder::new();
327 builder.base("text-sm");
328 builder.sm("text-base");
329
330 assert_eq!(builder.len(), 2);
331 let removed = builder.remove_breakpoint(Breakpoint::Sm);
332 assert_eq!(removed, vec!["text-base"]);
333 assert_eq!(builder.len(), 1);
334 assert_eq!(builder.get_classes(Breakpoint::Sm), Vec::<String>::new());
335 }
336
337 #[test]
338 fn test_responsive_builder_display() {
339 let mut builder = ResponsiveBuilder::new();
340 builder.base("text-sm");
341 builder.sm("text-base");
342
343 let result = format!("{}", builder);
344 assert!(result.contains("text-sm"));
345 assert!(result.contains("sm:text-base"));
346 }
347}