hypen-tailwind-parse 0.4.46

Minimal Tailwind CSS class parser for Hypen
Documentation
//! Border utilities: border width, radius, style

use crate::parser::CssProperty;

pub fn parse(utility: &str) -> Option<Vec<CssProperty>> {
    // Border radius
    if let Some(val) = utility.strip_prefix("rounded-") {
        let value = match val {
            "none" => "0px",
            "sm" => "0.125rem",
            "md" => "0.375rem",
            "lg" => "0.5rem",
            "xl" => "0.75rem",
            "2xl" => "1rem",
            "3xl" => "1.5rem",
            "full" => "9999px",
            // Directional
            "t-none" => {
                return Some(vec![
                    CssProperty::new("border-top-left-radius", "0px"),
                    CssProperty::new("border-top-right-radius", "0px"),
                ])
            }
            "t-sm" => {
                return Some(vec![
                    CssProperty::new("border-top-left-radius", "0.125rem"),
                    CssProperty::new("border-top-right-radius", "0.125rem"),
                ])
            }
            "t-md" => {
                return Some(vec![
                    CssProperty::new("border-top-left-radius", "0.375rem"),
                    CssProperty::new("border-top-right-radius", "0.375rem"),
                ])
            }
            "t-lg" => {
                return Some(vec![
                    CssProperty::new("border-top-left-radius", "0.5rem"),
                    CssProperty::new("border-top-right-radius", "0.5rem"),
                ])
            }
            "b-none" => {
                return Some(vec![
                    CssProperty::new("border-bottom-left-radius", "0px"),
                    CssProperty::new("border-bottom-right-radius", "0px"),
                ])
            }
            "b-lg" => {
                return Some(vec![
                    CssProperty::new("border-bottom-left-radius", "0.5rem"),
                    CssProperty::new("border-bottom-right-radius", "0.5rem"),
                ])
            }
            "l-lg" => {
                return Some(vec![
                    CssProperty::new("border-top-left-radius", "0.5rem"),
                    CssProperty::new("border-bottom-left-radius", "0.5rem"),
                ])
            }
            "r-lg" => {
                return Some(vec![
                    CssProperty::new("border-top-right-radius", "0.5rem"),
                    CssProperty::new("border-bottom-right-radius", "0.5rem"),
                ])
            }
            _ => return None,
        };
        return Some(vec![CssProperty::new("border-radius", value)]);
    }

    // Plain rounded (no suffix)
    if utility == "rounded" {
        return Some(vec![CssProperty::new("border-radius", "0.25rem")]);
    }

    // Border width
    if utility == "border" {
        return Some(vec![CssProperty::new("border-width", "1px")]);
    }
    if let Some(val) = utility.strip_prefix("border-") {
        // Check if it's a width value (number)
        let value = match val {
            "0" => "0px",
            "2" => "2px",
            "4" => "4px",
            "8" => "8px",
            // Directional borders: native renderers don't support per-side border width,
            // so we map to full border-width as a best-effort fallback.
            "t" | "r" | "b" | "l" => return Some(vec![CssProperty::new("border-width", "1px")]),
            "t-0" | "r-0" | "b-0" | "l-0" => {
                return Some(vec![CssProperty::new("border-width", "0px")])
            }
            "t-2" | "r-2" | "b-2" | "l-2" => {
                return Some(vec![CssProperty::new("border-width", "2px")])
            }
            // Border style
            "solid" => return Some(vec![CssProperty::new("border-style", "solid")]),
            "dashed" => return Some(vec![CssProperty::new("border-style", "dashed")]),
            "dotted" => return Some(vec![CssProperty::new("border-style", "dotted")]),
            "double" => return Some(vec![CssProperty::new("border-style", "double")]),
            "hidden" => return Some(vec![CssProperty::new("border-style", "hidden")]),
            "none" => return Some(vec![CssProperty::new("border-style", "none")]),
            _ => return None,
        };
        return Some(vec![CssProperty::new("border-width", value)]);
    }

    // Outline
    if utility == "outline" {
        return Some(vec![CssProperty::new("outline-style", "solid")]);
    }
    if utility == "outline-none" {
        return Some(vec![
            CssProperty::new("outline", "2px solid transparent"),
            CssProperty::new("outline-offset", "2px"),
        ]);
    }

    // Ring (focus ring)
    if utility == "ring" {
        return Some(vec![CssProperty::new(
            "box-shadow",
            "0 0 0 3px rgba(59, 130, 246, 0.5)",
        )]);
    }
    if let Some(val) = utility.strip_prefix("ring-") {
        let value = match val {
            "0" => "0 0 0 0px",
            "1" => "0 0 0 1px",
            "2" => "0 0 0 2px",
            "4" => "0 0 0 4px",
            "8" => "0 0 0 8px",
            "inset" => return Some(vec![CssProperty::new("--tw-ring-inset", "inset")]),
            // Ring offset
            "offset-0" => return Some(vec![CssProperty::new("--tw-ring-offset-width", "0px")]),
            "offset-1" => return Some(vec![CssProperty::new("--tw-ring-offset-width", "1px")]),
            "offset-2" => return Some(vec![CssProperty::new("--tw-ring-offset-width", "2px")]),
            "offset-4" => return Some(vec![CssProperty::new("--tw-ring-offset-width", "4px")]),
            "offset-8" => return Some(vec![CssProperty::new("--tw-ring-offset-width", "8px")]),
            _ => return None,
        };
        return Some(vec![CssProperty::new("box-shadow", value)]);
    }

    // Divide width (between children)
    if let Some(val) = utility.strip_prefix("divide-x-") {
        let width = match val {
            "0" => "0px",
            "2" => "2px",
            "4" => "4px",
            "8" => "8px",
            "reverse" => return Some(vec![CssProperty::new("--tw-divide-x-reverse", "1")]),
            _ => return None,
        };
        return Some(vec![
            CssProperty::new("--tw-divide-x-reverse", "0"),
            CssProperty::new(
                "border-right-width",
                &format!("calc({} * var(--tw-divide-x-reverse))", width),
            ),
            CssProperty::new(
                "border-left-width",
                &format!("calc({} * calc(1 - var(--tw-divide-x-reverse)))", width),
            ),
        ]);
    }
    if utility == "divide-x" {
        return Some(vec![
            CssProperty::new("--tw-divide-x-reverse", "0"),
            CssProperty::new(
                "border-right-width",
                "calc(1px * var(--tw-divide-x-reverse))",
            ),
            CssProperty::new(
                "border-left-width",
                "calc(1px * calc(1 - var(--tw-divide-x-reverse)))",
            ),
        ]);
    }

    if let Some(val) = utility.strip_prefix("divide-y-") {
        let width = match val {
            "0" => "0px",
            "2" => "2px",
            "4" => "4px",
            "8" => "8px",
            "reverse" => return Some(vec![CssProperty::new("--tw-divide-y-reverse", "1")]),
            _ => return None,
        };
        return Some(vec![
            CssProperty::new("--tw-divide-y-reverse", "0"),
            CssProperty::new(
                "border-top-width",
                &format!("calc({} * calc(1 - var(--tw-divide-y-reverse)))", width),
            ),
            CssProperty::new(
                "border-bottom-width",
                &format!("calc({} * var(--tw-divide-y-reverse))", width),
            ),
        ]);
    }
    if utility == "divide-y" {
        return Some(vec![
            CssProperty::new("--tw-divide-y-reverse", "0"),
            CssProperty::new(
                "border-top-width",
                "calc(1px * calc(1 - var(--tw-divide-y-reverse)))",
            ),
            CssProperty::new(
                "border-bottom-width",
                "calc(1px * var(--tw-divide-y-reverse))",
            ),
        ]);
    }

    // Divide style
    if let Some(val) = utility.strip_prefix("divide-") {
        let style = match val {
            "solid" => "solid",
            "dashed" => "dashed",
            "dotted" => "dotted",
            "double" => "double",
            "none" => "none",
            _ => return None,
        };
        return Some(vec![CssProperty::new("border-style", style)]);
    }

    None
}

/// Parse arbitrary border values like `rounded-[12px]`, `border-[3px]`
pub fn parse_arbitrary(prefix: &str, value: &str) -> Option<Vec<CssProperty>> {
    let property = match prefix {
        "rounded" => "border-radius",
        "border" => "border-width",
        _ => return None,
    };
    Some(vec![CssProperty::new(property, value)])
}

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

    #[test]
    fn test_rounded() {
        let props = parse("rounded").unwrap();
        assert_eq!(props[0].property, "border-radius");
        assert_eq!(props[0].value, "0.25rem");
    }

    #[test]
    fn test_rounded_lg() {
        let props = parse("rounded-lg").unwrap();
        assert_eq!(props[0].property, "border-radius");
        assert_eq!(props[0].value, "0.5rem");
    }

    #[test]
    fn test_rounded_full() {
        let props = parse("rounded-full").unwrap();
        assert_eq!(props[0].property, "border-radius");
        assert_eq!(props[0].value, "9999px");
    }

    #[test]
    fn test_border() {
        let props = parse("border").unwrap();
        assert_eq!(props[0].property, "border-width");
        assert_eq!(props[0].value, "1px");
    }

    #[test]
    fn test_border_2() {
        let props = parse("border-2").unwrap();
        assert_eq!(props[0].property, "border-width");
        assert_eq!(props[0].value, "2px");
    }

    #[test]
    fn test_ring() {
        let props = parse("ring").unwrap();
        assert_eq!(props[0].property, "box-shadow");
    }

    #[test]
    fn test_ring_2() {
        let props = parse("ring-2").unwrap();
        assert_eq!(props[0].property, "box-shadow");
        assert_eq!(props[0].value, "0 0 0 2px");
    }

    #[test]
    fn test_divide_y() {
        let props = parse("divide-y").unwrap();
        assert!(props.len() >= 2);
    }

    #[test]
    fn test_divide_x_2() {
        let props = parse("divide-x-2").unwrap();
        assert!(props.len() >= 2);
    }
}