fix(web): sync PWA theme color with app theme (#628)

This commit is contained in:
NightWatcher314
2026-05-17 11:52:48 +08:00
committed by GitHub
parent 7bb7c7d91a
commit 6a81597724
3 changed files with 97 additions and 12 deletions
+28 -12
View File
@@ -8,8 +8,7 @@
/>
<!-- PWA Meta Tags -->
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)" />
<meta name="theme-color" content="#1c1c1e" media="(prefers-color-scheme: dark)" />
<meta name="theme-color" content="#ffffff" />
<meta name="description" content="AI-powered development assistant" />
<!-- iOS PWA Meta Tags -->
@@ -25,13 +24,6 @@
<!-- Safari Pinned Tab -->
<link rel="mask-icon" href="/mask-icon.svg" color="#111827" />
<!-- Prevent flash of white background on dark mode -->
<style>
html { background: #fff }
@media (prefers-color-scheme: dark) {
html { background: #1c1c1e; color-scheme: dark }
}
</style>
<script>
(function () {
try {
@@ -46,10 +38,34 @@
})()
</script>
<script>
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.documentElement.setAttribute('data-theme', 'dark')
}
(function () {
var lightColor = '#ffffff'
var darkColor = '#1c1c1e'
function getInitialScheme() {
try {
var appearance = localStorage.getItem('hapi-appearance')
if (appearance === 'dark' || appearance === 'light') return appearance
} catch (error) {
// Ignore storage errors.
}
return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light'
}
var scheme = getInitialScheme()
var color = scheme === 'dark' ? darkColor : lightColor
document.documentElement.setAttribute('data-theme', scheme)
document.querySelector('meta[name="theme-color"]').setAttribute('content', color)
})()
</script>
<!-- Prevent flash of mismatched background on app launch -->
<style>
html { background: #fff }
html[data-theme="dark"] { background: #1c1c1e; color-scheme: dark }
</style>
<title>HAPI</title>
</head>
+46
View File
@@ -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<HTMLMetaElement>('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<HTMLMetaElement>('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<HTMLMetaElement>('meta[name="theme-color"]')?.content).toBe(getThemeColor('dark'))
act(() => {
result.current.setAppearance('light')
})
expect(document.documentElement).toHaveAttribute('data-theme', 'light')
expect(document.querySelector<HTMLMetaElement>('meta[name="theme-color"]')?.content).toBe(getThemeColor('light'))
})
})
+23
View File
@@ -6,6 +6,10 @@ type ColorScheme = 'light' | 'dark'
export type AppearancePreference = 'system' | 'dark' | 'light'
const APPEARANCE_KEY = 'hapi-appearance'
const THEME_COLORS: Record<ColorScheme, string> = {
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<HTMLMetaElement>('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 {