css_in_rs/
lib.rs

1//! A library for embedding dynamic CSS in Rust (wasm); inspired by [cssinjs/jss](https://cssinjs.org/)
2//!
3//! This crate is designed to be framework-independent.
4//! It currently provides integrations for [Dioxus](https://dioxuslabs.com/), which is disabled by default.
5//!
6//! ## Use case
7//! This crate allows to develop reusable components for the web which bundle their own
8//! styles. Thanks to dead-code-analysis in Rust, only the styles which are actually used will be included
9//! in the final wasm binary.
10//!
11//! Features:
12//! * A procmacro [make_styles!] to write css directly in Rust
13//! * A runtime to inject the styles on a as-need basis. If styles are not used, they
14//!   won't be included in the final binary
15//! * Styles will only be mounted once, even if requested multiple times
16//! * Dynamically created classnames to avoid collisions. You can choose common names
17//!   like `active` for multiple components without problems
18//! * Compile time checks: Rust will warn you if classnames defined in your styles are
19//!   never used. If they don't exist, you'll get an error
20//!
21//! ## Basic idea
22//! You embed your CSS code with classnames of your choice using the [make_styles!] procmacro.
23//! It will generate a new rust `struct` for the final runtime-names of the used css classes.
24//! For each css class in your style, a `classname: String` member will be available in the struct.
25//! See the documentation for [make_styles!] for more details.
26//!
27//! Styles generated this way can be mounted. On the first mount, classnames are generated at runtime
28//! to avoid collisions and returned in the struct created by the procmacro. You can therefore access
29//! the created classnames using the struct. Since the struct's type is generated at compile time,
30//! the compiler will complain if you use undefined css classes and warn you about unused classes.
31//!
32//! Styles are only mounted once. You can try to do it repeatedly, but it will be a no-op. You'll get
33//! a reference to the classnames-struct which was created the first time. Therefore, you can reuse
34//! your created styles in many components, and you do no not need to worry about mounting them too
35//! many times.
36//!
37//! #### Example (Dioxus):
38//! ```no_run
39//! # #[cfg(feature = "dioxus")] {
40//! #![allow(non_snake_case)]
41//!
42//! use css_in_rs::{Classes, EmptyTheme, make_styles, use_style_provider_quickstart};
43//! use dioxus::prelude::*;
44//!
45//! // Will create a new struct `MyClasses` with three members:
46//! //  `red_text`, `primary` and `disabled`
47//! make_styles! {
48//!     (_theme: EmptyTheme) -> MyClasses {
49//!         red_text {          // defines a new css class: `red_text`
50//!             color: "red",
51//!             margin: "5px",
52//!         },
53//!         "button" {          // does not define any new css classes
54//!             margin: "5px",
55//!             padding: "5px",
56//!             width: "10em",
57//!         },
58//!         "button.primary" {  // defines a new css class: `primary`
59//!             border: "2px solid red",
60//!         },
61//!         "button.disabled" { // Shows a rust warning: "field never read"
62//!             disabled: true,
63//!         },
64//!     }
65//! }
66//!
67//! fn Demo(cx: Scope) -> Element {
68//!     let classes: &MyClasses = MyClasses::use_style(cx);
69//!
70//!     render! {
71//!         div {
72//!             class: &classes.red_text as &str,
73//!             "This text is supposed to be red.",
74//!         }
75//!         button {
76//!             class: &classes.primary as &str,
77//!             "Click me",
78//!         }
79//!     }
80//! }
81//!
82//! fn App(cx: Scope) -> Element {
83//!     use_style_provider_quickstart(cx, || EmptyTheme);
84//!
85//!     cx.render(rsx! {
86//!         Demo {}
87//!     })
88//! }
89//!
90//! fn main() {
91//!     // launch the web app
92//!     dioxus_web::launch(App);
93//! }
94//! # }
95//! ```
96
97#![cfg_attr(feature = "unstable-doc-cfg", feature(doc_cfg))]
98
99#[doc_cfg(feature = "dioxus")]
100use dioxus::prelude::*;
101
102mod style_provider;
103
104pub mod backend;
105
106pub use css_in_rs_macro::make_styles;
107use doc_cfg::doc_cfg;
108pub use style_provider::StyleProvider;
109
110/// A trait for themes: Themes contain shared data which can be
111/// used in your styles.
112///
113/// For example, you can define color, paddings, and so on in a datatype.
114/// Alternatively, you can use css variables which is probably even better.
115/// However, themes are an easy way to precompute some things, for example
116/// complex box-shadows. They can also be changed at runtime which will
117/// update all styles which depend on this theme.
118///
119/// If you do not need theme support, you can use [EmptyTheme]  
120pub trait Theme: Clone + 'static {
121    fn fast_cmp(&self, other: &Self) -> bool;
122}
123
124/// An empty theme. Use if no theme support is needed.
125///
126/// If you do need themes (i.e. certain data which is shared
127/// between all your styles), use a custom type and implement
128/// the [Theme] trait.
129#[derive(Clone, Copy)]
130pub struct EmptyTheme;
131
132impl Theme for EmptyTheme {
133    fn fast_cmp(&self, _: &Self) -> bool {
134        true
135    }
136}
137
138/// This trait will be implemented by the classnames-struct generated
139/// by the [make_styles!] macro. You probably won't implement it yourself
140/// unless you need something very specific which the macro cannot handle.
141///
142/// Example
143/// ```
144/// # use css_in_rs::{Classes, EmptyTheme};
145/// struct MyClasses {
146///     active: String,
147///     disabled: String,
148/// }
149///
150/// impl Classes for MyClasses {
151///     type Theme = EmptyTheme;
152///
153///     fn generate(_: &Self::Theme, css: &mut String, counter: &mut u64) {
154///         use core::fmt::Write;
155///         writeln!(css, "css-{counter} {{ background-color: transparent; }}").unwrap();
156///         *counter += 1;
157///         writeln!(css, "css-{counter} {{ background-color: #f0f0f0; }}").unwrap();
158///         *counter += 1;
159///     }
160///
161///     fn new(start: u64) -> Self {
162///         MyClasses {
163///             active: format!("css-{}", start),
164///             disabled: format!("css-{}", start + 1),
165///         }
166///     }
167/// }
168/// ```
169pub trait Classes: Sized + 'static {
170    /// The [Theme] which this style depend on
171    type Theme: Theme;
172
173    /// Generate the CSS rules. Use the provided `counter` to obtain unique classnames and
174    /// increment it accordingly. The content of the rules may depend on the given theme,
175    /// but the classnames must be the same whenever this method is called. The classnames
176    /// must only depend on the `counter`, because they are cached.
177    ///
178    /// It is important that the number of classes does not change either. The runtime will
179    /// panic if a second call to `generate` returns a different counter than the first time.
180    ///
181    /// Usually, this method will introduce an arbitrary number `n` of the css classes and
182    /// increment the counter by exactly `n`. This `n` is usually a fixed constant
183    fn generate(theme: &Self::Theme, css: &mut String, counter: &mut u64);
184
185    /// The styles generated in [Self::generate] use unreadable classnames. The struct implementing
186    /// this type should provide the user a way to access those classnames. The `start`
187    /// parameter is the same as the `counter` param used in [Self::generate], which is necessary
188    /// because the dynamic classnames will depend on it.
189    fn new(start: u64) -> Self;
190
191    /// Mount this style and return a reference to the classnames (which are represented by
192    /// `Self`).
193    /// Note that the style will only be mounted once, even if you use this
194    /// hook from multiple components or your components will be used multiple
195    /// times. The classnames will be the same every time, as long as the
196    /// same [StyleProvider] is used, which is taken from the current context.
197    #[doc_cfg(feature = "dioxus")]
198    fn use_style(cx: &ScopeState) -> &Self {
199        let provider = use_style_provider(cx);
200        provider.use_styles(cx)
201    }
202}
203
204/// Quickly sets up a StyleProvider in the global document. Styles will be attached
205/// to `window.document.head`
206#[doc_cfg(feature = "dioxus")]
207pub fn use_style_provider_quickstart<'a, T: Theme>(
208    cx: &'a ScopeState,
209    make_theme: impl FnOnce() -> T,
210) -> &'a StyleProvider<T> {
211    let provider = cx.use_hook(|| StyleProvider::quickstart_web(make_theme()));
212    use_context_provider(cx, || provider.clone())
213}
214
215#[doc_cfg(feature = "dioxus")]
216pub fn use_style_provider<T: Theme>(cx: &ScopeState) -> &StyleProvider<T> {
217    use_context(cx).unwrap()
218}