From 6a1da97ef1c35ec2f691373776d415da27bb8883 Mon Sep 17 00:00:00 2001 From: batmanisko Date: Fri, 30 Jan 2026 07:47:03 +0100 Subject: [PATCH] feat: podpora dark mode --- client/index.html | 11 ++++++ client/src/App.scss | 22 +++++++++-- client/src/App.tsx | 2 +- client/src/components/Header.tsx | 7 +++- .../src/components/modals/SettingsModal.tsx | 19 +++++++-- client/src/context/settings.tsx | 39 +++++++++++++++++++ 6 files changed, 89 insertions(+), 11 deletions(-) diff --git a/client/index.html b/client/index.html index a04f1ed..3956158 100644 --- a/client/index.html +++ b/client/index.html @@ -10,6 +10,17 @@ Luncher + diff --git a/client/src/App.scss b/client/src/App.scss index 2e0cd20..c9fd1cf 100644 --- a/client/src/App.scss +++ b/client/src/App.scss @@ -1,3 +1,17 @@ +:root, [data-bs-theme="light"] { + --luncher-navbar-bg: #3c3c3c; + --luncher-action-icon: rgb(0, 89, 255); + --luncher-buyer-icon: #dbba00; + --luncher-text-muted: gray; +} + +[data-bs-theme="dark"] { + --luncher-navbar-bg: #1a1d21; + --luncher-action-icon: #5c9aff; + --luncher-buyer-icon: #ffd700; + --luncher-text-muted: #9ca3af; +} + .App { text-align: center; } @@ -87,7 +101,7 @@ body, } .navbar { - background-color: #3c3c3c; + background-color: var(--luncher-navbar-bg); padding-left: 20px; padding-right: 20px; } @@ -102,14 +116,14 @@ body, .table> :not(caption) { .action-icon { - color: rgb(0, 89, 255); + color: var(--luncher-action-icon); cursor: pointer; margin-left: 10px; padding: 0; } .buyer-icon { - color: #dbba00; + color: var(--luncher-buyer-icon); margin-left: 10px; padding: 0; } @@ -139,7 +153,7 @@ body, } .trusted-icon { - color: rgb(0, 89, 255); + color: var(--luncher-action-icon); margin-right: 10px; } diff --git a/client/src/App.tsx b/client/src/App.tsx index 8ed61e4..c4bbe4f 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -436,7 +436,7 @@ function App() { 0 ? "initial" : "hidden" }} onClick={() => handleDayChange(dayIndex - 1)} /> -

{data.date}

+

{data.date}

handleDayChange(dayIndex + 1)} /> diff --git a/client/src/components/Header.tsx b/client/src/components/Header.tsx index c88f602..7803d9b 100644 --- a/client/src/components/Header.tsx +++ b/client/src/components/Header.tsx @@ -2,7 +2,7 @@ import { useEffect, useState } from "react"; import { Navbar, Nav, NavDropdown } from "react-bootstrap"; import { useAuth } from "../context/auth"; import SettingsModal from "./modals/SettingsModal"; -import { useSettings } from "../context/settings"; +import { useSettings, ThemePreference } from "../context/settings"; import FeaturesVotingModal from "./modals/FeaturesVotingModal"; import PizzaCalculatorModal from "./modals/PizzaCalculatorModal"; import RefreshMenuModal from "./modals/RefreshMenuModal"; @@ -54,7 +54,7 @@ export default function Header() { return n !== Infinity && String(n) === str && n >= 0; } - const saveSettings = (bankAccountNumber?: string, bankAccountHolderName?: string, hideSoupsOption?: boolean) => { + const saveSettings = (bankAccountNumber?: string, bankAccountHolderName?: string, hideSoupsOption?: boolean, themePreference?: ThemePreference) => { if (bankAccountNumber) { try { // Validace kódu banky @@ -99,6 +99,9 @@ export default function Header() { settings?.setBankAccountNumber(bankAccountNumber); settings?.setBankAccountHolderName(bankAccountHolderName); settings?.setHideSoupsOption(hideSoupsOption); + if (themePreference) { + settings?.setThemePreference(themePreference); + } closeSettingsModal(); } diff --git a/client/src/components/modals/SettingsModal.tsx b/client/src/components/modals/SettingsModal.tsx index 4c4ba07..16e42ca 100644 --- a/client/src/components/modals/SettingsModal.tsx +++ b/client/src/components/modals/SettingsModal.tsx @@ -1,11 +1,11 @@ import { useRef } from "react"; -import { Modal, Button } from "react-bootstrap" -import { useSettings } from "../../context/settings"; +import { Modal, Button, Form } from "react-bootstrap" +import { useSettings, ThemePreference } from "../../context/settings"; type Props = { isOpen: boolean, onClose: () => void, - onSave: (bankAccountNumber?: string, bankAccountHolderName?: string, hideSoupsOption?: boolean) => void, + onSave: (bankAccountNumber?: string, bankAccountHolderName?: string, hideSoupsOption?: boolean, themePreference?: ThemePreference) => void, } /** Modální dialog pro uživatelská nastavení. */ @@ -14,12 +14,23 @@ export default function SettingsModal({ isOpen, onClose, onSave }: Readonly(null); const nameRef = useRef(null); const hideSoupsRef = useRef(null); + const themeRef = useRef(null); return

Nastavení

+

Vzhled

+ + Barevný motiv + + + + + + +

Obecné

Skrýt polévky @@ -34,7 +45,7 @@ export default function SettingsModal({ isOpen, onClose, onSave }: Readonly Storno - diff --git a/client/src/context/settings.tsx b/client/src/context/settings.tsx index 8bbdec4..5e67855 100644 --- a/client/src/context/settings.tsx +++ b/client/src/context/settings.tsx @@ -3,14 +3,19 @@ import React, { ReactNode, useContext, useEffect, useState } from "react" const BANK_ACCOUNT_NUMBER_KEY = 'bank_account_number'; const BANK_ACCOUNT_HOLDER_KEY = 'bank_account_holder_name'; const HIDE_SOUPS_KEY = 'hide_soups'; +const THEME_KEY = 'theme_preference'; + +export type ThemePreference = 'system' | 'light' | 'dark'; export type SettingsContextProps = { bankAccount?: string, holderName?: string, hideSoups?: boolean, + themePreference: ThemePreference, setBankAccountNumber: (accountNumber?: string) => void, setBankAccountHolderName: (holderName?: string) => void, setHideSoupsOption: (hideSoups?: boolean) => void, + setThemePreference: (theme: ThemePreference) => void, } type ContextProps = { @@ -32,6 +37,7 @@ function useProvideSettings(): SettingsContextProps { const [bankAccount, setBankAccount] = useState(); const [holderName, setHolderName] = useState(); const [hideSoups, setHideSoups] = useState(); + const [themePreference, setTheme] = useState('system'); useEffect(() => { const accountNumber = localStorage.getItem(BANK_ACCOUNT_NUMBER_KEY); @@ -46,6 +52,10 @@ function useProvideSettings(): SettingsContextProps { if (hideSoups !== null) { setHideSoups(hideSoups === 'true'); } + const savedTheme = localStorage.getItem(THEME_KEY) as ThemePreference | null; + if (savedTheme && ['system', 'light', 'dark'].includes(savedTheme)) { + setTheme(savedTheme); + } }, []) useEffect(() => { @@ -72,6 +82,29 @@ function useProvideSettings(): SettingsContextProps { } }, [hideSoups]); + useEffect(() => { + localStorage.setItem(THEME_KEY, themePreference); + }, [themePreference]); + + useEffect(() => { + const applyTheme = (theme: 'light' | 'dark') => { + document.documentElement.setAttribute('data-bs-theme', theme); + }; + + if (themePreference === 'system') { + const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); + applyTheme(mediaQuery.matches ? 'dark' : 'light'); + + const handler = (e: MediaQueryListEvent) => { + applyTheme(e.matches ? 'dark' : 'light'); + }; + mediaQuery.addEventListener('change', handler); + return () => mediaQuery.removeEventListener('change', handler); + } else { + applyTheme(themePreference); + } + }, [themePreference]); + function setBankAccountNumber(bankAccount?: string) { setBankAccount(bankAccount); } @@ -84,12 +117,18 @@ function useProvideSettings(): SettingsContextProps { setHideSoups(hideSoups); } + function setThemePreference(theme: ThemePreference) { + setTheme(theme); + } + return { bankAccount, holderName, hideSoups, + themePreference, setBankAccountNumber, setBankAccountHolderName, setHideSoupsOption, + setThemePreference, } }