1use crate::prelude::AsClasses;
6use std::fmt::{Debug, Display, Formatter};
7use std::ops::Deref;
8use yew::html::IntoPropValue;
9use yew::prelude::*;
10use yew_more_hooks::prelude::Breakpoint as BreakpointTrait;
11
12#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
14pub enum Breakpoint {
15 None,
16 Small,
17 Medium,
18 Large,
19 XLarge,
20 XXLarge,
21}
22
23impl BreakpointTrait for Breakpoint {
24 fn as_pixels(&self) -> usize {
25 match self {
26 Breakpoint::None => 0,
27 Breakpoint::Small => 576,
28 Breakpoint::Medium => 768,
29 Breakpoint::Large => 992,
30 Breakpoint::XLarge => 1200,
31 Breakpoint::XXLarge => 1450,
32 }
33 }
34
35 fn from_screen_width(pixels: usize) -> Self {
36 match pixels {
37 w if w < Breakpoint::Small.as_pixels() => Breakpoint::None,
38 w if w < Breakpoint::Medium.as_pixels() => Breakpoint::Small,
39 w if w < Breakpoint::Large.as_pixels() => Breakpoint::Medium,
40 w if w < Breakpoint::XLarge.as_pixels() => Breakpoint::Large,
41 w if w < Breakpoint::XXLarge.as_pixels() => Breakpoint::XLarge,
42 _ => Breakpoint::XXLarge,
43 }
44 }
45}
46
47#[hook]
48pub fn use_breakpoint() -> UseStateHandle<Breakpoint> {
49 yew_more_hooks::prelude::use_breakpoint()
50}
51
52#[derive(Clone, Debug, PartialEq)]
54pub struct WithBreakpoint<T>
55where
56 T: PartialEq,
57{
58 pub modifier: T,
59 pub on: Breakpoint,
60}
61
62impl<T: Eq> Eq for WithBreakpoint<T> {}
63
64impl<T> WithBreakpoint<T>
65where
66 T: PartialEq,
67{
68 pub fn new(modifier: T) -> Self {
69 Self {
70 on: Breakpoint::None,
71 modifier,
72 }
73 }
74
75 pub fn map<R, F>(self, f: F) -> WithBreakpoint<R>
76 where
77 R: PartialEq,
78 F: Fn(T) -> R,
79 {
80 WithBreakpoint {
81 on: self.on,
82 modifier: f(self.modifier),
83 }
84 }
85}
86
87#[derive(Clone, Debug, PartialEq)]
92pub struct WithBreakpoints<T>(Vec<WithBreakpoint<T>>)
93where
94 T: PartialEq;
95
96impl<T: Eq> Eq for WithBreakpoints<T> {}
97
98impl<T> Default for WithBreakpoints<T>
99where
100 T: PartialEq,
101{
102 fn default() -> Self {
103 Self(vec![])
104 }
105}
106
107impl<T> AsClasses for WithBreakpoints<T>
108where
109 T: PartialEq + AsClasses,
110{
111 fn extend_classes(&self, classes: &mut Classes) {
112 AsClasses::extend_classes(&self.0, classes)
113 }
114}
115
116impl<T> AsClasses for WithBreakpoint<T>
117where
118 T: PartialEq + AsClasses,
119{
120 fn extend_classes(&self, classes: &mut Classes) {
121 classes.extend(
123 self.modifier
124 .as_classes()
125 .into_iter()
126 .map(|c| format!("{}{}", c, self.on)),
127 )
128 }
129}
130
131impl<T> WithBreakpoints<T>
132where
133 T: Clone + PartialEq,
134{
135 pub fn mapped<R, F>(&self, f: F) -> WithBreakpoints<R>
136 where
137 R: PartialEq,
138 F: Fn(T) -> R,
139 {
140 WithBreakpoints(
141 self.0
142 .clone()
143 .into_iter()
144 .map(|i| i.map(&f))
145 .collect::<Vec<_>>(),
146 )
147 }
148}
149
150impl<T> From<Vec<WithBreakpoint<T>>> for WithBreakpoints<T>
151where
152 T: PartialEq,
153{
154 fn from(value: Vec<WithBreakpoint<T>>) -> Self {
155 Self(value)
156 }
157}
158
159impl<T> From<&[WithBreakpoint<T>]> for WithBreakpoints<T>
160where
161 T: Clone + PartialEq,
162{
163 fn from(value: &[WithBreakpoint<T>]) -> Self {
164 Self(Vec::from(value))
165 }
166}
167
168impl<T, const N: usize> From<[WithBreakpoint<T>; N]> for WithBreakpoints<T>
169where
170 T: PartialEq,
171{
172 fn from(value: [WithBreakpoint<T>; N]) -> Self {
173 Self(Vec::from(value))
174 }
175}
176
177impl<T> IntoIterator for WithBreakpoints<T>
178where
179 T: PartialEq,
180{
181 type Item = WithBreakpoint<T>;
182 type IntoIter = std::vec::IntoIter<WithBreakpoint<T>>;
183
184 fn into_iter(self) -> Self::IntoIter {
185 self.0.into_iter()
186 }
187}
188
189impl Display for Breakpoint {
190 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
191 match self {
192 Breakpoint::None => f.write_str(""),
193 Breakpoint::Small => f.write_str("-on-sm"),
194 Breakpoint::Medium => f.write_str("-on-md"),
195 Breakpoint::Large => f.write_str("-on-lg"),
196 Breakpoint::XLarge => f.write_str("-on-xl"),
197 Breakpoint::XXLarge => f.write_str("-on-2xl"),
198 }
199 }
200}
201
202pub trait WithBreakpointExt<T>: Sized
223where
224 T: PartialEq,
225{
226 fn on(self, breakpoint: Breakpoint) -> WithBreakpoint<T>;
227
228 fn all(self) -> WithBreakpoint<T> {
229 self.on(Breakpoint::None)
230 }
231
232 fn sm(self) -> WithBreakpoint<T> {
233 self.on(Breakpoint::Small)
234 }
235
236 fn md(self) -> WithBreakpoint<T> {
237 self.on(Breakpoint::Medium)
238 }
239
240 fn lg(self) -> WithBreakpoint<T> {
241 self.on(Breakpoint::Large)
242 }
243
244 fn xl(self) -> WithBreakpoint<T> {
245 self.on(Breakpoint::XLarge)
246 }
247
248 fn xxl(self) -> WithBreakpoint<T> {
249 self.on(Breakpoint::XXLarge)
250 }
251}
252
253impl<T> WithBreakpointExt<T> for T
254where
255 T: PartialEq,
256{
257 fn on(self, breakpoint: Breakpoint) -> WithBreakpoint<T> {
258 WithBreakpoint {
259 modifier: self,
260 on: breakpoint,
261 }
262 }
263}
264
265impl<T> IntoPropValue<Vec<WithBreakpoint<T>>> for WithBreakpoint<T>
266where
267 T: PartialEq,
268{
269 fn into_prop_value(self) -> Vec<WithBreakpoint<T>> {
270 vec![self]
271 }
272}
273
274impl<T> Deref for WithBreakpoints<T>
275where
276 T: PartialEq,
277{
278 type Target = Vec<WithBreakpoint<T>>;
279
280 fn deref(&self) -> &Self::Target {
281 &self.0
282 }
283}
284
285impl<T> From<T> for WithBreakpoint<T>
286where
287 T: PartialEq,
288{
289 fn from(modifier: T) -> Self {
290 Self::new(modifier)
291 }
292}
293
294impl<T> From<WithBreakpoint<T>> for WithBreakpoints<T>
295where
296 T: PartialEq,
297{
298 fn from(modifier: WithBreakpoint<T>) -> Self {
299 WithBreakpoints(vec![modifier])
300 }
301}
302
303impl<T> From<T> for WithBreakpoints<T>
304where
305 T: PartialEq,
306{
307 fn from(modifier: T) -> Self {
308 WithBreakpoints(vec![modifier.into()])
309 }
310}
311
312impl<T> IntoPropValue<WithBreakpoints<T>> for WithBreakpoint<T>
313where
314 T: PartialEq,
315{
316 fn into_prop_value(self) -> WithBreakpoints<T> {
317 vec![self].into()
318 }
319}
320
321impl<T> IntoPropValue<WithBreakpoints<T>> for Vec<WithBreakpoint<T>>
322where
323 T: PartialEq,
324{
325 fn into_prop_value(self) -> WithBreakpoints<T> {
326 self.into()
327 }
328}
329
330impl<T> IntoPropValue<WithBreakpoints<T>> for &[WithBreakpoint<T>]
331where
332 T: Clone + PartialEq,
333{
334 fn into_prop_value(self) -> WithBreakpoints<T> {
335 self.into()
336 }
337}
338
339impl<T, const N: usize> IntoPropValue<WithBreakpoints<T>> for [WithBreakpoint<T>; N]
340where
341 T: PartialEq,
342{
343 fn into_prop_value(self) -> WithBreakpoints<T> {
344 self.into()
345 }
346}
347
348impl<T, const N: usize> IntoPropValue<WithBreakpoints<T>> for [T; N]
349where
350 T: PartialEq,
351{
352 fn into_prop_value(self) -> WithBreakpoints<T> {
353 self.into_iter()
354 .map(WithBreakpoint::new)
355 .collect::<Vec<WithBreakpoint<T>>>()
356 .into()
357 }
358}
359
360impl<T> IntoPropValue<WithBreakpoints<T>> for &[T]
361where
362 T: PartialEq + Clone,
363{
364 fn into_prop_value(self) -> WithBreakpoints<T> {
365 self.iter()
366 .map(|i| WithBreakpoint::new(i.clone()))
367 .collect::<Vec<WithBreakpoint<T>>>()
368 .into()
369 }
370}
371
372impl<T> IntoPropValue<WithBreakpoints<T>> for Vec<T>
373where
374 T: PartialEq,
375{
376 fn into_prop_value(self) -> WithBreakpoints<T> {
377 self.into_iter()
378 .map(WithBreakpoint::new)
379 .collect::<Vec<WithBreakpoint<T>>>()
380 .into()
381 }
382}
383
384#[cfg(test)]
405mod test {
406 use crate::prelude::{AsClasses, WithBreakpoints};
407 use yew::prelude::*;
408
409 use super::*;
410
411 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
412 pub enum MockVariant {
413 Foo,
414 Bar,
415 Baz,
416 }
417
418 #[derive(PartialEq, Properties)]
419 struct MockComponentProperties {
420 pub variant: WithBreakpoints<MockVariant>,
421 }
422
423 #[function_component(MockComponent)]
424 fn component(_props: &MockComponentProperties) -> Html {
425 html!()
426 }
427
428 impl AsClasses for MockVariant {
429 fn extend_classes(&self, classes: &mut Classes) {
430 match self {
431 Self::Foo => {}
432 Self::Bar => classes.push("bar"),
433 Self::Baz => classes.push("foo bar"),
434 }
435 }
436 }
437
438 #[test]
439 fn test_empty_string() {
440 let prop: WithBreakpoints<String> = [].into();
441 assert_eq!(prop.as_classes(), Classes::from(""));
442 }
443
444 #[test]
445 fn test_single_string() {
446 let prop: WithBreakpoints<String> = "foo".to_string().into();
447 assert_eq!(prop.as_classes(), Classes::from("foo"));
448 }
449
450 #[test]
451 fn test_single_with_string() {
452 let prop: WithBreakpoints<String> = "foo".to_string().xxl().into();
453 assert_eq!(prop.as_classes(), Classes::from("foo-on-2xl"));
454 }
455
456 #[test]
457 fn test_some_string() {
458 let prop: WithBreakpoints<String> =
459 ["one".to_string().all(), "two".to_string().xxl()].into();
460 assert_eq!(prop.as_classes(), Classes::from("one two-on-2xl"));
461 }
462
463 #[test]
464 fn test_some_variant() {
465 let prop: WithBreakpoints<MockVariant> = [MockVariant::Foo.all()].into();
466 assert_eq!(prop.as_classes(), Classes::from(""));
467
468 let prop: WithBreakpoints<MockVariant> = [MockVariant::Bar.all()].into();
469 assert_eq!(prop.as_classes(), Classes::from("bar"));
470
471 let prop: WithBreakpoints<MockVariant> = [MockVariant::Baz.all()].into();
472 assert_eq!(prop.as_classes(), Classes::from("foo bar"));
473 }
474
475 #[test]
476 fn test_map() {
477 let prop: WithBreakpoints<bool> = [true.all(), true.lg()].into();
478 assert_eq!(
479 prop.mapped(|f| f.then(|| "static".to_string()))
480 .as_classes(),
481 Classes::from("static static-on-lg")
482 );
483 }
484
485 #[test]
486 fn compiles_assign_array() {
487 let _ = html!(<MockComponent variant={[MockVariant::Foo]} />);
488 let _ = html!(<MockComponent variant={[MockVariant::Foo, MockVariant::Bar]} />);
489 }
490}