1use crate::{use_media_query, use_window};
2use leptos::logging::error;
3use leptos::prelude::*;
4use leptos::reactive::wrappers::read::Signal;
5use paste::paste;
6use std::collections::HashMap;
7use std::fmt::Debug;
8use std::hash::Hash;
9
10pub fn use_breakpoints<K: Eq + Hash + Debug + Clone + Send + Sync>(
117 breakpoints: HashMap<K, u32>,
118) -> UseBreakpointsReturn<K> {
119 UseBreakpointsReturn { breakpoints }
120}
121
122#[derive(Clone)]
124pub struct UseBreakpointsReturn<K: Eq + Hash + Debug + Clone + Send + Sync> {
125 breakpoints: HashMap<K, u32>,
126}
127
128macro_rules! query_suffix {
129 (>) => {
130 ".1"
131 };
132 (<) => {
133 ".9"
134 };
135 (=) => {
136 ""
137 };
138}
139
140macro_rules! value_expr {
141 ($v:ident, >) => {
142 $v
143 };
144 ($v:ident, <) => {
145 $v - 1
146 };
147 ($v:ident, =) => {
148 $v
149 };
150}
151
152macro_rules! format_media_query {
153 ($cmp:tt, $suffix:tt, $v:ident) => {
154 format!(
155 "({}-width: {}{}px)",
156 $cmp,
157 value_expr!($v, $suffix),
158 query_suffix!($suffix)
159 )
160 };
161}
162
163macro_rules! impl_cmp_reactively {
164 ( #[$attr:meta]
165 $fn:ident, $cmp:tt, $suffix:tt) => {
166 paste! {
167 #[$attr]
169 pub fn $fn(&self, key: K) -> Signal<bool> {
170 if let Some(value) = self.breakpoints.get(&key) {
171 use_media_query(format_media_query!($cmp, $suffix, value))
172 } else {
173 self.not_found_signal(key)
174 }
175 }
176
177 #[$attr]
179 pub fn [<is_ $fn>](&self, key: K) -> bool {
180 if let Some(value) = self.breakpoints.get(&key) {
181 Self::match_(&format_media_query!($cmp, $suffix, value))
182 } else {
183 self.not_found(key)
184 }
185 }
186 }
187 };
188}
189
190impl<K> UseBreakpointsReturn<K>
191where
192 K: Eq + Hash + Debug + Clone + Send + Sync + 'static,
193{
194 fn match_(query: &str) -> bool {
195 if let Ok(Some(query_list)) = use_window().match_media(query) {
196 return query_list.matches();
197 }
198
199 false
200 }
201
202 fn not_found_signal(&self, key: K) -> Signal<bool> {
203 error!("Breakpoint \"{:?}\" not found", key);
204 Signal::derive(|| false)
205 }
206
207 fn not_found(&self, key: K) -> bool {
208 error!("Breakpoint \"{:?}\" not found", key);
209 false
210 }
211
212 impl_cmp_reactively!(
213 gt, "min", >
215 );
216 impl_cmp_reactively!(
217 ge, "min", =
219 );
220 impl_cmp_reactively!(
221 lt, "max", <
223 );
224 impl_cmp_reactively!(
225 le, "max", =
227 );
228
229 fn between_media_query(min: &u32, max: &u32) -> String {
230 format!("(min-width: {min}px) and (max-width: {}.9px)", max - 1)
231 }
232
233 pub fn between(&self, min_key: K, max_key: K) -> Signal<bool> {
235 if let Some(min) = self.breakpoints.get(&min_key) {
236 if let Some(max) = self.breakpoints.get(&max_key) {
237 use_media_query(Self::between_media_query(min, max))
238 } else {
239 self.not_found_signal(max_key)
240 }
241 } else {
242 self.not_found_signal(min_key)
243 }
244 }
245
246 pub fn is_between(&self, min_key: K, max_key: K) -> bool {
248 if let Some(min) = self.breakpoints.get(&min_key) {
249 if let Some(max) = self.breakpoints.get(&max_key) {
250 Self::match_(&Self::between_media_query(min, max))
251 } else {
252 self.not_found(max_key)
253 }
254 } else {
255 self.not_found(min_key)
256 }
257 }
258
259 pub fn current(&self) -> Signal<Vec<K>> {
261 let breakpoints = self.breakpoints.clone();
262 let keys: Vec<_> = breakpoints.keys().cloned().collect();
263
264 let ge = move |key: &K| {
265 let value = breakpoints
266 .get(key)
267 .expect("only used with keys() from the HashMap");
268
269 use_media_query(format_media_query!("min", =, value))
270 };
271
272 let signals: Vec<_> = keys.iter().map(ge.clone()).collect();
273
274 Signal::derive(move || {
275 keys.iter()
276 .cloned()
277 .zip(signals.iter().cloned())
278 .filter_map(|(key, signal)| signal.get().then_some(key))
279 .collect::<Vec<_>>()
280 })
281 }
282}
283
284#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
288pub enum BreakpointsTailwind {
289 Sm,
290 Md,
291 Lg,
292 Xl,
293 Xxl,
294}
295
296pub fn breakpoints_tailwind() -> HashMap<BreakpointsTailwind, u32> {
300 HashMap::from([
301 (BreakpointsTailwind::Sm, 640),
302 (BreakpointsTailwind::Md, 768),
303 (BreakpointsTailwind::Lg, 1024),
304 (BreakpointsTailwind::Xl, 1280),
305 (BreakpointsTailwind::Xxl, 1536),
306 ])
307}
308
309#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
313pub enum BreakpointsBootstrapV5 {
314 Sm,
315 Md,
316 Lg,
317 Xl,
318 Xxl,
319}
320
321pub fn breakpoints_bootstrap_v5() -> HashMap<BreakpointsBootstrapV5, u32> {
325 HashMap::from([
326 (BreakpointsBootstrapV5::Sm, 576),
327 (BreakpointsBootstrapV5::Md, 768),
328 (BreakpointsBootstrapV5::Lg, 992),
329 (BreakpointsBootstrapV5::Xl, 1200),
330 (BreakpointsBootstrapV5::Xxl, 1400),
331 ])
332}
333
334#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
338pub enum BreakpointsMaterial {
339 Xs,
340 Sm,
341 Md,
342 Lg,
343 Xl,
344}
345
346pub fn breakpoints_material() -> HashMap<BreakpointsMaterial, u32> {
350 HashMap::from([
351 (BreakpointsMaterial::Xs, 1),
352 (BreakpointsMaterial::Sm, 600),
353 (BreakpointsMaterial::Md, 900),
354 (BreakpointsMaterial::Lg, 1200),
355 (BreakpointsMaterial::Xl, 1536),
356 ])
357}
358
359#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
363pub enum BreakpointsAntDesign {
364 Xs,
365 Sm,
366 Md,
367 Lg,
368 Xl,
369 Xxl,
370}
371
372pub fn breakpoints_ant_design() -> HashMap<BreakpointsAntDesign, u32> {
376 HashMap::from([
377 (BreakpointsAntDesign::Xs, 480),
378 (BreakpointsAntDesign::Sm, 576),
379 (BreakpointsAntDesign::Md, 768),
380 (BreakpointsAntDesign::Lg, 992),
381 (BreakpointsAntDesign::Xl, 1200),
382 (BreakpointsAntDesign::Xxl, 1600),
383 ])
384}
385
386#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
390pub enum BreakpointsQuasar {
391 Xs,
392 Sm,
393 Md,
394 Lg,
395 Xl,
396}
397
398pub fn breakpoints_quasar() -> HashMap<BreakpointsQuasar, u32> {
402 HashMap::from([
403 (BreakpointsQuasar::Xs, 1),
404 (BreakpointsQuasar::Sm, 600),
405 (BreakpointsQuasar::Md, 1024),
406 (BreakpointsQuasar::Lg, 1440),
407 (BreakpointsQuasar::Xl, 1920),
408 ])
409}
410
411#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
415pub enum BreakpointsSemantic {
416 Mobile,
417 Tablet,
418 SmallMonitor,
419 LargeMonitor,
420}
421
422pub fn breakpoints_semantic() -> HashMap<BreakpointsSemantic, u32> {
426 HashMap::from([
427 (BreakpointsSemantic::Mobile, 1),
428 (BreakpointsSemantic::Tablet, 768),
429 (BreakpointsSemantic::SmallMonitor, 992),
430 (BreakpointsSemantic::LargeMonitor, 1200),
431 ])
432}
433
434#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
438pub enum BreakpointsMasterCss {
439 Xxxs,
440 Xxs,
441 Xs,
442 Sm,
443 Md,
444 Lg,
445 Xl,
446 Xxl,
447 Xxxl,
448 Xxxxl,
449}
450
451pub fn breakpoints_master_css() -> HashMap<BreakpointsMasterCss, u32> {
455 HashMap::from([
456 (BreakpointsMasterCss::Xxxs, 360),
457 (BreakpointsMasterCss::Xxs, 480),
458 (BreakpointsMasterCss::Xs, 600),
459 (BreakpointsMasterCss::Sm, 768),
460 (BreakpointsMasterCss::Md, 1024),
461 (BreakpointsMasterCss::Lg, 1280),
462 (BreakpointsMasterCss::Xl, 1440),
463 (BreakpointsMasterCss::Xxl, 1600),
464 (BreakpointsMasterCss::Xxxl, 1920),
465 (BreakpointsMasterCss::Xxxxl, 2560),
466 ])
467}