tailwind_rs_core/utilities/
sizing.rs

1//! Sizing utilities for tailwind-rs
2//!
3//! This module provides utilities for width, height, min-width, max-width,
4//! min-height, and max-height. It follows Tailwind CSS conventions and
5//! provides type-safe sizing values.
6
7use crate::classes::ClassBuilder;
8use serde::{Deserialize, Serialize};
9use std::fmt;
10
11/// Sizing utility values for width and height
12/// 
13/// # Examples
14/// 
15/// ```rust
16/// use tailwind_rs_core::utilities::sizing::SizingValue;
17/// use tailwind_rs_core::classes::ClassBuilder;
18/// 
19/// let classes = ClassBuilder::new()
20///     .width(SizingValue::Full)
21///     .height(SizingValue::Screen)
22///     .build();
23/// ```
24#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
25pub enum SizingValue {
26    /// Zero sizing (0)
27    Zero,
28    /// 1px sizing
29    Px,
30    /// Fractional sizing (0.5, 1.5, 2.5, 3.5)
31    Fractional(f32),
32    /// Integer sizing (1, 2, 3, 4, 5, 6, 8, 10, 12, 16, 20, 24, 32, 40, 48, 56, 64, 72, 80, 96)
33    Integer(u32),
34    /// Auto sizing
35    Auto,
36    /// Full sizing (100%)
37    Full,
38    /// Screen sizing (100vh for height, 100vw for width)
39    Screen,
40    /// Min content sizing
41    Min,
42    /// Max content sizing
43    Max,
44    /// Fit content sizing
45    Fit,
46    /// Fractional sizing (1/2, 1/3, 2/3, 1/4, 2/4, 3/4, 1/5, 2/5, 3/5, 4/5, 1/6, 2/6, 3/6, 4/6, 5/6)
47    Fraction(Fraction),
48    /// 12-column grid fractions (1/12, 2/12, 3/12, 4/12, 5/12, 6/12, 7/12, 8/12, 9/12, 10/12, 11/12)
49    GridFraction(GridFraction),
50}
51
52/// Fractional sizing values
53#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
54pub enum Fraction {
55    /// 1/2
56    Half,
57    /// 1/3
58    Third,
59    /// 2/3
60    TwoThirds,
61    /// 1/4
62    Quarter,
63    /// 2/4
64    TwoQuarters,
65    /// 3/4
66    ThreeQuarters,
67    /// 1/5
68    Fifth,
69    /// 2/5
70    TwoFifths,
71    /// 3/5
72    ThreeFifths,
73    /// 4/5
74    FourFifths,
75    /// 1/6
76    Sixth,
77    /// 2/6
78    TwoSixths,
79    /// 3/6
80    ThreeSixths,
81    /// 4/6
82    FourSixths,
83    /// 5/6
84    FiveSixths,
85}
86
87/// 12-column grid fractions
88#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
89pub enum GridFraction {
90    /// 1/12
91    OneTwelfth,
92    /// 2/12
93    TwoTwelfths,
94    /// 3/12
95    ThreeTwelfths,
96    /// 4/12
97    FourTwelfths,
98    /// 5/12
99    FiveTwelfths,
100    /// 6/12
101    SixTwelfths,
102    /// 7/12
103    SevenTwelfths,
104    /// 8/12
105    EightTwelfths,
106    /// 9/12
107    NineTwelfths,
108    /// 10/12
109    TenTwelfths,
110    /// 11/12
111    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    /// Convert to CSS value
163    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(), // For height, will be overridden for width
172            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    /// Convert to CSS value for width (screen becomes 100vw)
181    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    /// Convert to CSS value for height (screen becomes 100vh)
189    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    /// Convert to class name
197    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    /// Get all valid sizing values
215    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        // Add fractions
252        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        // Add grid fractions
271        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
371/// Trait for adding width utilities to a class builder
372pub trait WidthUtilities {
373    /// Set width
374    fn width(self, value: SizingValue) -> Self;
375    
376    /// Set min-width
377    fn min_width(self, value: SizingValue) -> Self;
378    
379    /// Set max-width
380    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
397/// Trait for adding height utilities to a class builder
398pub trait HeightUtilities {
399    /// Set height
400    fn height(self, value: SizingValue) -> Self;
401    
402    /// Set min-height
403    fn min_height(self, value: SizingValue) -> Self;
404    
405    /// Set max-height
406    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}