Merge pull request 'darkmode' (#41) from darkmode into styles-nino
Reviewed-on: #41
This commit was merged in pull request #41.
This commit is contained in:
@@ -16,7 +16,8 @@ serde = { workspace = true }
|
|||||||
wasm-bindgen-futures = "0.4.70"
|
wasm-bindgen-futures = "0.4.70"
|
||||||
web-sys = { version = "0.3.95", features = [
|
web-sys = { version = "0.3.95", features = [
|
||||||
"Window","Document","Request","Response","Headers", "HtmlSelectElement", "RequestCredentials",
|
"Window","Document","Request","Response","Headers", "HtmlSelectElement", "RequestCredentials",
|
||||||
"SubmitEvent","InputEvent","HtmlInputElement","Event", "HtmlFormElement", "MouseEvent"
|
"SubmitEvent","InputEvent","HtmlInputElement","Event", "HtmlFormElement", "MouseEvent",
|
||||||
|
"Element", "MediaQueryList", "DomTokenList"
|
||||||
] }
|
] }
|
||||||
gloo-net = "0.7.0"
|
gloo-net = "0.7.0"
|
||||||
gloo-storage = "0.4.0"
|
gloo-storage = "0.4.0"
|
||||||
|
|||||||
65
frontend/src/dark_mode.rs
Normal file
65
frontend/src/dark_mode.rs
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
use gloo_storage::{LocalStorage, Storage};
|
||||||
|
use web_sys::window;
|
||||||
|
use yew::prelude::*;
|
||||||
|
|
||||||
|
#[function_component(DarkModeToggle)]
|
||||||
|
pub fn dark_mode_toggle() -> Html {
|
||||||
|
// 1. Initialize state from LocalStorage or system preference
|
||||||
|
let is_dark = use_state(|| {
|
||||||
|
LocalStorage::get::<bool>("dark-mode").unwrap_or_else(|_| {
|
||||||
|
// Fallback to system preference if not set
|
||||||
|
window()
|
||||||
|
.and_then(|w| w.match_media("(prefers-color-scheme: dark)").ok().flatten())
|
||||||
|
.map(|m| m.matches())
|
||||||
|
.unwrap_or(false)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. Synchronize the class on the body when the state changes
|
||||||
|
{
|
||||||
|
let is_dark = is_dark.clone();
|
||||||
|
use_effect_with(is_dark, |is_dark| {
|
||||||
|
if let Some(win) = window() {
|
||||||
|
if let Some(doc) = win.document() {
|
||||||
|
if let Some(body) = doc.body() {
|
||||||
|
if **is_dark {
|
||||||
|
let _ = body.class_list().add_1("theme-dark");
|
||||||
|
let _ = body.class_list().remove_1("theme-light");
|
||||||
|
let _ = LocalStorage::set("dark-mode", true);
|
||||||
|
} else {
|
||||||
|
let _ = body.class_list().add_1("theme-light");
|
||||||
|
let _ = body.class_list().remove_1("theme-dark");
|
||||||
|
let _ = LocalStorage::set("dark-mode", false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|| ()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Toggle action
|
||||||
|
let onclick = {
|
||||||
|
let is_dark = is_dark.clone();
|
||||||
|
Callback::from(move |_| {
|
||||||
|
is_dark.set(!*is_dark);
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<button class="dark-mode-toggle" {onclick}>
|
||||||
|
if *is_dark {
|
||||||
|
// Sun Icon for switching to light mode
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<circle cx="12" cy="12" r="4"/>
|
||||||
|
<path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41"/>
|
||||||
|
</svg>
|
||||||
|
} else {
|
||||||
|
// Moon Icon for switching to dark mode
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/>
|
||||||
|
</svg>
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
mod auth;
|
mod auth;
|
||||||
mod pages;
|
mod pages;
|
||||||
|
mod dark_mode;
|
||||||
use crate::auth::ProtectedRoute;
|
use crate::auth::ProtectedRoute;
|
||||||
use crate::pages::*;
|
use crate::pages::*;
|
||||||
use gloo_net::http::Request;
|
use gloo_net::http::Request;
|
||||||
@@ -271,6 +272,7 @@ fn switch(route: Route) -> Html {
|
|||||||
pub fn app() -> Html {
|
pub fn app() -> Html {
|
||||||
html! {
|
html! {
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
|
<dark_mode::DarkModeToggle />
|
||||||
<Switch<Route> render={switch} />
|
<Switch<Route> render={switch} />
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -386,7 +386,12 @@ pub fn sidebar() -> Html {
|
|||||||
<SidebarStateProvider>
|
<SidebarStateProvider>
|
||||||
<nav class="sidebar user">
|
<nav class="sidebar user">
|
||||||
<ul>
|
<ul>
|
||||||
<Link<crate::Route> to={crate::Route::Home}>{ "" }</Link<crate::Route>>
|
<Link<crate::Route> to={crate::Route::Home} classes="home">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="home-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
|
||||||
|
<polyline points="9 22 9 12 15 12 15 22"/>
|
||||||
|
</svg>
|
||||||
|
</Link<crate::Route>>
|
||||||
<TicketMenu/>
|
<TicketMenu/>
|
||||||
<li class="logout-item">
|
<li class="logout-item">
|
||||||
<button
|
<button
|
||||||
@@ -406,7 +411,12 @@ pub fn sidebar() -> Html {
|
|||||||
<SidebarStateProvider>
|
<SidebarStateProvider>
|
||||||
<nav class="sidebar admin">
|
<nav class="sidebar admin">
|
||||||
<ul>
|
<ul>
|
||||||
<Link<crate::Route> to={crate::Route::Home}>{ "" }</Link<crate::Route>>
|
<Link<crate::Route> to={crate::Route::Home} classes="home">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="home-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
|
||||||
|
<polyline points="9 22 9 12 15 12 15 22"/>
|
||||||
|
</svg>
|
||||||
|
</Link<crate::Route>>
|
||||||
<TicketMenu/>
|
<TicketMenu/>
|
||||||
<UsersMenu/>
|
<UsersMenu/>
|
||||||
<Link<crate::Route> to={crate::Route::Diagnostics}>{ "Statistiken" }</Link<crate::Route>>
|
<Link<crate::Route> to={crate::Route::Diagnostics}>{ "Statistiken" }</Link<crate::Route>>
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
// Color Palette (Reference Style)
|
// Color Palette (Reference Style)
|
||||||
$color-bg: #f0f2f5;
|
$color-bg: var(--color-bg);
|
||||||
$color-bg-dark: #121212;
|
$color-bg-dark: var(--color-bg-dark);
|
||||||
$color-container: #ffffff;
|
$color-container: var(--color-container);
|
||||||
$color-container-dark: #333333;
|
$color-container-dark: var(--color-container-dark);
|
||||||
$color-sidebar: #0f172a;
|
$color-sidebar: #0f172a;
|
||||||
$color-primary: #2b79c2;
|
$color-primary: #2b79c2;
|
||||||
$color-primary-hover: #1d5fa0;
|
$color-primary-hover: #1d5fa0;
|
||||||
$color-accent: #2b79c2;
|
$color-accent: #2b79c2;
|
||||||
$color-muted: #6b7280;
|
$color-muted: #6b7280;
|
||||||
$color-text: #111827;
|
$color-text: var(--color-text);
|
||||||
$color-text-dark: #e2e2e2;
|
$color-text-dark: var(--color-text-dark);
|
||||||
|
|
||||||
// Status Colors
|
// Status Colors
|
||||||
$color-status-todo: #ffcccc;
|
$color-status-todo: #ffcccc;
|
||||||
|
|||||||
75
frontend/src/styles/components/_dark_mode.scss
Normal file
75
frontend/src/styles/components/_dark_mode.scss
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
:root {
|
||||||
|
--color-bg: #f0f2f5;
|
||||||
|
--color-bg-dark: #121212;
|
||||||
|
--color-container: #ffffff;
|
||||||
|
--color-container-dark: #333333;
|
||||||
|
--color-text: #111827;
|
||||||
|
--color-text-dark: #e2e2e2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Automatically respect standard media query if no override is set
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--color-bg: #121212;
|
||||||
|
--color-container: #333333;
|
||||||
|
--color-text: #e2e2e2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force Light Mode overrides
|
||||||
|
body.theme-light {
|
||||||
|
--color-bg: #f0f2f5;
|
||||||
|
--color-bg-dark: #f0f2f5;
|
||||||
|
--color-container: #ffffff;
|
||||||
|
--color-container-dark: #ffffff;
|
||||||
|
--color-text: #111827;
|
||||||
|
--color-text-dark: #111827;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force Dark Mode overrides
|
||||||
|
body.theme-dark {
|
||||||
|
--color-bg: #121212;
|
||||||
|
--color-bg-dark: #121212;
|
||||||
|
--color-container: #333333;
|
||||||
|
--color-container-dark: #333333;
|
||||||
|
--color-text: #e2e2e2;
|
||||||
|
--color-text-dark: #e2e2e2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fixed positioning ensures NO other component's layout is ever altered
|
||||||
|
.dark-mode-toggle {
|
||||||
|
position: fixed;
|
||||||
|
top: 1rem;
|
||||||
|
right: 1rem;
|
||||||
|
z-index: 9999;
|
||||||
|
width: 55px;
|
||||||
|
height: 55px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 1px solid rgba(128, 128, 128, 0.2);
|
||||||
|
background-color: var(--color-container);
|
||||||
|
color: var(--color-text);
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
stroke: var(--color-text);
|
||||||
|
transition: stroke 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.15);
|
||||||
|
background-color: var(--color-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -49,6 +49,18 @@
|
|||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.home {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-svg {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
&.user { width: 220px; background: darken($color-sidebar, 6%); }
|
&.user { width: 220px; background: darken($color-sidebar, 6%); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
@use "components/diagnostics";
|
@use "components/diagnostics";
|
||||||
@use "components/pages";
|
@use "components/pages";
|
||||||
@use "components/setup";
|
@use "components/setup";
|
||||||
|
@use "components/dark_mode";
|
||||||
|
|
||||||
* {
|
* {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|||||||
Reference in New Issue
Block a user