Skip to main content

oxiui_theme/
breakpoint.rs

1//! Responsive breakpoints matching common CSS media-query thresholds.
2//!
3//! The breakpoints follow the Bootstrap 5 / Tailwind CSS convention:
4//!
5//! | Variant | Viewport width         |
6//! |---------|------------------------|
7//! | `Xs`    | < 576 px               |
8//! | `Sm`    | 576 – 767 px           |
9//! | `Md`    | 768 – 991 px           |
10//! | `Lg`    | 992 – 1199 px          |
11//! | `Xl`    | 1200 – 1535 px         |
12//! | `Xxl`   | ≥ 1536 px              |
13
14/// A responsive breakpoint corresponding to a viewport-width range.
15#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
16pub enum Breakpoint {
17    /// Extra-small: viewport width below 576 logical pixels.
18    Xs,
19    /// Small: viewport width from 576 to 767 logical pixels.
20    Sm,
21    /// Medium: viewport width from 768 to 991 logical pixels.
22    Md,
23    /// Large: viewport width from 992 to 1199 logical pixels.
24    Lg,
25    /// Extra-large: viewport width from 1200 to 1535 logical pixels.
26    Xl,
27    /// Extra-extra-large: viewport width at or above 1536 logical pixels.
28    Xxl,
29}
30
31impl Breakpoint {
32    /// Determine the active breakpoint for a given viewport `width` in logical
33    /// pixels.
34    pub fn for_width(width: f32) -> Self {
35        if width < 576.0 {
36            Breakpoint::Xs
37        } else if width < 768.0 {
38            Breakpoint::Sm
39        } else if width < 992.0 {
40            Breakpoint::Md
41        } else if width < 1200.0 {
42            Breakpoint::Lg
43        } else if width < 1536.0 {
44            Breakpoint::Xl
45        } else {
46            Breakpoint::Xxl
47        }
48    }
49
50    /// The minimum viewport width (in logical pixels) at which this breakpoint
51    /// activates.
52    pub fn min_width(self) -> f32 {
53        match self {
54            Breakpoint::Xs => 0.0,
55            Breakpoint::Sm => 576.0,
56            Breakpoint::Md => 768.0,
57            Breakpoint::Lg => 992.0,
58            Breakpoint::Xl => 1200.0,
59            Breakpoint::Xxl => 1536.0,
60        }
61    }
62
63    /// Returns `true` when `width` is at or above this breakpoint's minimum.
64    ///
65    /// Equivalent to a CSS `@media (min-width: …)` query.
66    pub fn matches_width(self, width: f32) -> bool {
67        width >= self.min_width()
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    #[test]
76    fn breakpoint_xs_below_576() {
77        assert_eq!(Breakpoint::for_width(400.0), Breakpoint::Xs);
78        assert_eq!(Breakpoint::for_width(0.0), Breakpoint::Xs);
79    }
80
81    #[test]
82    fn breakpoint_sm_at_576() {
83        assert_eq!(Breakpoint::for_width(576.0), Breakpoint::Sm);
84        assert_eq!(Breakpoint::for_width(767.9), Breakpoint::Sm);
85    }
86
87    #[test]
88    fn breakpoint_md_768_to_991() {
89        assert_eq!(Breakpoint::for_width(900.0), Breakpoint::Md);
90    }
91
92    #[test]
93    fn breakpoint_xxl_at_1536() {
94        assert_eq!(Breakpoint::for_width(1536.0), Breakpoint::Xxl);
95        assert_eq!(Breakpoint::for_width(2000.0), Breakpoint::Xxl);
96    }
97
98    #[test]
99    fn breakpoint_ordering() {
100        assert!(Breakpoint::Xs < Breakpoint::Sm);
101        assert!(Breakpoint::Sm < Breakpoint::Md);
102        assert!(Breakpoint::Md < Breakpoint::Lg);
103        assert!(Breakpoint::Lg < Breakpoint::Xl);
104        assert!(Breakpoint::Xl < Breakpoint::Xxl);
105    }
106
107    #[test]
108    fn min_width_xs_is_zero() {
109        assert_eq!(Breakpoint::Xs.min_width(), 0.0);
110    }
111
112    #[test]
113    fn matches_width_true_and_false() {
114        assert!(Breakpoint::Md.matches_width(900.0));
115        assert!(!Breakpoint::Xl.matches_width(900.0));
116    }
117}