Skip to content

Commit 7488b95

Browse files
committed
feat: implement Root component for HTML structure and theme handling
1 parent b3e1c9f commit 7488b95

2 files changed

Lines changed: 60 additions & 0 deletions

File tree

‎app/+html.tsx‎

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { ScrollViewStyleReset } from 'expo-router/html'
2+
import type { PropsWithChildren } from 'react'
3+
4+
// Keep in sync with lib/theme.ts (createMaterialTheme('#60A5FA', { variant: 'vibrant' })).
5+
const LIGHT_BACKGROUND = '#f8f9ff'
6+
const DARK_BACKGROUND = '#0e141c'
7+
8+
/**
9+
* Static rendering bakes the light theme into the HTML, so dark-mode visitors
10+
* would see a light page until the bundle hydrates and re-renders. This shell
11+
* paints the correct background before any JS runs and keeps the light-baked
12+
* content hidden for dark-mode visitors until the client applies the real
13+
* theme (`theme-ready`, added by hooks/use-color-scheme.web.ts). If the bundle
14+
* never runs, the animation fallback reveals the content after 4s.
15+
*/
16+
const themeGuardCss = `
17+
:root {
18+
color-scheme: light dark;
19+
background-color: ${LIGHT_BACKGROUND};
20+
}
21+
@media (prefers-color-scheme: dark) {
22+
:root {
23+
background-color: ${DARK_BACKGROUND};
24+
}
25+
body:not(.theme-ready) {
26+
visibility: hidden;
27+
animation: theme-guard-timeout 0s 4s forwards;
28+
}
29+
@keyframes theme-guard-timeout {
30+
to { visibility: visible; }
31+
}
32+
}
33+
`
34+
35+
export default function Root({ children }: PropsWithChildren) {
36+
return (
37+
<html lang="en">
38+
<head>
39+
<meta charSet="utf-8" />
40+
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
41+
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
42+
<ScrollViewStyleReset />
43+
<style dangerouslySetInnerHTML={{ __html: themeGuardCss }} />
44+
<noscript>
45+
<style dangerouslySetInnerHTML={{ __html: 'body { visibility: visible !important; }' }} />
46+
</noscript>
47+
</head>
48+
<body>{children}</body>
49+
</html>
50+
)
51+
}

‎hooks/use-color-scheme.web.ts‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,15 @@ export function useColorScheme() {
1313
setHasHydrated(true)
1414
}, [])
1515

16+
// Runs after the commit that rendered the real scheme, so app/+html.tsx can
17+
// keep the light-baked static content hidden for dark-mode visitors until
18+
// the correct theme is actually in the DOM.
19+
useEffect(() => {
20+
if (hasHydrated) {
21+
document.body.classList.add('theme-ready')
22+
}
23+
}, [hasHydrated])
24+
1625
const scheme = useSystemColorScheme()
1726
return hasHydrated ? scheme : 'light'
1827
}

0 commit comments

Comments
 (0)