tailwind_rs_core/responsive/
grid.rs1use super::breakpoints::Breakpoint;
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
11pub struct ResponsiveGrid {
12 pub columns: HashMap<Breakpoint, u32>,
14 pub gap: HashMap<Breakpoint, u32>,
16 pub row_gap: HashMap<Breakpoint, u32>,
18 pub column_gap: HashMap<Breakpoint, u32>,
20}
21
22impl ResponsiveGrid {
23 pub fn new() -> Self {
25 Self::default()
26 }
27
28 pub fn with_base(columns: u32, gap: u32) -> Self {
30 let mut grid = Self::new();
31 grid.set_columns(Breakpoint::Base, columns);
32 grid.set_gap(Breakpoint::Base, gap);
33 grid
34 }
35
36 pub fn set_columns(&mut self, breakpoint: Breakpoint, columns: u32) {
38 self.columns.insert(breakpoint, columns);
39 }
40
41 pub fn set_gap(&mut self, breakpoint: Breakpoint, gap: u32) {
43 self.gap.insert(breakpoint, gap);
44 }
45
46 pub fn set_row_gap(&mut self, breakpoint: Breakpoint, gap: u32) {
48 self.row_gap.insert(breakpoint, gap);
49 }
50
51 pub fn set_column_gap(&mut self, breakpoint: Breakpoint, gap: u32) {
53 self.column_gap.insert(breakpoint, gap);
54 }
55
56 pub fn get_columns(&self, breakpoint: Breakpoint) -> Option<u32> {
58 self.columns.get(&breakpoint).copied()
59 }
60
61 pub fn get_gap(&self, breakpoint: Breakpoint) -> Option<u32> {
63 self.gap.get(&breakpoint).copied()
64 }
65
66 pub fn get_row_gap(&self, breakpoint: Breakpoint) -> Option<u32> {
68 self.row_gap.get(&breakpoint).copied()
69 }
70
71 pub fn get_column_gap(&self, breakpoint: Breakpoint) -> Option<u32> {
73 self.column_gap.get(&breakpoint).copied()
74 }
75
76 pub fn get_columns_or_base(&self, breakpoint: Breakpoint) -> Option<u32> {
78 self.columns
79 .get(&breakpoint)
80 .copied()
81 .or_else(|| self.columns.get(&Breakpoint::Base).copied())
82 }
83
84 pub fn get_gap_or_base(&self, breakpoint: Breakpoint) -> Option<u32> {
86 self.gap
87 .get(&breakpoint)
88 .copied()
89 .or_else(|| self.gap.get(&Breakpoint::Base).copied())
90 }
91
92 pub fn to_css_classes(&self) -> String {
94 let mut classes = Vec::new();
95
96 for (breakpoint, &columns) in &self.columns {
98 let class = if columns == 1 {
99 "grid-cols-1".to_string()
100 } else {
101 format!("grid-cols-{}", columns)
102 };
103
104 if *breakpoint == Breakpoint::Base {
105 classes.push(class);
106 } else {
107 classes.push(format!("{}{}", breakpoint.prefix(), class));
108 }
109 }
110
111 for (breakpoint, &gap) in &self.gap {
113 let class = if gap == 0 {
114 "gap-0".to_string()
115 } else {
116 format!("gap-{}", gap)
117 };
118
119 if *breakpoint == Breakpoint::Base {
120 classes.push(class);
121 } else {
122 classes.push(format!("{}{}", breakpoint.prefix(), class));
123 }
124 }
125
126 for (breakpoint, &gap) in &self.row_gap {
128 let class = if gap == 0 {
129 "gap-y-0".to_string()
130 } else {
131 format!("gap-y-{}", gap)
132 };
133
134 if *breakpoint == Breakpoint::Base {
135 classes.push(class);
136 } else {
137 classes.push(format!("{}{}", breakpoint.prefix(), class));
138 }
139 }
140
141 for (breakpoint, &gap) in &self.column_gap {
143 let class = if gap == 0 {
144 "gap-x-0".to_string()
145 } else {
146 format!("gap-x-{}", gap)
147 };
148
149 if *breakpoint == Breakpoint::Base {
150 classes.push(class);
151 } else {
152 classes.push(format!("{}{}", breakpoint.prefix(), class));
153 }
154 }
155
156 classes.join(" ")
157 }
158
159 pub fn to_css_classes_for_width(&self, screen_width: u32) -> String {
161 let mut classes = Vec::new();
162
163 let target_breakpoint = self.get_breakpoint_for_width(screen_width);
165
166 if let Some(columns) = self.get_columns_or_base(target_breakpoint) {
168 let class = if columns == 1 {
169 "grid-cols-1".to_string()
170 } else {
171 format!("grid-cols-{}", columns)
172 };
173 classes.push(class);
174 }
175
176 if let Some(gap) = self.get_gap_or_base(target_breakpoint) {
178 let class = if gap == 0 {
179 "gap-0".to_string()
180 } else {
181 format!("gap-{}", gap)
182 };
183 classes.push(class);
184 }
185
186 if let Some(gap) = self.get_row_gap(target_breakpoint) {
188 let class = if gap == 0 {
189 "gap-y-0".to_string()
190 } else {
191 format!("gap-y-{}", gap)
192 };
193 classes.push(class);
194 }
195
196 if let Some(gap) = self.get_column_gap(target_breakpoint) {
198 let class = if gap == 0 {
199 "gap-x-0".to_string()
200 } else {
201 format!("gap-x-{}", gap)
202 };
203 classes.push(class);
204 }
205
206 classes.join(" ")
207 }
208
209 fn get_breakpoint_for_width(&self, screen_width: u32) -> Breakpoint {
211 if screen_width >= Breakpoint::Xl2.min_width() {
212 Breakpoint::Xl2
213 } else if screen_width >= Breakpoint::Xl.min_width() {
214 Breakpoint::Xl
215 } else if screen_width >= Breakpoint::Lg.min_width() {
216 Breakpoint::Lg
217 } else if screen_width >= Breakpoint::Md.min_width() {
218 Breakpoint::Md
219 } else if screen_width >= Breakpoint::Sm.min_width() {
220 Breakpoint::Sm
221 } else {
222 Breakpoint::Base
223 }
224 }
225
226 pub fn is_empty(&self) -> bool {
228 self.columns.is_empty()
229 && self.gap.is_empty()
230 && self.row_gap.is_empty()
231 && self.column_gap.is_empty()
232 }
233
234 pub fn len(&self) -> usize {
236 let mut count = 0;
237 count += self.columns.len();
238 count += self.gap.len();
239 count += self.row_gap.len();
240 count += self.column_gap.len();
241 count
242 }
243
244 pub fn clear(&mut self) {
246 self.columns.clear();
247 self.gap.clear();
248 self.row_gap.clear();
249 self.column_gap.clear();
250 }
251}
252
253impl Default for ResponsiveGrid {
254 fn default() -> Self {
255 let mut grid = Self {
256 columns: HashMap::new(),
257 gap: HashMap::new(),
258 row_gap: HashMap::new(),
259 column_gap: HashMap::new(),
260 };
261
262 grid.set_columns(Breakpoint::Base, 1);
264 grid.set_gap(Breakpoint::Base, 0);
265
266 grid
267 }
268}
269
270#[cfg(test)]
271mod tests {
272 use super::*;
273
274 #[test]
275 fn test_responsive_grid_new() {
276 let grid = ResponsiveGrid::new();
277 assert_eq!(grid.get_columns(Breakpoint::Base), Some(1));
278 assert_eq!(grid.get_gap(Breakpoint::Base), Some(0));
279 }
280
281 #[test]
282 fn test_responsive_grid_with_base() {
283 let grid = ResponsiveGrid::with_base(3, 4);
284 assert_eq!(grid.get_columns(Breakpoint::Base), Some(3));
285 assert_eq!(grid.get_gap(Breakpoint::Base), Some(4));
286 }
287
288 #[test]
289 fn test_responsive_grid_set_get() {
290 let mut grid = ResponsiveGrid::new();
291
292 grid.set_columns(Breakpoint::Sm, 2);
293 grid.set_columns(Breakpoint::Md, 3);
294 grid.set_columns(Breakpoint::Lg, 4);
295 grid.set_gap(Breakpoint::Sm, 2);
296 grid.set_gap(Breakpoint::Md, 4);
297 grid.set_row_gap(Breakpoint::Lg, 6);
298 grid.set_column_gap(Breakpoint::Xl, 8);
299
300 assert_eq!(grid.get_columns(Breakpoint::Sm), Some(2));
301 assert_eq!(grid.get_columns(Breakpoint::Md), Some(3));
302 assert_eq!(grid.get_columns(Breakpoint::Lg), Some(4));
303 assert_eq!(grid.get_gap(Breakpoint::Sm), Some(2));
304 assert_eq!(grid.get_gap(Breakpoint::Md), Some(4));
305 assert_eq!(grid.get_row_gap(Breakpoint::Lg), Some(6));
306 assert_eq!(grid.get_column_gap(Breakpoint::Xl), Some(8));
307 }
308
309 #[test]
310 fn test_responsive_grid_get_or_base() {
311 let mut grid = ResponsiveGrid::new();
312 grid.set_columns(Breakpoint::Base, 1);
313 grid.set_columns(Breakpoint::Sm, 2);
314 grid.set_gap(Breakpoint::Base, 0);
315 grid.set_gap(Breakpoint::Md, 4);
316
317 assert_eq!(grid.get_columns_or_base(Breakpoint::Base), Some(1));
318 assert_eq!(grid.get_columns_or_base(Breakpoint::Sm), Some(2));
319 assert_eq!(grid.get_columns_or_base(Breakpoint::Md), Some(1)); assert_eq!(grid.get_gap_or_base(Breakpoint::Base), Some(0));
321 assert_eq!(grid.get_gap_or_base(Breakpoint::Sm), Some(0)); assert_eq!(grid.get_gap_or_base(Breakpoint::Md), Some(4));
323 }
324
325 #[test]
326 fn test_responsive_grid_to_css_classes() {
327 let mut grid = ResponsiveGrid::new();
328 grid.set_columns(Breakpoint::Base, 1);
329 grid.set_columns(Breakpoint::Sm, 2);
330 grid.set_columns(Breakpoint::Md, 3);
331 grid.set_gap(Breakpoint::Base, 0);
332 grid.set_gap(Breakpoint::Sm, 2);
333 grid.set_gap(Breakpoint::Md, 4);
334
335 let classes = grid.to_css_classes();
336 assert!(classes.contains("grid-cols-1"));
337 assert!(classes.contains("sm:grid-cols-2"));
338 assert!(classes.contains("md:grid-cols-3"));
339 assert!(classes.contains("gap-0"));
340 assert!(classes.contains("sm:gap-2"));
341 assert!(classes.contains("md:gap-4"));
342 }
343
344 #[test]
345 fn test_responsive_grid_to_css_classes_for_width() {
346 let mut grid = ResponsiveGrid::new();
347 grid.set_columns(Breakpoint::Base, 1);
348 grid.set_columns(Breakpoint::Sm, 2);
349 grid.set_columns(Breakpoint::Md, 3);
350 grid.set_gap(Breakpoint::Base, 0);
351 grid.set_gap(Breakpoint::Sm, 2);
352 grid.set_gap(Breakpoint::Md, 4);
353
354 let classes_0 = grid.to_css_classes_for_width(0);
356 assert!(classes_0.contains("grid-cols-1"));
357 assert!(classes_0.contains("gap-0"));
358 assert!(!classes_0.contains("grid-cols-2"));
359 assert!(!classes_0.contains("gap-2"));
360
361 let classes_640 = grid.to_css_classes_for_width(640);
363 assert!(classes_640.contains("grid-cols-2"));
364 assert!(classes_640.contains("gap-2"));
365 assert!(!classes_640.contains("grid-cols-3"));
366 assert!(!classes_640.contains("gap-4"));
367
368 let classes_768 = grid.to_css_classes_for_width(768);
370 assert!(classes_768.contains("grid-cols-3"));
371 assert!(classes_768.contains("gap-4"));
372 }
373
374 #[test]
375 fn test_responsive_grid_is_empty() {
376 let grid = ResponsiveGrid::new();
377 assert!(!grid.is_empty()); let empty_grid = ResponsiveGrid {
380 columns: HashMap::new(),
381 gap: HashMap::new(),
382 row_gap: HashMap::new(),
383 column_gap: HashMap::new(),
384 };
385 assert!(empty_grid.is_empty());
386 }
387
388 #[test]
389 fn test_responsive_grid_clear() {
390 let mut grid = ResponsiveGrid::new();
391 grid.set_columns(Breakpoint::Sm, 2);
392 grid.set_gap(Breakpoint::Md, 4);
393
394 assert!(!grid.is_empty());
395 grid.clear();
396 assert!(grid.is_empty());
397 }
398}