theme.rs
⎇
Raw
1//! Tri-state color theme: auto (follow the OS), light, dark.
2//!
3//! The choice is persisted in localStorage and applied as a class on
4//! `<html>`; `app.css` resolves the palette variables accordingly.
5//! "Auto" means no class at all, so the stylesheet falls back to
6//! `prefers-color-scheme`.
7
8use crate::icons::IconName;
9
10const STORAGE_KEY: &str = "fb-theme";
11
12#[derive(Clone, Copy, PartialEq, Eq, Debug)]
13pub enum Theme {
14 /// Follow the OS `prefers-color-scheme` (the default).
15 Auto,
16 Light,
17 Dark,
18}
19
20impl Theme {
21 /// The i18n key of this state's display name.
22 pub const fn name_key(self) -> &'static str {
23 match self {
24 Theme::Auto => crate::i18n::k::THEME_AUTO,
25 Theme::Light => crate::i18n::k::THEME_LIGHT,
26 Theme::Dark => crate::i18n::k::THEME_DARK,
27 }
28 }
29
30 /// The next state in the toggle cycle: auto → light → dark → auto.
31 pub const fn next(self) -> Theme {
32 match self {
33 Theme::Auto => Theme::Light,
34 Theme::Light => Theme::Dark,
35 Theme::Dark => Theme::Auto,
36 }
37 }
38
39 /// Lowercase name, used in tooltips and as the localStorage value.
40 pub const fn label(self) -> &'static str {
41 match self {
42 Theme::Auto => "auto",
43 Theme::Light => "light",
44 Theme::Dark => "dark",
45 }
46 }
47
48 /// The glyph the toggle button shows for this state.
49 pub const fn icon(self) -> IconName {
50 match self {
51 Theme::Auto => IconName::ThemeAuto,
52 Theme::Light => IconName::ThemeLight,
53 Theme::Dark => IconName::ThemeDark,
54 }
55 }
56
57 /// Persist the choice.
58 pub fn store(self) {
59 crate::util::storage_set(STORAGE_KEY, self.label());
60 }
61
62 /// The persisted choice; anything missing or invalid is
63 /// [`Theme::Auto`].
64 pub fn load() -> Theme {
65 match crate::util::storage_get(STORAGE_KEY).as_deref() {
66 Some("light") => Theme::Light,
67 Some("dark") => Theme::Dark,
68 _ => Theme::Auto,
69 }
70 }
71
72 /// Apply to `<html>`: auto clears the class (the stylesheet falls back
73 /// to `prefers-color-scheme`), light/dark force a palette.
74 pub fn apply(self) {
75 let Some(html) = web_sys::window()
76 .and_then(|w| w.document())
77 .and_then(|d| d.document_element())
78 else {
79 return;
80 };
81 html.set_class_name(match self {
82 Theme::Auto => "",
83 t => t.label(),
84 });
85 }
86}
87