Dark mode contrast, tuner display and misc UI fixes

- color-scheme per theme so native controls (selects, checkboxes, number
  inputs, sliders) render dark-native instead of black-on-black.
- Themed .control-select text color; new .control-input class replaces
  hardcoded white-on-black inline input styles.
- Tuner widget stays mounted when enabled: idle state (dimmed, needle parked,
  '—') instead of appearing/disappearing. Fully theme-var based (was
  hardcoded white — invisible in light mode).
- Remove CSS transitions on per-frame values (tuner needle, level meter):
  transitions interrupted 60x/sec are left stuck by WebKit/Safari; the
  display EMA from the detection loop replaces them.
- VexFlow notation: resolve CSS var() to concrete colors (SVG attributes
  can't resolve var(), silently fell back to black) and override VexFlow's
  hardcoded #444 ledger-line default; re-render on theme changes
  (useLayoutEffect so children read the updated attribute in time).
- Themed switch toggles (were hardcoded #ddd / white — glowed in dark mode).
- Fixed-height pitch readout so the layout stops jumping when a note starts
  or stops sounding.
- Split SettingsContext into context/Provider/hook files (fast-refresh);
  memoized provider functions; typed webkitAudioContext; prefer-const;
  removed 'as any' casts. ESLint now passes clean.
This commit is contained in:
2026-09-13 18:52:27 +02:00
parent 079d574b66
commit 6e60ced477
13 changed files with 212 additions and 121 deletions

View File

@@ -1,36 +0,0 @@
import React, { createContext, useContext, useState, type ReactNode } from 'react';
import { type AppSettings, DEFAULT_SETTINGS } from '../types/SettingsTypes';
interface SettingsContextType {
settings: AppSettings;
updateSettings: (newSettings: AppSettings | ((prev: AppSettings) => AppSettings)) => void;
updateSetting: <K extends keyof AppSettings>(key: K, value: AppSettings[K]) => void;
}
const SettingsContext = createContext<SettingsContextType | undefined>(undefined);
export const SettingsProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
const [settings, setSettings] = useState<AppSettings>(DEFAULT_SETTINGS);
const updateSettings = (newSettings: AppSettings | ((prev: AppSettings) => AppSettings)) => {
setSettings(newSettings);
};
const updateSetting = <K extends keyof AppSettings>(key: K, value: AppSettings[K]) => {
setSettings(prev => ({ ...prev, [key]: value }));
};
return (
<SettingsContext.Provider value={{ settings, updateSettings, updateSetting }}>
{children}
</SettingsContext.Provider>
);
};
export const useSettings = () => {
const context = useContext(SettingsContext);
if (context === undefined) {
throw new Error('useSettings must be used within a SettingsProvider');
}
return context;
};

View File

@@ -0,0 +1,27 @@
import React, { useCallback, useMemo, useState, type ReactNode } from 'react';
import { type AppSettings, DEFAULT_SETTINGS } from '../types/SettingsTypes';
import { SettingsContext, type SettingsContextType } from './settings-context';
export const SettingsProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
const [settings, setSettings] = useState<AppSettings>(DEFAULT_SETTINGS);
// Stable identities so consumers can safely use these in effect deps
const updateSettings = useCallback<SettingsContextType['updateSettings']>((newSettings) => {
setSettings(newSettings);
}, []);
const updateSetting = useCallback<SettingsContextType['updateSetting']>((key, value) => {
setSettings(prev => ({ ...prev, [key]: value }));
}, []);
const value = useMemo<SettingsContextType>(
() => ({ settings, updateSettings, updateSetting }),
[settings, updateSettings, updateSetting]
);
return (
<SettingsContext.Provider value={value}>
{children}
</SettingsContext.Provider>
);
};

View File

@@ -0,0 +1,10 @@
import { createContext } from 'react';
import { type AppSettings } from '../types/SettingsTypes';
export interface SettingsContextType {
settings: AppSettings;
updateSettings: (newSettings: AppSettings | ((prev: AppSettings) => AppSettings)) => void;
updateSetting: <K extends keyof AppSettings>(key: K, value: AppSettings[K]) => void;
}
export const SettingsContext = createContext<SettingsContextType | undefined>(undefined);

View File

@@ -0,0 +1,10 @@
import { useContext } from 'react';
import { SettingsContext } from './settings-context';
export const useSettings = () => {
const context = useContext(SettingsContext);
if (context === undefined) {
throw new Error('useSettings must be used within a SettingsProvider');
}
return context;
};