feat: vylepšená podpora themes
CI / Generate TypeScript types (push) Successful in 1m3s
CI / Server unit tests (push) Successful in 30s
CI / Build server (push) Successful in 25s
CI / Build client (push) Successful in 36s
CI / Playwright E2E tests (push) Successful in 1m20s
CI / Build and push Docker image (push) Successful in 2m46s
CI / Notify (push) Successful in 1s
CI / Generate TypeScript types (push) Successful in 1m3s
CI / Server unit tests (push) Successful in 30s
CI / Build server (push) Successful in 25s
CI / Build client (push) Successful in 36s
CI / Playwright E2E tests (push) Successful in 1m20s
CI / Build and push Docker image (push) Successful in 2m46s
CI / Notify (push) Successful in 1s
This commit is contained in:
@@ -87,42 +87,6 @@
|
||||
--luncher-shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.5), 0 4px 6px -4px rgb(0 0 0 / 0.4);
|
||||
}
|
||||
|
||||
// Modrý motiv – světlý
|
||||
[data-luncher-color="blue"][data-bs-theme="light"] {
|
||||
--luncher-primary: #2563eb;
|
||||
--luncher-primary-hover: #1d4ed8;
|
||||
--luncher-primary-light: #dbeafe;
|
||||
--luncher-action-icon: #2563eb;
|
||||
--luncher-success: #2563eb;
|
||||
}
|
||||
|
||||
// Modrý motiv – tmavý
|
||||
[data-luncher-color="blue"][data-bs-theme="dark"] {
|
||||
--luncher-primary: #3b82f6;
|
||||
--luncher-primary-hover: #60a5fa;
|
||||
--luncher-primary-light: #172554;
|
||||
--luncher-action-icon: #3b82f6;
|
||||
--luncher-success: #3b82f6;
|
||||
}
|
||||
|
||||
// Fialový motiv – světlý
|
||||
[data-luncher-color="purple"][data-bs-theme="light"] {
|
||||
--luncher-primary: #7c3aed;
|
||||
--luncher-primary-hover: #6d28d9;
|
||||
--luncher-primary-light: #ede9fe;
|
||||
--luncher-action-icon: #7c3aed;
|
||||
--luncher-success: #7c3aed;
|
||||
}
|
||||
|
||||
// Fialový motiv – tmavý
|
||||
[data-luncher-color="purple"][data-bs-theme="dark"] {
|
||||
--luncher-primary: #a78bfa;
|
||||
--luncher-primary-hover: #c4b5fd;
|
||||
--luncher-primary-light: #2e1065;
|
||||
--luncher-action-icon: #a78bfa;
|
||||
--luncher-success: #a78bfa;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// BASE STYLES
|
||||
// ============================================
|
||||
@@ -263,32 +227,6 @@ body {
|
||||
transform: rotate(15deg);
|
||||
}
|
||||
|
||||
// Přizpůsobení pro NavDropdown (palette ikona)
|
||||
&.nav-item.dropdown {
|
||||
.dropdown-toggle {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--luncher-navbar-text);
|
||||
padding: 8px 12px;
|
||||
font-size: 1.1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
|
||||
@@ -2,7 +2,8 @@ import { useEffect, useState } from "react";
|
||||
import { Navbar, Nav, NavDropdown, Modal, Button } from "react-bootstrap";
|
||||
import { useAuth } from "../context/auth";
|
||||
import SettingsModal from "./modals/SettingsModal";
|
||||
import { useSettings, ThemePreference, ColorTheme } from "../context/settings";
|
||||
import { useSettings, ThemePreference } from "../context/settings";
|
||||
import HuePicker from "./HuePicker";
|
||||
import FeaturesVotingModal from "./modals/FeaturesVotingModal";
|
||||
import PizzaCalculatorModal from "./modals/PizzaCalculatorModal";
|
||||
import RefreshMenuModal from "./modals/RefreshMenuModal";
|
||||
@@ -13,7 +14,7 @@ import { useNavigate } from "react-router";
|
||||
import { STATS_URL, OBJEDNANI_URL } from "../AppRoutes";
|
||||
import { FeatureRequest, getVotes, updateVote, LunchChoices, getChangelogs } from "../../../types";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faSun, faMoon, faPalette } from "@fortawesome/free-solid-svg-icons";
|
||||
import { faSun, faMoon } from "@fortawesome/free-solid-svg-icons";
|
||||
import { formatDateString } from "../Utils";
|
||||
|
||||
const LAST_SEEN_CHANGELOG_KEY = "lastChangelogDate";
|
||||
@@ -40,25 +41,7 @@ export default function Header({ choices, dayIndex }: Props) {
|
||||
const [clearMockModalOpen, setClearMockModalOpen] = useState<boolean>(false);
|
||||
const [featureVotes, setFeatureVotes] = useState<FeatureRequest[] | undefined>([]);
|
||||
|
||||
// Zjistíme aktuální efektivní téma (pro zobrazení správné ikony)
|
||||
const [effectiveTheme, setEffectiveTheme] = useState<'light' | 'dark'>('light');
|
||||
|
||||
useEffect(() => {
|
||||
const updateEffectiveTheme = () => {
|
||||
if (settings?.themePreference === 'system') {
|
||||
const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
setEffectiveTheme(isDark ? 'dark' : 'light');
|
||||
} else {
|
||||
setEffectiveTheme(settings?.themePreference || 'light');
|
||||
}
|
||||
};
|
||||
|
||||
updateEffectiveTheme();
|
||||
|
||||
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
mediaQuery.addEventListener('change', updateEffectiveTheme);
|
||||
return () => mediaQuery.removeEventListener('change', updateEffectiveTheme);
|
||||
}, [settings?.themePreference]);
|
||||
const effectiveDark = settings?.effectiveDark ?? false;
|
||||
|
||||
useEffect(() => {
|
||||
if (auth?.login) {
|
||||
@@ -110,8 +93,7 @@ export default function Header({ choices, dayIndex }: Props) {
|
||||
}
|
||||
|
||||
const toggleTheme = () => {
|
||||
// Přepínáme mezi light a dark (ignorujeme system pro jednoduchost)
|
||||
const newTheme: ThemePreference = effectiveTheme === 'dark' ? 'light' : 'dark';
|
||||
const newTheme: ThemePreference = effectiveDark ? 'light' : 'dark';
|
||||
settings?.setThemePreference(newTheme);
|
||||
}
|
||||
|
||||
@@ -195,21 +177,16 @@ export default function Header({ choices, dayIndex }: Props) {
|
||||
<button
|
||||
className="theme-toggle"
|
||||
onClick={toggleTheme}
|
||||
title={effectiveTheme === 'dark' ? 'Přepnout na světlý režim' : 'Přepnout na tmavý režim'}
|
||||
title={effectiveDark ? 'Přepnout na světlý režim' : 'Přepnout na tmavý režim'}
|
||||
aria-label="Přepnout světlý/tmavý režim"
|
||||
>
|
||||
<FontAwesomeIcon icon={effectiveTheme === 'dark' ? faSun : faMoon} />
|
||||
<FontAwesomeIcon icon={effectiveDark ? faSun : faMoon} />
|
||||
</button>
|
||||
<NavDropdown
|
||||
align="end"
|
||||
title={<FontAwesomeIcon icon={faPalette} />}
|
||||
id="color-theme-dropdown"
|
||||
className="theme-toggle"
|
||||
>
|
||||
<NavDropdown.Item active={settings?.colorTheme === 'green'} onClick={() => settings?.setColorTheme('green' as ColorTheme)}>🟢 Zelený</NavDropdown.Item>
|
||||
<NavDropdown.Item active={settings?.colorTheme === 'blue'} onClick={() => settings?.setColorTheme('blue' as ColorTheme)}>🔵 Modrý</NavDropdown.Item>
|
||||
<NavDropdown.Item active={settings?.colorTheme === 'purple'} onClick={() => settings?.setColorTheme('purple' as ColorTheme)}>🟣 Fialový</NavDropdown.Item>
|
||||
</NavDropdown>
|
||||
<HuePicker
|
||||
accentHue={settings?.accentHue ?? 142}
|
||||
isDark={effectiveDark}
|
||||
onChange={hue => settings?.setAccentHue(hue)}
|
||||
/>
|
||||
<NavDropdown align="end" title={auth?.login} id="basic-nav-dropdown">
|
||||
<NavDropdown.Item onClick={() => setSettingsModalOpen(true)}>Nastavení</NavDropdown.Item>
|
||||
<NavDropdown.Item onClick={() => setRefreshMenuModalOpen(true)}>Přenačtení menu</NavDropdown.Item>
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
.hue-picker-dropdown {
|
||||
.dropdown-toggle {
|
||||
background: transparent !important;
|
||||
border: none !important;
|
||||
color: var(--luncher-navbar-text) !important;
|
||||
padding: 8px 12px;
|
||||
font-size: 1.1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
border-radius: var(--luncher-radius-sm);
|
||||
transition: var(--luncher-transition);
|
||||
|
||||
&::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.1) !important;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.3) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.hue-picker-panel {
|
||||
padding: 0 !important;
|
||||
min-width: 240px;
|
||||
|
||||
.hue-picker-inner {
|
||||
padding: 14px 16px;
|
||||
}
|
||||
|
||||
.hue-picker-label {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
color: var(--luncher-text-secondary);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.hue-slider {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 100%;
|
||||
height: 12px;
|
||||
border-radius: 6px;
|
||||
background: linear-gradient(
|
||||
to right,
|
||||
hsl(0 70% 50%), hsl(30 70% 50%), hsl(60 70% 50%), hsl(90 70% 50%),
|
||||
hsl(120 70% 50%), hsl(150 70% 50%), hsl(180 70% 50%), hsl(210 70% 50%),
|
||||
hsl(240 70% 50%), hsl(270 70% 50%), hsl(300 70% 50%), hsl(330 70% 50%), hsl(360 70% 50%)
|
||||
);
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
margin-bottom: 14px;
|
||||
|
||||
&::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
background: white;
|
||||
border: 2px solid rgba(0, 0, 0, 0.25);
|
||||
cursor: pointer;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
|
||||
transition: transform 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.15);
|
||||
}
|
||||
}
|
||||
|
||||
&::-moz-range-thumb {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
background: white;
|
||||
border: 2px solid rgba(0, 0, 0, 0.25);
|
||||
cursor: pointer;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
.hue-presets {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 14px;
|
||||
|
||||
.hue-swatch {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid transparent;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
transition: transform 0.15s ease, border-color 0.15s ease;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.15);
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-color: var(--luncher-text);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.hue-preview {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding-top: 4px;
|
||||
border-top: 1px solid var(--luncher-border);
|
||||
|
||||
.hue-preview-chip {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: var(--luncher-radius-sm);
|
||||
flex-shrink: 0;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
||||
transition: background 0.2s ease;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 0.8rem;
|
||||
color: var(--luncher-text-secondary);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import { Dropdown } from 'react-bootstrap';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faPalette } from '@fortawesome/free-solid-svg-icons';
|
||||
import './HuePicker.scss';
|
||||
|
||||
const PRESETS = [
|
||||
{ hue: 142, label: 'Zelená' },
|
||||
{ hue: 217, label: 'Modrá' },
|
||||
{ hue: 263, label: 'Fialová' },
|
||||
{ hue: 0, label: 'Červená' },
|
||||
{ hue: 28, label: 'Oranžová' },
|
||||
{ hue: 340, label: 'Růžová' },
|
||||
];
|
||||
|
||||
type Props = {
|
||||
accentHue: number;
|
||||
isDark: boolean;
|
||||
onChange: (hue: number) => void;
|
||||
};
|
||||
|
||||
function swatchColor(hue: number, isDark: boolean): string {
|
||||
return `hsl(${hue} 70% ${isDark ? 55 : 38}%)`;
|
||||
}
|
||||
|
||||
export default function HuePicker({ accentHue, isDark, onChange }: Props) {
|
||||
return (
|
||||
<Dropdown align="end" autoClose="outside" className="hue-picker-dropdown">
|
||||
<Dropdown.Toggle
|
||||
as="button"
|
||||
className="theme-toggle"
|
||||
aria-label="Barva zvýraznění"
|
||||
title="Barva zvýraznění"
|
||||
>
|
||||
<FontAwesomeIcon icon={faPalette} />
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu className="hue-picker-panel">
|
||||
<div className="hue-picker-inner">
|
||||
<div className="hue-picker-label">Barva zvýraznění</div>
|
||||
<input
|
||||
type="range"
|
||||
min={0}
|
||||
max={360}
|
||||
value={accentHue}
|
||||
onChange={e => onChange(parseInt(e.target.value, 10))}
|
||||
className="hue-slider"
|
||||
aria-label="Odstín barvy zvýraznění"
|
||||
/>
|
||||
<div className="hue-presets">
|
||||
{PRESETS.map(p => (
|
||||
<button
|
||||
key={p.hue}
|
||||
className={`hue-swatch${accentHue === p.hue ? ' active' : ''}`}
|
||||
style={{ background: swatchColor(p.hue, isDark) }}
|
||||
title={p.label}
|
||||
onClick={() => onChange(p.hue)}
|
||||
aria-label={p.label}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className="hue-preview">
|
||||
<div
|
||||
className="hue-preview-chip"
|
||||
style={{ background: swatchColor(accentHue, isDark) }}
|
||||
/>
|
||||
<span>Aktuální barva zvýraznění</span>
|
||||
</div>
|
||||
</div>
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
@@ -4,22 +4,23 @@ 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';
|
||||
const COLOR_THEME_KEY = 'color_theme';
|
||||
const ACCENT_HUE_KEY = 'accent_hue';
|
||||
const LEGACY_COLOR_THEME_KEY = 'color_theme';
|
||||
|
||||
export type ThemePreference = 'system' | 'light' | 'dark';
|
||||
export type ColorTheme = 'green' | 'blue' | 'purple';
|
||||
|
||||
export type SettingsContextProps = {
|
||||
bankAccount?: string,
|
||||
holderName?: string,
|
||||
hideSoups?: boolean,
|
||||
themePreference: ThemePreference,
|
||||
colorTheme: ColorTheme,
|
||||
accentHue: number,
|
||||
effectiveDark: boolean,
|
||||
setBankAccountNumber: (accountNumber?: string) => void,
|
||||
setBankAccountHolderName: (holderName?: string) => void,
|
||||
setHideSoupsOption: (hideSoups?: boolean) => void,
|
||||
setThemePreference: (theme: ThemePreference) => void,
|
||||
setColorTheme: (color: ColorTheme) => void,
|
||||
setAccentHue: (hue: number) => void,
|
||||
}
|
||||
|
||||
type ContextProps = {
|
||||
@@ -49,16 +50,58 @@ function getInitialTheme(): ThemePreference {
|
||||
return 'system';
|
||||
}
|
||||
|
||||
function getInitialColorTheme(): ColorTheme {
|
||||
function getInitialAccentHue(): number {
|
||||
try {
|
||||
const saved = localStorage.getItem(COLOR_THEME_KEY) as ColorTheme | null;
|
||||
if (saved && ['green', 'blue', 'purple'].includes(saved)) {
|
||||
return saved;
|
||||
const saved = localStorage.getItem(ACCENT_HUE_KEY);
|
||||
if (saved !== null) {
|
||||
const n = parseInt(saved, 10);
|
||||
if (!isNaN(n) && n >= 0 && n <= 360) return n;
|
||||
}
|
||||
} catch (e) {
|
||||
// Migrace ze starého string formátu (green/blue/purple)
|
||||
const old = localStorage.getItem(LEGACY_COLOR_THEME_KEY);
|
||||
if (old === 'blue') return 217;
|
||||
if (old === 'purple') return 263;
|
||||
} catch {
|
||||
// localStorage nedostupný
|
||||
}
|
||||
return 'green';
|
||||
return 142;
|
||||
}
|
||||
|
||||
// Převod HSL na relativní jas dle WCAG (pro výpočet kontrastu s bílým textem)
|
||||
function hslToRelativeLuminance(h: number, s: number, l: number): number {
|
||||
const sn = s / 100, ln = l / 100;
|
||||
const a = sn * Math.min(ln, 1 - ln);
|
||||
const ch = (n: number) => {
|
||||
const k = (n + h / 30) % 12;
|
||||
return ln - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
|
||||
};
|
||||
const toLinear = (c: number) => c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
||||
return 0.2126 * toLinear(ch(0)) + 0.7152 * toLinear(ch(8)) + 0.0722 * toLinear(ch(4));
|
||||
}
|
||||
|
||||
// Najde nejnižší světlost, při které má barva dostatečný kontrast s bílým textem (WCAG AA 4.5:1)
|
||||
function adjustedL(hue: number, sat: number, targetL: number): number {
|
||||
let l = targetL;
|
||||
while (l >= 5) {
|
||||
const lum = hslToRelativeLuminance(hue, sat, l);
|
||||
if (1.05 / (lum + 0.05) >= 4.5) return l;
|
||||
l -= 1;
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
function applyAccentColors(hue: number, isDark: boolean): void {
|
||||
const sat = 70;
|
||||
const baseL = adjustedL(hue, sat, isDark ? 55 : 38);
|
||||
const hoverL = isDark ? Math.min(baseL + 10, 80) : Math.max(baseL - 10, 10);
|
||||
const root = document.documentElement;
|
||||
root.style.setProperty('--luncher-primary', `hsl(${hue} ${sat}% ${baseL}%)`);
|
||||
root.style.setProperty('--luncher-primary-hover', `hsl(${hue} ${sat}% ${hoverL}%)`);
|
||||
root.style.setProperty('--luncher-primary-light', isDark
|
||||
? `hsl(${hue} 60% 12%)`
|
||||
: `hsl(${hue} 60% 92%)`);
|
||||
root.style.setProperty('--luncher-action-icon', `hsl(${hue} ${sat}% ${baseL}%)`);
|
||||
root.style.setProperty('--luncher-success', `hsl(${hue} ${sat}% ${baseL}%)`);
|
||||
}
|
||||
|
||||
function useProvideSettings(): SettingsContextProps {
|
||||
@@ -66,7 +109,15 @@ function useProvideSettings(): SettingsContextProps {
|
||||
const [holderName, setHolderName] = useState<string | undefined>();
|
||||
const [hideSoups, setHideSoups] = useState<boolean | undefined>();
|
||||
const [themePreference, setTheme] = useState<ThemePreference>(getInitialTheme);
|
||||
const [colorTheme, setColor] = useState<ColorTheme>(getInitialColorTheme);
|
||||
const [accentHue, setHue] = useState<number>(getInitialAccentHue);
|
||||
const [effectiveDark, setEffectiveDark] = useState<boolean>(() => {
|
||||
try {
|
||||
const pref = localStorage.getItem(THEME_KEY) as ThemePreference | null;
|
||||
if (pref === 'dark') return true;
|
||||
if (pref === 'light') return false;
|
||||
} catch { /* noop */ }
|
||||
return window.matchMedia?.('(prefers-color-scheme: dark)').matches ?? false;
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const accountNumber = localStorage.getItem(BANK_ACCOUNT_NUMBER_KEY);
|
||||
@@ -112,29 +163,27 @@ function useProvideSettings(): SettingsContextProps {
|
||||
}, [themePreference]);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem(COLOR_THEME_KEY, colorTheme);
|
||||
document.documentElement.setAttribute('data-luncher-color', colorTheme);
|
||||
}, [colorTheme]);
|
||||
|
||||
useEffect(() => {
|
||||
const applyTheme = (theme: 'light' | 'dark') => {
|
||||
document.documentElement.setAttribute('data-bs-theme', theme);
|
||||
const applyTheme = (dark: boolean) => {
|
||||
document.documentElement.setAttribute('data-bs-theme', dark ? 'dark' : 'light');
|
||||
setEffectiveDark(dark);
|
||||
};
|
||||
|
||||
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);
|
||||
const mq = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
applyTheme(mq.matches);
|
||||
const handler = (e: MediaQueryListEvent) => applyTheme(e.matches);
|
||||
mq.addEventListener('change', handler);
|
||||
return () => mq.removeEventListener('change', handler);
|
||||
} else {
|
||||
applyTheme(themePreference);
|
||||
applyTheme(themePreference === 'dark');
|
||||
}
|
||||
}, [themePreference]);
|
||||
|
||||
// Aplikuje accent barvy při změně hue nebo přepnutí světlý/tmavý
|
||||
useEffect(() => {
|
||||
localStorage.setItem(ACCENT_HUE_KEY, String(accentHue));
|
||||
applyAccentColors(accentHue, effectiveDark);
|
||||
}, [accentHue, effectiveDark]);
|
||||
|
||||
function setBankAccountNumber(bankAccount?: string) {
|
||||
setBankAccount(bankAccount);
|
||||
}
|
||||
@@ -151,8 +200,8 @@ function useProvideSettings(): SettingsContextProps {
|
||||
setTheme(theme);
|
||||
}
|
||||
|
||||
function setColorTheme(color: ColorTheme) {
|
||||
setColor(color);
|
||||
function setAccentHue(hue: number) {
|
||||
setHue(hue);
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -160,11 +209,12 @@ function useProvideSettings(): SettingsContextProps {
|
||||
holderName,
|
||||
hideSoups,
|
||||
themePreference,
|
||||
colorTheme,
|
||||
accentHue,
|
||||
effectiveDark,
|
||||
setBankAccountNumber,
|
||||
setBankAccountHolderName,
|
||||
setHideSoupsOption,
|
||||
setThemePreference,
|
||||
setColorTheme,
|
||||
setAccentHue,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user