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.get(&breakpoint).copied()
79 .or_else(|| self.columns.get(&Breakpoint::Base).copied())
80 }
81
82 pub fn get_gap_or_base(&self, breakpoint: Breakpoint) -> Option<u32> {
84 self.gap.get(&breakpoint).copied()
85 .or_else(|| self.gap.get(&Breakpoint::Base).copied())
86 }
87
88 pub fn to_css_classes(&self) -> String {
90 let mut classes = Vec::new();
91
92 for (breakpoint, &columns) in &self.columns {
94 let class = if columns == 1 {
95 "grid-cols-1".to_string()
96 } else {
97 format!("grid-cols-{}", columns)
98 };
99
100 if *breakpoint == Breakpoint::Base {
101 classes.push(class);
102 } else {
103 classes.push(format!("{}{}", breakpoint.prefix(), class));
104 }
105 }
106
107 for (breakpoint, &gap) in &self.gap {
109 let class = if gap == 0 {
110 "gap-0".to_string()
111 } else {
112 format!("gap-{}", gap)
113 };
114
115 if *breakpoint == Breakpoint::Base {
116 classes.push(class);
117 } else {
118 classes.push(format!("{}{}", breakpoint.prefix(), class));
119 }
120 }
121
122 for (breakpoint, &gap) in &self.row_gap {
124 let class = if gap == 0 {
125 "gap-y-0".to_string()
126 } else {
127 format!("gap-y-{}", gap)
128 };
129
130 if *breakpoint == Breakpoint::Base {
131 classes.push(class);
132 } else {
133 classes.push(format!("{}{}", breakpoint.prefix(), class));
134 }
135 }
136
137 for (breakpoint, &gap) in &self.column_gap {
139 let class = if gap == 0 {
140 "gap-x-0".to_string()
141 } else {
142 format!("gap-x-{}", gap)
143 };
144
145 if *breakpoint == Breakpoint::Base {
146 classes.push(class);
147 } else {
148 classes.push(format!("{}{}", breakpoint.prefix(), class));
149 }
150 }
151
152 classes.join(" ")
153 }
154
155 pub fn to_css_classes_for_width(&self, screen_width: u32) -> String {
157 let mut classes = Vec::new();
158
159 let target_breakpoint = self.get_breakpoint_for_width(screen_width);
161
162 if let Some(columns) = self.get_columns_or_base(target_breakpoint) {
164 let class = if columns == 1 {
165 "grid-cols-1".to_string()
166 } else {
167 format!("grid-cols-{}", columns)
168 };
169 classes.push(class);
170 }
171
172 if let Some(gap) = self.get_gap_or_base(target_breakpoint) {
174 let class = if gap == 0 {
175 "gap-0".to_string()
176 } else {
177 format!("gap-{}", gap)
178 };
179 classes.push(class);
180 }
181
182 if let Some(gap) = self.get_row_gap(target_breakpoint) {
184 let class = if gap == 0 {
185 "gap-y-0".to_string()
186 } else {
187 format!("gap-y-{}", gap)
188 };
189 classes.push(class);
190 }
191
192 if let Some(gap) = self.get_column_gap(target_breakpoint) {
194 let class = if gap == 0 {
195 "gap-x-0".to_string()
196 } else {
197 format!("gap-x-{}", gap)
198 };
199 classes.push(class);
200 }
201
202 classes.join(" ")
203 }
204
205 fn get_breakpoint_for_width(&self, screen_width: u32) -> Breakpoint {
207 if screen_width >= Breakpoint::Xl2.min_width() {
208 Breakpoint::Xl2
209 } else if screen_width >= Breakpoint::Xl.min_width() {
210 Breakpoint::Xl
211 } else if screen_width >= Breakpoint::Lg.min_width() {
212 Breakpoint::Lg
213 } else if screen_width >= Breakpoint::Md.min_width() {
214 Breakpoint::Md
215 } else if screen_width >= Breakpoint::Sm.min_width() {
216 Breakpoint::Sm
217 } else {
218 Breakpoint::Base
219 }
220 }
221
222 pub fn is_empty(&self) -> bool {
224 self.columns.is_empty() && self.gap.is_empty() && self.row_gap.is_empty() && self.column_gap.is_empty()
225 }
226
227 pub fn len(&self) -> usize {
229 let mut count = 0;
230 count += self.columns.len();
231 count += self.gap.len();
232 count += self.row_gap.len();
233 count += self.column_gap.len();
234 count
235 }
236
237 pub fn clear(&mut self) {
239 self.columns.clear();
240 self.gap.clear();
241 self.row_gap.clear();
242 self.column_gap.clear();
243 }
244}
245
246impl Default for ResponsiveGrid {
247 fn default() -> Self {
248 let mut grid = Self {
249 columns: HashMap::new(),
250 gap: HashMap::new(),
251 row_gap: HashMap::new(),
252 column_gap: HashMap::new(),
253 };
254
255 grid.set_columns(Breakpoint::Base, 1);
257 grid.set_gap(Breakpoint::Base, 0);
258
259 grid
260 }
261}
262
263#[cfg(test)]
264mod tests {
265 use super::*;
266
267 #[test]
268 fn test_responsive_grid_new() {
269 let grid = ResponsiveGrid::new();
270 assert_eq!(grid.get_columns(Breakpoint::Base), Some(1));
271 assert_eq!(grid.get_gap(Breakpoint::Base), Some(0));
272 }
273
274 #[test]
275 fn test_responsive_grid_with_base() {
276 let grid = ResponsiveGrid::with_base(3, 4);
277 assert_eq!(grid.get_columns(Breakpoint::Base), Some(3));
278 assert_eq!(grid.get_gap(Breakpoint::Base), Some(4));
279 }
280
281 #[test]
282 fn test_responsive_grid_set_get() {
283 let mut grid = ResponsiveGrid::new();
284
285 grid.set_columns(Breakpoint::Sm, 2);
286 grid.set_columns(Breakpoint::Md, 3);
287 grid.set_columns(Breakpoint::Lg, 4);
288 grid.set_gap(Breakpoint::Sm, 2);
289 grid.set_gap(Breakpoint::Md, 4);
290 grid.set_row_gap(Breakpoint::Lg, 6);
291 grid.set_column_gap(Breakpoint::Xl, 8);
292
293 assert_eq!(grid.get_columns(Breakpoint::Sm), Some(2));
294 assert_eq!(grid.get_columns(Breakpoint::Md), Some(3));
295 assert_eq!(grid.get_columns(Breakpoint::Lg), Some(4));
296 assert_eq!(grid.get_gap(Breakpoint::Sm), Some(2));
297 assert_eq!(grid.get_gap(Breakpoint::Md), Some(4));
298 assert_eq!(grid.get_row_gap(Breakpoint::Lg), Some(6));
299 assert_eq!(grid.get_column_gap(Breakpoint::Xl), Some(8));
300 }
301
302 #[test]
303 fn test_responsive_grid_get_or_base() {
304 let mut grid = ResponsiveGrid::new();
305 grid.set_columns(Breakpoint::Base, 1);
306 grid.set_columns(Breakpoint::Sm, 2);
307 grid.set_gap(Breakpoint::Base, 0);
308 grid.set_gap(Breakpoint::Md, 4);
309
310 assert_eq!(grid.get_columns_or_base(Breakpoint::Base), Some(1));
311 assert_eq!(grid.get_columns_or_base(Breakpoint::Sm), Some(2));
312 assert_eq!(grid.get_columns_or_base(Breakpoint::Md), Some(1)); assert_eq!(grid.get_gap_or_base(Breakpoint::Base), Some(0));
314 assert_eq!(grid.get_gap_or_base(Breakpoint::Sm), Some(0)); assert_eq!(grid.get_gap_or_base(Breakpoint::Md), Some(4));
316 }
317
318 #[test]
319 fn test_responsive_grid_to_css_classes() {
320 let mut grid = ResponsiveGrid::new();
321 grid.set_columns(Breakpoint::Base, 1);
322 grid.set_columns(Breakpoint::Sm, 2);
323 grid.set_columns(Breakpoint::Md, 3);
324 grid.set_gap(Breakpoint::Base, 0);
325 grid.set_gap(Breakpoint::Sm, 2);
326 grid.set_gap(Breakpoint::Md, 4);
327
328 let classes = grid.to_css_classes();
329 assert!(classes.contains("grid-cols-1"));
330 assert!(classes.contains("sm:grid-cols-2"));
331 assert!(classes.contains("md:grid-cols-3"));
332 assert!(classes.contains("gap-0"));
333 assert!(classes.contains("sm:gap-2"));
334 assert!(classes.contains("md:gap-4"));
335 }
336
337 #[test]
338 fn test_responsive_grid_to_css_classes_for_width() {
339 let mut grid = ResponsiveGrid::new();
340 grid.set_columns(Breakpoint::Base, 1);
341 grid.set_columns(Breakpoint::Sm, 2);
342 grid.set_columns(Breakpoint::Md, 3);
343 grid.set_gap(Breakpoint::Base, 0);
344 grid.set_gap(Breakpoint::Sm, 2);
345 grid.set_gap(Breakpoint::Md, 4);
346
347 let classes_0 = grid.to_css_classes_for_width(0);
349 assert!(classes_0.contains("grid-cols-1"));
350 assert!(classes_0.contains("gap-0"));
351 assert!(!classes_0.contains("grid-cols-2"));
352 assert!(!classes_0.contains("gap-2"));
353
354 let classes_640 = grid.to_css_classes_for_width(640);
356 assert!(classes_640.contains("grid-cols-2"));
357 assert!(classes_640.contains("gap-2"));
358 assert!(!classes_640.contains("grid-cols-3"));
359 assert!(!classes_640.contains("gap-4"));
360
361 let classes_768 = grid.to_css_classes_for_width(768);
363 assert!(classes_768.contains("grid-cols-3"));
364 assert!(classes_768.contains("gap-4"));
365 }
366
367 #[test]
368 fn test_responsive_grid_is_empty() {
369 let grid = ResponsiveGrid::new();
370 assert!(!grid.is_empty()); let mut empty_grid = ResponsiveGrid {
373 columns: HashMap::new(),
374 gap: HashMap::new(),
375 row_gap: HashMap::new(),
376 column_gap: HashMap::new(),
377 };
378 assert!(empty_grid.is_empty());
379 }
380
381 #[test]
382 fn test_responsive_grid_clear() {
383 let mut grid = ResponsiveGrid::new();
384 grid.set_columns(Breakpoint::Sm, 2);
385 grid.set_gap(Breakpoint::Md, 4);
386
387 assert!(!grid.is_empty());
388 grid.clear();
389 assert!(grid.is_empty());
390 }
391}