Skip to content

Commit

Permalink
Fix: UI flash when changing the theme (#131)
Browse files Browse the repository at this point in the history
  • Loading branch information
Bowen7 authored Aug 17, 2024
1 parent 08b60d4 commit 64a8e8d
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 47 deletions.
2 changes: 1 addition & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Toaster } from '@/components/ui/toaster'

export default function App() {
return (
<ThemeProvider defaultTheme="dark" storageKey="theme">
<ThemeProvider>
<Router>
<div className="h-screen w-screen flex flex-col">
<Header />
Expand Down
35 changes: 8 additions & 27 deletions src/components/mode-toggle/index.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,17 @@
import { MoonIcon, SunIcon } from '@radix-ui/react-icons'
import { Button } from '@/components/ui/button'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
import { useTheme } from '@/components/theme-provider'

export function ModeToggle() {
const { setTheme } = useTheme()
const { theme, setTheme } = useTheme()

const onClick = () => {
setTheme(theme === 'dark' ? 'light' : 'dark')
}

return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon" className="h-8 w-8">
<SunIcon className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<MoonIcon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Toggle theme</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setTheme('light')}>
Light
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme('dark')}>
Dark
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme('system')}>
System
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<Button variant="outline" size="icon" className="h-8 w-8" onClick={onClick}>
{theme === 'dark' ? <MoonIcon className="absolute h-[1.2rem] w-[1.2rem]" /> : <SunIcon className="h-[1.2rem] w-[1.2rem]" />}
</Button>
)
}
49 changes: 30 additions & 19 deletions src/components/theme-provider/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createContext, useContext, useEffect, useMemo, useState } from 'react'
import { createContext, useContext, useLayoutEffect, useMemo, useState } from 'react'

type Theme = 'dark' | 'light' | 'system'
type Theme = 'dark' | 'light'

type ThemeProviderProps = {
children: React.ReactNode
Expand All @@ -14,38 +14,49 @@ type ThemeProviderState = {
}

const initialState: ThemeProviderState = {
theme: 'system',
theme: 'light',
setTheme: () => null,
}

const ThemeProviderContext = createContext<ThemeProviderState>(initialState)

// credit: pacocoursey/next-themes
// https://github.com/pacocoursey/next-themes/blob/bf0c5a45eaf6fb2b336a6b93840e4ec572bc08c8/next-themes/src/index.tsx#L218-L236
const disableTransition = () => {
const css = document.createElement('style')
css.appendChild(
document.createTextNode(
`*,*::before,*::after{-webkit-transition:none!important;-moz-transition:none!important;-o-transition:none!important;-ms-transition:none!important;transition:none!important}`,
),
)
document.head.appendChild(css)

return () => {
// Force restyle
;(() => window.getComputedStyle(document.body))()

// Wait for next tick before removing
setTimeout(() => {
document.head.removeChild(css)
}, 1)
}
}

export function ThemeProvider({
children,
defaultTheme = 'system',
storageKey = 'vite-ui-theme',
storageKey = 'theme',
...props
}: ThemeProviderProps) {
const [theme, setTheme] = useState<Theme>(
() => (localStorage.getItem(storageKey) as Theme) || defaultTheme,
() => localStorage.getItem(storageKey) === 'dark' ? 'dark' : 'light',
)

useEffect(() => {
useLayoutEffect(() => {
const root = window.document.documentElement

const enableTransition = disableTransition()
root.classList.remove('light', 'dark')

if (theme === 'system') {
const systemTheme = window.matchMedia('(prefers-color-scheme: dark)')
.matches
? 'dark'
: 'light'

root.classList.add(systemTheme)
return
}

root.classList.add(theme)
enableTransition()
}, [theme])

const value = useMemo(() => ({
Expand Down

0 comments on commit 64a8e8d

Please sign in to comment.