patternfly_yew/core/
breakpoint.rs

1/*
2 * Grow.on(Lg)
3 */
4
5use 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/// Breakpoint definitions
13#[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/// A combination of a style/variant for a specific [`Breakpoint`].
53#[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/// A set of variants for breakpoints.
88///
89/// This is typically used by components which support different variants for different breakpoints,
90/// including a default one.
91#[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        // get as classes, but then extend but the breakpoint rules
122        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
202/// Helps creating [`WithBreakpoint`] instances.
203///
204/// ## Example
205///
206/// The following example populates the `cols` attribute of a `GridItem`. I requires a number of
207/// columns to span, which can be specified for the different breakpoints.
208///
209/// ```rust
210/// use yew::prelude::*;
211/// use patternfly_yew::prelude::*;
212///
213/// #[function_component(Example)]
214/// fn example() -> Html {
215///   html!(
216///     <Grid>
217///         <GridItem cols={[4.all(), 8.lg()]}>{"cell"}</GridItem>
218///     </Grid>
219///   )
220/// }
221/// ```
222pub 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/*
385impl<T, const N: usize> Transformer<[T; N], WithBreakpoints<T>> for yew::virtual_dom::VComp
386where
387    T: Clone + Debug + PartialEq + ToString,
388{
389    fn transform(from: [T; N]) -> WithBreakpoints<T> {
390        WithBreakpoints(from.iter().map(|i| i.clone().into()).collect::<Vec<_>>())
391    }
392}
393
394impl<T> Transformer<&[T], WithBreakpoints<T>> for yew::virtual_dom::VComp
395where
396    T: Clone + Debug + PartialEq + ToString,
397{
398    fn transform(from: &[T]) -> WithBreakpoints<T> {
399        WithBreakpoints(from.iter().map(|i| i.clone().into()).collect::<Vec<_>>())
400    }
401}
402*/
403
404#[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}