import React, { createContext, useState, useEffect, useContext, useCallback } from 'react'; /** * Theme selection, applied as `data-theme` on . * * 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 ( {children} ); }; export const useTheme = () => { const ctx = useContext(ThemeContext); if (!ctx) throw new Error('useTheme must be used within a ThemeProvider'); return ctx; }; export default ThemeContext;