mirror of
https://github.com/nexu-io/open-design.git
synced 2026-10-27 09:51:12 +01:00
* feat(web): add dark mode support with system/light/dark toggle Closes #93. Adds a three-way theme toggle (System / Light / Dark) to Settings > Appearance. The chosen preference is persisted in localStorage alongside the existing config and applied immediately via a data-theme attribute on <html>. Implementation - AppTheme type ('system' | 'light' | 'dark') added to AppConfig - CSS custom properties for the dark palette defined under [data-theme="dark"] and @media (prefers-color-scheme: dark) so the System option follows the OS without JavaScript - Appearance section added to SettingsDialog with a sun-moon icon - Several hardcoded light-only colour values replaced with CSS variable equivalents so they adapt correctly in dark mode - i18n: appearance/theme strings added to all 11 supported locales Note: theme variables are currently inlined in index.css. A follow-up could extract them into per-theme files to support community themes (e.g. Catppuccin) -- happy to explore that direction if it fits the project roadmap. Also fixes (pre-existing) - Syntax error in locales/tr.ts that was masking missing-key errors - Missing translation keys in de, es-ES, ja, tr locales - locales.test.ts EXPECTED_LOCALES not updated after Korean was added * fix(web): apply theme before first paint to prevent FOUC Add a synchronous inline script in layout.tsx that reads the saved theme from localStorage and sets data-theme on <html> before React hydrates, eliminating the flash of unstyled content that occurred when the theme was only applied inside a useEffect. * fix(web): use useLayoutEffect for theme sync to avoid 1-frame flash useEffect fires after the browser paints, so switching themes in Settings could show a single-frame flicker of the old theme. useLayoutEffect fires synchronously before the paint, making theme transitions atomic. Safe because the component tree is rendered with ssr:false. * fix(web): fix 3-column theme selector layout and add seg-title overflow guard .seg-control used a hardcoded 2-column grid; the theme toggle has 3 buttons so Dark was falling onto a second row. Switch to a CSS custom property (--seg-cols, default 2) so each usage can declare its own column count. Also add white-space/overflow/text-overflow to .seg-title so long translations never overflow their cell. * fix(i18n): add missing maxTokens keys to Dict and Korean locale settings.maxTokens and settings.maxTokensHint were used in SettingsDialog and present in all locale files but absent from the Dict interface, causing typecheck to fail. Also adds the two keys to the Korean locale to restore full locale parity. Fixes the blocking typecheck failure flagged in PR review. * fix(i18n): remove duplicate maxTokens keys in ko.ts and Dict Rebase introduced duplicate settings.maxTokens and settings.maxTokensHint in both ko.ts and types.ts (Dict interface), causing TS1117/TS2300 errors. Remove the second occurrence of each to restore clean typecheck. * test(i18n): add pl to EXPECTED_LOCALES in locales.test.ts Polish locale was added upstream in #273; update the hard-coded list so the locale parity test passes on CI. * fix(web): live-preview theme in Settings before Save AppearanceSection now applies data-theme to <html> immediately on toggle via useLayoutEffect, so the page re-themes in real time while the dialog is open. SettingsDialogs useLayoutEffect cleanup reverts to the original saved theme when the dialog is dismissed without saving.