Skip to main content

orbital_theme/
theme.rs

1use crate::tokens::{ColorTheme, CommonTheme};
2use crate::{BrandPalette, ThemeOptions, ThemeOverrides};
3
4#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
5pub enum ThemeMode {
6    #[default]
7    Light,
8    Dark,
9}
10
11impl ThemeMode {
12    pub fn as_name(self) -> &'static str {
13        match self {
14            Self::Light => "light",
15            Self::Dark => "dark",
16        }
17    }
18}
19
20/// Bundles all injectable tokens for one theme scope.
21#[derive(Clone)]
22pub struct Theme {
23    pub mode: ThemeMode,
24    pub color: ColorTheme,
25    pub common: CommonTheme,
26    pub options: ThemeOptions,
27}
28
29impl Theme {
30    pub fn light() -> Self {
31        Self::for_mode(ThemeMode::Light)
32    }
33
34    pub fn dark() -> Self {
35        Self::for_mode(ThemeMode::Dark)
36    }
37
38    pub fn for_mode(mode: ThemeMode) -> Self {
39        let mut common = CommonTheme::new();
40        crate::ramps::apply_orbital_shape_defaults(&mut common);
41        Self {
42            mode,
43            color: match mode {
44                ThemeMode::Light => ColorTheme::light(),
45                ThemeMode::Dark => ColorTheme::dark(),
46            },
47            common,
48            options: ThemeOptions::default(),
49        }
50    }
51
52    pub fn custom(base: ThemeMode, overrides: ThemeOverrides) -> Self {
53        let mut theme = Self::for_mode(base);
54        theme.apply_overrides(overrides);
55        theme
56    }
57
58    pub fn with_brand(base: ThemeMode, brand: BrandPalette) -> Self {
59        Self::custom(
60            base,
61            ThemeOverrides {
62                brand: Some(brand),
63                ..Default::default()
64            },
65        )
66    }
67
68    pub fn apply_overrides(&mut self, overrides: ThemeOverrides) {
69        if let Some(mode) = overrides.mode {
70            self.mode = mode;
71            self.rebuild_color_tokens();
72        }
73
74        if let Some(brand) = overrides.brand {
75            self.options.brand = Some(brand);
76            self.rebuild_color_tokens();
77        }
78
79        if let Some(density) = overrides.density {
80            self.options.density = density;
81            self.rebuild_common_tokens();
82            crate::ramps::apply_density(&mut self.common, density);
83        }
84
85        if let Some(typography) = overrides.typography {
86            crate::ramps::apply_typography(&mut self.common, &typography);
87        }
88
89        if let Some(spacing) = overrides.spacing {
90            crate::ramps::apply_spacing_scale(&mut self.common, spacing);
91        }
92
93        if let Some(elevation) = overrides.elevation {
94            self.options.elevation = elevation;
95            self.rebuild_color_tokens();
96            crate::ramps::apply_elevation_scale(&mut self.color, elevation);
97        }
98
99        if let Some(shape) = overrides.shape {
100            crate::ramps::apply_shape_overrides(&mut self.common, &shape);
101        }
102    }
103
104    fn rebuild_common_tokens(&mut self) {
105        let mut common = CommonTheme::new();
106        crate::ramps::apply_orbital_shape_defaults(&mut common);
107        self.common = common;
108    }
109
110    pub(crate) fn rebuild_color_tokens(&mut self) {
111        self.color = match self.mode {
112            ThemeMode::Light => ColorTheme::light(),
113            ThemeMode::Dark => ColorTheme::dark(),
114        };
115        if let Some(brand) = &self.options.brand {
116            let ramp = crate::ramps::brand_ramp(&brand.primary);
117            self.color = match self.mode {
118                ThemeMode::Light => ColorTheme::custom_light(&ramp),
119                ThemeMode::Dark => ColorTheme::custom_dark(&ramp),
120            };
121        }
122        if (self.options.elevation.multiplier - 1.0).abs() > f32::EPSILON {
123            crate::ramps::apply_elevation_scale(&mut self.color, self.options.elevation);
124        }
125    }
126
127    pub fn use_theme(default: impl Fn() -> Theme) -> leptos::prelude::ReadSignal<Theme> {
128        crate::context::ThemeInjection::use_theme(default)
129    }
130
131    pub fn use_rw_theme() -> leptos::prelude::RwSignal<Theme> {
132        crate::context::ThemeInjection::use_rw_theme()
133    }
134}
135
136impl Default for Theme {
137    fn default() -> Self {
138        Self::light()
139    }
140}
141
142impl Theme {
143    pub(crate) fn write_css_vars(&self, out: &mut String) {
144        self.common.write_orb_common_css_vars(out);
145        self.common.write_orb_motion_css_vars(out);
146        self.color.write_orb_color_css_vars(out);
147    }
148}