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::Fractional(1.5),
113 SpacingValue::Fractional(2.5),
114 SpacingValue::Fractional(3.5),
115 SpacingValue::Integer(1),
116 SpacingValue::Integer(2),
117 SpacingValue::Integer(3),
118 SpacingValue::Integer(4),
119 SpacingValue::Integer(5),
120 SpacingValue::Integer(6),
121 SpacingValue::Integer(8),
122 SpacingValue::Integer(10),
123 SpacingValue::Integer(12),
124 SpacingValue::Integer(16),
125 SpacingValue::Integer(20),
126 SpacingValue::Integer(24),
127 SpacingValue::Integer(32),
128 SpacingValue::Integer(40),
129 SpacingValue::Integer(48),
130 SpacingValue::Integer(56),
131 SpacingValue::Integer(64),
132 SpacingValue::Integer(72),
133 SpacingValue::Integer(80),
134 SpacingValue::Integer(96),
135 SpacingValue::Auto,
136 SpacingValue::Full,
137 SpacingValue::Screen,
138 SpacingValue::Min,
139 SpacingValue::Max,
140 SpacingValue::Fit,
141 ]
142 }
143}
144
145impl fmt::Display for SpacingValue {
146 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147 write!(f, "{}", self.to_class_name())
148 }
149}
150
151pub trait PaddingUtilities {
153 fn padding(self, value: SpacingValue) -> Self;
155
156 fn padding_x(self, value: SpacingValue) -> Self;
158
159 fn padding_y(self, value: SpacingValue) -> Self;
161
162 fn padding_top(self, value: SpacingValue) -> Self;
164
165 fn padding_right(self, value: SpacingValue) -> Self;
167
168 fn padding_bottom(self, value: SpacingValue) -> Self;
170
171 fn padding_left(self, value: SpacingValue) -> Self;
173
174 fn padding_start(self, value: SpacingValue) -> Self;
176
177 fn padding_end(self, value: SpacingValue) -> Self;
179}
180
181impl PaddingUtilities for ClassBuilder {
182 fn padding(self, value: SpacingValue) -> Self {
183 self.class(format!("p-{}", value.to_class_name()))
184 }
185
186 fn padding_x(self, value: SpacingValue) -> Self {
187 self.class(format!("px-{}", value.to_class_name()))
188 }
189
190 fn padding_y(self, value: SpacingValue) -> Self {
191 self.class(format!("py-{}", value.to_class_name()))
192 }
193
194 fn padding_top(self, value: SpacingValue) -> Self {
195 self.class(format!("pt-{}", value.to_class_name()))
196 }
197
198 fn padding_right(self, value: SpacingValue) -> Self {
199 self.class(format!("pr-{}", value.to_class_name()))
200 }
201
202 fn padding_bottom(self, value: SpacingValue) -> Self {
203 self.class(format!("pb-{}", value.to_class_name()))
204 }
205
206 fn padding_left(self, value: SpacingValue) -> Self {
207 self.class(format!("pl-{}", value.to_class_name()))
208 }
209
210 fn padding_start(self, value: SpacingValue) -> Self {
211 self.class(format!("ps-{}", value.to_class_name()))
212 }
213
214 fn padding_end(self, value: SpacingValue) -> Self {
215 self.class(format!("pe-{}", value.to_class_name()))
216 }
217}
218
219pub trait MarginUtilities {
221 fn margin(self, value: SpacingValue) -> Self;
223
224 fn margin_x(self, value: SpacingValue) -> Self;
226
227 fn margin_y(self, value: SpacingValue) -> Self;
229
230 fn margin_top(self, value: SpacingValue) -> Self;
232
233 fn margin_right(self, value: SpacingValue) -> Self;
235
236 fn margin_bottom(self, value: SpacingValue) -> Self;
238
239 fn margin_left(self, value: SpacingValue) -> Self;
241
242 fn margin_start(self, value: SpacingValue) -> Self;
244
245 fn margin_end(self, value: SpacingValue) -> Self;
247
248 fn margin_negative(self, value: SpacingValue) -> Self;
250
251 fn margin_x_negative(self, value: SpacingValue) -> Self;
253
254 fn margin_y_negative(self, value: SpacingValue) -> Self;
256
257 fn margin_top_negative(self, value: SpacingValue) -> Self;
259
260 fn margin_right_negative(self, value: SpacingValue) -> Self;
262
263 fn margin_bottom_negative(self, value: SpacingValue) -> Self;
265
266 fn margin_left_negative(self, value: SpacingValue) -> Self;
268}
269
270impl MarginUtilities for ClassBuilder {
271 fn margin(self, value: SpacingValue) -> Self {
272 self.class(format!("m-{}", value.to_class_name()))
273 }
274
275 fn margin_x(self, value: SpacingValue) -> Self {
276 self.class(format!("mx-{}", value.to_class_name()))
277 }
278
279 fn margin_y(self, value: SpacingValue) -> Self {
280 self.class(format!("my-{}", value.to_class_name()))
281 }
282
283 fn margin_top(self, value: SpacingValue) -> Self {
284 self.class(format!("mt-{}", value.to_class_name()))
285 }
286
287 fn margin_right(self, value: SpacingValue) -> Self {
288 self.class(format!("mr-{}", value.to_class_name()))
289 }
290
291 fn margin_bottom(self, value: SpacingValue) -> Self {
292 self.class(format!("mb-{}", value.to_class_name()))
293 }
294
295 fn margin_left(self, value: SpacingValue) -> Self {
296 self.class(format!("ml-{}", value.to_class_name()))
297 }
298
299 fn margin_start(self, value: SpacingValue) -> Self {
300 self.class(format!("ms-{}", value.to_class_name()))
301 }
302
303 fn margin_end(self, value: SpacingValue) -> Self {
304 self.class(format!("me-{}", value.to_class_name()))
305 }
306
307 fn margin_negative(self, value: SpacingValue) -> Self {
308 self.class(format!("-m-{}", value.to_class_name()))
309 }
310
311 fn margin_x_negative(self, value: SpacingValue) -> Self {
312 self.class(format!("-mx-{}", value.to_class_name()))
313 }
314
315 fn margin_y_negative(self, value: SpacingValue) -> Self {
316 self.class(format!("-my-{}", value.to_class_name()))
317 }
318
319 fn margin_top_negative(self, value: SpacingValue) -> Self {
320 self.class(format!("-mt-{}", value.to_class_name()))
321 }
322
323 fn margin_right_negative(self, value: SpacingValue) -> Self {
324 self.class(format!("-mr-{}", value.to_class_name()))
325 }
326
327 fn margin_bottom_negative(self, value: SpacingValue) -> Self {
328 self.class(format!("-mb-{}", value.to_class_name()))
329 }
330
331 fn margin_left_negative(self, value: SpacingValue) -> Self {
332 self.class(format!("-ml-{}", value.to_class_name()))
333 }
334}
335
336pub trait GapUtilities {
338 fn gap(self, value: SpacingValue) -> Self;
340
341 fn gap_x(self, value: SpacingValue) -> Self;
343
344 fn gap_y(self, value: SpacingValue) -> Self;
346}
347
348impl GapUtilities for ClassBuilder {
349 fn gap(self, value: SpacingValue) -> Self {
350 self.class(format!("gap-{}", value.to_class_name()))
351 }
352
353 fn gap_x(self, value: SpacingValue) -> Self {
354 self.class(format!("gap-x-{}", value.to_class_name()))
355 }
356
357 fn gap_y(self, value: SpacingValue) -> Self {
358 self.class(format!("gap-y-{}", value.to_class_name()))
359 }
360}
361
362#[cfg(test)]
363mod tests {
364 use super::*;
365
366 #[test]
367 fn test_spacing_value_to_css_value() {
368 assert_eq!(SpacingValue::Zero.to_css_value(), "0");
369 assert_eq!(SpacingValue::Px.to_css_value(), "1px");
370 assert_eq!(SpacingValue::Fractional(0.5).to_css_value(), "0.125rem");
371 assert_eq!(SpacingValue::Integer(4).to_css_value(), "1rem");
372 assert_eq!(SpacingValue::Auto.to_css_value(), "auto");
373 assert_eq!(SpacingValue::Full.to_css_value(), "100%");
374 assert_eq!(SpacingValue::Screen.to_css_value(), "100vh");
375 }
376
377 #[test]
378 fn test_spacing_value_to_class_name() {
379 assert_eq!(SpacingValue::Zero.to_class_name(), "0");
380 assert_eq!(SpacingValue::Px.to_class_name(), "px");
381 assert_eq!(SpacingValue::Fractional(0.5).to_class_name(), "0.5");
382 assert_eq!(SpacingValue::Integer(4).to_class_name(), "4");
383 assert_eq!(SpacingValue::Auto.to_class_name(), "auto");
384 assert_eq!(SpacingValue::Full.to_class_name(), "full");
385 assert_eq!(SpacingValue::Screen.to_class_name(), "screen");
386 }
387
388 #[test]
389 fn test_padding_utilities() {
390 let classes = ClassBuilder::new()
391 .padding(SpacingValue::Integer(4))
392 .padding_x(SpacingValue::Integer(6))
393 .padding_y(SpacingValue::Integer(2))
394 .build();
395
396 let css_classes = classes.to_css_classes();
397 assert!(css_classes.contains("p-4"));
398 assert!(css_classes.contains("px-6"));
399 assert!(css_classes.contains("py-2"));
400 }
401
402 #[test]
403 fn test_margin_utilities() {
404 let classes = ClassBuilder::new()
405 .margin(SpacingValue::Integer(8))
406 .margin_x(SpacingValue::Integer(4))
407 .margin_y(SpacingValue::Integer(2))
408 .build();
409
410 let css_classes = classes.to_css_classes();
411 assert!(css_classes.contains("m-8"));
412 assert!(css_classes.contains("mx-4"));
413 assert!(css_classes.contains("my-2"));
414 }
415
416 #[test]
417 fn test_negative_margin_utilities() {
418 let classes = ClassBuilder::new()
419 .margin_negative(SpacingValue::Integer(4))
420 .margin_x_negative(SpacingValue::Integer(2))
421 .margin_y_negative(SpacingValue::Integer(1))
422 .build();
423
424 let css_classes = classes.to_css_classes();
425 assert!(css_classes.contains("-m-4"));
426 assert!(css_classes.contains("-mx-2"));
427 assert!(css_classes.contains("-my-1"));
428 }
429
430 #[test]
431 fn test_gap_utilities() {
432 let classes = ClassBuilder::new()
433 .gap(SpacingValue::Integer(4))
434 .gap_x(SpacingValue::Integer(6))
435 .gap_y(SpacingValue::Integer(2))
436 .build();
437
438 let css_classes = classes.to_css_classes();
439 assert!(css_classes.contains("gap-4"));
440 assert!(css_classes.contains("gap-x-6"));
441 assert!(css_classes.contains("gap-y-2"));
442 }
443
444 #[test]
445 fn test_fractional_spacing() {
446 let classes = ClassBuilder::new()
447 .padding(SpacingValue::Fractional(0.5))
448 .padding_x(SpacingValue::Fractional(1.5))
449 .padding_y(SpacingValue::Fractional(2.5))
450 .build();
451
452 let css_classes = classes.to_css_classes();
453 assert!(css_classes.contains("p-0.5"));
454 assert!(css_classes.contains("px-1.5"));
455 assert!(css_classes.contains("py-2.5"));
456 }
457
458 #[test]
459 fn test_special_spacing_values() {
460 let classes = ClassBuilder::new()
461 .padding(SpacingValue::Auto)
462 .margin(SpacingValue::Full)
463 .gap(SpacingValue::Screen)
464 .build();
465
466 let css_classes = classes.to_css_classes();
467 assert!(css_classes.contains("p-auto"));
468 assert!(css_classes.contains("m-full"));
469 assert!(css_classes.contains("gap-screen"));
470 }
471}