1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
//! A library for embedding dynamic CSS in Rust (wasm); inspired by [cssinjs/JSS](https://cssinjs.org/)
//!
//! This crate is designed to be framework-independent.
//! It currently provides integrations for [Dioxus](https://dioxuslabs.com/), which is disabled by default.
//!
//! ## Use case
//! This crate allows to develop reusable components for the web which bundle their own
//! styles. Dead-Code-Analysis works as usually with Rust: If you do not need a certain component,
//! its styles won't be embedded in the final binary.
//!
//! The classnames are generated at runtime to avoid collisions (i.e. `css-123`).
//!
//! ## Basic idea
//! The [make_styles!] procmacro allows you to write CSS-like style sheets directly in Rust. You can
//! use normal class names without worrying about collisions. Even if another component uses the same
//! name, it does not matter. The procmacro automatically determines the classnames from the style
//! and generates a helper class containing the real class names.
//!
//! #### Example (Dioxus):
//! ```no_run
//! # #[cfg(feature = "dioxus")] {
//! #![allow(non_snake_case)]
//!
//! use css_in_rs::{Classes, EmptyTheme, make_styles, use_style_provider_root};
//! use dioxus::prelude::*;
//!
//! make_styles! {
//!     (_theme: EmptyTheme) -> MyClasses {
//!         red_text {
//!             color: "red",
//!             margin: "5px",
//!         },
//!         "button" {
//!             margin: "5px",
//!             padding: "5px",
//!             width: "10em",
//!         },
//!         "button.primary" {
//!             border: "2px solid red",
//!         },
//!         "button.disabled" { // Shows a rust warning: "field never read"
//!             disabled: true,
//!         },
//!     }
//! }
//!
//! fn Demo(cx: Scope) -> Element {
//!     let classes: &MyClasses = MyClasses::use_style(cx);
//!
//!     render! {
//!         div {
//!             class: &classes.red_text as &str,
//!             "This text is supposed to be red.",
//!         }
//!         button {
//!             class: &classes.primary as &str,
//!             "Click me",
//!         }
//!     }
//! }
//!
//! fn App(cx: Scope) -> Element {
//!     let document = web_sys::window().unwrap().document().unwrap();
//!     let root = document.get_element_by_id("main").unwrap();
//!     
//!     use_style_provider_root(cx, &root, || EmptyTheme);
//!
//!     cx.render(rsx! {
//!         Demo {}
//!     })
//! }
//!
//! fn main() {
//!     // launch the web app
//!     dioxus_web::launch(App);
//! }
//! # }
//! ```

#[cfg(feature = "dioxus")]
use dioxus::prelude::*;

mod style_provider;

pub use css_in_rs_macro::make_styles;
pub use style_provider::StyleProvider;

pub trait Theme: Clone + 'static {
    fn fast_cmp(&self, other: &Self) -> bool;
}

#[derive(Clone, Copy)]
pub struct EmptyTheme;

impl Theme for EmptyTheme {
    fn fast_cmp(&self, _: &Self) -> bool {
        true
    }
}

pub trait Classes: Sized + 'static {
    type Theme: Theme;
    fn generate(theme: &Self::Theme, css: &mut String, counter: &mut u64);
    fn new(start: u64) -> Self;

    #[cfg(feature = "dioxus")]
    fn use_style(cx: &ScopeState) -> &Self {
        let provider = use_style_provider(cx);
        provider.use_styles(cx)
    }
}

#[cfg(feature = "dioxus")]
pub fn use_style_provider_root<'a, T: Theme>(
    cx: &'a ScopeState,
    some_elem: &web_sys::Element,
    make_theme: impl FnOnce() -> T,
) -> &'a StyleProvider<T> {
    let provider = cx.use_hook(|| StyleProvider::new_and_mount(some_elem, make_theme()));
    use_context_provider(cx, || provider.clone())
}

#[cfg(feature = "dioxus")]
pub fn use_style_provider<T: Theme>(cx: &ScopeState) -> &StyleProvider<T> {
    use_context(cx).unwrap()
}