AkurAI Build
Menu

Public repository / master

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

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

//! Borders: widths, styles, `divide-*` and radius.
//!
//! There is no preflight reset, so width utilities also set the side's
//! `border-style` from `var(--tw-border-style,solid)`; `border-dashed` and
//! friends only set that variable, so `border-t border-dashed` dashes the top
//! edge without turning on the other three.
//!
//! **Flat corners (house rule):** radius is at most 6px and there are no pills.
//! `rounded-sm` = 2px, `rounded` = 4px, `rounded-md` = 6px, and `rounded-lg`,
//! `-xl`, `-2xl`, `-3xl` and `-full` all clamp to 6px. Arbitrary radii clamp
//! too: `rounded-[20px]` ⇒ 6px, `rounded-[50%]` ⇒ `min(50%,6px)`.

use super::value::{arbitrary_hinted, integer, looks_like_length, trim_float};
use super::{register, Utility, BETWEEN_CHILDREN};

/// Largest radius any utility may emit, in px.
pub const MAX_RADIUS_PX: f64 = 6.0;

const BORDER_STYLE_PROPS: &[&str] = &[register!("--tw-border-style")];

/// `(suffix, sides, tier)` for the per-side border utilities.
const SIDES: &[(&str, &[&str], u8)] = &[
    ("x", &["left", "right"], 1),
    ("y", &["top", "bottom"], 1),
    ("t", &["top"], 2),
    ("r", &["right"], 2),
    ("b", &["bottom"], 2),
    ("l", &["left"], 2),
];

const RADII: &[(&str, &str)] = &[
    ("none", "0px"),
    ("sm", "2px"),
    ("md", "6px"),
    ("lg", "6px"),
    ("xl", "6px"),
    ("2xl", "6px"),
    ("3xl", "6px"),
    ("full", "6px"),
];

/// Radius when no size is given (`rounded`, `rounded-t`).
const DEFAULT_RADIUS: &str = "4px";

/// `(suffix, corners, tier)` for per-side/corner radius utilities.
const CORNERS: &[(&str, &[&str], u8)] = &[
    ("t", &["top-left", "top-right"], 1),
    ("r", &["top-right", "bottom-right"], 1),
    ("b", &["bottom-right", "bottom-left"], 1),
    ("l", &["top-left", "bottom-left"], 1),
    ("tl", &["top-left"], 2),
    ("tr", &["top-right"], 2),
    ("br", &["bottom-right"], 2),
    ("bl", &["bottom-left"], 2),
];

pub fn resolve(u: &str) -> Option<Utility> {
    border_style(u)
        .or_else(|| border_width(u))
        .or_else(|| divide(u))
        .or_else(|| rounded(u))
}

fn border_style(u: &str) -> Option<Utility> {
    match u.strip_prefix("border-")? {
        s @ ("solid" | "dashed" | "dotted" | "double") => {
            Some(Utility::decl("--tw-border-style", s).extras(BORDER_STYLE_PROPS))
        }
        s @ ("none" | "hidden") => Some(
            Utility::body(&format!("--tw-border-style:{s};border-style:{s}"))
                .extras(BORDER_STYLE_PROPS),
        ),
        _ => None,
    }
}

/// `border`, `border-2`, `border-[3px]`, `border-t`, `border-x-4`.
fn border_width(u: &str) -> Option<Utility> {
    let rest = u.strip_prefix("border")?;
    if let Some(width) = width_suffix(rest) {
        return Some(width_decls(None, &width).tier(0));
    }
    let rest = rest.strip_prefix('-')?;
    SIDES.iter().find_map(|&(side, edges, tier)| {
        let width = width_suffix(rest.strip_prefix(side)?)?;
        let mut decls = Vec::new();
        for edge in edges {
            decls.extend(width_decls(Some(edge), &width).decls);
        }
        Some(Utility::from_decls(decls).tier(tier))
    })
}

/// `""` ⇒ 1px, `-N` ⇒ Npx, `-[len]` ⇒ len.
fn width_suffix(rest: &str) -> Option<String> {
    if rest.is_empty() {
        return Some("1px".into());
    }
    let v = rest.strip_prefix('-')?;
    if let Some(n) = integer(v) {
        return Some(format!("{n}px"));
    }
    let (hint, value) = arbitrary_hinted(v)?;
    let is_width = match hint.as_deref() {
        Some("length") | Some("width") => true,
        None => looks_like_length(&value),
        Some(_) => false,
    };
    is_width.then_some(value)
}

/// Width plus the edge's style; a zero width needs no style.
fn width_decls(edge: Option<&str>, width: &str) -> Utility {
    let prop = |what: &str| match edge {
        Some(e) => format!("border-{e}-{what}"),
        None => format!("border-{what}"),
    };
    let mut decls = Vec::new();
    if width != "0px" {
        decls.push(format!("{}:var(--tw-border-style,solid)", prop("style")));
    }
    decls.push(format!("{}:{width}", prop("width")));
    Utility::from_decls(decls)
}

/// `divide-x`, `divide-y-2`: borders between children.
fn divide(u: &str) -> Option<Utility> {
    let rest = u.strip_prefix("divide-")?;
    let (edge, rest) = match rest.split_at_checked(1)? {
        ("x", rest) => ("left", rest),
        ("y", rest) => ("top", rest),
        _ => return None,
    };
    let width = width_suffix(rest)?;
    Some(
        Utility::body(&format!(
            "border-{edge}-style:solid;border-{edge}-width:{width}"
        ))
        .child(BETWEEN_CHILDREN),
    )
}

/// `rounded`, `rounded-md`, `rounded-t`, `rounded-tl-sm`, `rounded-[3px]`.
fn rounded(u: &str) -> Option<Utility> {
    let rest = u.strip_prefix("rounded")?;
    if rest.is_empty() {
        return Some(Utility::decl("border-radius", DEFAULT_RADIUS));
    }
    let rest = rest.strip_prefix('-')?;
    if let Some(r) = radius(rest) {
        return Some(Utility::decl("border-radius", &r));
    }
    CORNERS.iter().find_map(|&(side, corners, tier)| {
        let r = match rest.strip_prefix(side)? {
            "" => DEFAULT_RADIUS.to_string(),
            size => radius(size.strip_prefix('-')?)?,
        };
        let props: Vec<String> = corners
            .iter()
            .map(|c| format!("border-{c}-radius:{r}"))
            .collect();
        Some(Utility::from_decls(props).tier(tier))
    })
}

/// A named or arbitrary radius, clamped to [`MAX_RADIUS_PX`].
fn radius(v: &str) -> Option<String> {
    if let Some((_, r)) = RADII.iter().find(|(k, _)| *k == v) {
        return Some((*r).to_string());
    }
    let (hint, value) = arbitrary_hinted(v)?;
    if hint.is_some() {
        return None;
    }
    Some(clamp_radius(&value))
}

/// Clamp an arbitrary radius to 6px: px/rem/em are compared numerically
/// (1rem = 16px); anything else is wrapped in `min(…,6px)`.
fn clamp_radius(value: &str) -> String {
    let px = [("px", 1.0), ("rem", 16.0), ("em", 16.0)]
        .iter()
        .find_map(|(unit, scale)| {
            let n: f64 = value.strip_suffix(unit)?.parse().ok()?;
            Some(n * scale)
        })
        .or_else(|| (value == "0").then_some(0.0));
    match px {
        Some(px) if px > MAX_RADIUS_PX => format!("{}px", trim_float(MAX_RADIUS_PX)),
        Some(_) => value.to_string(),
        None => format!("min({value},{}px)", trim_float(MAX_RADIUS_PX)),
    }
}

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

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

    #[test]
    fn clamp_rule() {
        assert_eq!(clamp_radius("6px"), "6px");
        assert_eq!(clamp_radius("6.5px"), "6px");
        assert_eq!(clamp_radius("0.25rem"), "0.25rem");
        assert_eq!(clamp_radius("1rem"), "6px");
        assert_eq!(clamp_radius("var(--r)"), "min(var(--r),6px)");
        for named in RADII {
            let px: f64 = named.1.trim_end_matches("px").parse().unwrap();
            assert!(
                px <= MAX_RADIUS_PX,
                "{} exceeds the flat-corner rule",
                named.0
            );
        }
    }

    #[test]
    fn corners_default_and_sized() {
        assert_eq!(
            decl("rounded-l").as_deref(),
            Some("border-top-left-radius:4px;border-bottom-left-radius:4px")
        );
        assert_eq!(
            decl("rounded-tl-[1px]").as_deref(),
            Some("border-top-left-radius:1px")
        );
        assert_eq!(decl("rounded-q"), None);
        assert_eq!(decl("rounded-t-q"), None);
    }

    #[test]
    fn widths_and_sides() {
        assert_eq!(
            decl("border-b-0").as_deref(),
            Some("border-bottom-width:0px")
        );
        assert_eq!(decl("border-accent"), None);
        assert_eq!(decl("border-t-accent"), None);
        assert_eq!(decl("border-[#fff]"), None);
        assert_eq!(
            decl("border-[length:var(--w)]").as_deref(),
            Some("border-style:var(--tw-border-style,solid);border-width:var(--w)")
        );
        assert_eq!(decl("divide-z"), None);
    }
}