AkurAI Build
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);
    }
}