added print, download as pdf. no puppeteer, we'll see
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
import React, { createContext, useState, useEffect, useContext, useCallback } from 'react';
|
||||
|
||||
/**
|
||||
* Theme selection, applied as `data-theme` on <html>.
|
||||
*
|
||||
* The attribute lives on documentElement rather than on a React root div so
|
||||
* that anything rendered outside the tree — portals, overlays, the native
|
||||
* scrollbar via color-scheme — picks the theme up too.
|
||||
*
|
||||
* `classic` is the default on purpose: an operator's screen must not change
|
||||
* because a new theme shipped. Cockpit is opt-in, per browser.
|
||||
*/
|
||||
|
||||
const ThemeContext = createContext(null);
|
||||
|
||||
export const THEMES = [
|
||||
{ id: 'classic', label: 'Classic' },
|
||||
{ id: 'cockpit', label: 'Cockpit' }
|
||||
];
|
||||
|
||||
const STORAGE_KEY = 'scalesapp.theme';
|
||||
const DEFAULT_THEME = 'classic';
|
||||
|
||||
const isValid = (id) => THEMES.some(t => t.id === id);
|
||||
|
||||
/** Read the stored choice. localStorage throws in some privacy modes, and can
|
||||
* hold a theme id that no longer exists, so both are handled here once. */
|
||||
const readStored = () => {
|
||||
try {
|
||||
const saved = localStorage.getItem(STORAGE_KEY);
|
||||
return isValid(saved) ? saved : DEFAULT_THEME;
|
||||
} catch {
|
||||
return DEFAULT_THEME;
|
||||
}
|
||||
};
|
||||
|
||||
export const ThemeProvider = ({ children }) => {
|
||||
const [theme, setThemeState] = useState(readStored);
|
||||
|
||||
useEffect(() => {
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
}, [theme]);
|
||||
|
||||
const setTheme = useCallback((id) => {
|
||||
if (!isValid(id)) return;
|
||||
setThemeState(id);
|
||||
try {
|
||||
localStorage.setItem(STORAGE_KEY, id);
|
||||
} catch {
|
||||
// A theme that cannot be remembered is still worth applying for this
|
||||
// session, so a storage failure is not an error the user needs to see.
|
||||
}
|
||||
}, []);
|
||||
|
||||
const toggleTheme = useCallback(() => {
|
||||
setThemeState(prev => {
|
||||
const next = prev === 'classic' ? 'cockpit' : 'classic';
|
||||
try {
|
||||
localStorage.setItem(STORAGE_KEY, next);
|
||||
} catch { /* see above */ }
|
||||
return next;
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ThemeContext.Provider value={{ theme, setTheme, toggleTheme, themes: THEMES }}>
|
||||
{children}
|
||||
</ThemeContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useTheme = () => {
|
||||
const ctx = useContext(ThemeContext);
|
||||
if (!ctx) throw new Error('useTheme must be used within a ThemeProvider');
|
||||
return ctx;
|
||||
};
|
||||
|
||||
export default ThemeContext;
|
||||
Reference in New Issue
Block a user