From 0f09a5e22d619724643d2cd35423add419ef80c1 Mon Sep 17 00:00:00 2001 From: annieversary Date: Sun, 10 Jul 2022 19:31:09 +0100 Subject: [PATCH] media queries --- src/class.rs | 25 +++++++++++++++++-- src/lib.rs | 18 +++++++++++++- src/media_queries.rs | 57 ++++++++++++++++++++++++++++++++++++++++++++ src/modifiers.rs | 32 +++++++++++++++++++++++++ 4 files changed, 129 insertions(+), 3 deletions(-) create mode 100644 src/media_queries.rs create mode 100644 src/modifiers.rs diff --git a/src/class.rs b/src/class.rs index 99b0c4c..2c85483 100644 --- a/src/class.rs +++ b/src/class.rs @@ -1,4 +1,8 @@ -use crate::{Zephyr, ZephyrError}; +use crate::{ + media_queries::{ReducedMotion, Responsive}, + modifiers::Modifiers, + Zephyr, ZephyrError, +}; #[derive(PartialEq, Debug)] pub(crate) struct Class<'a> { @@ -6,7 +10,7 @@ pub(crate) struct Class<'a> { pub value: Option<&'a str>, /// if true, no replacements will be done on `value` pub value_literal: bool, - pub modifiers: Vec<&'a str>, + pub modifiers: Modifiers<'a>, pub pseudo: Option<&'a str>, /// the original unparsed value /// needed to generate the css selector @@ -23,7 +27,9 @@ impl<'a> Class<'a> { } = self; let mut rest = modifiers + .all .iter() + .filter(|m| Responsive::from_str(*m).is_none() && ReducedMotion::from_str(*m).is_none()) .map(|m| -> &str { z.modifiers.get(*m).map(AsRef::as_ref).unwrap_or(m) }) .collect::>() .join(":"); @@ -56,6 +62,8 @@ impl<'a> Class<'a> { r } + /// generates the css rule for this class + /// does not generate the corresponding media query pub(crate) fn generate(&self, z: &Zephyr) -> Result { let property = z .properties @@ -87,4 +95,17 @@ impl<'a> Class<'a> { Err(ZephyrError::ValueMissing) } } + + pub fn generate_with_media_query(&self, z: &Zephyr) -> Result { + let mut css = self.generate(z)?; + + if let Some(r) = &self.modifiers.responsive { + css = r.wrap(&css); + } + if let Some(r) = &self.modifiers.reduced_motion { + css = r.wrap(&css); + } + + Ok(css) + } } diff --git a/src/lib.rs b/src/lib.rs index a49d2ca..fac5fdf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,8 @@ use crate::{defaults::*, parse::*}; mod class; mod defaults; +mod media_queries; +mod modifiers; mod parse; #[cfg(feature = "inventory")] @@ -97,6 +99,8 @@ impl Zephyr { } }) // we ignore errors + // TODO change this to call parse_class directly + // TODO then group by media query .flat_map(|c| match self.generate_class(c) { Ok(v) => Some(v), Err(err) => { @@ -116,7 +120,7 @@ impl Zephyr { /// this one returns an error if parsing or generating fails pub fn generate_class(&self, class: &str) -> Result { let c = parse_class(class)?; - c.generate(self) + c.generate_with_media_query(self) } } @@ -235,4 +239,16 @@ mod tests { r#".border\{1px_solid_black\}{border:1px_solid_black;}.w\{full\}{width:full;}"# ); } + + #[test] + fn generate_with_media_query() { + let z = Zephyr::new(); + + // the curly brackets indicate that the value should not go through replacements + let classes = z.generate_classes(["m[1rem]sm"]); + assert_eq!( + classes, + r#"@media(min-width:640px){.m\[1rem\]sm{margin:1rem;}}"# + ); + } } diff --git a/src/media_queries.rs b/src/media_queries.rs new file mode 100644 index 0000000..8beb046 --- /dev/null +++ b/src/media_queries.rs @@ -0,0 +1,57 @@ +#[derive(PartialEq, Debug)] +pub(crate) enum Responsive { + Sm, + Md, + Lg, + Xl, + Xxl, +} + +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 from_str(s: &str) -> Option { + match s { + "sm" => Some(Responsive::Sm), + "md" => Some(Responsive::Md), + "lg" => Some(Responsive::Lg), + "xl" => Some(Responsive::Xl), + "xxl" => Some(Responsive::Xxl), + _ => None, + } + } +} + +#[derive(PartialEq, Debug)] +pub enum ReducedMotion { + MotionReduce, + MotionSafe, +} + +impl ReducedMotion { + pub fn wrap(&self, css: &str) -> String { + match self { + Self::MotionReduce => wrap_in_query(css, "prefers-reduced-motion:reduce"), + Self::MotionSafe => wrap_in_query(css, "prefers-reduced-motion:no-preference"), + } + } + pub fn from_str(s: &str) -> Option { + match s { + "motion-reduce" => Some(ReducedMotion::MotionReduce), + "motion-safe" => Some(ReducedMotion::MotionSafe), + _ => None, + } + } +} + +fn wrap_in_query(css: &str, query: &str) -> String { + format!("@media({query}){{{css}}}") +} diff --git a/src/modifiers.rs b/src/modifiers.rs new file mode 100644 index 0000000..d0df0c1 --- /dev/null +++ b/src/modifiers.rs @@ -0,0 +1,32 @@ +use crate::media_queries::{ReducedMotion, Responsive}; + +#[derive(PartialEq, Debug)] +pub(crate) struct Modifiers<'a> { + pub all: Vec<&'a str>, + pub responsive: Option, + pub reduced_motion: Option, +} + +impl<'a> Modifiers<'a> { + pub(crate) fn new(all: Vec<&'a str>) -> Self { + let mut responsive = None; + let mut reduced_motion = None; + + for m in &all { + responsive = Responsive::from_str(m); + reduced_motion = ReducedMotion::from_str(m); + } + + Self { + all, + responsive, + reduced_motion, + } + } +} + +impl<'a> From> for Modifiers<'a> { + fn from(v: Vec<&'a str>) -> Self { + Self::new(v) + } +}