1use crate::classes::ClassBuilder;
8use serde::{Deserialize, Serialize};
9use std::fmt;
10
11#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
25pub enum SizingValue {
26 Zero,
28 Px,
30 Fractional(f32),
32 Integer(u32),
34 Auto,
36 Full,
38 Screen,
40 Min,
42 Max,
44 Fit,
46 Fraction(Fraction),
48 GridFraction(GridFraction),
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
54pub enum Fraction {
55 Half,
57 Third,
59 TwoThirds,
61 Quarter,
63 TwoQuarters,
65 ThreeQuarters,
67 Fifth,
69 TwoFifths,
71 ThreeFifths,
73 FourFifths,
75 Sixth,
77 TwoSixths,
79 ThreeSixths,
81 FourSixths,
83 FiveSixths,
85}
86
87#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
89pub enum GridFraction {
90 OneTwelfth,
92 TwoTwelfths,
94 ThreeTwelfths,
96 FourTwelfths,
98 FiveTwelfths,
100 SixTwelfths,
102 SevenTwelfths,
104 EightTwelfths,
106 NineTwelfths,
108 TenTwelfths,
110 ElevenTwelfths,
112}
113
114impl std::hash::Hash for SizingValue {
115 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
116 match self {
117 SizingValue::Zero => 0u8.hash(state),
118 SizingValue::Px => 1u8.hash(state),
119 SizingValue::Fractional(f) => {
120 2u8.hash(state);
121 ((f * 1000.0) as u32).hash(state);
122 }
123 SizingValue::Integer(i) => {
124 3u8.hash(state);
125 i.hash(state);
126 }
127 SizingValue::Auto => 4u8.hash(state),
128 SizingValue::Full => 5u8.hash(state),
129 SizingValue::Screen => 6u8.hash(state),
130 SizingValue::Min => 7u8.hash(state),
131 SizingValue::Max => 8u8.hash(state),
132 SizingValue::Fit => 9u8.hash(state),
133 SizingValue::Fraction(f) => {
134 10u8.hash(state);
135 std::mem::discriminant(f).hash(state);
136 }
137 SizingValue::GridFraction(gf) => {
138 11u8.hash(state);
139 std::mem::discriminant(gf).hash(state);
140 }
141 }
142 }
143}
144
145impl std::hash::Hash for Fraction {
146 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
147 std::mem::discriminant(self).hash(state);
148 }
149}
150
151impl std::hash::Hash for GridFraction {
152 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
153 std::mem::discriminant(self).hash(state);
154 }
155}
156
157impl std::cmp::Eq for SizingValue {}
158impl std::cmp::Eq for Fraction {}
159impl std::cmp::Eq for GridFraction {}
160
161impl SizingValue {
162 pub fn to_css_value(&self) -> String {
164 match self {
165 SizingValue::Zero => "0".to_string(),
166 SizingValue::Px => "1px".to_string(),
167 SizingValue::Fractional(f) => format!("{}rem", f * 0.25),
168 SizingValue::Integer(i) => format!("{}rem", *i as f32 * 0.25),
169 SizingValue::Auto => "auto".to_string(),
170 SizingValue::Full => "100%".to_string(),
171 SizingValue::Screen => "100vh".to_string(), SizingValue::Min => "min-content".to_string(),
173 SizingValue::Max => "max-content".to_string(),
174 SizingValue::Fit => "fit-content".to_string(),
175 SizingValue::Fraction(f) => f.to_css_value(),
176 SizingValue::GridFraction(gf) => gf.to_css_value(),
177 }
178 }
179
180 pub fn to_css_value_width(&self) -> String {
182 match self {
183 SizingValue::Screen => "100vw".to_string(),
184 _ => self.to_css_value(),
185 }
186 }
187
188 pub fn to_css_value_height(&self) -> String {
190 match self {
191 SizingValue::Screen => "100vh".to_string(),
192 _ => self.to_css_value(),
193 }
194 }
195
196 pub fn to_class_name(&self) -> String {
198 match self {
199 SizingValue::Zero => "0".to_string(),
200 SizingValue::Px => "px".to_string(),
201 SizingValue::Fractional(f) => format!("{}", f),
202 SizingValue::Integer(i) => i.to_string(),
203 SizingValue::Auto => "auto".to_string(),
204 SizingValue::Full => "full".to_string(),
205 SizingValue::Screen => "screen".to_string(),
206 SizingValue::Min => "min".to_string(),
207 SizingValue::Max => "max".to_string(),
208 SizingValue::Fit => "fit".to_string(),
209 SizingValue::Fraction(f) => f.to_class_name(),
210 SizingValue::GridFraction(gf) => gf.to_class_name(),
211 }
212 }
213
214 pub fn all_values() -> Vec<SizingValue> {
216 let mut values = vec![
217 SizingValue::Zero,
218 SizingValue::Px,
219 SizingValue::Fractional(0.5),
220 SizingValue::Fractional(1.5),
221 SizingValue::Fractional(2.5),
222 SizingValue::Fractional(3.5),
223 SizingValue::Integer(1),
224 SizingValue::Integer(2),
225 SizingValue::Integer(3),
226 SizingValue::Integer(4),
227 SizingValue::Integer(5),
228 SizingValue::Integer(6),
229 SizingValue::Integer(8),
230 SizingValue::Integer(10),
231 SizingValue::Integer(12),
232 SizingValue::Integer(16),
233 SizingValue::Integer(20),
234 SizingValue::Integer(24),
235 SizingValue::Integer(32),
236 SizingValue::Integer(40),
237 SizingValue::Integer(48),
238 SizingValue::Integer(56),
239 SizingValue::Integer(64),
240 SizingValue::Integer(72),
241 SizingValue::Integer(80),
242 SizingValue::Integer(96),
243 SizingValue::Auto,
244 SizingValue::Full,
245 SizingValue::Screen,
246 SizingValue::Min,
247 SizingValue::Max,
248 SizingValue::Fit,
249 ];
250
251 values.extend([
253 SizingValue::Fraction(Fraction::Half),
254 SizingValue::Fraction(Fraction::Third),
255 SizingValue::Fraction(Fraction::TwoThirds),
256 SizingValue::Fraction(Fraction::Quarter),
257 SizingValue::Fraction(Fraction::TwoQuarters),
258 SizingValue::Fraction(Fraction::ThreeQuarters),
259 SizingValue::Fraction(Fraction::Fifth),
260 SizingValue::Fraction(Fraction::TwoFifths),
261 SizingValue::Fraction(Fraction::ThreeFifths),
262 SizingValue::Fraction(Fraction::FourFifths),
263 SizingValue::Fraction(Fraction::Sixth),
264 SizingValue::Fraction(Fraction::TwoSixths),
265 SizingValue::Fraction(Fraction::ThreeSixths),
266 SizingValue::Fraction(Fraction::FourSixths),
267 SizingValue::Fraction(Fraction::FiveSixths),
268 ]);
269
270 values.extend([
272 SizingValue::GridFraction(GridFraction::OneTwelfth),
273 SizingValue::GridFraction(GridFraction::TwoTwelfths),
274 SizingValue::GridFraction(GridFraction::ThreeTwelfths),
275 SizingValue::GridFraction(GridFraction::FourTwelfths),
276 SizingValue::GridFraction(GridFraction::FiveTwelfths),
277 SizingValue::GridFraction(GridFraction::SixTwelfths),
278 SizingValue::GridFraction(GridFraction::SevenTwelfths),
279 SizingValue::GridFraction(GridFraction::EightTwelfths),
280 SizingValue::GridFraction(GridFraction::NineTwelfths),
281 SizingValue::GridFraction(GridFraction::TenTwelfths),
282 SizingValue::GridFraction(GridFraction::ElevenTwelfths),
283 ]);
284
285 values
286 }
287}
288
289impl Fraction {
290 pub fn to_css_value(&self) -> String {
291 match self {
292 Fraction::Half => "50%".to_string(),
293 Fraction::Third => "33.333333%".to_string(),
294 Fraction::TwoThirds => "66.666667%".to_string(),
295 Fraction::Quarter => "25%".to_string(),
296 Fraction::TwoQuarters => "50%".to_string(),
297 Fraction::ThreeQuarters => "75%".to_string(),
298 Fraction::Fifth => "20%".to_string(),
299 Fraction::TwoFifths => "40%".to_string(),
300 Fraction::ThreeFifths => "60%".to_string(),
301 Fraction::FourFifths => "80%".to_string(),
302 Fraction::Sixth => "16.666667%".to_string(),
303 Fraction::TwoSixths => "33.333333%".to_string(),
304 Fraction::ThreeSixths => "50%".to_string(),
305 Fraction::FourSixths => "66.666667%".to_string(),
306 Fraction::FiveSixths => "83.333333%".to_string(),
307 }
308 }
309
310 pub fn to_class_name(&self) -> String {
311 match self {
312 Fraction::Half => "1/2".to_string(),
313 Fraction::Third => "1/3".to_string(),
314 Fraction::TwoThirds => "2/3".to_string(),
315 Fraction::Quarter => "1/4".to_string(),
316 Fraction::TwoQuarters => "2/4".to_string(),
317 Fraction::ThreeQuarters => "3/4".to_string(),
318 Fraction::Fifth => "1/5".to_string(),
319 Fraction::TwoFifths => "2/5".to_string(),
320 Fraction::ThreeFifths => "3/5".to_string(),
321 Fraction::FourFifths => "4/5".to_string(),
322 Fraction::Sixth => "1/6".to_string(),
323 Fraction::TwoSixths => "2/6".to_string(),
324 Fraction::ThreeSixths => "3/6".to_string(),
325 Fraction::FourSixths => "4/6".to_string(),
326 Fraction::FiveSixths => "5/6".to_string(),
327 }
328 }
329}
330
331impl GridFraction {
332 pub fn to_css_value(&self) -> String {
333 match self {
334 GridFraction::OneTwelfth => "8.333333%".to_string(),
335 GridFraction::TwoTwelfths => "16.666667%".to_string(),
336 GridFraction::ThreeTwelfths => "25%".to_string(),
337 GridFraction::FourTwelfths => "33.333333%".to_string(),
338 GridFraction::FiveTwelfths => "41.666667%".to_string(),
339 GridFraction::SixTwelfths => "50%".to_string(),
340 GridFraction::SevenTwelfths => "58.333333%".to_string(),
341 GridFraction::EightTwelfths => "66.666667%".to_string(),
342 GridFraction::NineTwelfths => "75%".to_string(),
343 GridFraction::TenTwelfths => "83.333333%".to_string(),
344 GridFraction::ElevenTwelfths => "91.666667%".to_string(),
345 }
346 }
347
348 pub fn to_class_name(&self) -> String {
349 match self {
350 GridFraction::OneTwelfth => "1/12".to_string(),
351 GridFraction::TwoTwelfths => "2/12".to_string(),
352 GridFraction::ThreeTwelfths => "3/12".to_string(),
353 GridFraction::FourTwelfths => "4/12".to_string(),
354 GridFraction::FiveTwelfths => "5/12".to_string(),
355 GridFraction::SixTwelfths => "6/12".to_string(),
356 GridFraction::SevenTwelfths => "7/12".to_string(),
357 GridFraction::EightTwelfths => "8/12".to_string(),
358 GridFraction::NineTwelfths => "9/12".to_string(),
359 GridFraction::TenTwelfths => "10/12".to_string(),
360 GridFraction::ElevenTwelfths => "11/12".to_string(),
361 }
362 }
363}
364
365impl fmt::Display for SizingValue {
366 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
367 write!(f, "{}", self.to_class_name())
368 }
369}
370
371pub trait WidthUtilities {
373 fn width(self, value: SizingValue) -> Self;
375
376 fn min_width(self, value: SizingValue) -> Self;
378
379 fn max_width(self, value: SizingValue) -> Self;
381}
382
383impl WidthUtilities for ClassBuilder {
384 fn width(self, value: SizingValue) -> Self {
385 self.class(format!("w-{}", value.to_class_name()))
386 }
387
388 fn min_width(self, value: SizingValue) -> Self {
389 self.class(format!("min-w-{}", value.to_class_name()))
390 }
391
392 fn max_width(self, value: SizingValue) -> Self {
393 self.class(format!("max-w-{}", value.to_class_name()))
394 }
395}
396
397pub trait HeightUtilities {
399 fn height(self, value: SizingValue) -> Self;
401
402 fn min_height(self, value: SizingValue) -> Self;
404
405 fn max_height(self, value: SizingValue) -> Self;
407}
408
409impl HeightUtilities for ClassBuilder {
410 fn height(self, value: SizingValue) -> Self {
411 self.class(format!("h-{}", value.to_class_name()))
412 }
413
414 fn min_height(self, value: SizingValue) -> Self {
415 self.class(format!("min-h-{}", value.to_class_name()))
416 }
417
418 fn max_height(self, value: SizingValue) -> Self {
419 self.class(format!("max-h-{}", value.to_class_name()))
420 }
421}
422
423#[cfg(test)]
424mod tests {
425 use super::*;
426
427 #[test]
428 fn test_sizing_value_to_css_value() {
429 assert_eq!(SizingValue::Zero.to_css_value(), "0");
430 assert_eq!(SizingValue::Px.to_css_value(), "1px");
431 assert_eq!(SizingValue::Fractional(0.5).to_css_value(), "0.125rem");
432 assert_eq!(SizingValue::Integer(4).to_css_value(), "1rem");
433 assert_eq!(SizingValue::Auto.to_css_value(), "auto");
434 assert_eq!(SizingValue::Full.to_css_value(), "100%");
435 assert_eq!(SizingValue::Screen.to_css_value(), "100vh");
436 }
437
438 #[test]
439 fn test_sizing_value_to_css_value_width() {
440 assert_eq!(SizingValue::Screen.to_css_value_width(), "100vw");
441 assert_eq!(SizingValue::Full.to_css_value_width(), "100%");
442 }
443
444 #[test]
445 fn test_sizing_value_to_css_value_height() {
446 assert_eq!(SizingValue::Screen.to_css_value_height(), "100vh");
447 assert_eq!(SizingValue::Full.to_css_value_height(), "100%");
448 }
449
450 #[test]
451 fn test_fraction_to_css_value() {
452 assert_eq!(Fraction::Half.to_css_value(), "50%");
453 assert_eq!(Fraction::Third.to_css_value(), "33.333333%");
454 assert_eq!(Fraction::TwoThirds.to_css_value(), "66.666667%");
455 }
456
457 #[test]
458 fn test_fraction_to_class_name() {
459 assert_eq!(Fraction::Half.to_class_name(), "1/2");
460 assert_eq!(Fraction::Third.to_class_name(), "1/3");
461 assert_eq!(Fraction::TwoThirds.to_class_name(), "2/3");
462 }
463
464 #[test]
465 fn test_grid_fraction_to_css_value() {
466 assert_eq!(GridFraction::OneTwelfth.to_css_value(), "8.333333%");
467 assert_eq!(GridFraction::SixTwelfths.to_css_value(), "50%");
468 assert_eq!(GridFraction::ElevenTwelfths.to_css_value(), "91.666667%");
469 }
470
471 #[test]
472 fn test_grid_fraction_to_class_name() {
473 assert_eq!(GridFraction::OneTwelfth.to_class_name(), "1/12");
474 assert_eq!(GridFraction::SixTwelfths.to_class_name(), "6/12");
475 assert_eq!(GridFraction::ElevenTwelfths.to_class_name(), "11/12");
476 }
477
478 #[test]
479 fn test_width_utilities() {
480 let classes = ClassBuilder::new()
481 .width(SizingValue::Full)
482 .min_width(SizingValue::Integer(4))
483 .max_width(SizingValue::Integer(8))
484 .build();
485
486 let css_classes = classes.to_css_classes();
487 assert!(css_classes.contains("w-full"));
488 assert!(css_classes.contains("min-w-4"));
489 assert!(css_classes.contains("max-w-8"));
490 }
491
492 #[test]
493 fn test_height_utilities() {
494 let classes = ClassBuilder::new()
495 .height(SizingValue::Screen)
496 .min_height(SizingValue::Integer(4))
497 .max_height(SizingValue::Integer(8))
498 .build();
499
500 let css_classes = classes.to_css_classes();
501 assert!(css_classes.contains("h-screen"));
502 assert!(css_classes.contains("min-h-4"));
503 assert!(css_classes.contains("max-h-8"));
504 }
505
506 #[test]
507 fn test_fractional_sizing() {
508 let classes = ClassBuilder::new()
509 .width(SizingValue::Fraction(Fraction::Half))
510 .height(SizingValue::Fraction(Fraction::Third))
511 .build();
512
513 let css_classes = classes.to_css_classes();
514 assert!(css_classes.contains("w-1/2"));
515 assert!(css_classes.contains("h-1/3"));
516 }
517
518 #[test]
519 fn test_grid_fractional_sizing() {
520 let classes = ClassBuilder::new()
521 .width(SizingValue::GridFraction(GridFraction::SixTwelfths))
522 .height(SizingValue::GridFraction(GridFraction::FourTwelfths))
523 .build();
524
525 let css_classes = classes.to_css_classes();
526 assert!(css_classes.contains("w-6/12"));
527 assert!(css_classes.contains("h-4/12"));
528 }
529
530 #[test]
531 fn test_special_sizing_values() {
532 let classes = ClassBuilder::new()
533 .width(SizingValue::Auto)
534 .height(SizingValue::Fit)
535 .min_width(SizingValue::Min)
536 .max_width(SizingValue::Max)
537 .build();
538
539 let css_classes = classes.to_css_classes();
540 assert!(css_classes.contains("w-auto"));
541 assert!(css_classes.contains("h-fit"));
542 assert!(css_classes.contains("min-w-min"));
543 assert!(css_classes.contains("max-w-max"));
544 }
545}