use crate::classes::ClassBuilder;
use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum SpacingValue {
Zero,
Px,
Fractional(f32),
Integer(u32),
Auto,
Full,
Screen,
Min,
Max,
Fit,
}
impl std::hash::Hash for SpacingValue {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
match self {
SpacingValue::Zero => 0u8.hash(state),
SpacingValue::Px => 1u8.hash(state),
SpacingValue::Fractional(f) => {
2u8.hash(state);
((f * 1000.0) as u32).hash(state);
}
SpacingValue::Integer(i) => {
3u8.hash(state);
i.hash(state);
}
SpacingValue::Auto => 4u8.hash(state),
SpacingValue::Full => 5u8.hash(state),
SpacingValue::Screen => 6u8.hash(state),
SpacingValue::Min => 7u8.hash(state),
SpacingValue::Max => 8u8.hash(state),
SpacingValue::Fit => 9u8.hash(state),
}
}
}
impl std::cmp::Eq for SpacingValue {}
impl SpacingValue {
pub fn to_css_value(&self) -> String {
match self {
SpacingValue::Zero => "0".to_string(),
SpacingValue::Px => "1px".to_string(),
SpacingValue::Fractional(f) => format!("{}rem", f * 0.25), SpacingValue::Integer(i) => format!("{}rem", *i as f32 * 0.25),
SpacingValue::Auto => "auto".to_string(),
SpacingValue::Full => "100%".to_string(),
SpacingValue::Screen => "100vh".to_string(),
SpacingValue::Min => "min-content".to_string(),
SpacingValue::Max => "max-content".to_string(),
SpacingValue::Fit => "fit-content".to_string(),
}
}
pub fn to_class_name(&self) -> String {
match self {
SpacingValue::Zero => "0".to_string(),
SpacingValue::Px => "px".to_string(),
SpacingValue::Fractional(f) => format!("{}", f),
SpacingValue::Integer(i) => i.to_string(),
SpacingValue::Auto => "auto".to_string(),
SpacingValue::Full => "full".to_string(),
SpacingValue::Screen => "screen".to_string(),
SpacingValue::Min => "min".to_string(),
SpacingValue::Max => "max".to_string(),
SpacingValue::Fit => "fit".to_string(),
}
}
pub fn all_values() -> Vec<SpacingValue> {
vec![
SpacingValue::Zero,
SpacingValue::Px,
SpacingValue::Fractional(0.5),
SpacingValue::Integer(1),
SpacingValue::Fractional(1.5),
SpacingValue::Integer(2),
SpacingValue::Fractional(2.5),
SpacingValue::Integer(3),
SpacingValue::Fractional(3.5),
SpacingValue::Integer(4),
SpacingValue::Integer(5),
SpacingValue::Integer(6),
SpacingValue::Integer(7),
SpacingValue::Integer(8),
SpacingValue::Integer(9),
SpacingValue::Integer(10),
SpacingValue::Integer(11),
SpacingValue::Integer(12),
SpacingValue::Integer(14),
SpacingValue::Integer(16),
SpacingValue::Integer(20),
SpacingValue::Integer(24),
SpacingValue::Integer(28),
SpacingValue::Integer(32),
SpacingValue::Integer(36),
SpacingValue::Integer(40),
SpacingValue::Integer(44),
SpacingValue::Integer(48),
SpacingValue::Integer(52),
SpacingValue::Integer(56),
SpacingValue::Integer(60),
SpacingValue::Integer(64),
SpacingValue::Integer(72),
SpacingValue::Integer(80),
SpacingValue::Integer(96),
SpacingValue::Auto,
SpacingValue::Full,
SpacingValue::Screen,
SpacingValue::Min,
SpacingValue::Max,
SpacingValue::Fit,
]
}
}
impl fmt::Display for SpacingValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_class_name())
}
}
pub trait PaddingUtilities {
fn padding(self, value: SpacingValue) -> Self;
fn padding_x(self, value: SpacingValue) -> Self;
fn padding_y(self, value: SpacingValue) -> Self;
fn padding_top(self, value: SpacingValue) -> Self;
fn padding_right(self, value: SpacingValue) -> Self;
fn padding_bottom(self, value: SpacingValue) -> Self;
fn padding_left(self, value: SpacingValue) -> Self;
fn padding_start(self, value: SpacingValue) -> Self;
fn padding_end(self, value: SpacingValue) -> Self;
}
impl PaddingUtilities for ClassBuilder {
fn padding(self, value: SpacingValue) -> Self {
self.class(format!("p-{}", value.to_class_name()))
}
fn padding_x(self, value: SpacingValue) -> Self {
self.class(format!("px-{}", value.to_class_name()))
}
fn padding_y(self, value: SpacingValue) -> Self {
self.class(format!("py-{}", value.to_class_name()))
}
fn padding_top(self, value: SpacingValue) -> Self {
self.class(format!("pt-{}", value.to_class_name()))
}
fn padding_right(self, value: SpacingValue) -> Self {
self.class(format!("pr-{}", value.to_class_name()))
}
fn padding_bottom(self, value: SpacingValue) -> Self {
self.class(format!("pb-{}", value.to_class_name()))
}
fn padding_left(self, value: SpacingValue) -> Self {
self.class(format!("pl-{}", value.to_class_name()))
}
fn padding_start(self, value: SpacingValue) -> Self {
self.class(format!("ps-{}", value.to_class_name()))
}
fn padding_end(self, value: SpacingValue) -> Self {
self.class(format!("pe-{}", value.to_class_name()))
}
}
pub trait MarginUtilities {
fn margin(self, value: SpacingValue) -> Self;
fn margin_x(self, value: SpacingValue) -> Self;
fn margin_y(self, value: SpacingValue) -> Self;
fn margin_top(self, value: SpacingValue) -> Self;
fn margin_right(self, value: SpacingValue) -> Self;
fn margin_bottom(self, value: SpacingValue) -> Self;
fn margin_left(self, value: SpacingValue) -> Self;
fn margin_start(self, value: SpacingValue) -> Self;
fn margin_end(self, value: SpacingValue) -> Self;
fn margin_negative(self, value: SpacingValue) -> Self;
fn margin_x_negative(self, value: SpacingValue) -> Self;
fn margin_y_negative(self, value: SpacingValue) -> Self;
fn margin_top_negative(self, value: SpacingValue) -> Self;
fn margin_right_negative(self, value: SpacingValue) -> Self;
fn margin_bottom_negative(self, value: SpacingValue) -> Self;
fn margin_left_negative(self, value: SpacingValue) -> Self;
}
impl MarginUtilities for ClassBuilder {
fn margin(self, value: SpacingValue) -> Self {
self.class(format!("m-{}", value.to_class_name()))
}
fn margin_x(self, value: SpacingValue) -> Self {
self.class(format!("mx-{}", value.to_class_name()))
}
fn margin_y(self, value: SpacingValue) -> Self {
self.class(format!("my-{}", value.to_class_name()))
}
fn margin_top(self, value: SpacingValue) -> Self {
self.class(format!("mt-{}", value.to_class_name()))
}
fn margin_right(self, value: SpacingValue) -> Self {
self.class(format!("mr-{}", value.to_class_name()))
}
fn margin_bottom(self, value: SpacingValue) -> Self {
self.class(format!("mb-{}", value.to_class_name()))
}
fn margin_left(self, value: SpacingValue) -> Self {
self.class(format!("ml-{}", value.to_class_name()))
}
fn margin_start(self, value: SpacingValue) -> Self {
self.class(format!("ms-{}", value.to_class_name()))
}
fn margin_end(self, value: SpacingValue) -> Self {
self.class(format!("me-{}", value.to_class_name()))
}
fn margin_negative(self, value: SpacingValue) -> Self {
self.class(format!("-m-{}", value.to_class_name()))
}
fn margin_x_negative(self, value: SpacingValue) -> Self {
self.class(format!("-mx-{}", value.to_class_name()))
}
fn margin_y_negative(self, value: SpacingValue) -> Self {
self.class(format!("-my-{}", value.to_class_name()))
}
fn margin_top_negative(self, value: SpacingValue) -> Self {
self.class(format!("-mt-{}", value.to_class_name()))
}
fn margin_right_negative(self, value: SpacingValue) -> Self {
self.class(format!("-mr-{}", value.to_class_name()))
}
fn margin_bottom_negative(self, value: SpacingValue) -> Self {
self.class(format!("-mb-{}", value.to_class_name()))
}
fn margin_left_negative(self, value: SpacingValue) -> Self {
self.class(format!("-ml-{}", value.to_class_name()))
}
}
pub trait GapUtilities {
fn gap(self, value: SpacingValue) -> Self;
fn gap_x(self, value: SpacingValue) -> Self;
fn gap_y(self, value: SpacingValue) -> Self;
}
impl GapUtilities for ClassBuilder {
fn gap(self, value: SpacingValue) -> Self {
self.class(format!("gap-{}", value.to_class_name()))
}
fn gap_x(self, value: SpacingValue) -> Self {
self.class(format!("gap-x-{}", value.to_class_name()))
}
fn gap_y(self, value: SpacingValue) -> Self {
self.class(format!("gap-y-{}", value.to_class_name()))
}
}
pub trait SpaceBetweenUtilities {
fn space_x(self, value: SpacingValue) -> Self;
fn space_y(self, value: SpacingValue) -> Self;
fn space_x_reverse(self) -> Self;
fn space_y_reverse(self) -> Self;
}
impl SpaceBetweenUtilities for ClassBuilder {
fn space_x(self, value: SpacingValue) -> Self {
self.class(format!("space-x-{}", value.to_class_name()))
}
fn space_y(self, value: SpacingValue) -> Self {
self.class(format!("space-y-{}", value.to_class_name()))
}
fn space_x_reverse(self) -> Self {
self.class("space-x-reverse".to_string())
}
fn space_y_reverse(self) -> Self {
self.class("space-y-reverse".to_string())
}
}
impl ClassBuilder {
pub fn space_x_2(self) -> Self {
self.space_x(SpacingValue::Integer(2))
}
pub fn space_x_4(self) -> Self {
self.space_x(SpacingValue::Integer(4))
}
pub fn space_y_2(self) -> Self {
self.space_y(SpacingValue::Integer(2))
}
pub fn space_y_4(self) -> Self {
self.space_y(SpacingValue::Integer(4))
}
}
pub trait SpacingDivideUtilities {
fn divide_x(self, value: SpacingValue) -> Self;
fn divide_y(self, value: SpacingValue) -> Self;
fn divide_x_reverse(self) -> Self;
fn divide_y_reverse(self) -> Self;
}
impl SpacingDivideUtilities for ClassBuilder {
fn divide_x(self, value: SpacingValue) -> Self {
self.class(format!("divide-x-{}", value.to_class_name()))
}
fn divide_y(self, value: SpacingValue) -> Self {
self.class(format!("divide-y-{}", value.to_class_name()))
}
fn divide_x_reverse(self) -> Self {
self.class("divide-x-reverse".to_string())
}
fn divide_y_reverse(self) -> Self {
self.class("divide-y-reverse".to_string())
}
}
impl ClassBuilder {
pub fn divide_x_2(self) -> Self {
self.divide_x(SpacingValue::Integer(2))
}
pub fn divide_x_4(self) -> Self {
self.divide_x(SpacingValue::Integer(4))
}
pub fn divide_y_2(self) -> Self {
self.divide_y(SpacingValue::Integer(2))
}
pub fn divide_y_4(self) -> Self {
self.divide_y(SpacingValue::Integer(4))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_spacing_value_to_css_value() {
assert_eq!(SpacingValue::Zero.to_css_value(), "0");
assert_eq!(SpacingValue::Px.to_css_value(), "1px");
assert_eq!(SpacingValue::Fractional(0.5).to_css_value(), "0.125rem");
assert_eq!(SpacingValue::Integer(4).to_css_value(), "1rem");
assert_eq!(SpacingValue::Auto.to_css_value(), "auto");
assert_eq!(SpacingValue::Full.to_css_value(), "100%");
assert_eq!(SpacingValue::Screen.to_css_value(), "100vh");
}
#[test]
fn test_spacing_value_to_class_name() {
assert_eq!(SpacingValue::Zero.to_class_name(), "0");
assert_eq!(SpacingValue::Px.to_class_name(), "px");
assert_eq!(SpacingValue::Fractional(0.5).to_class_name(), "0.5");
assert_eq!(SpacingValue::Integer(4).to_class_name(), "4");
assert_eq!(SpacingValue::Auto.to_class_name(), "auto");
assert_eq!(SpacingValue::Full.to_class_name(), "full");
assert_eq!(SpacingValue::Screen.to_class_name(), "screen");
}
#[test]
fn test_padding_utilities() {
let classes = ClassBuilder::new()
.padding(SpacingValue::Integer(4))
.padding_x(SpacingValue::Integer(6))
.padding_y(SpacingValue::Integer(2))
.build();
let css_classes = classes.to_css_classes();
assert!(css_classes.contains("p-4"));
assert!(css_classes.contains("px-6"));
assert!(css_classes.contains("py-2"));
}
#[test]
fn test_margin_utilities() {
let classes = ClassBuilder::new()
.margin(SpacingValue::Integer(8))
.margin_x(SpacingValue::Integer(4))
.margin_y(SpacingValue::Integer(2))
.build();
let css_classes = classes.to_css_classes();
assert!(css_classes.contains("m-8"));
assert!(css_classes.contains("mx-4"));
assert!(css_classes.contains("my-2"));
}
#[test]
fn test_negative_margin_utilities() {
let classes = ClassBuilder::new()
.margin_negative(SpacingValue::Integer(4))
.margin_x_negative(SpacingValue::Integer(2))
.margin_y_negative(SpacingValue::Integer(1))
.build();
let css_classes = classes.to_css_classes();
assert!(css_classes.contains("-m-4"));
assert!(css_classes.contains("-mx-2"));
assert!(css_classes.contains("-my-1"));
}
#[test]
fn test_gap_utilities() {
let classes = ClassBuilder::new()
.gap(SpacingValue::Integer(4))
.gap_x(SpacingValue::Integer(6))
.gap_y(SpacingValue::Integer(2))
.build();
let css_classes = classes.to_css_classes();
assert!(css_classes.contains("gap-4"));
assert!(css_classes.contains("gap-x-6"));
assert!(css_classes.contains("gap-y-2"));
}
#[test]
fn test_fractional_spacing() {
let classes = ClassBuilder::new()
.padding(SpacingValue::Fractional(0.5))
.padding_x(SpacingValue::Fractional(1.5))
.padding_y(SpacingValue::Fractional(2.5))
.build();
let css_classes = classes.to_css_classes();
assert!(css_classes.contains("p-0.5"));
assert!(css_classes.contains("px-1.5"));
assert!(css_classes.contains("py-2.5"));
}
#[test]
fn test_special_spacing_values() {
let classes = ClassBuilder::new()
.padding(SpacingValue::Auto)
.margin(SpacingValue::Full)
.gap(SpacingValue::Screen)
.build();
let css_classes = classes.to_css_classes();
assert!(css_classes.contains("p-auto"));
assert!(css_classes.contains("m-full"));
assert!(css_classes.contains("gap-screen"));
}
#[test]
fn test_all_tailwind_spacing_values() {
let expected_values = vec![
"0", "px", "0.5", "1", "1.5", "2", "2.5", "3", "3.5", "4", "5", "6", "7", "8", "9",
"10", "11", "12", "14", "16", "20", "24", "28", "32", "36", "40", "44", "48", "52",
"56", "60", "64", "72", "80", "96",
];
let actual_values: Vec<String> = SpacingValue::all_values()
.iter()
.map(|v| v.to_class_name())
.collect();
for expected in expected_values {
assert!(
actual_values.contains(&expected.to_string()),
"Missing spacing value: {}",
expected
);
}
}
#[test]
fn test_space_between_utilities() {
let classes = ClassBuilder::new()
.space_x_4() .space_y_2() .space_x_reverse() .space_y_reverse() .build();
let css_classes = classes.to_css_classes();
assert!(css_classes.contains("space-x-4"));
assert!(css_classes.contains("space-y-2"));
assert!(css_classes.contains("space-x-reverse"));
assert!(css_classes.contains("space-y-reverse"));
}
#[test]
fn test_divide_utilities() {
let classes = ClassBuilder::new()
.divide_x_2() .divide_y_4() .divide_x_reverse() .divide_y_reverse() .build();
let css_classes = classes.to_css_classes();
assert!(css_classes.contains("divide-x-2"));
assert!(css_classes.contains("divide-y-4"));
assert!(css_classes.contains("divide-x-reverse"));
assert!(css_classes.contains("divide-y-reverse"));
}
}