tailwind_rs_core/responsive/
responsive_values.rs1use super::breakpoints::Breakpoint;
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
11pub struct ResponsiveValue<T> {
12 pub values: HashMap<Breakpoint, T>,
14}
15
16impl<T> ResponsiveValue<T> {
17 pub fn new() -> Self {
19 Self {
20 values: HashMap::new(),
21 }
22 }
23
24 pub fn with_base(base: T) -> Self {
26 let mut values = HashMap::new();
27 values.insert(Breakpoint::Base, base);
28 Self { values }
29 }
30
31 pub fn set_breakpoint(&mut self, breakpoint: Breakpoint, value: T) {
33 self.values.insert(breakpoint, value);
34 }
35
36 pub fn get_breakpoint(&self, breakpoint: Breakpoint) -> Option<&T> {
38 self.values.get(&breakpoint)
39 }
40
41 pub fn get_breakpoint_or_base(&self, breakpoint: Breakpoint) -> Option<&T> {
43 self.values
44 .get(&breakpoint)
45 .or_else(|| self.values.get(&Breakpoint::Base))
46 }
47
48 pub fn get_base(&self) -> Option<&T> {
50 self.values.get(&Breakpoint::Base)
51 }
52
53 pub fn has_breakpoint(&self, breakpoint: Breakpoint) -> bool {
55 self.values.contains_key(&breakpoint)
56 }
57
58 pub fn get_breakpoints(&self) -> Vec<Breakpoint> {
60 self.values.keys().cloned().collect()
61 }
62
63 pub fn is_empty(&self) -> bool {
65 self.values.is_empty()
66 }
67
68 pub fn len(&self) -> usize {
70 self.values.len()
71 }
72
73 pub fn clear(&mut self) {
75 self.values.clear();
76 }
77
78 pub fn remove_breakpoint(&mut self, breakpoint: Breakpoint) -> Option<T> {
80 self.values.remove(&breakpoint)
81 }
82
83 pub fn get_for_width(&self, screen_width: u32) -> Option<&T> {
85 let active_breakpoints: Vec<Breakpoint> = self
87 .values
88 .keys()
89 .filter(|&&bp| screen_width >= bp.min_width())
90 .cloned()
91 .collect();
92
93 if active_breakpoints.is_empty() {
94 return self.get_base();
95 }
96
97 let best_breakpoint = active_breakpoints
99 .into_iter()
100 .max_by_key(|bp| bp.min_width())?;
101
102 self.get_breakpoint(best_breakpoint)
103 }
104
105 pub fn to_css_classes<F>(&self, class_generator: F) -> String
107 where
108 F: Fn(&T) -> String,
109 {
110 let mut classes = Vec::new();
111
112 for breakpoint in Breakpoint::all() {
113 if let Some(value) = self.get_breakpoint(breakpoint) {
114 let class = class_generator(value);
115 if !class.is_empty() {
116 if breakpoint == Breakpoint::Base {
117 classes.push(class);
118 } else {
119 classes.push(format!("{}{}", breakpoint.prefix(), class));
120 }
121 }
122 }
123 }
124
125 classes.join(" ")
126 }
127}
128
129impl<T> Default for ResponsiveValue<T> {
130 fn default() -> Self {
131 Self::new()
132 }
133}
134
135impl<T> From<T> for ResponsiveValue<T> {
136 fn from(value: T) -> Self {
137 Self::with_base(value)
138 }
139}
140
141impl<T> From<HashMap<Breakpoint, T>> for ResponsiveValue<T> {
142 fn from(values: HashMap<Breakpoint, T>) -> Self {
143 Self { values }
144 }
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150
151 #[test]
152 fn test_responsive_value_new() {
153 let rv = ResponsiveValue::<String>::new();
154 assert!(rv.is_empty());
155 assert_eq!(rv.len(), 0);
156 }
157
158 #[test]
159 fn test_responsive_value_with_base() {
160 let rv = ResponsiveValue::with_base("base".to_string());
161 assert!(!rv.is_empty());
162 assert_eq!(rv.len(), 1);
163 assert_eq!(rv.get_base(), Some(&"base".to_string()));
164 }
165
166 #[test]
167 fn test_responsive_value_set_get_breakpoint() {
168 let mut rv = ResponsiveValue::new();
169 rv.set_breakpoint(Breakpoint::Base, "base".to_string());
170 rv.set_breakpoint(Breakpoint::Sm, "sm".to_string());
171
172 assert_eq!(
173 rv.get_breakpoint(Breakpoint::Base),
174 Some(&"base".to_string())
175 );
176 assert_eq!(rv.get_breakpoint(Breakpoint::Sm), Some(&"sm".to_string()));
177 assert_eq!(rv.get_breakpoint(Breakpoint::Md), None);
178 }
179
180 #[test]
181 fn test_responsive_value_get_breakpoint_or_base() {
182 let mut rv = ResponsiveValue::new();
183 rv.set_breakpoint(Breakpoint::Base, "base".to_string());
184 rv.set_breakpoint(Breakpoint::Sm, "sm".to_string());
185
186 assert_eq!(
187 rv.get_breakpoint_or_base(Breakpoint::Base),
188 Some(&"base".to_string())
189 );
190 assert_eq!(
191 rv.get_breakpoint_or_base(Breakpoint::Sm),
192 Some(&"sm".to_string())
193 );
194 assert_eq!(
195 rv.get_breakpoint_or_base(Breakpoint::Md),
196 Some(&"base".to_string())
197 );
198 }
199
200 #[test]
201 fn test_responsive_value_has_breakpoint() {
202 let mut rv = ResponsiveValue::new();
203 rv.set_breakpoint(Breakpoint::Base, "base".to_string());
204 rv.set_breakpoint(Breakpoint::Sm, "sm".to_string());
205
206 assert!(rv.has_breakpoint(Breakpoint::Base));
207 assert!(rv.has_breakpoint(Breakpoint::Sm));
208 assert!(!rv.has_breakpoint(Breakpoint::Md));
209 }
210
211 #[test]
212 fn test_responsive_value_get_breakpoints() {
213 let mut rv = ResponsiveValue::new();
214 rv.set_breakpoint(Breakpoint::Base, "base".to_string());
215 rv.set_breakpoint(Breakpoint::Sm, "sm".to_string());
216 rv.set_breakpoint(Breakpoint::Lg, "lg".to_string());
217
218 let breakpoints = rv.get_breakpoints();
219 assert_eq!(breakpoints.len(), 3);
220 assert!(breakpoints.contains(&Breakpoint::Base));
221 assert!(breakpoints.contains(&Breakpoint::Sm));
222 assert!(breakpoints.contains(&Breakpoint::Lg));
223 }
224
225 #[test]
226 fn test_responsive_value_clear() {
227 let mut rv = ResponsiveValue::with_base("base".to_string());
228 rv.set_breakpoint(Breakpoint::Sm, "sm".to_string());
229
230 assert!(!rv.is_empty());
231 rv.clear();
232 assert!(rv.is_empty());
233 }
234
235 #[test]
236 fn test_responsive_value_remove_breakpoint() {
237 let mut rv = ResponsiveValue::with_base("base".to_string());
238 rv.set_breakpoint(Breakpoint::Sm, "sm".to_string());
239
240 assert_eq!(rv.len(), 2);
241 let removed = rv.remove_breakpoint(Breakpoint::Sm);
242 assert_eq!(removed, Some("sm".to_string()));
243 assert_eq!(rv.len(), 1);
244 assert!(!rv.has_breakpoint(Breakpoint::Sm));
245 }
246
247 #[test]
248 fn test_responsive_value_get_for_width() {
249 let mut rv = ResponsiveValue::new();
250 rv.set_breakpoint(Breakpoint::Base, "base".to_string());
251 rv.set_breakpoint(Breakpoint::Sm, "sm".to_string());
252 rv.set_breakpoint(Breakpoint::Md, "md".to_string());
253
254 assert_eq!(rv.get_for_width(0), Some(&"base".to_string()));
256
257 assert_eq!(rv.get_for_width(640), Some(&"sm".to_string()));
259
260 assert_eq!(rv.get_for_width(768), Some(&"md".to_string()));
262
263 assert_eq!(rv.get_for_width(1000), Some(&"md".to_string()));
265 }
266
267 #[test]
268 fn test_responsive_value_to_css_classes() {
269 let mut rv = ResponsiveValue::new();
270 rv.set_breakpoint(Breakpoint::Base, "text-sm".to_string());
271 rv.set_breakpoint(Breakpoint::Sm, "text-base".to_string());
272 rv.set_breakpoint(Breakpoint::Md, "text-lg".to_string());
273
274 let classes = rv.to_css_classes(|v| v.clone());
275 assert!(classes.contains("text-sm"));
276 assert!(classes.contains("sm:text-base"));
277 assert!(classes.contains("md:text-lg"));
278 }
279
280 #[test]
281 fn test_responsive_value_from() {
282 let rv = ResponsiveValue::from("base".to_string());
283 assert_eq!(rv.get_base(), Some(&"base".to_string()));
284
285 let mut map = HashMap::new();
286 map.insert(Breakpoint::Base, "base".to_string());
287 map.insert(Breakpoint::Sm, "sm".to_string());
288
289 let rv = ResponsiveValue::from(map);
290 assert_eq!(rv.len(), 2);
291 assert_eq!(
292 rv.get_breakpoint(Breakpoint::Base),
293 Some(&"base".to_string())
294 );
295 assert_eq!(rv.get_breakpoint(Breakpoint::Sm), Some(&"sm".to_string()));
296 }
297}