diff --git a/web/index.html b/web/index.html index b764f311..fabb2eee 100644 --- a/web/index.html +++ b/web/index.html @@ -8,8 +8,7 @@ /> - - + @@ -25,13 +24,6 @@ - - + + HAPI diff --git a/web/src/hooks/useTheme.test.ts b/web/src/hooks/useTheme.test.ts new file mode 100644 index 00000000..9468320a --- /dev/null +++ b/web/src/hooks/useTheme.test.ts @@ -0,0 +1,46 @@ +import { act, renderHook } from '@testing-library/react' +import { beforeEach, describe, expect, it } from 'vitest' +import { getThemeColor, initializeTheme, useAppearance } from '@/hooks/useTheme' + +describe('useTheme', () => { + beforeEach(() => { + localStorage.clear() + document.documentElement.removeAttribute('data-theme') + document.head.querySelectorAll('meta[name="theme-color"]').forEach((meta) => meta.remove()) + }) + + it('applies the stored dark appearance to the document and browser theme color', () => { + localStorage.setItem('hapi-appearance', 'dark') + + initializeTheme() + + expect(document.documentElement).toHaveAttribute('data-theme', 'dark') + expect(document.querySelector('meta[name="theme-color"]')?.content).toBe(getThemeColor('dark')) + }) + + it('creates a browser theme color meta tag when the page does not provide one', () => { + initializeTheme() + + const meta = document.querySelector('meta[name="theme-color"]') + expect(meta?.content).toBe(getThemeColor('light')) + expect(meta?.hasAttribute('media')).toBe(false) + }) + + it('updates the browser theme color when appearance changes', () => { + const { result } = renderHook(() => useAppearance()) + + act(() => { + result.current.setAppearance('dark') + }) + + expect(document.documentElement).toHaveAttribute('data-theme', 'dark') + expect(document.querySelector('meta[name="theme-color"]')?.content).toBe(getThemeColor('dark')) + + act(() => { + result.current.setAppearance('light') + }) + + expect(document.documentElement).toHaveAttribute('data-theme', 'light') + expect(document.querySelector('meta[name="theme-color"]')?.content).toBe(getThemeColor('light')) + }) +}) diff --git a/web/src/hooks/useTheme.ts b/web/src/hooks/useTheme.ts index 6d177fcb..7fda73dd 100644 --- a/web/src/hooks/useTheme.ts +++ b/web/src/hooks/useTheme.ts @@ -6,6 +6,10 @@ type ColorScheme = 'light' | 'dark' export type AppearancePreference = 'system' | 'dark' | 'light' const APPEARANCE_KEY = 'hapi-appearance' +const THEME_COLORS: Record = { + light: '#ffffff', + dark: '#1c1c1e', +} function isBrowser(): boolean { return typeof window !== 'undefined' && typeof document !== 'undefined' @@ -77,8 +81,27 @@ function isIOS(): boolean { return /iPad|iPhone|iPod/.test(navigator.userAgent) } +function applyBrowserThemeColor(scheme: ColorScheme): void { + if (!isBrowser()) return + + let meta = document.querySelector('meta[name="theme-color"]') + if (!meta) { + meta = document.createElement('meta') + meta.name = 'theme-color' + document.head.appendChild(meta) + } + + meta.content = THEME_COLORS[scheme] + meta.removeAttribute('media') +} + +export function getThemeColor(scheme: ColorScheme): string { + return THEME_COLORS[scheme] +} + function applyTheme(scheme: ColorScheme): void { document.documentElement.setAttribute('data-theme', scheme) + applyBrowserThemeColor(scheme) } function applyPlatform(): void {