1const LIGHT: &str = r#"
4:root {
5 --bg: #faf9f5;
6 --bg-sub: #f3f1ea;
7 --bg-card: #ffffff;
8 --bg-code: #f0ede4;
9 --bd: #e5e1d6;
10 --bd-sub: #ede9df;
11 --t1: #1f1e1d;
12 --t2: #5c5852;
13 --t3: #9c9589;
14 --t4: #c4beb4;
15 --accent: #c96442;
16 --accent-h: #b5572d;
17 --accent-bg: #f9ede8;
18 --green: #2d8a4e;
19 --orange: #b56613;
20 --red: #c0392b;
21 --purple: #7952b3;
22 --cyan: #1a7a8a;
23 --blue: #1c6bbb;
24 --s2xx: #2d8a4e;
25 --s3xx: #1a7a8a;
26 --s4xx: #b56613;
27 --s5xx: #c0392b;
28 --sb-t: #eceae2;
29 --sb-th: #c8c3b8;
30 --shadow-sm: 0 1px 2px rgba(31, 30, 29, 0.06);
31 --shadow-md: 0 4px 12px rgba(31, 30, 29, 0.1);
32}
33"#;
34
35const DARK: &str = r#"
36[data-theme="dark"] {
37 --bg: #1e1c19;
38 --bg-sub: #252320;
39 --bg-card: #2d2b27;
40 --bg-code: #201f1c;
41 --bd: #3a3733;
42 --bd-sub: #302e2b;
43 --t1: #ede8df;
44 --t2: #9c9487;
45 --t3: #6b6560;
46 --t4: #3d3a36;
47 --accent: #d4714f;
48 --accent-h: #e07d5a;
49 --accent-bg: #2e2119;
50 --green: #4caf72;
51 --orange: #d4943a;
52 --red: #e05c4b;
53 --purple: #a07fd0;
54 --cyan: #38bcd4;
55 --blue: #5a9fe0;
56 --s2xx: #4caf72;
57 --s3xx: #38bcd4;
58 --s4xx: #d4943a;
59 --s5xx: #e05c4b;
60 --sb-t: #252320;
61 --sb-th: #3a3733;
62 --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
63 --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.45);
64}
65"#;
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
68pub enum ThemeMode {
69 Light,
71 Dark,
73 #[default]
75 System,
76}
77
78impl std::str::FromStr for ThemeMode {
79 type Err = ();
80
81 fn from_str(s: &str) -> Result<Self, Self::Err> {
83 Ok(match s.to_lowercase().as_str() {
84 "light" => ThemeMode::Light,
85 "dark" => ThemeMode::Dark,
86 _ => ThemeMode::System,
87 })
88 }
89}
90
91impl ThemeMode {
92 pub fn as_str(&self) -> &'static str {
94 match self {
95 ThemeMode::Light => "light",
96 ThemeMode::Dark => "dark",
97 ThemeMode::System => "system",
98 }
99 }
100
101 pub fn get_css(&self) -> String {
103 match self {
104 ThemeMode::Light => LIGHT.to_string(),
105 ThemeMode::Dark => DARK.to_string(),
106 ThemeMode::System => format!("{}{}", LIGHT, DARK),
107 }
108 }
109}
110
111pub fn get_theme_css(theme: &str) -> String {
113 theme.parse().unwrap_or(ThemeMode::System).get_css()
114}
115
116pub fn get_complete_theme_css() -> String {
118 ThemeMode::System.get_css()
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124
125 #[test]
126 fn test_theme_mode_from_str() {
127 assert_eq!("dark".parse::<ThemeMode>().unwrap(), ThemeMode::Dark);
128 assert_eq!("DARK".parse::<ThemeMode>().unwrap(), ThemeMode::Dark);
129 assert_eq!("light".parse::<ThemeMode>().unwrap(), ThemeMode::Light);
130 assert_eq!("system".parse::<ThemeMode>().unwrap(), ThemeMode::System);
131 assert_eq!("".parse::<ThemeMode>().unwrap(), ThemeMode::System);
132 }
133
134 #[test]
135 fn test_theme_mode_get_css() {
136 let light_css = ThemeMode::Light.get_css();
137 assert!(light_css.contains(":root"));
138 assert!(!light_css.contains("[data-theme=\"dark\"]"));
139
140 let dark_css = ThemeMode::Dark.get_css();
141 assert!(dark_css.contains("[data-theme=\"dark\"]"));
142
143 let system_css = ThemeMode::System.get_css();
144 assert!(system_css.contains(":root"));
145 assert!(system_css.contains("[data-theme=\"dark\"]"));
146 }
147
148 #[test]
149 fn test_get_theme_css() {
150 let css = get_theme_css("");
151 assert!(css.contains(":root"));
152 assert!(css.contains("[data-theme=\"dark\"]"));
153
154 let css = get_theme_css("dark");
155 assert!(css.contains("[data-theme=\"dark\"]"));
156
157 let css = get_theme_css("light");
158 assert!(css.contains(":root"));
159 }
160
161 #[test]
162 fn test_get_complete_theme_css() {
163 let css = get_complete_theme_css();
164 assert!(css.contains(":root"));
165 assert!(css.contains("[data-theme=\"dark\"]"));
166 }
167}