Skip to main content

radix_leptos_primitives/theming/layout_system/
responsive.rs

1use serde::{Deserialize, Serialize};
2use super::container::ContainerMaxWidth;
3
4/// Breakpoint system configuration
5#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
6pub struct BreakpointSystem {
7    pub breakpoints: Vec<Breakpoint>,
8    pub container_max_widths: Vec<ContainerMaxWidth>,
9}
10
11impl Default for BreakpointSystem {
12    fn default() -> Self {
13        Self {
14            breakpoints: [
15                Breakpoint::ExtraSmall,
16                Breakpoint::Small,
17                Breakpoint::Medium,
18                Breakpoint::Large,
19                Breakpoint::ExtraLarge,
20                Breakpoint::ExtraExtraLarge,
21            ]
22            .to_vec(),
23            container_max_widths: [
24                ContainerMaxWidth::Small,
25                ContainerMaxWidth::Medium,
26                ContainerMaxWidth::Large,
27                ContainerMaxWidth::ExtraLarge,
28                ContainerMaxWidth::Fluid,
29            ]
30            .to_vec(),
31        }
32    }
33}
34
35/// Breakpoint enum
36#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
37pub enum Breakpoint {
38    ExtraSmall,
39    Small,
40    Medium,
41    Large,
42    ExtraLarge,
43    ExtraExtraLarge,
44}
45
46impl Breakpoint {
47    pub fn as_str(&self) -> &'static str {
48        match self {
49            Breakpoint::ExtraSmall => "xs",
50            Breakpoint::Small => "sm",
51            Breakpoint::Medium => "md",
52            Breakpoint::Large => "lg",
53            Breakpoint::ExtraLarge => "xl",
54            Breakpoint::ExtraExtraLarge => "2xl",
55        }
56    }
57
58    pub fn min_width(&self) -> f64 {
59        match self {
60            Breakpoint::ExtraSmall => 0.0,
61            Breakpoint::Small => 640.0,
62            Breakpoint::Medium => 768.0,
63            Breakpoint::Large => 1024.0,
64            Breakpoint::ExtraLarge => 1280.0,
65            Breakpoint::ExtraExtraLarge => 1536.0,
66        }
67    }
68}
69
70// ContainerMaxWidth is defined in the container module
71
72/// Responsive utility functions
73pub mod responsive_utils {
74    use super::*;
75
76    /// Get breakpoint for a given width
77    pub fn get_breakpoint_for_width(breakpoint_system: &BreakpointSystem, width: f64) -> Breakpoint {
78        let mut current_breakpoint = Breakpoint::ExtraSmall;
79        
80        for &breakpoint in &breakpoint_system.breakpoints {
81            if width >= breakpoint.min_width() {
82                current_breakpoint = breakpoint;
83            } else {
84                break;
85            }
86        }
87        
88        current_breakpoint
89    }
90
91    /// Check if width matches breakpoint
92    pub fn matches_breakpoint(breakpoint: Breakpoint, width: f64) -> bool {
93        width >= breakpoint.min_width()
94    }
95
96    /// Get container max width for breakpoint
97    pub fn get_container_max_width_for_breakpoint(
98        breakpoint_system: &BreakpointSystem,
99        breakpoint: Breakpoint,
100    ) -> Option<ContainerMaxWidth> {
101        // Simple mapping logic - can be made more sophisticated
102        match breakpoint {
103            Breakpoint::ExtraSmall => Some(ContainerMaxWidth::Small),
104            Breakpoint::Small => Some(ContainerMaxWidth::Small),
105            Breakpoint::Medium => Some(ContainerMaxWidth::Medium),
106            Breakpoint::Large => Some(ContainerMaxWidth::Large),
107            Breakpoint::ExtraLarge => Some(ContainerMaxWidth::ExtraLarge),
108            Breakpoint::ExtraExtraLarge => Some(ContainerMaxWidth::ExtraLarge),
109        }
110    }
111
112    /// Generate responsive CSS media queries
113    pub fn generate_media_query(breakpoint: Breakpoint) -> String {
114        format!("@media (min-width: {}px)", breakpoint.min_width())
115    }
116
117    /// Validate breakpoint system
118    pub fn validate_breakpoint_system(breakpoint_system: &BreakpointSystem) -> bool {
119        !breakpoint_system.breakpoints.is_empty() && !breakpoint_system.container_max_widths.is_empty()
120    }
121}
122
123#[cfg(test)]
124mod responsive_tests {
125    use super::*;
126
127    #[test]
128    fn test_breakpoint_system_default() {
129        let breakpoints = BreakpointSystem::default();
130        assert!(breakpoints.breakpoints.contains(&Breakpoint::ExtraSmall));
131        assert!(breakpoints.breakpoints.contains(&Breakpoint::Small));
132        assert!(breakpoints.breakpoints.contains(&Breakpoint::Medium));
133        assert!(breakpoints.breakpoints.contains(&Breakpoint::Large));
134        assert!(breakpoints.breakpoints.contains(&Breakpoint::ExtraLarge));
135        assert!(breakpoints
136            .breakpoints
137            .contains(&Breakpoint::ExtraExtraLarge));
138        assert!(breakpoints
139            .container_max_widths
140            .contains(&ContainerMaxWidth::Small));
141        assert!(breakpoints
142            .container_max_widths
143            .contains(&ContainerMaxWidth::Medium));
144        assert!(breakpoints
145            .container_max_widths
146            .contains(&ContainerMaxWidth::Large));
147        assert!(breakpoints
148            .container_max_widths
149            .contains(&ContainerMaxWidth::ExtraLarge));
150        assert!(breakpoints
151            .container_max_widths
152            .contains(&ContainerMaxWidth::Fluid));
153    }
154
155    #[test]
156    fn test_breakpoint_enum() {
157        assert_eq!(Breakpoint::ExtraSmall.as_str(), "xs");
158        assert_eq!(Breakpoint::Small.as_str(), "sm");
159        assert_eq!(Breakpoint::Medium.as_str(), "md");
160        assert_eq!(Breakpoint::Large.as_str(), "lg");
161        assert_eq!(Breakpoint::ExtraLarge.as_str(), "xl");
162        assert_eq!(Breakpoint::ExtraExtraLarge.as_str(), "2xl");
163    }
164
165    #[test]
166    fn test_breakpoint_min_width() {
167        assert_eq!(Breakpoint::ExtraSmall.min_width(), 0.0);
168        assert_eq!(Breakpoint::Small.min_width(), 640.0);
169        assert_eq!(Breakpoint::Medium.min_width(), 768.0);
170        assert_eq!(Breakpoint::Large.min_width(), 1024.0);
171        assert_eq!(Breakpoint::ExtraLarge.min_width(), 1280.0);
172        assert_eq!(Breakpoint::ExtraExtraLarge.min_width(), 1536.0);
173    }
174
175    #[test]
176    fn test_get_breakpoint_for_width() {
177        let breakpoint_system = BreakpointSystem::default();
178        
179        assert_eq!(
180            responsive_utils::get_breakpoint_for_width(&breakpoint_system, 0.0),
181            Breakpoint::ExtraSmall
182        );
183        assert_eq!(
184            responsive_utils::get_breakpoint_for_width(&breakpoint_system, 640.0),
185            Breakpoint::Small
186        );
187        assert_eq!(
188            responsive_utils::get_breakpoint_for_width(&breakpoint_system, 768.0),
189            Breakpoint::Medium
190        );
191        assert_eq!(
192            responsive_utils::get_breakpoint_for_width(&breakpoint_system, 1024.0),
193            Breakpoint::Large
194        );
195        assert_eq!(
196            responsive_utils::get_breakpoint_for_width(&breakpoint_system, 1280.0),
197            Breakpoint::ExtraLarge
198        );
199        assert_eq!(
200            responsive_utils::get_breakpoint_for_width(&breakpoint_system, 1536.0),
201            Breakpoint::ExtraExtraLarge
202        );
203    }
204
205    #[test]
206    fn test_matches_breakpoint() {
207        assert!(responsive_utils::matches_breakpoint(Breakpoint::ExtraSmall, 0.0));
208        assert!(responsive_utils::matches_breakpoint(Breakpoint::Small, 640.0));
209        assert!(responsive_utils::matches_breakpoint(Breakpoint::Small, 700.0));
210        assert!(!responsive_utils::matches_breakpoint(Breakpoint::Small, 500.0));
211    }
212
213    #[test]
214    fn test_get_container_max_width_for_breakpoint() {
215        let breakpoint_system = BreakpointSystem::default();
216        
217        assert_eq!(
218            responsive_utils::get_container_max_width_for_breakpoint(
219                &breakpoint_system,
220                Breakpoint::ExtraSmall
221            ),
222            Some(ContainerMaxWidth::Small)
223        );
224        assert_eq!(
225            responsive_utils::get_container_max_width_for_breakpoint(
226                &breakpoint_system,
227                Breakpoint::Large
228            ),
229            Some(ContainerMaxWidth::Large)
230        );
231    }
232
233    #[test]
234    fn test_generate_media_query() {
235        assert_eq!(
236            responsive_utils::generate_media_query(Breakpoint::Small),
237            "@media (min-width: 640px)"
238        );
239        assert_eq!(
240            responsive_utils::generate_media_query(Breakpoint::Large),
241            "@media (min-width: 1024px)"
242        );
243    }
244
245    #[test]
246    fn test_validate_breakpoint_system() {
247        let valid_system = BreakpointSystem::default();
248        assert!(responsive_utils::validate_breakpoint_system(&valid_system));
249
250        let invalid_system = BreakpointSystem {
251            breakpoints: vec![],
252            container_max_widths: vec![],
253        };
254        assert!(!responsive_utils::validate_breakpoint_system(&invalid_system));
255    }
256
257    // Property-based tests
258    #[test]
259    fn test_breakpoint_property_based() {
260        use proptest::prelude::*;
261        
262        proptest!(|(breakpoint in prop::sample::select(&[
263            Breakpoint::ExtraSmall,
264            Breakpoint::Small,
265            Breakpoint::Medium,
266            Breakpoint::Large,
267            Breakpoint::ExtraLarge,
268            Breakpoint::ExtraExtraLarge,
269        ]))| {
270            let breakpoint_str = breakpoint.as_str();
271            let min_width = breakpoint.min_width();
272            assert!(!breakpoint_str.is_empty());
273            assert!(min_width >= 0.0);
274        });
275    }
276}