build class_list

A reactive helper that ensures normalized class list strings in frontend frameworks like Leptos

7 releases

0.1.7 Aug 23, 2023
0.1.6 Aug 23, 2023

#597 in Rust patterns

MIT license

13KB
174 lines

class_list

GitHub Badge crates.io Badge docs.rs Badge Build Status Badge

A reactive helper that ensures normalized class list strings in frontend frameworks like Leptos.

Usage

Examples provided will be for the Leptos framework post-{context removal}, but it will work pre-{context removal} and might work in other similar frameworks.

This library is meant to be agnostic and has no runtime dependencies, but it has only been tested with Leptos.

cargo add class_list

Example

class_list![] by default wraps itself in a move closure, meaning it will be reactive by default.

let (count, set_count) = create_signal(0);

set_interval(
	move || {
		set_count.update(|count| *count += 1);
	},
	Duration::from_millis(100),
);

let count_class = move || format!("count-{}", count());
let count_is_even = move || count() % 2 == 0;

view! {
	<div class=class_list![
		"default-class-names",
		// Closures get called automatically.
		count_class,
		// Closures can be written directly into the macro.
		move || format!("count-{}", count()),
		// Both Option and Result can be used as values.
		// None and Err result in no class name.
		Some("option"),
		None::<String>,
		// More conveniently, class names can be bound to reactive toggles.
		// "even" will only be applied when `count_is_even()` is true.
		// You also don't need to call closures here.
		"even" <=> count_is_even
	] />
}

Options

Each option must be followed by a ;.

Raw

To generate a non-reactive String, add the raw option to the beginning.

class_list![
	raw;
	"default-class-names",
]

Clone

Rarely, you may need to clone something before passing it in.

The macro makes this easy with the clone option which clones the variable before it gets moved into the closure.

If possible try to avoid needing this option in the first place.

class_list![
	clone[count_class];
	"default-class-names",
]

Crate

This should never be needed because it's automatically supplied by a wrapper macro_rules!.

In a case where the trait imports cannot be resolved--such as when used inside of another library--, the path can be redefined.

The path should lead to the root of this library.

__class_list![
	crate = ::your_lib::class_list;
	"default-class-names",
]

Implementing Traits

If you'd like to simply pass a type to the macro instead of converting it every time, you can implement the ClassList and ClassToggle types.

Check out traits.rs to see the default implementations which are good examples of implementing them.

// If you're using a type you don't own,
// you must wrap it in a new struct.
struct Bool(bool);

impl ClassList for Bool {
	fn to_class_list(&self, normalize: bool) -> String {
		// If the string could contain multiple class names
		// you should use `normalize` to determine whether
		// or not to call `.to_class_list()` on it before
		// returning.
		// If you're lazy you could always normalize, but
		// then the string will be normalized multiple
		// times for every update in the macro.
		if self.0 {
			"true".into()
		} else {
			"false".into()
		}
	}
}
impl ClassToggle for Bool {
	fn to_class_toggle(&self) -> bool {
		self.0
	}
}

// Option, Result, and Fn are implemented in a way which
// allows any new type you implement to be automatically
// passed through.
assert_eq!(
	class_list![
		// ClassList
		move || Bool(false),
		Bool(true),
		"class",
		// ClassToggle
		"hidden" <=> move || Bool(false),
		"list",
	](),
	"false true class list".to_string()
);

Dependencies

~205–640KB
~15K SLoC