#[macro_export]
macro_rules! clsx {
() => {
String::new()
};
(@internal $classes:ident ,) => {};
(@internal $classes:ident) => {};
($class:expr) => {
$class.to_string()
};
(@internal $classes:ident $class:expr => $cond:expr, $($rest:tt)*) => {{
if $cond {
$classes.push($class.to_string());
}
$crate::clsx!(@internal $classes $($rest)*);
}};
(@internal $classes:ident $class:expr => $cond:expr) => {{
if $cond {
$classes.push($class.to_string());
}
}};
(@internal $classes:ident $class:expr, $($rest:tt)*) => {{
$classes.push($class.to_string());
$crate::clsx!(@internal $classes $($rest)*);
}};
(@internal $classes:ident $class:expr) => {{
$classes.push($class.to_string());
}};
($($args:tt)*) => {{
let mut classes = Vec::new();
$crate::clsx!(@internal classes $($args)*);
classes.into_iter()
.filter(|s| !s.is_empty())
.collect::<Vec<_>>()
.join(" ")
}};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_usage() {
assert_eq!(clsx!("class1"), "class1");
assert_eq!(clsx!("class1", "class2"), "class1 class2");
assert_eq!(clsx!("class1", "", "class2"), "class1 class2");
}
#[test]
fn test_conditional_usage() {
assert_eq!(clsx!("class1" => true, "class2" => false), "class1");
assert_eq!(clsx!("class1" => false, "class2" => true), "class2");
}
#[test]
fn test_mixed_usage() {
let dynamic_class = "dynamic";
assert_eq!(
clsx!("static", dynamic_class, "conditional" => true),
"static dynamic conditional"
);
assert_eq!(
clsx!("base", "always", "active" => true, "disabled" => false),
"base always active"
);
}
#[test]
fn test_empty_input() {
assert_eq!(clsx!(), "");
}
#[test]
fn test_complex_usage() {
let is_active = true;
let is_disabled = false;
assert_eq!(
clsx!("header", "button" => is_active, "hidden" => is_disabled, "footer"),
"header button footer"
);
}
#[test]
fn test_trailing_comma() {
assert_eq!(clsx!("class1", "class2",), "class1 class2");
assert_eq!(clsx!("class1" => true, "class2" => false,), "class1");
}
}