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
127
128
129
130
131
132
133
134
135
136
137
//! 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_quickstart};
//! 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 {
//!     use_style_provider_quickstart(cx, || EmptyTheme);
//!
//!     cx.render(rsx! {
//!         Demo {}
//!     })
//! }
//!
//! fn main() {
//!     // launch the web app
//!     dioxus_web::launch(App);
//! }
//! # }
//! ```

#![cfg_attr(feature = "unstable-doc-cfg", feature(doc_cfg))]

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

mod style_provider;

pub use css_in_rs_macro::make_styles;
use doc_cfg::doc_cfg;
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;

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

/// Quickly sets up a StyleProvider in the global document. Styles will be attached
/// to `window.document.head`
#[doc_cfg(feature = "dioxus")]
pub fn use_style_provider_quickstart<'a, T: Theme>(
    cx: &'a ScopeState,
    make_theme: impl FnOnce() -> T,
) -> &'a StyleProvider<T> {
    let provider = cx.use_hook(|| StyleProvider::quickstart_web(make_theme()));
    use_context_provider(cx, || provider.clone())
}

#[doc_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())
}

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