use crate::parser::CssProperty;
fn radius_value(name: &str) -> Option<&'static str> {
match name {
"none" => Some("0px"),
"xs" => Some("0.125rem"),
"sm" => Some("0.125rem"),
"" => Some("0.25rem"), "md" => Some("0.375rem"),
"lg" => Some("0.5rem"),
"xl" => Some("0.75rem"),
"2xl" => Some("1rem"),
"3xl" => Some("1.5rem"),
"4xl" => Some("2rem"),
"full" => Some("9999px"),
_ => None,
}
}
fn directional_radius_props(side: &str) -> Option<&'static [&'static str]> {
match side {
"t" => Some(&["border-top-left-radius", "border-top-right-radius"]),
"b" => Some(&["border-bottom-left-radius", "border-bottom-right-radius"]),
"l" => Some(&["border-top-left-radius", "border-bottom-left-radius"]),
"r" => Some(&["border-top-right-radius", "border-bottom-right-radius"]),
"tl" => Some(&["border-top-left-radius"]),
"tr" => Some(&["border-top-right-radius"]),
"bl" => Some(&["border-bottom-left-radius"]),
"br" => Some(&["border-bottom-right-radius"]),
"s" | "ss" => Some(&["border-top-left-radius", "border-bottom-left-radius"]),
"e" | "ee" => Some(&["border-top-right-radius", "border-bottom-right-radius"]),
"es" => Some(&["border-bottom-left-radius"]),
"se" => Some(&["border-bottom-right-radius"]),
"ss-corner" => Some(&["border-top-left-radius"]),
_ => None,
}
}
pub fn parse(utility: &str) -> Option<Vec<CssProperty>> {
if let Some(rest) = utility.strip_prefix("rounded-") {
if let Some(value) = radius_value(rest) {
return Some(vec![CssProperty::new("border-radius", value)]);
}
if let Some(dash) = rest.find('-') {
let side = &rest[..dash];
let size = &rest[dash + 1..];
if let (Some(props), Some(value)) = (directional_radius_props(side), radius_value(size))
{
return Some(props.iter().map(|p| CssProperty::new(p, value)).collect());
}
}
return None;
}
if utility == "rounded" {
return Some(vec![CssProperty::new("border-radius", "0.25rem")]);
}
if utility == "border" {
return Some(vec![CssProperty::new("border-width", "1px")]);
}
if let Some(val) = utility.strip_prefix("border-") {
let value = match val {
"0" => "0px",
"2" => "2px",
"4" => "4px",
"8" => "8px",
"t" => return Some(vec![CssProperty::new("border-top-width", "1px")]),
"r" => return Some(vec![CssProperty::new("border-right-width", "1px")]),
"b" => return Some(vec![CssProperty::new("border-bottom-width", "1px")]),
"l" => return Some(vec![CssProperty::new("border-left-width", "1px")]),
"t-0" => return Some(vec![CssProperty::new("border-top-width", "0px")]),
"r-0" => return Some(vec![CssProperty::new("border-right-width", "0px")]),
"b-0" => return Some(vec![CssProperty::new("border-bottom-width", "0px")]),
"l-0" => return Some(vec![CssProperty::new("border-left-width", "0px")]),
"t-2" => return Some(vec![CssProperty::new("border-top-width", "2px")]),
"r-2" => return Some(vec![CssProperty::new("border-right-width", "2px")]),
"b-2" => return Some(vec![CssProperty::new("border-bottom-width", "2px")]),
"l-2" => return Some(vec![CssProperty::new("border-left-width", "2px")]),
"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)]);
}
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"),
]);
}
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")]),
"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)]);
}
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))",
),
]);
}
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
}
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);
}
#[test]
fn test_directional_rounded_full_matrix() {
let sides = ["t", "b", "l", "r", "tl", "tr", "bl", "br"];
let sizes = ["none", "sm", "md", "lg", "xl", "2xl", "3xl", "full"];
for side in sides {
for size in sizes {
let utility = format!("rounded-{side}-{size}");
let props = parse(&utility)
.unwrap_or_else(|| panic!("missing directional radius: {utility}"));
assert!(
!props.is_empty(),
"utility {utility} produced no properties"
);
for p in &props {
assert!(
p.property.ends_with("-radius"),
"{utility} produced non-radius property {}",
p.property
);
}
}
}
}
#[test]
fn test_rounded_t_xl_specific() {
let props = parse("rounded-t-xl").unwrap();
assert_eq!(props.len(), 2);
assert_eq!(props[0].property, "border-top-left-radius");
assert_eq!(props[0].value, "0.75rem");
assert_eq!(props[1].property, "border-top-right-radius");
assert_eq!(props[1].value, "0.75rem");
}
#[test]
fn test_rounded_tr_2xl() {
let props = parse("rounded-tr-2xl").unwrap();
assert_eq!(props.len(), 1);
assert_eq!(props[0].property, "border-top-right-radius");
assert_eq!(props[0].value, "1rem");
}
}