use super::breakpoints::Breakpoint;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ResponsiveGrid {
pub columns: HashMap<Breakpoint, u32>,
pub gap: HashMap<Breakpoint, u32>,
pub row_gap: HashMap<Breakpoint, u32>,
pub column_gap: HashMap<Breakpoint, u32>,
}
impl ResponsiveGrid {
pub fn new() -> Self {
Self::default()
}
pub fn with_base(columns: u32, gap: u32) -> Self {
let mut grid = Self::new();
grid.set_columns(Breakpoint::Base, columns);
grid.set_gap(Breakpoint::Base, gap);
grid
}
pub fn set_columns(&mut self, breakpoint: Breakpoint, columns: u32) {
self.columns.insert(breakpoint, columns);
}
pub fn set_gap(&mut self, breakpoint: Breakpoint, gap: u32) {
self.gap.insert(breakpoint, gap);
}
pub fn set_row_gap(&mut self, breakpoint: Breakpoint, gap: u32) {
self.row_gap.insert(breakpoint, gap);
}
pub fn set_column_gap(&mut self, breakpoint: Breakpoint, gap: u32) {
self.column_gap.insert(breakpoint, gap);
}
pub fn get_columns(&self, breakpoint: Breakpoint) -> Option<u32> {
self.columns.get(&breakpoint).copied()
}
pub fn get_gap(&self, breakpoint: Breakpoint) -> Option<u32> {
self.gap.get(&breakpoint).copied()
}
pub fn get_row_gap(&self, breakpoint: Breakpoint) -> Option<u32> {
self.row_gap.get(&breakpoint).copied()
}
pub fn get_column_gap(&self, breakpoint: Breakpoint) -> Option<u32> {
self.column_gap.get(&breakpoint).copied()
}
pub fn get_columns_or_base(&self, breakpoint: Breakpoint) -> Option<u32> {
self.columns
.get(&breakpoint)
.copied()
.or_else(|| self.columns.get(&Breakpoint::Base).copied())
}
pub fn get_gap_or_base(&self, breakpoint: Breakpoint) -> Option<u32> {
self.gap
.get(&breakpoint)
.copied()
.or_else(|| self.gap.get(&Breakpoint::Base).copied())
}
pub fn to_css_classes(&self) -> String {
let mut classes = Vec::new();
for (breakpoint, &columns) in &self.columns {
let class = if columns == 1 {
"grid-cols-1".to_string()
} else {
format!("grid-cols-{}", columns)
};
if *breakpoint == Breakpoint::Base {
classes.push(class);
} else {
classes.push(format!("{}{}", breakpoint.prefix(), class));
}
}
for (breakpoint, &gap) in &self.gap {
let class = if gap == 0 {
"gap-0".to_string()
} else {
format!("gap-{}", gap)
};
if *breakpoint == Breakpoint::Base {
classes.push(class);
} else {
classes.push(format!("{}{}", breakpoint.prefix(), class));
}
}
for (breakpoint, &gap) in &self.row_gap {
let class = if gap == 0 {
"gap-y-0".to_string()
} else {
format!("gap-y-{}", gap)
};
if *breakpoint == Breakpoint::Base {
classes.push(class);
} else {
classes.push(format!("{}{}", breakpoint.prefix(), class));
}
}
for (breakpoint, &gap) in &self.column_gap {
let class = if gap == 0 {
"gap-x-0".to_string()
} else {
format!("gap-x-{}", gap)
};
if *breakpoint == Breakpoint::Base {
classes.push(class);
} else {
classes.push(format!("{}{}", breakpoint.prefix(), class));
}
}
classes.join(" ")
}
pub fn to_css_classes_for_width(&self, screen_width: u32) -> String {
let mut classes = Vec::new();
let target_breakpoint = self.get_breakpoint_for_width(screen_width);
if let Some(columns) = self.get_columns_or_base(target_breakpoint) {
let class = if columns == 1 {
"grid-cols-1".to_string()
} else {
format!("grid-cols-{}", columns)
};
classes.push(class);
}
if let Some(gap) = self.get_gap_or_base(target_breakpoint) {
let class = if gap == 0 {
"gap-0".to_string()
} else {
format!("gap-{}", gap)
};
classes.push(class);
}
if let Some(gap) = self.get_row_gap(target_breakpoint) {
let class = if gap == 0 {
"gap-y-0".to_string()
} else {
format!("gap-y-{}", gap)
};
classes.push(class);
}
if let Some(gap) = self.get_column_gap(target_breakpoint) {
let class = if gap == 0 {
"gap-x-0".to_string()
} else {
format!("gap-x-{}", gap)
};
classes.push(class);
}
classes.join(" ")
}
fn get_breakpoint_for_width(&self, screen_width: u32) -> Breakpoint {
if screen_width >= Breakpoint::Xl2.min_width() {
Breakpoint::Xl2
} else if screen_width >= Breakpoint::Xl.min_width() {
Breakpoint::Xl
} else if screen_width >= Breakpoint::Lg.min_width() {
Breakpoint::Lg
} else if screen_width >= Breakpoint::Md.min_width() {
Breakpoint::Md
} else if screen_width >= Breakpoint::Sm.min_width() {
Breakpoint::Sm
} else {
Breakpoint::Base
}
}
pub fn is_empty(&self) -> bool {
self.columns.is_empty()
&& self.gap.is_empty()
&& self.row_gap.is_empty()
&& self.column_gap.is_empty()
}
pub fn len(&self) -> usize {
let mut count = 0;
count += self.columns.len();
count += self.gap.len();
count += self.row_gap.len();
count += self.column_gap.len();
count
}
pub fn clear(&mut self) {
self.columns.clear();
self.gap.clear();
self.row_gap.clear();
self.column_gap.clear();
}
}
impl Default for ResponsiveGrid {
fn default() -> Self {
let mut grid = Self {
columns: HashMap::new(),
gap: HashMap::new(),
row_gap: HashMap::new(),
column_gap: HashMap::new(),
};
grid.set_columns(Breakpoint::Base, 1);
grid.set_gap(Breakpoint::Base, 0);
grid
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_responsive_grid_new() {
let grid = ResponsiveGrid::new();
assert_eq!(grid.get_columns(Breakpoint::Base), Some(1));
assert_eq!(grid.get_gap(Breakpoint::Base), Some(0));
}
#[test]
fn test_responsive_grid_with_base() {
let grid = ResponsiveGrid::with_base(3, 4);
assert_eq!(grid.get_columns(Breakpoint::Base), Some(3));
assert_eq!(grid.get_gap(Breakpoint::Base), Some(4));
}
#[test]
fn test_responsive_grid_set_get() {
let mut grid = ResponsiveGrid::new();
grid.set_columns(Breakpoint::Sm, 2);
grid.set_columns(Breakpoint::Md, 3);
grid.set_columns(Breakpoint::Lg, 4);
grid.set_gap(Breakpoint::Sm, 2);
grid.set_gap(Breakpoint::Md, 4);
grid.set_row_gap(Breakpoint::Lg, 6);
grid.set_column_gap(Breakpoint::Xl, 8);
assert_eq!(grid.get_columns(Breakpoint::Sm), Some(2));
assert_eq!(grid.get_columns(Breakpoint::Md), Some(3));
assert_eq!(grid.get_columns(Breakpoint::Lg), Some(4));
assert_eq!(grid.get_gap(Breakpoint::Sm), Some(2));
assert_eq!(grid.get_gap(Breakpoint::Md), Some(4));
assert_eq!(grid.get_row_gap(Breakpoint::Lg), Some(6));
assert_eq!(grid.get_column_gap(Breakpoint::Xl), Some(8));
}
#[test]
fn test_responsive_grid_get_or_base() {
let mut grid = ResponsiveGrid::new();
grid.set_columns(Breakpoint::Base, 1);
grid.set_columns(Breakpoint::Sm, 2);
grid.set_gap(Breakpoint::Base, 0);
grid.set_gap(Breakpoint::Md, 4);
assert_eq!(grid.get_columns_or_base(Breakpoint::Base), Some(1));
assert_eq!(grid.get_columns_or_base(Breakpoint::Sm), Some(2));
assert_eq!(grid.get_columns_or_base(Breakpoint::Md), Some(1)); assert_eq!(grid.get_gap_or_base(Breakpoint::Base), Some(0));
assert_eq!(grid.get_gap_or_base(Breakpoint::Sm), Some(0)); assert_eq!(grid.get_gap_or_base(Breakpoint::Md), Some(4));
}
#[test]
fn test_responsive_grid_to_css_classes() {
let mut grid = ResponsiveGrid::new();
grid.set_columns(Breakpoint::Base, 1);
grid.set_columns(Breakpoint::Sm, 2);
grid.set_columns(Breakpoint::Md, 3);
grid.set_gap(Breakpoint::Base, 0);
grid.set_gap(Breakpoint::Sm, 2);
grid.set_gap(Breakpoint::Md, 4);
let classes = grid.to_css_classes();
assert!(classes.contains("grid-cols-1"));
assert!(classes.contains("sm:grid-cols-2"));
assert!(classes.contains("md:grid-cols-3"));
assert!(classes.contains("gap-0"));
assert!(classes.contains("sm:gap-2"));
assert!(classes.contains("md:gap-4"));
}
#[test]
fn test_responsive_grid_to_css_classes_for_width() {
let mut grid = ResponsiveGrid::new();
grid.set_columns(Breakpoint::Base, 1);
grid.set_columns(Breakpoint::Sm, 2);
grid.set_columns(Breakpoint::Md, 3);
grid.set_gap(Breakpoint::Base, 0);
grid.set_gap(Breakpoint::Sm, 2);
grid.set_gap(Breakpoint::Md, 4);
let classes_0 = grid.to_css_classes_for_width(0);
assert!(classes_0.contains("grid-cols-1"));
assert!(classes_0.contains("gap-0"));
assert!(!classes_0.contains("grid-cols-2"));
assert!(!classes_0.contains("gap-2"));
let classes_640 = grid.to_css_classes_for_width(640);
assert!(classes_640.contains("grid-cols-2"));
assert!(classes_640.contains("gap-2"));
assert!(!classes_640.contains("grid-cols-3"));
assert!(!classes_640.contains("gap-4"));
let classes_768 = grid.to_css_classes_for_width(768);
assert!(classes_768.contains("grid-cols-3"));
assert!(classes_768.contains("gap-4"));
}
#[test]
fn test_responsive_grid_is_empty() {
let grid = ResponsiveGrid::new();
assert!(!grid.is_empty());
let empty_grid = ResponsiveGrid {
columns: HashMap::new(),
gap: HashMap::new(),
row_gap: HashMap::new(),
column_gap: HashMap::new(),
};
assert!(empty_grid.is_empty());
}
#[test]
fn test_responsive_grid_clear() {
let mut grid = ResponsiveGrid::new();
grid.set_columns(Breakpoint::Sm, 2);
grid.set_gap(Breakpoint::Md, 4);
assert!(!grid.is_empty());
grid.clear();
assert!(grid.is_empty());
}
}