radix_leptos_primitives/theming/layout_system/
responsive.rs1use serde::{Deserialize, Serialize};
2use super::container::ContainerMaxWidth;
3
4#[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#[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
70pub mod responsive_utils {
74 use super::*;
75
76 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 pub fn matches_breakpoint(breakpoint: Breakpoint, width: f64) -> bool {
93 width >= breakpoint.min_width()
94 }
95
96 pub fn get_container_max_width_for_breakpoint(
98 breakpoint_system: &BreakpointSystem,
99 breakpoint: Breakpoint,
100 ) -> Option<ContainerMaxWidth> {
101 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 pub fn generate_media_query(breakpoint: Breakpoint) -> String {
114 format!("@media (min-width: {}px)", breakpoint.min_width())
115 }
116
117 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 #[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}