mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
fix(web): sync PWA theme color with app theme (#628)
This commit is contained in:
+28
-12
@@ -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>
|
||||
|
||||
@@ -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'))
|
||||
})
|
||||
})
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user