#![forbid(unsafe_code)]
#![doc = include_str!("../README.md")]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub enum BreakpointName {
Xs,
Sm,
Md,
Lg,
Xl,
Xxl,
}
impl BreakpointName {
pub fn as_str(self) -> &'static str {
match self {
Self::Xs => "xs",
Self::Sm => "sm",
Self::Md => "md",
Self::Lg => "lg",
Self::Xl => "xl",
Self::Xxl => "xxl",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub struct Breakpoint {
name: BreakpointName,
min_width: u32,
}
impl Breakpoint {
pub fn new(name: BreakpointName, min_width: u32) -> Self {
Self { name, min_width }
}
pub fn name(self) -> BreakpointName {
self.name
}
pub fn min_width(self) -> u32 {
self.min_width
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct BreakpointRange {
min_width: Option<u32>,
max_width: Option<u32>,
}
impl BreakpointRange {
pub fn new(min_width: Option<u32>, max_width: Option<u32>) -> Self {
Self {
min_width,
max_width,
}
}
pub fn contains(self, width: u32) -> bool {
self.min_width.is_none_or(|minimum| width >= minimum)
&& self.max_width.is_none_or(|maximum| width < maximum)
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct BreakpointSet {
breakpoints: Vec<Breakpoint>,
}
impl BreakpointSet {
pub fn new(breakpoints: Vec<Breakpoint>) -> Self {
let mut set = Self { breakpoints };
set.breakpoints
.sort_by_key(|breakpoint| breakpoint.min_width);
set
}
pub fn defaults() -> Self {
Self::new(vec![
Breakpoint::new(BreakpointName::Xs, 0),
Breakpoint::new(BreakpointName::Sm, 480),
Breakpoint::new(BreakpointName::Md, 768),
Breakpoint::new(BreakpointName::Lg, 1024),
Breakpoint::new(BreakpointName::Xl, 1280),
Breakpoint::new(BreakpointName::Xxl, 1536),
])
}
pub fn breakpoints(&self) -> &[Breakpoint] {
&self.breakpoints
}
pub fn matching(&self, width: u32) -> Option<Breakpoint> {
self.breakpoints
.iter()
.copied()
.filter(|breakpoint| width >= breakpoint.min_width)
.max_by_key(|breakpoint| breakpoint.min_width)
}
}
#[cfg(test)]
mod tests {
use super::{Breakpoint, BreakpointName, BreakpointRange, BreakpointSet};
#[test]
fn matches_default_breakpoints() {
let set = BreakpointSet::defaults();
assert_eq!(
set.matching(320).map(Breakpoint::name),
Some(BreakpointName::Xs)
);
assert_eq!(
set.matching(800).map(Breakpoint::name),
Some(BreakpointName::Md)
);
assert_eq!(
set.matching(1600).map(Breakpoint::name),
Some(BreakpointName::Xxl)
);
}
#[test]
fn checks_breakpoint_ranges() {
let range = BreakpointRange::new(Some(480), Some(768));
assert!(range.contains(480));
assert!(range.contains(767));
assert!(!range.contains(768));
assert_eq!(BreakpointName::Lg.as_str(), "lg");
}
}