1use crate::css::Color;
7use crate::tokens::DesignTokens;
8
9#[derive(Clone, Debug)]
11pub struct Theme {
12 pub tokens: DesignTokens,
14 pub name: String,
16 pub is_dark: bool,
18}
19
20impl Theme {
21 pub fn new(name: &str, tokens: DesignTokens, is_dark: bool) -> Self {
23 Self {
24 tokens,
25 name: name.to_string(),
26 is_dark,
27 }
28 }
29
30 pub fn light() -> Self {
32 Self {
33 tokens: DesignTokens::default(),
34 name: "light".to_string(),
35 is_dark: false,
36 }
37 }
38
39 pub fn dark() -> Self {
41 let mut tokens = DesignTokens::default();
42 tokens.colors.background.base = Color::hex("#212529");
44 tokens.colors.background.elevated = Color::hex("#2d3238");
45 tokens.colors.text.primary = Color::hex("#ffffff");
46 tokens.colors.text.secondary = Color::hex("#adb5bd");
47
48 Self {
49 tokens,
50 name: "dark".to_string(),
51 is_dark: true,
52 }
53 }
54
55 pub fn custom(name: &str, tokens: DesignTokens) -> Self {
57 Self {
58 tokens,
59 name: name.to_string(),
60 is_dark: false,
61 }
62 }
63
64 pub fn to_css_vars(&self) -> String {
66 self.tokens.to_css_vars()
67 }
68
69 pub fn name(&self) -> &str {
71 &self.name
72 }
73
74 pub fn is_dark(&self) -> bool {
76 self.is_dark
77 }
78}
79
80#[derive(Clone, Debug)]
82pub struct ThemeManager {
83 current: Theme,
84 themes: std::collections::HashMap<String, Theme>,
85}
86
87impl ThemeManager {
88 pub fn new(initial_theme: Theme) -> Self {
90 let mut themes = std::collections::HashMap::new();
91 themes.insert(initial_theme.name.clone(), initial_theme.clone());
92
93 Self {
94 current: initial_theme,
95 themes,
96 }
97 }
98
99 pub fn add_theme(&mut self, theme: Theme) {
101 self.themes.insert(theme.name.clone(), theme);
102 }
103
104 pub fn switch_to(&mut self, name: &str) -> Option<&Theme> {
106 if let Some(theme) = self.themes.get(name) {
107 self.current = theme.clone();
108 Some(&self.current)
109 } else {
110 None
111 }
112 }
113
114 pub fn current(&self) -> &Theme {
116 &self.current
117 }
118
119 pub fn theme_names(&self) -> Vec<&String> {
121 self.themes.keys().collect()
122 }
123}
124
125impl Default for ThemeManager {
126 fn default() -> Self {
127 Self::new(Theme::light())
128 }
129}
130
131pub fn apply_theme(theme: &Theme) {
133 let css = theme.to_css_vars();
134 crate::register_global_style(&css);
135
136 #[cfg(all(feature = "csr", target_arch = "wasm32"))]
137 {
138 crate::csr::inject_styles_csr(&css);
139 }
140}
141
142pub fn apply_theme_with_transition(theme: &Theme, duration: &str) {
144 let css = format!(
145 r#"
146 :root {{
147 transition: background-color {}, color {}, border-color {};
148 }}
149 {}
150 "#,
151 duration,
152 duration,
153 duration,
154 theme.to_css_vars()
155 );
156
157 crate::register_global_style(&css);
158
159 #[cfg(all(feature = "csr", target_arch = "wasm32"))]
160 {
161 crate::csr::inject_styles_csr(&css);
162 }
163}
164
165pub fn system_theme_detection() -> String {
167 r#"
168 @media (prefers-color-scheme: dark) {
169 :root {
170 color-scheme: dark;
171 }
172 }
173
174 @media (prefers-color-scheme: light) {
175 :root {
176 color-scheme: light;
177 }
178 }
179 "#
180 .to_string()
181}
182
183pub fn register_system_theme() {
185 let css = system_theme_detection();
186 crate::register_global_style(&css);
187
188 #[cfg(all(feature = "csr", target_arch = "wasm32"))]
189 {
190 crate::csr::inject_styles_csr(&css);
191 }
192}