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(
83 &mut self,
84 base: impl Into<String>,
85 sm: Option<String>,
86 md: Option<String>,
87 lg: Option<String>,
88 xl: Option<String>,
89 xl2: Option<String>,
90 ) -> &mut Self {
91 self.base(base);
92
93 if let Some(sm_class) = sm {
94 self.sm(sm_class);
95 }
96 if let Some(md_class) = md {
97 self.md(md_class);
98 }
99 if let Some(lg_class) = lg {
100 self.lg(lg_class);
101 }
102 if let Some(xl_class) = xl {
103 self.xl(xl_class);
104 }
105 if let Some(xl2_class) = xl2 {
106 self.xl2(xl2_class);
107 }
108
109 self
110 }
111
112 pub fn get_classes(&self, breakpoint: Breakpoint) -> Vec<String> {
114 self.classes.get(&breakpoint).cloned().unwrap_or_default()
115 }
116
117 pub fn get_all_classes(&self) -> HashMap<Breakpoint, Vec<String>> {
119 self.classes.clone()
120 }
121
122 pub fn is_empty(&self) -> bool {
124 self.classes.is_empty() || self.classes.values().all(|classes| classes.is_empty())
125 }
126
127 pub fn len(&self) -> usize {
129 self.classes.len()
130 }
131
132 pub fn clear(&mut self) {
134 self.classes.clear();
135 }
136
137 pub fn remove_breakpoint(&mut self, breakpoint: Breakpoint) -> Vec<String> {
139 self.classes.remove(&breakpoint).unwrap_or_default()
140 }
141
142 pub fn build(&self) -> String {
144 let mut classes = Vec::new();
145
146 for breakpoint in Breakpoint::all() {
148 if let Some(breakpoint_classes) = self.classes.get(&breakpoint) {
149 if !breakpoint_classes.is_empty() {
150 let breakpoint_classes_str = breakpoint_classes.join(" ");
151 if breakpoint == Breakpoint::Base {
152 classes.push(breakpoint_classes_str);
153 } else {
154 classes.push(format!("{}{}", breakpoint.prefix(), breakpoint_classes_str));
155 }
156 }
157 }
158 }
159
160 classes.join(" ")
161 }
162
163 pub fn build_for_width(&self, screen_width: u32) -> String {
165 let mut classes = Vec::new();
166
167 let target_breakpoint = self.config.get_breakpoint_for_width(screen_width);
169
170 for breakpoint in Breakpoint::all() {
172 if breakpoint.min_width() <= target_breakpoint.min_width() {
173 if let Some(breakpoint_classes) = self.classes.get(&breakpoint) {
174 if !breakpoint_classes.is_empty() {
175 let breakpoint_classes_str = breakpoint_classes.join(" ");
176 if breakpoint == Breakpoint::Base {
177 classes.push(breakpoint_classes_str);
178 } else {
179 classes.push(format!(
180 "{}{}",
181 breakpoint.prefix(),
182 breakpoint_classes_str
183 ));
184 }
185 }
186 }
187 }
188 }
189
190 classes.join(" ")
191 }
192
193 pub fn get_config(&self) -> &ResponsiveConfig {
195 &self.config
196 }
197
198 pub fn update_config(&mut self, config: ResponsiveConfig) {
200 self.config = config;
201 }
202}
203
204impl Default for ResponsiveBuilder {
205 fn default() -> Self {
206 Self {
207 classes: HashMap::new(),
208 config: ResponsiveConfig::default(),
209 }
210 }
211}
212
213impl std::fmt::Display for ResponsiveBuilder {
214 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
215 write!(f, "{}", self.build())
216 }
217}
218
219#[cfg(test)]
220mod tests {
221 use super::*;
222
223 #[test]
224 fn test_responsive_builder_new() {
225 let builder = ResponsiveBuilder::new();
226 assert!(builder.is_empty());
227 assert_eq!(builder.len(), 0);
228 }
229
230 #[test]
231 fn test_responsive_builder_add_class() {
232 let mut builder = ResponsiveBuilder::new();
233 builder.add_class(Breakpoint::Base, "text-sm");
234 builder.add_class(Breakpoint::Sm, "text-base");
235
236 assert!(!builder.is_empty());
237 assert_eq!(builder.len(), 2);
238 assert_eq!(builder.get_classes(Breakpoint::Base), vec!["text-sm"]);
239 assert_eq!(builder.get_classes(Breakpoint::Sm), vec!["text-base"]);
240 }
241
242 #[test]
243 fn test_responsive_builder_add_classes() {
244 let mut builder = ResponsiveBuilder::new();
245 builder.add_classes(
246 Breakpoint::Base,
247 vec!["text-sm".to_string(), "font-medium".to_string()],
248 );
249
250 assert_eq!(
251 builder.get_classes(Breakpoint::Base),
252 vec!["text-sm", "font-medium"]
253 );
254 }
255
256 #[test]
257 fn test_responsive_builder_breakpoint_methods() {
258 let mut builder = ResponsiveBuilder::new();
259 builder.base("text-sm");
260 builder.sm("text-base");
261 builder.md("text-lg");
262 builder.lg("text-xl");
263 builder.xl("text-2xl");
264 builder.xl2("text-3xl");
265
266 assert_eq!(builder.get_classes(Breakpoint::Base), vec!["text-sm"]);
267 assert_eq!(builder.get_classes(Breakpoint::Sm), vec!["text-base"]);
268 assert_eq!(builder.get_classes(Breakpoint::Md), vec!["text-lg"]);
269 assert_eq!(builder.get_classes(Breakpoint::Lg), vec!["text-xl"]);
270 assert_eq!(builder.get_classes(Breakpoint::Xl), vec!["text-2xl"]);
271 assert_eq!(builder.get_classes(Breakpoint::Xl2), vec!["text-3xl"]);
272 }
273
274 #[test]
275 fn test_responsive_builder_responsive() {
276 let mut builder = ResponsiveBuilder::new();
277 builder.responsive(
278 "text-sm",
279 Some("text-base".to_string()),
280 Some("text-lg".to_string()),
281 None,
282 None,
283 None,
284 );
285
286 assert_eq!(builder.get_classes(Breakpoint::Base), vec!["text-sm"]);
287 assert_eq!(builder.get_classes(Breakpoint::Sm), vec!["text-base"]);
288 assert_eq!(builder.get_classes(Breakpoint::Md), vec!["text-lg"]);
289 assert_eq!(builder.get_classes(Breakpoint::Lg), Vec::<String>::new());
290 }
291
292 #[test]
293 fn test_responsive_builder_build() {
294 let mut builder = ResponsiveBuilder::new();
295 builder.base("text-sm");
296 builder.sm("text-base");
297 builder.md("text-lg");
298
299 let result = builder.build();
300 assert!(result.contains("text-sm"));
301 assert!(result.contains("sm:text-base"));
302 assert!(result.contains("md:text-lg"));
303 }
304
305 #[test]
306 fn test_responsive_builder_build_for_width() {
307 let mut builder = ResponsiveBuilder::new();
308 builder.base("text-sm");
309 builder.sm("text-base");
310 builder.md("text-lg");
311
312 let result_0 = builder.build_for_width(0);
314 assert!(result_0.contains("text-sm"));
315 assert!(!result_0.contains("sm:"));
316 assert!(!result_0.contains("md:"));
317
318 let result_640 = builder.build_for_width(640);
320 assert!(result_640.contains("text-sm"));
321 assert!(result_640.contains("sm:text-base"));
322 assert!(!result_640.contains("md:"));
323
324 let result_768 = builder.build_for_width(768);
326 assert!(result_768.contains("text-sm"));
327 assert!(result_768.contains("sm:text-base"));
328 assert!(result_768.contains("md:text-lg"));
329 }
330
331 #[test]
332 fn test_responsive_builder_clear() {
333 let mut builder = ResponsiveBuilder::new();
334 builder.base("text-sm");
335 builder.sm("text-base");
336
337 assert!(!builder.is_empty());
338 builder.clear();
339 assert!(builder.is_empty());
340 }
341
342 #[test]
343 fn test_responsive_builder_remove_breakpoint() {
344 let mut builder = ResponsiveBuilder::new();
345 builder.base("text-sm");
346 builder.sm("text-base");
347
348 assert_eq!(builder.len(), 2);
349 let removed = builder.remove_breakpoint(Breakpoint::Sm);
350 assert_eq!(removed, vec!["text-base"]);
351 assert_eq!(builder.len(), 1);
352 assert_eq!(builder.get_classes(Breakpoint::Sm), Vec::<String>::new());
353 }
354
355 #[test]
356 fn test_responsive_builder_display() {
357 let mut builder = ResponsiveBuilder::new();
358 builder.base("text-sm");
359 builder.sm("text-base");
360
361 let result = format!("{}", builder);
362 assert!(result.contains("text-sm"));
363 assert!(result.contains("sm:text-base"));
364 }
365}