Skip to content

Commit 266ce31

Browse files
Copilotlarp0
andcommitted
Implement dark mode support with CSS variables and theme store
Co-authored-by: larp0 <[email protected]>
1 parent 3291014 commit 266ce31

File tree

5 files changed

+168
-32
lines changed

5 files changed

+168
-32
lines changed

opensvm-dioxus/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ web-sys = { version = "0.3.64", features = [
3333
"Clipboard",
3434
"ClipboardEvent",
3535
"Location",
36+
"MediaQueryList",
3637
], optional = true }
3738
gloo = { version = "0.10.0", optional = true, features = ["futures"] }
3839
console_log = { version = "1.0.0", optional = true }

opensvm-dioxus/src/app.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ use crate::routes::{
66
solanow::SolanowPage, transaction::TransactionPage, validators::ValidatorsPage,
77
wallet::WalletPage,
88
};
9+
use crate::stores::theme_store::{use_theme_store, get_current_theme, Theme};
10+
11+
#[cfg(feature = "web")]
12+
use web_sys::{window, MediaQueryList};
913

1014
// Define the routes for our app
1115
#[derive(Routable, Clone)]
@@ -83,6 +87,37 @@ fn NotFound(cx: Scope, #[allow(unused_variables)] route: Vec<String>) -> Element
8387

8488
// Main App component
8589
pub fn App(cx: Scope) -> Element {
90+
let theme_store = use_theme_store(cx);
91+
let current_theme = get_current_theme(theme_store);
92+
93+
// Apply theme to document body
94+
use_effect(cx, (&current_theme,), |(theme,)| {
95+
async move {
96+
#[cfg(feature = "web")]
97+
{
98+
if let Some(window) = web_sys::window() {
99+
if let Some(document) = window.document() {
100+
if let Some(body) = document.body() {
101+
let theme_str = match theme {
102+
Theme::Light => "light",
103+
Theme::Dark => "dark",
104+
Theme::System => {
105+
// Use media query to determine system theme
106+
if let Ok(Some(media_query)) = window.match_media("(prefers-color-scheme: dark)") {
107+
if media_query.matches() { "dark" } else { "light" }
108+
} else {
109+
"light"
110+
}
111+
}
112+
};
113+
let _ = body.set_attribute("data-theme", theme_str);
114+
}
115+
}
116+
}
117+
}
118+
}
119+
});
120+
86121
cx.render(rsx! {
87122
style { include_str!("./assets/styles.css") }
88123
Router::<Route> {}
Lines changed: 83 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,60 @@
1+
/* CSS Variables for theming */
2+
:root {
3+
/* Light theme */
4+
--primary: #00D181;
5+
--primary-light: #E6F9F3;
6+
--background: #FFFFFF;
7+
--surface: #F8F9FA;
8+
--surface-light: #F1F3F5;
9+
--text: #1A1B1E;
10+
--text-secondary: #6C757D;
11+
--text-tertiary: #ADB5BD;
12+
--border: #E9ECEF;
13+
--success: #00D181;
14+
--error: #FF4B4B;
15+
--warning: #FFB800;
16+
}
17+
18+
/* Dark theme */
19+
@media (prefers-color-scheme: dark) {
20+
:root {
21+
--background: #1A1B1E;
22+
--surface: #212529;
23+
--surface-light: #343A40;
24+
--text: #F8F9FA;
25+
--text-secondary: #ADB5BD;
26+
--text-tertiary: #6C757D;
27+
--border: #343A40;
28+
}
29+
}
30+
31+
/* Dark theme override class */
32+
[data-theme="dark"] {
33+
--background: #1A1B1E;
34+
--surface: #212529;
35+
--surface-light: #343A40;
36+
--text: #F8F9FA;
37+
--text-secondary: #ADB5BD;
38+
--text-tertiary: #6C757D;
39+
--border: #343A40;
40+
}
41+
42+
/* Light theme override class */
43+
[data-theme="light"] {
44+
--primary: #00D181;
45+
--primary-light: #E6F9F3;
46+
--background: #FFFFFF;
47+
--surface: #F8F9FA;
48+
--surface-light: #F1F3F5;
49+
--text: #1A1B1E;
50+
--text-secondary: #6C757D;
51+
--text-tertiary: #ADB5BD;
52+
--border: #E9ECEF;
53+
--success: #00D181;
54+
--error: #FF4B4B;
55+
--warning: #FFB800;
56+
}
57+
158
/* Base styles */
259
* {
360
box-sizing: border-box;
@@ -8,22 +65,44 @@
865
body {
966
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
1067
line-height: 1.6;
11-
color: #333;
12-
background-color: #f5f5f5;
68+
color: var(--text);
69+
background-color: var(--background);
70+
transition: background-color 0.3s ease, color 0.3s ease;
1371
}
1472

1573
/* Page layouts */
16-
.explorer-page, .not-found-page {
74+
.explorer-page, .not-found-page, .transaction-page {
1775
padding: 2rem;
1876
max-width: 1200px;
1977
margin: 0 auto;
78+
background-color: var(--background);
79+
color: var(--text);
2080
}
2181

2282
h1 {
2383
margin-bottom: 1rem;
24-
color: #512da8;
84+
color: var(--primary);
2585
}
2686

2787
p {
2888
margin-bottom: 1rem;
89+
color: var(--text);
90+
}
91+
92+
/* UI components */
93+
.surface {
94+
background-color: var(--surface);
95+
border: 1px solid var(--border);
96+
}
97+
98+
.surface-light {
99+
background-color: var(--surface-light);
100+
}
101+
102+
.text-secondary {
103+
color: var(--text-secondary);
104+
}
105+
106+
.text-tertiary {
107+
color: var(--text-tertiary);
29108
}

opensvm-dioxus/src/stores/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
//! State management for the application
22
33
// Re-export stores here as needed
4+
pub mod theme_store;

opensvm-dioxus/src/stores/theme_store.rs

Lines changed: 48 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use dioxus::prelude::*;
22
use serde::{Deserialize, Serialize};
3-
use wasm_bindgen::prelude::*;
43
use web_sys::Storage;
54

65
// Define the theme options
@@ -17,61 +16,82 @@ pub struct ThemeState {
1716
pub theme: Theme,
1817
}
1918

20-
// Create a signal for the theme state
21-
pub fn use_theme_store() -> Signal<ThemeState> {
22-
// Create a static signal to ensure the state persists across renders
23-
static THEME_STORE: Signal<ThemeState> = Signal::new(ThemeState {
24-
theme: Theme::System,
25-
});
26-
27-
// Initialize the store with data from local storage if available
28-
use_hook(|| {
19+
impl Default for ThemeState {
20+
fn default() -> Self {
21+
Self {
22+
theme: Theme::System,
23+
}
24+
}
25+
}
26+
27+
// Create a hook for the theme state
28+
pub fn use_theme_store(cx: &ScopeState) -> &UseState<ThemeState> {
29+
let theme_state = use_state(cx, || {
30+
// Try to load from local storage
2931
if let Some(storage) = get_local_storage() {
3032
if let Ok(Some(stored_data)) = storage.get_item("theme-storage") {
3133
if let Ok(theme_state) = serde_json::from_str::<ThemeState>(&stored_data) {
32-
THEME_STORE.set(theme_state);
34+
return theme_state;
3335
}
3436
}
3537
}
38+
ThemeState::default()
3639
});
37-
38-
THEME_STORE
40+
41+
// Save to local storage whenever the state changes
42+
use_effect(cx, (theme_state,), |(state,)| {
43+
let state = state.get();
44+
save_to_local_storage(state);
45+
async move {}
46+
});
47+
48+
theme_state
3949
}
4050

4151
// Helper function to get local storage
4252
fn get_local_storage() -> Option<Storage> {
43-
let window = web_sys::window()?;
44-
window.local_storage().ok()?
53+
#[cfg(feature = "web")]
54+
{
55+
let window = web_sys::window()?;
56+
window.local_storage().ok()?
57+
}
58+
#[cfg(not(feature = "web"))]
59+
None
4560
}
4661

4762
// Helper function to save state to local storage
4863
fn save_to_local_storage(state: &ThemeState) {
49-
if let Some(storage) = get_local_storage() {
50-
if let Ok(json) = serde_json::to_string(state) {
51-
let _ = storage.set_item("theme-storage", &json);
64+
#[cfg(feature = "web")]
65+
{
66+
if let Some(storage) = get_local_storage() {
67+
if let Ok(json) = serde_json::to_string(state) {
68+
let _ = storage.set_item("theme-storage", &json);
69+
}
5270
}
5371
}
5472
}
5573

5674
// Set theme function
57-
pub fn set_theme(theme_store: Signal<ThemeState>, theme: Theme) {
58-
let mut state = theme_store.read().clone();
75+
pub fn set_theme(theme_store: &UseState<ThemeState>, theme: Theme) {
76+
let mut state = theme_store.get().clone();
5977
state.theme = theme;
60-
theme_store.set(state.clone());
61-
save_to_local_storage(&state);
78+
theme_store.set(state);
6279
}
6380

6481
// Get current theme function
65-
pub fn get_current_theme(theme_store: Signal<ThemeState>) -> Theme {
66-
let state = theme_store.read();
82+
pub fn get_current_theme(theme_store: &UseState<ThemeState>) -> Theme {
83+
let state = theme_store.get();
6784

6885
match state.theme {
6986
Theme::System => {
7087
// Check system preference using media query
71-
if let Some(window) = web_sys::window() {
72-
if let Ok(Some(media_query)) = window.match_media("(prefers-color-scheme: dark)") {
73-
if media_query.matches() {
74-
return Theme::Dark;
88+
#[cfg(feature = "web")]
89+
{
90+
if let Some(window) = web_sys::window() {
91+
if let Ok(Some(media_query)) = window.match_media("(prefers-color-scheme: dark)") {
92+
if media_query.matches() {
93+
return Theme::Dark;
94+
}
7595
}
7696
}
7797
}

0 commit comments

Comments
 (0)