AkurAI Build
Menu

Public repository / master

AkurAI-Framework / crates/css/src/utilities/spacing.rs

Latest change 3aae9f4e85241bcb775c31f5c03094becd3da56a - Pure-Rust UI layer: akurai-ui, akurai-crypto, Tailwind-grade akurai-css by Ólafur Búi Ólafsson

//! Spacing on the 4px scale: padding, margin, gap and `space-*`.
//!
//! `p-2` ⇒ `padding:8px`; steps `0.5`/`1.5`/`2.5`/`3.5`, `px` (1px) and
//! `[arbitrary]` values are accepted, margins also take `auto` and a leading
//! `-` (`-mt-2` ⇒ `margin-top:-8px`).

use super::value::{negate, spacing};
use super::{prefixed, Utility, BETWEEN_CHILDREN};

/// Padding rows, longest prefix first: `(prefix, properties, tier)`.
const PADDING: &[(&str, &[&str], u8)] = &[
    ("px", &["padding-left", "padding-right"], 1),
    ("py", &["padding-top", "padding-bottom"], 1),
    ("pt", &["padding-top"], 2),
    ("pr", &["padding-right"], 2),
    ("pb", &["padding-bottom"], 2),
    ("pl", &["padding-left"], 2),
    ("p", &["padding"], 0),
];

const MARGIN: &[(&str, &[&str], u8)] = &[
    ("mx", &["margin-left", "margin-right"], 1),
    ("my", &["margin-top", "margin-bottom"], 1),
    ("mt", &["margin-top"], 2),
    ("mr", &["margin-right"], 2),
    ("mb", &["margin-bottom"], 2),
    ("ml", &["margin-left"], 2),
    ("m", &["margin"], 0),
];

const GAP: &[(&str, &[&str], u8)] = &[
    ("gap-x", &["column-gap"], 1),
    ("gap-y", &["row-gap"], 1),
    ("gap", &["gap"], 0),
];

const SPACE: &[(&str, &[&str], u8)] = &[
    ("space-x", &["margin-left"], 0),
    ("space-y", &["margin-top"], 0),
];

pub fn resolve(u: &str) -> Option<Utility> {
    prefixed(u, PADDING, spacing)
        .or_else(|| prefixed(u, MARGIN, margin_value))
        .or_else(|| prefixed(u, GAP, spacing))
        .or_else(|| prefixed(u, SPACE, spacing).map(|s| s.child(BETWEEN_CHILDREN)))
}

/// `-m*` and `-space-*`.
pub fn negative(u: &str) -> Option<Utility> {
    let neg = |v: &str| spacing(v).and_then(|s| negate(&s));
    prefixed(u, MARGIN, neg).or_else(|| prefixed(u, SPACE, neg).map(|s| s.child(BETWEEN_CHILDREN)))
}

fn margin_value(v: &str) -> Option<String> {
    if v == "auto" {
        return Some("auto".into());
    }
    spacing(v)
}

#[cfg(test)]
mod tests {
    use super::*;

    fn decl(u: &str) -> Option<String> {
        resolve(u).map(|x| x.declaration())
    }

    #[test]
    fn padding_and_margin() {
        assert_eq!(decl("pr-1").as_deref(), Some("padding-right:4px"));
        assert_eq!(
            decl("my-auto").as_deref(),
            Some("margin-top:auto;margin-bottom:auto")
        );
        assert_eq!(decl("p-auto"), None);
    }

    #[test]
    fn negatives() {
        let n = |u: &str| negative(u).map(|x| x.declaration());
        assert_eq!(n("mt-2").as_deref(), Some("margin-top:-8px"));
        assert_eq!(n("m-0").as_deref(), Some("margin:0px"));
        assert_eq!(n("m-auto"), None);
        assert_eq!(n("p-2"), None);
        assert_eq!(n("m-[3px]").as_deref(), Some("margin:-3px"));
    }

    #[test]
    fn space_has_child_selector() {
        let s = resolve("space-y-2").unwrap();
        assert_eq!(s.child, Some(BETWEEN_CHILDREN));
        assert_eq!(s.declaration(), "margin-top:8px");
    }
}