# class_list [GitHub Badge][GitHub Link] [crates.io Badge][crates.io Link] [docs.rs Badge][docs.rs Link] [Build Status Badge][Build Status Link] [GitHub Link]: https://github.com/Kyza/class_list [crates.io Link]: https://crates.io/crates/class_list [docs.rs Link]: https://docs.rs/class_list [Build Status Link]: https://github.com/Kyza/class_list/actions?query=branch%3Atrunk A reactive helper that ensures normalized class list strings in frontend frameworks like [Leptos](https://github.com/leptos-rs/leptos). ## Usage Examples provided will be for the [Leptos](https://github.com/leptos-rs/leptos) framework [post-{context removal}](https://github.com/leptos-rs/leptos/discussions/1509), 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. ```bash cargo add class_list ``` ### Example `class_list![]` by default wraps itself in a move closure, meaning it will be reactive by default. ```rs 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! {
, // 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. ```rs 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. ```rs 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. ```rs __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](https://github.com/Kyza/class_list/blob/trunk/class_list/src/traits.rs) to see the default implementations which are good examples of implementing them. ```rs // 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() ); ```