Skip to main content

openapi_ui/
theme.rs

1//! Built-in light and dark themes.
2
3const 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 theme only
70    Light,
71    /// Dark theme only
72    Dark,
73    /// Both themes with user toggle (default)
74    #[default]
75    System,
76}
77
78impl ThemeMode {
79    /// Create a ThemeMode from a string slice.
80    pub fn from_str(s: &str) -> Self {
81        match s.to_lowercase().as_str() {
82            "light" => ThemeMode::Light,
83            "dark" => ThemeMode::Dark,
84            _ => ThemeMode::System,
85        }
86    }
87
88    /// Convert ThemeMode to a string slice.
89    pub fn as_str(&self) -> &'static str {
90        match self {
91            ThemeMode::Light => "light",
92            ThemeMode::Dark => "dark",
93            ThemeMode::System => "system",
94        }
95    }
96
97    /// Returns the CSS variables for this theme mode.
98    pub fn get_css(&self) -> String {
99        match self {
100            ThemeMode::Light => LIGHT.to_string(),
101            ThemeMode::Dark => DARK.to_string(),
102            ThemeMode::System => format!("{}{}", LIGHT, DARK),
103        }
104    }
105}
106
107/// Returns the CSS for a theme by name (`"light"`, `"dark"`, or `"system"`).
108pub fn get_theme_css(theme: &str) -> String {
109    ThemeMode::from_str(theme).get_css()
110}
111
112/// Returns both light and dark theme CSS combined.
113pub fn get_complete_theme_css() -> String {
114    ThemeMode::System.get_css()
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120
121    #[test]
122    fn test_theme_mode_from_str() {
123        assert_eq!(ThemeMode::from_str("dark"), ThemeMode::Dark);
124        assert_eq!(ThemeMode::from_str("DARK"), ThemeMode::Dark);
125        assert_eq!(ThemeMode::from_str("light"), ThemeMode::Light);
126        assert_eq!(ThemeMode::from_str("system"), ThemeMode::System);
127        assert_eq!(ThemeMode::from_str(""), ThemeMode::System);
128    }
129
130    #[test]
131    fn test_theme_mode_get_css() {
132        let light_css = ThemeMode::Light.get_css();
133        assert!(light_css.contains(":root"));
134        assert!(!light_css.contains("[data-theme=\"dark\"]"));
135
136        let dark_css = ThemeMode::Dark.get_css();
137        assert!(dark_css.contains("[data-theme=\"dark\"]"));
138
139        let system_css = ThemeMode::System.get_css();
140        assert!(system_css.contains(":root"));
141        assert!(system_css.contains("[data-theme=\"dark\"]"));
142    }
143
144    #[test]
145    fn test_get_theme_css() {
146        let css = get_theme_css("");
147        assert!(css.contains(":root"));
148        assert!(css.contains("[data-theme=\"dark\"]"));
149
150        let css = get_theme_css("dark");
151        assert!(css.contains("[data-theme=\"dark\"]"));
152
153        let css = get_theme_css("light");
154        assert!(css.contains(":root"));
155    }
156
157    #[test]
158    fn test_get_complete_theme_css() {
159        let css = get_complete_theme_css();
160        assert!(css.contains(":root"));
161        assert!(css.contains("[data-theme=\"dark\"]"));
162    }
163}