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#[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}