Skip to content

Commit

Permalink
refactor(theme): default the theme to the system one
Browse files Browse the repository at this point in the history
  • Loading branch information
gdarchen committed Jul 4, 2024
1 parent d285455 commit 599f5e7
Showing 1 changed file with 109 additions and 22 deletions.
131 changes: 109 additions & 22 deletions src/components/theme-switch/ThemeSwitch.tsx
Original file line number Diff line number Diff line change
@@ -1,51 +1,138 @@
import React, { FC, useEffect, useState } from 'react'
import { HiOutlineMoon, HiSun } from 'react-icons/hi'
import { HiOutlineDesktopComputer } from 'react-icons/hi'
import { tv } from 'tailwind-variants'
import { useLocalStorage } from 'usehooks-ts'

import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/react'

const themeIcon = tv({
base: 'size-3/4 lg:size-1/2',
variants: {
active: {
true: 'text-primary',
},
},
})

const menuItem = tv({
base: 'flex w-full cursor-pointer items-center px-4 py-2 text-sm text-gray-800 hover:bg-gray-100/60 dark:text-white dark:hover:bg-gray-600/70',
variants: {
active: {
true: 'bg-gray-100 text-primary dark:bg-gray-600 dark:text-blue-400',
},
},
})

const menuItemIcon = tv({
base: 'mr-4 text-lg',
})

const ThemeSwitch: FC = () => {
const [theme, setTheme] = useLocalStorage('theme', 'dark')
const [isLight, setIsLight] = useState(theme === 'light')
const [isSystemDark, setIsSystemDark] = useState(false)
const [theme, setTheme] = useLocalStorage<string | undefined>(
'theme',
undefined,
)
const [mounted, setMounted] = useState(false)

useEffect(() => {
setIsLight(theme === 'light')
}, [theme])

// Trick to avoid hydration mismatch on SVG tags
useEffect(() => {
setMounted(true)
}, [])

// Event listener to detect system theme changes
useEffect(() => {
document.body.classList.remove('light', 'dark')
document.body.classList.add(theme)
}, [theme])
if (typeof window === 'undefined') {
return
}
// Initialize the state
setIsSystemDark(window.matchMedia('(prefers-color-scheme: dark)').matches)

const handleThemeChange = () => {
setTheme(isLight ? 'dark' : 'light')
// Detect further changes
const listener = (e: MediaQueryListEvent) => setIsSystemDark(e.matches)
const darkThemeMq = window.matchMedia('(prefers-color-scheme: dark)')
darkThemeMq.addEventListener('change', listener)
return () => darkThemeMq.removeEventListener('change', listener)
}, [])

useEffect(() => {
// If the theme is `dark` or `system` (and system theme is `dark`)
if (theme === 'dark' || (theme === undefined && isSystemDark)) {
document.documentElement.classList.add('dark')
} else {
document.documentElement.classList.remove('dark')
}
}, [theme, isSystemDark])

// Callback when a theme is selected
const handleThemeChange = (newTheme: string): void => {
// In case the `system` theme is selected, remove it from the LocalStorage
if (newTheme === 'system') {
setTheme(undefined)
return
}

// Else persist the manually selected theme
setTheme(newTheme)
}

if (!mounted) {
return null
}

return (
<button
aria-label="Use dark mode"
className="flex size-9 items-center justify-center rounded-full text-primary transition-transform hover:bg-hovered-light active:scale-95 dark:text-primary dark:hover:bg-hovered lg:size-10 lg:text-gray-900 lg:dark:text-white"
onClick={handleThemeChange}
>
{theme === 'light' ? (
<HiOutlineMoon className={themeIcon()} />
) : (
<HiSun className={themeIcon()} />
)}
</button>
<div className="relative inline-block text-left font-content">
<Menu>
<MenuButton
aria-label="Use dark mode"
className="flex size-9 items-center justify-center rounded-full text-primary transition-transform hover:bg-hovered-light active:scale-95 dark:text-gray-300 dark:hover:bg-hovered lg:size-10 lg:text-gray-900"
>
{theme === 'dark' || (theme === undefined && isSystemDark) ? (
<HiOutlineMoon className={themeIcon({ active: !!theme })} />
) : (
<HiSun className={themeIcon({ active: !!theme })} />
)}
</MenuButton>

<MenuItems
modal={false}
className="absolute right-0 z-10 mt-2 w-32 origin-top-right rounded-md bg-white shadow-lg ring-1 ring-black/5 transition data-[closed]:scale-95 data-[closed]:opacity-0 data-[enter]:duration-100 data-[leave]:duration-75 data-[enter]:ease-out data-[leave]:ease-in focus:outline-none dark:bg-gray-700"
>
<div className="py-1">
<MenuItem>
<button
className={menuItem({ active: theme === 'light' })}
onClick={() => handleThemeChange('light')}
>
<HiSun className={menuItemIcon()} /> Light
</button>
</MenuItem>

<MenuItem>
<button
className={menuItem({ active: theme === 'dark' })}
onClick={() => handleThemeChange('dark')}
>
<HiOutlineMoon className={menuItemIcon()} /> Dark
</button>
</MenuItem>

<MenuItem>
<button
className={menuItem({ active: theme === undefined })}
onClick={() => {
handleThemeChange('system')
close()
}}
>
<HiOutlineDesktopComputer className={menuItemIcon()} /> System
</button>
</MenuItem>
</div>
</MenuItems>
</Menu>
</div>
)
}

Expand Down

0 comments on commit 599f5e7

Please sign in to comment.