1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
6pub enum BreakpointName {
7 Xs,
8 Sm,
9 Md,
10 Lg,
11 Xl,
12 Xxl,
13}
14
15impl BreakpointName {
16 pub fn as_str(self) -> &'static str {
17 match self {
18 Self::Xs => "xs",
19 Self::Sm => "sm",
20 Self::Md => "md",
21 Self::Lg => "lg",
22 Self::Xl => "xl",
23 Self::Xxl => "xxl",
24 }
25 }
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
30pub struct Breakpoint {
31 name: BreakpointName,
32 min_width: u32,
33}
34
35impl Breakpoint {
36 pub fn new(name: BreakpointName, min_width: u32) -> Self {
37 Self { name, min_width }
38 }
39
40 pub fn name(self) -> BreakpointName {
41 self.name
42 }
43
44 pub fn min_width(self) -> u32 {
45 self.min_width
46 }
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
51pub struct BreakpointRange {
52 min_width: Option<u32>,
53 max_width: Option<u32>,
54}
55
56impl BreakpointRange {
57 pub fn new(min_width: Option<u32>, max_width: Option<u32>) -> Self {
58 Self {
59 min_width,
60 max_width,
61 }
62 }
63
64 pub fn contains(self, width: u32) -> bool {
65 self.min_width.is_none_or(|minimum| width >= minimum)
66 && self.max_width.is_none_or(|maximum| width < maximum)
67 }
68}
69
70#[derive(Debug, Clone, Default, PartialEq, Eq)]
72pub struct BreakpointSet {
73 breakpoints: Vec<Breakpoint>,
74}
75
76impl BreakpointSet {
77 pub fn new(breakpoints: Vec<Breakpoint>) -> Self {
78 let mut set = Self { breakpoints };
79 set.breakpoints
80 .sort_by_key(|breakpoint| breakpoint.min_width);
81 set
82 }
83
84 pub fn defaults() -> Self {
85 Self::new(vec![
86 Breakpoint::new(BreakpointName::Xs, 0),
87 Breakpoint::new(BreakpointName::Sm, 480),
88 Breakpoint::new(BreakpointName::Md, 768),
89 Breakpoint::new(BreakpointName::Lg, 1024),
90 Breakpoint::new(BreakpointName::Xl, 1280),
91 Breakpoint::new(BreakpointName::Xxl, 1536),
92 ])
93 }
94
95 pub fn breakpoints(&self) -> &[Breakpoint] {
96 &self.breakpoints
97 }
98
99 pub fn matching(&self, width: u32) -> Option<Breakpoint> {
100 self.breakpoints
101 .iter()
102 .copied()
103 .filter(|breakpoint| width >= breakpoint.min_width)
104 .max_by_key(|breakpoint| breakpoint.min_width)
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::{Breakpoint, BreakpointName, BreakpointRange, BreakpointSet};
111
112 #[test]
113 fn matches_default_breakpoints() {
114 let set = BreakpointSet::defaults();
115
116 assert_eq!(
117 set.matching(320).map(Breakpoint::name),
118 Some(BreakpointName::Xs)
119 );
120 assert_eq!(
121 set.matching(800).map(Breakpoint::name),
122 Some(BreakpointName::Md)
123 );
124 assert_eq!(
125 set.matching(1600).map(Breakpoint::name),
126 Some(BreakpointName::Xxl)
127 );
128 }
129
130 #[test]
131 fn checks_breakpoint_ranges() {
132 let range = BreakpointRange::new(Some(480), Some(768));
133
134 assert!(range.contains(480));
135 assert!(range.contains(767));
136 assert!(!range.contains(768));
137 assert_eq!(BreakpointName::Lg.as_str(), "lg");
138 }
139}