Menu
Public repository / master
AkurAI-Framework / crates/css/src/utilities/sizing.rs
Latest change 3aae9f4e85241bcb775c31f5c03094becd3da56a - Pure-Rust UI layer: akurai-ui, akurai-crypto, Tailwind-grade akurai-css by Ólafur Búi Ólafsson
//! Sizing: `w`, `h`, `size`, `min-w`, `min-h`, `max-w`, `max-h`.
//!
//! Values: the spacing scale, fractions (`1/2` ⇒ `50%`), `full`, `auto`,
//! `screen` (`100vw`/`100vh` by axis), `svh`/`lvh`/`dvh` (and `*vw`), `min`,
//! `max`, `fit` (`*-content`), `none` for the max-* pair, the named `max-w`
//! widths (`xs`…`7xl`, `prose`, `screen-sm`…`screen-2xl`), and `[arbitrary]`.
use super::value::length_or_fraction;
use super::Utility;
#[derive(Clone, Copy, PartialEq, Eq)]
enum Axis {
Width,
Height,
Both,
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Bound {
Exact,
Min,
Max,
}
/// `(prefix, axis, bound)`, longest prefix first.
const PREFIXES: &[(&str, Axis, Bound)] = &[
("min-w", Axis::Width, Bound::Min),
("min-h", Axis::Height, Bound::Min),
("max-w", Axis::Width, Bound::Max),
("max-h", Axis::Height, Bound::Max),
("size", Axis::Both, Bound::Exact),
("w", Axis::Width, Bound::Exact),
("h", Axis::Height, Bound::Exact),
];
/// Named `max-w-*` widths (Tailwind's container scale).
const MAX_WIDTHS: &[(&str, &str)] = &[
("xs", "20rem"),
("sm", "24rem"),
("md", "28rem"),
("lg", "32rem"),
("xl", "36rem"),
("2xl", "42rem"),
("3xl", "48rem"),
("4xl", "56rem"),
("5xl", "64rem"),
("6xl", "72rem"),
("7xl", "80rem"),
("prose", "65ch"),
("screen-sm", "640px"),
("screen-md", "768px"),
("screen-lg", "1024px"),
("screen-xl", "1280px"),
("screen-2xl", "1536px"),
];
pub fn resolve(u: &str) -> Option<Utility> {
PREFIXES.iter().find_map(|&(prefix, axis, bound)| {
let v = u.strip_prefix(prefix)?.strip_prefix('-')?;
let value = size_value(v, axis, bound)?;
let props = properties(axis, bound);
// `size-*` sets both axes, so `w-*`/`h-*` must be able to override it.
let tier = u8::from(axis != Axis::Both && bound == Bound::Exact);
Some(Utility::props(props, &value).tier(tier))
})
}
fn properties(axis: Axis, bound: Bound) -> &'static [&'static str] {
match (axis, bound) {
(Axis::Both, _) => &["width", "height"],
(Axis::Width, Bound::Exact) => &["width"],
(Axis::Width, Bound::Min) => &["min-width"],
(Axis::Width, Bound::Max) => &["max-width"],
(Axis::Height, Bound::Exact) => &["height"],
(Axis::Height, Bound::Min) => &["min-height"],
(Axis::Height, Bound::Max) => &["max-height"],
}
}
fn size_value(v: &str, axis: Axis, bound: Bound) -> Option<String> {
let viewport = if axis == Axis::Width { "vw" } else { "vh" };
let keyword = match v {
"auto" if bound == Bound::Exact => "auto".to_string(),
"none" if bound == Bound::Max => "none".to_string(),
"min" => "min-content".to_string(),
"max" => "max-content".to_string(),
"fit" => "fit-content".to_string(),
"screen" if axis != Axis::Both => format!("100{viewport}"),
"svh" | "lvh" | "dvh" if axis == Axis::Height => format!("100{v}"),
"svw" | "lvw" | "dvw" if axis == Axis::Width => format!("100{v}"),
_ => {
if axis == Axis::Width && bound == Bound::Max {
if let Some((_, w)) = MAX_WIDTHS.iter().find(|(k, _)| *k == v) {
return Some((*w).to_string());
}
}
return length_or_fraction(v);
}
};
Some(keyword)
}
#[cfg(test)]
mod tests {
use super::*;
fn decl(u: &str) -> Option<String> {
resolve(u).map(|x| x.declaration())
}
#[test]
fn keywords_respect_axis_and_bound() {
assert_eq!(decl("min-w-auto"), None);
assert_eq!(decl("w-none"), None);
assert_eq!(decl("max-h-none").as_deref(), Some("max-height:none"));
assert_eq!(decl("w-dvh"), None);
assert_eq!(decl("w-dvw").as_deref(), Some("width:100dvw"));
assert_eq!(decl("max-h-md"), None);
assert_eq!(decl("size-full").as_deref(), Some("width:100%;height:100%"));
assert_eq!(decl("size-screen"), None);
}
#[test]
fn exact_axis_outranks_size() {
assert_eq!(resolve("size-4").unwrap().tier, 0);
assert_eq!(resolve("w-4").unwrap().tier, 1);
assert_eq!(resolve("max-w-4").unwrap().tier, 0);
}
}