1use crate::classes::ClassBuilder;
7use serde::{Deserialize, Serialize};
8use std::fmt;
9
10#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
24pub enum SpacingValue {
25 Zero,
27 Px,
29 Fractional(f32),
31 Integer(u32),
33 Auto,
35 Full,
37 Screen,
39 Min,
41 Max,
43 Fit,
45}
46
47impl std::hash::Hash for SpacingValue {
48 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
49 match self {
50 SpacingValue::Zero => 0u8.hash(state),
51 SpacingValue::Px => 1u8.hash(state),
52 SpacingValue::Fractional(f) => {
53 2u8.hash(state);
54 ((f * 1000.0) as u32).hash(state);
56 }
57 SpacingValue::Integer(i) => {
58 3u8.hash(state);
59 i.hash(state);
60 }
61 SpacingValue::Auto => 4u8.hash(state),
62 SpacingValue::Full => 5u8.hash(state),
63 SpacingValue::Screen => 6u8.hash(state),
64 SpacingValue::Min => 7u8.hash(state),
65 SpacingValue::Max => 8u8.hash(state),
66 SpacingValue::Fit => 9u8.hash(state),
67 }
68 }
69}
70
71impl std::cmp::Eq for SpacingValue {}
72
73impl SpacingValue {
74 pub fn to_css_value(&self) -> String {
76 match self {
77 SpacingValue::Zero => "0".to_string(),
78 SpacingValue::Px => "1px".to_string(),
79 SpacingValue::Fractional(f) => format!("{}rem", f * 0.25), SpacingValue::Integer(i) => format!("{}rem", *i as f32 * 0.25),
81 SpacingValue::Auto => "auto".to_string(),
82 SpacingValue::Full => "100%".to_string(),
83 SpacingValue::Screen => "100vh".to_string(),
84 SpacingValue::Min => "min-content".to_string(),
85 SpacingValue::Max => "max-content".to_string(),
86 SpacingValue::Fit => "fit-content".to_string(),
87 }
88 }
89
90 pub fn to_class_name(&self) -> String {
92 match self {
93 SpacingValue::Zero => "0".to_string(),
94 SpacingValue::Px => "px".to_string(),
95 SpacingValue::Fractional(f) => format!("{}", f),
96 SpacingValue::Integer(i) => i.to_string(),
97 SpacingValue::Auto => "auto".to_string(),
98 SpacingValue::Full => "full".to_string(),
99 SpacingValue::Screen => "screen".to_string(),
100 SpacingValue::Min => "min".to_string(),
101 SpacingValue::Max => "max".to_string(),
102 SpacingValue::Fit => "fit".to_string(),
103 }
104 }
105
106 pub fn all_values() -> Vec<SpacingValue> {
108 vec![
109 SpacingValue::Zero,
110 SpacingValue::Px,
111 SpacingValue::Fractional(0.5),
112 SpacingValue::Integer(1),
113 SpacingValue::Fractional(1.5),
114 SpacingValue::Integer(2),
115 SpacingValue::Fractional(2.5),
116 SpacingValue::Integer(3),
117 SpacingValue::Fractional(3.5),
118 SpacingValue::Integer(4),
119 SpacingValue::Integer(5),
120 SpacingValue::Integer(6),
121 SpacingValue::Integer(7),
122 SpacingValue::Integer(8),
123 SpacingValue::Integer(9),
124 SpacingValue::Integer(10),
125 SpacingValue::Integer(11),
126 SpacingValue::Integer(12),
127 SpacingValue::Integer(14),
128 SpacingValue::Integer(16),
129 SpacingValue::Integer(20),
130 SpacingValue::Integer(24),
131 SpacingValue::Integer(28),
132 SpacingValue::Integer(32),
133 SpacingValue::Integer(36),
134 SpacingValue::Integer(40),
135 SpacingValue::Integer(44),
136 SpacingValue::Integer(48),
137 SpacingValue::Integer(52),
138 SpacingValue::Integer(56),
139 SpacingValue::Integer(60),
140 SpacingValue::Integer(64),
141 SpacingValue::Integer(72),
142 SpacingValue::Integer(80),
143 SpacingValue::Integer(96),
144 SpacingValue::Auto,
145 SpacingValue::Full,
146 SpacingValue::Screen,
147 SpacingValue::Min,
148 SpacingValue::Max,
149 SpacingValue::Fit,
150 ]
151 }
152}
153
154impl fmt::Display for SpacingValue {
155 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
156 write!(f, "{}", self.to_class_name())
157 }
158}
159
160pub trait PaddingUtilities {
162 fn padding(self, value: SpacingValue) -> Self;
164
165 fn padding_x(self, value: SpacingValue) -> Self;
167
168 fn padding_y(self, value: SpacingValue) -> Self;
170
171 fn padding_top(self, value: SpacingValue) -> Self;
173
174 fn padding_right(self, value: SpacingValue) -> Self;
176
177 fn padding_bottom(self, value: SpacingValue) -> Self;
179
180 fn padding_left(self, value: SpacingValue) -> Self;
182
183 fn padding_start(self, value: SpacingValue) -> Self;
185
186 fn padding_end(self, value: SpacingValue) -> Self;
188}
189
190impl PaddingUtilities for ClassBuilder {
191 fn padding(self, value: SpacingValue) -> Self {
192 self.class(format!("p-{}", value.to_class_name()))
193 }
194
195 fn padding_x(self, value: SpacingValue) -> Self {
196 self.class(format!("px-{}", value.to_class_name()))
197 }
198
199 fn padding_y(self, value: SpacingValue) -> Self {
200 self.class(format!("py-{}", value.to_class_name()))
201 }
202
203 fn padding_top(self, value: SpacingValue) -> Self {
204 self.class(format!("pt-{}", value.to_class_name()))
205 }
206
207 fn padding_right(self, value: SpacingValue) -> Self {
208 self.class(format!("pr-{}", value.to_class_name()))
209 }
210
211 fn padding_bottom(self, value: SpacingValue) -> Self {
212 self.class(format!("pb-{}", value.to_class_name()))
213 }
214
215 fn padding_left(self, value: SpacingValue) -> Self {
216 self.class(format!("pl-{}", value.to_class_name()))
217 }
218
219 fn padding_start(self, value: SpacingValue) -> Self {
220 self.class(format!("ps-{}", value.to_class_name()))
221 }
222
223 fn padding_end(self, value: SpacingValue) -> Self {
224 self.class(format!("pe-{}", value.to_class_name()))
225 }
226}
227
228pub trait MarginUtilities {
230 fn margin(self, value: SpacingValue) -> Self;
232
233 fn margin_x(self, value: SpacingValue) -> Self;
235
236 fn margin_y(self, value: SpacingValue) -> Self;
238
239 fn margin_top(self, value: SpacingValue) -> Self;
241
242 fn margin_right(self, value: SpacingValue) -> Self;
244
245 fn margin_bottom(self, value: SpacingValue) -> Self;
247
248 fn margin_left(self, value: SpacingValue) -> Self;
250
251 fn margin_start(self, value: SpacingValue) -> Self;
253
254 fn margin_end(self, value: SpacingValue) -> Self;
256
257 fn margin_negative(self, value: SpacingValue) -> Self;
259
260 fn margin_x_negative(self, value: SpacingValue) -> Self;
262
263 fn margin_y_negative(self, value: SpacingValue) -> Self;
265
266 fn margin_top_negative(self, value: SpacingValue) -> Self;
268
269 fn margin_right_negative(self, value: SpacingValue) -> Self;
271
272 fn margin_bottom_negative(self, value: SpacingValue) -> Self;
274
275 fn margin_left_negative(self, value: SpacingValue) -> Self;
277}
278
279impl MarginUtilities for ClassBuilder {
280 fn margin(self, value: SpacingValue) -> Self {
281 self.class(format!("m-{}", value.to_class_name()))
282 }
283
284 fn margin_x(self, value: SpacingValue) -> Self {
285 self.class(format!("mx-{}", value.to_class_name()))
286 }
287
288 fn margin_y(self, value: SpacingValue) -> Self {
289 self.class(format!("my-{}", value.to_class_name()))
290 }
291
292 fn margin_top(self, value: SpacingValue) -> Self {
293 self.class(format!("mt-{}", value.to_class_name()))
294 }
295
296 fn margin_right(self, value: SpacingValue) -> Self {
297 self.class(format!("mr-{}", value.to_class_name()))
298 }
299
300 fn margin_bottom(self, value: SpacingValue) -> Self {
301 self.class(format!("mb-{}", value.to_class_name()))
302 }
303
304 fn margin_left(self, value: SpacingValue) -> Self {
305 self.class(format!("ml-{}", value.to_class_name()))
306 }
307
308 fn margin_start(self, value: SpacingValue) -> Self {
309 self.class(format!("ms-{}", value.to_class_name()))
310 }
311
312 fn margin_end(self, value: SpacingValue) -> Self {
313 self.class(format!("me-{}", value.to_class_name()))
314 }
315
316 fn margin_negative(self, value: SpacingValue) -> Self {
317 self.class(format!("-m-{}", value.to_class_name()))
318 }
319
320 fn margin_x_negative(self, value: SpacingValue) -> Self {
321 self.class(format!("-mx-{}", value.to_class_name()))
322 }
323
324 fn margin_y_negative(self, value: SpacingValue) -> Self {
325 self.class(format!("-my-{}", value.to_class_name()))
326 }
327
328 fn margin_top_negative(self, value: SpacingValue) -> Self {
329 self.class(format!("-mt-{}", value.to_class_name()))
330 }
331
332 fn margin_right_negative(self, value: SpacingValue) -> Self {
333 self.class(format!("-mr-{}", value.to_class_name()))
334 }
335
336 fn margin_bottom_negative(self, value: SpacingValue) -> Self {
337 self.class(format!("-mb-{}", value.to_class_name()))
338 }
339
340 fn margin_left_negative(self, value: SpacingValue) -> Self {
341 self.class(format!("-ml-{}", value.to_class_name()))
342 }
343}
344
345pub trait GapUtilities {
347 fn gap(self, value: SpacingValue) -> Self;
349
350 fn gap_x(self, value: SpacingValue) -> Self;
352
353 fn gap_y(self, value: SpacingValue) -> Self;
355}
356
357impl GapUtilities for ClassBuilder {
358 fn gap(self, value: SpacingValue) -> Self {
359 self.class(format!("gap-{}", value.to_class_name()))
360 }
361
362 fn gap_x(self, value: SpacingValue) -> Self {
363 self.class(format!("gap-x-{}", value.to_class_name()))
364 }
365
366 fn gap_y(self, value: SpacingValue) -> Self {
367 self.class(format!("gap-y-{}", value.to_class_name()))
368 }
369}
370
371pub trait SpaceBetweenUtilities {
373 fn space_x(self, value: SpacingValue) -> Self;
375
376 fn space_y(self, value: SpacingValue) -> Self;
378
379 fn space_x_reverse(self) -> Self;
381
382 fn space_y_reverse(self) -> Self;
384}
385
386impl SpaceBetweenUtilities for ClassBuilder {
387 fn space_x(self, value: SpacingValue) -> Self {
388 self.class(format!("space-x-{}", value.to_class_name()))
389 }
390
391 fn space_y(self, value: SpacingValue) -> Self {
392 self.class(format!("space-y-{}", value.to_class_name()))
393 }
394
395 fn space_x_reverse(self) -> Self {
396 self.class("space-x-reverse".to_string())
397 }
398
399 fn space_y_reverse(self) -> Self {
400 self.class("space-y-reverse".to_string())
401 }
402}
403
404impl ClassBuilder {
406 pub fn space_x_2(self) -> Self {
408 self.space_x(SpacingValue::Integer(2))
409 }
410
411 pub fn space_x_4(self) -> Self {
413 self.space_x(SpacingValue::Integer(4))
414 }
415
416 pub fn space_y_2(self) -> Self {
418 self.space_y(SpacingValue::Integer(2))
419 }
420
421 pub fn space_y_4(self) -> Self {
423 self.space_y(SpacingValue::Integer(4))
424 }
425}
426
427pub trait SpacingDivideUtilities {
429 fn divide_x(self, value: SpacingValue) -> Self;
431
432 fn divide_y(self, value: SpacingValue) -> Self;
434
435 fn divide_x_reverse(self) -> Self;
437
438 fn divide_y_reverse(self) -> Self;
440}
441
442impl SpacingDivideUtilities for ClassBuilder {
443 fn divide_x(self, value: SpacingValue) -> Self {
444 self.class(format!("divide-x-{}", value.to_class_name()))
445 }
446
447 fn divide_y(self, value: SpacingValue) -> Self {
448 self.class(format!("divide-y-{}", value.to_class_name()))
449 }
450
451 fn divide_x_reverse(self) -> Self {
452 self.class("divide-x-reverse".to_string())
453 }
454
455 fn divide_y_reverse(self) -> Self {
456 self.class("divide-y-reverse".to_string())
457 }
458}
459
460impl ClassBuilder {
462 pub fn divide_x_2(self) -> Self {
464 self.divide_x(SpacingValue::Integer(2))
465 }
466
467 pub fn divide_x_4(self) -> Self {
469 self.divide_x(SpacingValue::Integer(4))
470 }
471
472 pub fn divide_y_2(self) -> Self {
474 self.divide_y(SpacingValue::Integer(2))
475 }
476
477 pub fn divide_y_4(self) -> Self {
479 self.divide_y(SpacingValue::Integer(4))
480 }
481}
482
483#[cfg(test)]
484mod tests {
485 use super::*;
486
487 #[test]
488 fn test_spacing_value_to_css_value() {
489 assert_eq!(SpacingValue::Zero.to_css_value(), "0");
490 assert_eq!(SpacingValue::Px.to_css_value(), "1px");
491 assert_eq!(SpacingValue::Fractional(0.5).to_css_value(), "0.125rem");
492 assert_eq!(SpacingValue::Integer(4).to_css_value(), "1rem");
493 assert_eq!(SpacingValue::Auto.to_css_value(), "auto");
494 assert_eq!(SpacingValue::Full.to_css_value(), "100%");
495 assert_eq!(SpacingValue::Screen.to_css_value(), "100vh");
496 }
497
498 #[test]
499 fn test_spacing_value_to_class_name() {
500 assert_eq!(SpacingValue::Zero.to_class_name(), "0");
501 assert_eq!(SpacingValue::Px.to_class_name(), "px");
502 assert_eq!(SpacingValue::Fractional(0.5).to_class_name(), "0.5");
503 assert_eq!(SpacingValue::Integer(4).to_class_name(), "4");
504 assert_eq!(SpacingValue::Auto.to_class_name(), "auto");
505 assert_eq!(SpacingValue::Full.to_class_name(), "full");
506 assert_eq!(SpacingValue::Screen.to_class_name(), "screen");
507 }
508
509 #[test]
510 fn test_padding_utilities() {
511 let classes = ClassBuilder::new()
512 .padding(SpacingValue::Integer(4))
513 .padding_x(SpacingValue::Integer(6))
514 .padding_y(SpacingValue::Integer(2))
515 .build();
516
517 let css_classes = classes.to_css_classes();
518 assert!(css_classes.contains("p-4"));
519 assert!(css_classes.contains("px-6"));
520 assert!(css_classes.contains("py-2"));
521 }
522
523 #[test]
524 fn test_margin_utilities() {
525 let classes = ClassBuilder::new()
526 .margin(SpacingValue::Integer(8))
527 .margin_x(SpacingValue::Integer(4))
528 .margin_y(SpacingValue::Integer(2))
529 .build();
530
531 let css_classes = classes.to_css_classes();
532 assert!(css_classes.contains("m-8"));
533 assert!(css_classes.contains("mx-4"));
534 assert!(css_classes.contains("my-2"));
535 }
536
537 #[test]
538 fn test_negative_margin_utilities() {
539 let classes = ClassBuilder::new()
540 .margin_negative(SpacingValue::Integer(4))
541 .margin_x_negative(SpacingValue::Integer(2))
542 .margin_y_negative(SpacingValue::Integer(1))
543 .build();
544
545 let css_classes = classes.to_css_classes();
546 assert!(css_classes.contains("-m-4"));
547 assert!(css_classes.contains("-mx-2"));
548 assert!(css_classes.contains("-my-1"));
549 }
550
551 #[test]
552 fn test_gap_utilities() {
553 let classes = ClassBuilder::new()
554 .gap(SpacingValue::Integer(4))
555 .gap_x(SpacingValue::Integer(6))
556 .gap_y(SpacingValue::Integer(2))
557 .build();
558
559 let css_classes = classes.to_css_classes();
560 assert!(css_classes.contains("gap-4"));
561 assert!(css_classes.contains("gap-x-6"));
562 assert!(css_classes.contains("gap-y-2"));
563 }
564
565 #[test]
566 fn test_fractional_spacing() {
567 let classes = ClassBuilder::new()
568 .padding(SpacingValue::Fractional(0.5))
569 .padding_x(SpacingValue::Fractional(1.5))
570 .padding_y(SpacingValue::Fractional(2.5))
571 .build();
572
573 let css_classes = classes.to_css_classes();
574 assert!(css_classes.contains("p-0.5"));
575 assert!(css_classes.contains("px-1.5"));
576 assert!(css_classes.contains("py-2.5"));
577 }
578
579 #[test]
580 fn test_special_spacing_values() {
581 let classes = ClassBuilder::new()
582 .padding(SpacingValue::Auto)
583 .margin(SpacingValue::Full)
584 .gap(SpacingValue::Screen)
585 .build();
586
587 let css_classes = classes.to_css_classes();
588 assert!(css_classes.contains("p-auto"));
589 assert!(css_classes.contains("m-full"));
590 assert!(css_classes.contains("gap-screen"));
591 }
592
593 #[test]
595 fn test_all_tailwind_spacing_values() {
596 let expected_values = vec![
598 "0", "px", "0.5", "1", "1.5", "2", "2.5", "3", "3.5", "4", "5", "6", "7", "8", "9", "10",
599 "11", "12", "14", "16", "20", "24", "28", "32", "36", "40", "44", "48", "52", "56", "60",
600 "64", "72", "80", "96"
601 ];
602
603 let actual_values: Vec<String> = SpacingValue::all_values()
604 .iter()
605 .map(|v| v.to_class_name())
606 .collect();
607
608 for expected in expected_values {
609 assert!(
610 actual_values.contains(&expected.to_string()),
611 "Missing spacing value: {}",
612 expected
613 );
614 }
615 }
616
617 #[test]
619 fn test_space_between_utilities() {
620 let classes = ClassBuilder::new()
621 .space_x_4() .space_y_2() .space_x_reverse() .space_y_reverse() .build();
626
627 let css_classes = classes.to_css_classes();
628 assert!(css_classes.contains("space-x-4"));
629 assert!(css_classes.contains("space-y-2"));
630 assert!(css_classes.contains("space-x-reverse"));
631 assert!(css_classes.contains("space-y-reverse"));
632 }
633
634 #[test]
636 fn test_divide_utilities() {
637 let classes = ClassBuilder::new()
638 .divide_x_2() .divide_y_4() .divide_x_reverse() .divide_y_reverse() .build();
643
644 let css_classes = classes.to_css_classes();
645 assert!(css_classes.contains("divide-x-2"));
646 assert!(css_classes.contains("divide-y-4"));
647 assert!(css_classes.contains("divide-x-reverse"));
648 assert!(css_classes.contains("divide-y-reverse"));
649 }
650}