media-query ranges, group queries on same class

This commit is contained in:
annieversary 2022-08-16 13:26:11 +01:00
parent 871fce954d
commit 4cdb7919b2
5 changed files with 179 additions and 26 deletions

View File

@ -1,7 +1,7 @@
use std::borrow::Cow;
use crate::{
media_queries::{ReducedMotion, Responsive},
media_queries::{wrap_in_query, ReducedMotion, Responsive},
modifiers::Modifiers,
Zephyr, ZephyrError,
};
@ -74,6 +74,8 @@ impl<'a> Class<'a> {
.replace('$', "\\$")
.replace('\'', "\\'")
.replace('*', "\\*")
.replace('<', "\\<")
.replace('@', "\\@")
.replace('%', "\\%");
r.insert(0, '.');
r
@ -119,16 +121,19 @@ impl<'a> Class<'a> {
}
pub fn generate_with_media_query(&self, z: &Zephyr) -> Result<String, ZephyrError> {
let mut css = self.generate(z)?;
let css = self.generate(z)?;
dbg!(&self.modifiers);
let mut queries: Vec<String> = vec![];
if let Some(r) = &self.modifiers.responsive {
css = r.wrap(&css);
queries.extend(r.queries());
}
if let Some(r) = &self.modifiers.reduced_motion {
css = r.wrap(&css);
queries.extend(r.queries().iter().map(ToString::to_string));
}
Ok(css)
Ok(wrap_in_query(css, &queries))
}
}

View File

@ -3,7 +3,7 @@ use std::collections::HashMap;
use crate::SpecialDeclaration;
fn vec_to_hashmap(v: &[(&str, &str)]) -> HashMap<String, String> {
v.into_iter()
v.iter()
.map(|(a, b)| (a.to_string(), b.to_string()))
.collect::<HashMap<_, _>>()
}

View File

@ -1,5 +1,11 @@
#[derive(PartialEq, Debug)]
pub(crate) enum Responsive {
pub(crate) struct Responsive {
breakpoint: Breakpoint,
range: Range,
}
#[derive(PartialEq, Debug)]
pub(crate) enum Breakpoint {
Sm,
Md,
Lg,
@ -7,27 +13,87 @@ pub(crate) enum Responsive {
Xxl,
}
#[derive(PartialEq, Debug, Default)]
pub(crate) enum Range {
#[default]
Gte,
Lt,
Exact,
}
impl Responsive {
pub fn wrap(&self, css: &str) -> String {
match self {
Responsive::Sm => wrap_in_query(css, "min-width:640px"),
Responsive::Md => wrap_in_query(css, "min-width:768px"),
Responsive::Lg => wrap_in_query(css, "min-width:1024px"),
Responsive::Xl => wrap_in_query(css, "min-width:1280px"),
Responsive::Xxl => wrap_in_query(css, "min-width:1536px"),
pub fn queries(&self) -> Vec<String> {
match self.range {
Range::Gte => vec![format!("min-width:{}px", self.breakpoint.width())],
Range::Lt => vec![format!("max-width:{}.9px", self.breakpoint.width() - 1)],
Range::Exact => {
if let Some(n) = self.breakpoint.next() {
vec![
format!("min-width:{}px", self.breakpoint.width()),
format!("max-width:{}.9px", n.width() - 1),
]
} else {
vec![format!("min-width:{}px", self.breakpoint.width())]
}
}
}
}
pub fn from_str(s: &str) -> Option<Self> {
if s.is_empty() {
return None;
}
if let Some(p) = s.strip_prefix('<') {
Some(Responsive {
breakpoint: Breakpoint::from_str(p)?,
range: Range::Lt,
})
} else if let Some(p) = s.strip_prefix('@') {
Some(Responsive {
breakpoint: Breakpoint::from_str(p)?,
range: Range::Exact,
})
} else {
Some(Responsive {
breakpoint: Breakpoint::from_str(s)?,
range: Range::Gte,
})
}
}
}
impl Breakpoint {
pub fn from_str(s: &str) -> Option<Self> {
match s {
"sm" => Some(Responsive::Sm),
"md" => Some(Responsive::Md),
"lg" => Some(Responsive::Lg),
"xl" => Some(Responsive::Xl),
"xxl" => Some(Responsive::Xxl),
"sm" => Some(Self::Sm),
"md" => Some(Self::Md),
"lg" => Some(Self::Lg),
"xl" => Some(Self::Xl),
"xxl" => Some(Self::Xxl),
_ => None,
}
}
fn width(&self) -> u16 {
match self {
Breakpoint::Sm => 640,
Breakpoint::Md => 768,
Breakpoint::Lg => 1024,
Breakpoint::Xl => 1280,
Breakpoint::Xxl => 1536,
}
}
const fn next(&self) -> Option<Self> {
match self {
Breakpoint::Sm => Some(Breakpoint::Md),
Breakpoint::Md => Some(Breakpoint::Lg),
Breakpoint::Lg => Some(Breakpoint::Xl),
Breakpoint::Xl => Some(Breakpoint::Xxl),
Breakpoint::Xxl => None,
}
}
}
#[derive(PartialEq, Debug)]
@ -37,10 +103,10 @@ pub enum ReducedMotion {
}
impl ReducedMotion {
pub fn wrap(&self, css: &str) -> String {
pub fn queries(&self) -> &[&str] {
match self {
Self::MotionReduce => wrap_in_query(css, "prefers-reduced-motion:reduce"),
Self::MotionSafe => wrap_in_query(css, "prefers-reduced-motion:no-preference"),
Self::MotionReduce => &["prefers-reduced-motion:reduce"],
Self::MotionSafe => &["prefers-reduced-motion:no-preference"],
}
}
pub fn from_str(s: &str) -> Option<Self> {
@ -52,6 +118,70 @@ impl ReducedMotion {
}
}
fn wrap_in_query(css: &str, query: &str) -> String {
format!("@media({query}){{{css}}}")
pub(crate) fn wrap_in_query(css: String, queries: &[String]) -> String {
if queries.is_empty() {
return css;
}
let query = queries
.iter()
.map(|s| format!("({s})"))
.collect::<Vec<String>>()
.join("and");
format!("@media{query}{{{css}}}")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_responsive() {
let r = Responsive::from_str("<lg");
assert_eq!(
r,
Some(Responsive {
breakpoint: Breakpoint::Lg,
range: Range::Lt
})
);
let r = Responsive::from_str("@xl");
assert_eq!(
r,
Some(Responsive {
breakpoint: Breakpoint::Xl,
range: Range::Exact
})
);
let r = Responsive::from_str("@sm");
assert_eq!(
r,
Some(Responsive {
breakpoint: Breakpoint::Sm,
range: Range::Exact
})
);
let r = Responsive::from_str("xxl");
assert_eq!(
r,
Some(Responsive {
breakpoint: Breakpoint::Xxl,
range: Range::Gte
})
);
}
#[test]
fn generate_queries() {
let r = Responsive::from_str("<lg").unwrap().queries();
assert_eq!(r, &["max-width:1023.9px"]);
let r = Responsive::from_str("@xl").unwrap().queries();
assert_eq!(r, &["min-width:1280px", "max-width:1535.9px"]);
let r = ReducedMotion::MotionReduce.queries();
assert_eq!(r, &["prefers-reduced-motion:reduce"]);
}
}

View File

@ -13,8 +13,8 @@ impl<'a> Modifiers<'a> {
let mut reduced_motion = None;
for m in &all {
responsive = Responsive::from_str(m);
reduced_motion = ReducedMotion::from_str(m);
responsive = responsive.or_else(|| Responsive::from_str(m));
reduced_motion = reduced_motion.or_else(|| ReducedMotion::from_str(m));
}
Self {

View File

@ -121,6 +121,24 @@ fn generate_with_media_query() {
classes,
r#"@media(min-width:640px){.m\[1rem\]sm{margin:1rem}}"#
);
let classes = z.generate_classes(["m[1rem]<md"]);
assert_eq!(
classes,
r#"@media(max-width:767.9px){.m\[1rem\]\<md{margin:1rem}}"#
);
let classes = z.generate_classes(["m[1rem]motion-reduce"]);
assert_eq!(
classes,
r#"@media(prefers-reduced-motion:reduce){.m\[1rem\]motion-reduce{margin:1rem}}"#
);
let classes = z.generate_classes(["m[1rem]@xl,motion-reduce"]);
assert_eq!(
classes,
r#"@media(min-width:1280px)and(max-width:1535.9px)and(prefers-reduced-motion:reduce){.m\[1rem\]\@xl,motion-reduce{margin:1rem}}"#
);
}
#[test]