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.get(&breakpoint).or_else(|| self.values.get(&Breakpoint::Base))
44 }
45
46 pub fn get_base(&self) -> Option<&T> {
48 self.values.get(&Breakpoint::Base)
49 }
50
51 pub fn has_breakpoint(&self, breakpoint: Breakpoint) -> bool {
53 self.values.contains_key(&breakpoint)
54 }
55
56 pub fn get_breakpoints(&self) -> Vec<Breakpoint> {
58 self.values.keys().cloned().collect()
59 }
60
61 pub fn is_empty(&self) -> bool {
63 self.values.is_empty()
64 }
65
66 pub fn len(&self) -> usize {
68 self.values.len()
69 }
70
71 pub fn clear(&mut self) {
73 self.values.clear();
74 }
75
76 pub fn remove_breakpoint(&mut self, breakpoint: Breakpoint) -> Option<T> {
78 self.values.remove(&breakpoint)
79 }
80
81 pub fn get_for_width(&self, screen_width: u32) -> Option<&T> {
83 let active_breakpoints: Vec<Breakpoint> = self.values
85 .keys()
86 .filter(|&&bp| screen_width >= bp.min_width())
87 .cloned()
88 .collect();
89
90 if active_breakpoints.is_empty() {
91 return self.get_base();
92 }
93
94 let best_breakpoint = active_breakpoints
96 .into_iter()
97 .max_by_key(|bp| bp.min_width())?;
98
99 self.get_breakpoint(best_breakpoint)
100 }
101
102 pub fn to_css_classes<F>(&self, class_generator: F) -> String
104 where
105 F: Fn(&T) -> String,
106 {
107 let mut classes = Vec::new();
108
109 for breakpoint in Breakpoint::all() {
110 if let Some(value) = self.get_breakpoint(breakpoint) {
111 let class = class_generator(value);
112 if !class.is_empty() {
113 if breakpoint == Breakpoint::Base {
114 classes.push(class);
115 } else {
116 classes.push(format!("{}{}", breakpoint.prefix(), class));
117 }
118 }
119 }
120 }
121
122 classes.join(" ")
123 }
124}
125
126impl<T> Default for ResponsiveValue<T> {
127 fn default() -> Self {
128 Self::new()
129 }
130}
131
132impl<T> From<T> for ResponsiveValue<T> {
133 fn from(value: T) -> Self {
134 Self::with_base(value)
135 }
136}
137
138impl<T> From<HashMap<Breakpoint, T>> for ResponsiveValue<T> {
139 fn from(values: HashMap<Breakpoint, T>) -> Self {
140 Self { values }
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 #[test]
149 fn test_responsive_value_new() {
150 let rv = ResponsiveValue::<String>::new();
151 assert!(rv.is_empty());
152 assert_eq!(rv.len(), 0);
153 }
154
155 #[test]
156 fn test_responsive_value_with_base() {
157 let rv = ResponsiveValue::with_base("base".to_string());
158 assert!(!rv.is_empty());
159 assert_eq!(rv.len(), 1);
160 assert_eq!(rv.get_base(), Some(&"base".to_string()));
161 }
162
163 #[test]
164 fn test_responsive_value_set_get_breakpoint() {
165 let mut rv = ResponsiveValue::new();
166 rv.set_breakpoint(Breakpoint::Base, "base".to_string());
167 rv.set_breakpoint(Breakpoint::Sm, "sm".to_string());
168
169 assert_eq!(rv.get_breakpoint(Breakpoint::Base), Some(&"base".to_string()));
170 assert_eq!(rv.get_breakpoint(Breakpoint::Sm), Some(&"sm".to_string()));
171 assert_eq!(rv.get_breakpoint(Breakpoint::Md), None);
172 }
173
174 #[test]
175 fn test_responsive_value_get_breakpoint_or_base() {
176 let mut rv = ResponsiveValue::new();
177 rv.set_breakpoint(Breakpoint::Base, "base".to_string());
178 rv.set_breakpoint(Breakpoint::Sm, "sm".to_string());
179
180 assert_eq!(rv.get_breakpoint_or_base(Breakpoint::Base), Some(&"base".to_string()));
181 assert_eq!(rv.get_breakpoint_or_base(Breakpoint::Sm), Some(&"sm".to_string()));
182 assert_eq!(rv.get_breakpoint_or_base(Breakpoint::Md), Some(&"base".to_string()));
183 }
184
185 #[test]
186 fn test_responsive_value_has_breakpoint() {
187 let mut rv = ResponsiveValue::new();
188 rv.set_breakpoint(Breakpoint::Base, "base".to_string());
189 rv.set_breakpoint(Breakpoint::Sm, "sm".to_string());
190
191 assert!(rv.has_breakpoint(Breakpoint::Base));
192 assert!(rv.has_breakpoint(Breakpoint::Sm));
193 assert!(!rv.has_breakpoint(Breakpoint::Md));
194 }
195
196 #[test]
197 fn test_responsive_value_get_breakpoints() {
198 let mut rv = ResponsiveValue::new();
199 rv.set_breakpoint(Breakpoint::Base, "base".to_string());
200 rv.set_breakpoint(Breakpoint::Sm, "sm".to_string());
201 rv.set_breakpoint(Breakpoint::Lg, "lg".to_string());
202
203 let breakpoints = rv.get_breakpoints();
204 assert_eq!(breakpoints.len(), 3);
205 assert!(breakpoints.contains(&Breakpoint::Base));
206 assert!(breakpoints.contains(&Breakpoint::Sm));
207 assert!(breakpoints.contains(&Breakpoint::Lg));
208 }
209
210 #[test]
211 fn test_responsive_value_clear() {
212 let mut rv = ResponsiveValue::with_base("base".to_string());
213 rv.set_breakpoint(Breakpoint::Sm, "sm".to_string());
214
215 assert!(!rv.is_empty());
216 rv.clear();
217 assert!(rv.is_empty());
218 }
219
220 #[test]
221 fn test_responsive_value_remove_breakpoint() {
222 let mut rv = ResponsiveValue::with_base("base".to_string());
223 rv.set_breakpoint(Breakpoint::Sm, "sm".to_string());
224
225 assert_eq!(rv.len(), 2);
226 let removed = rv.remove_breakpoint(Breakpoint::Sm);
227 assert_eq!(removed, Some("sm".to_string()));
228 assert_eq!(rv.len(), 1);
229 assert!(!rv.has_breakpoint(Breakpoint::Sm));
230 }
231
232 #[test]
233 fn test_responsive_value_get_for_width() {
234 let mut rv = ResponsiveValue::new();
235 rv.set_breakpoint(Breakpoint::Base, "base".to_string());
236 rv.set_breakpoint(Breakpoint::Sm, "sm".to_string());
237 rv.set_breakpoint(Breakpoint::Md, "md".to_string());
238
239 assert_eq!(rv.get_for_width(0), Some(&"base".to_string()));
241
242 assert_eq!(rv.get_for_width(640), Some(&"sm".to_string()));
244
245 assert_eq!(rv.get_for_width(768), Some(&"md".to_string()));
247
248 assert_eq!(rv.get_for_width(1000), Some(&"md".to_string()));
250 }
251
252 #[test]
253 fn test_responsive_value_to_css_classes() {
254 let mut rv = ResponsiveValue::new();
255 rv.set_breakpoint(Breakpoint::Base, "text-sm".to_string());
256 rv.set_breakpoint(Breakpoint::Sm, "text-base".to_string());
257 rv.set_breakpoint(Breakpoint::Md, "text-lg".to_string());
258
259 let classes = rv.to_css_classes(|v| v.clone());
260 assert!(classes.contains("text-sm"));
261 assert!(classes.contains("sm:text-base"));
262 assert!(classes.contains("md:text-lg"));
263 }
264
265 #[test]
266 fn test_responsive_value_from() {
267 let rv = ResponsiveValue::from("base".to_string());
268 assert_eq!(rv.get_base(), Some(&"base".to_string()));
269
270 let mut map = HashMap::new();
271 map.insert(Breakpoint::Base, "base".to_string());
272 map.insert(Breakpoint::Sm, "sm".to_string());
273
274 let rv = ResponsiveValue::from(map);
275 assert_eq!(rv.len(), 2);
276 assert_eq!(rv.get_breakpoint(Breakpoint::Base), Some(&"base".to_string()));
277 assert_eq!(rv.get_breakpoint(Breakpoint::Sm), Some(&"sm".to_string()));
278 }
279}