From 599f5e7d56a36b68f45b08f6c5d4aa127da7eb8d Mon Sep 17 00:00:00 2001 From: Gautier Darchen Date: Thu, 4 Jul 2024 12:01:19 +0200 Subject: [PATCH] refactor(theme): default the theme to the system one --- src/components/theme-switch/ThemeSwitch.tsx | 131 ++++++++++++++++---- 1 file changed, 109 insertions(+), 22 deletions(-) diff --git a/src/components/theme-switch/ThemeSwitch.tsx b/src/components/theme-switch/ThemeSwitch.tsx index 2d86dd7..d6dd16e 100644 --- a/src/components/theme-switch/ThemeSwitch.tsx +++ b/src/components/theme-switch/ThemeSwitch.tsx @@ -1,33 +1,80 @@ 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( + '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) { @@ -35,17 +82,57 @@ const ThemeSwitch: FC = () => { } return ( - +
+ + + {theme === 'dark' || (theme === undefined && isSystemDark) ? ( + + ) : ( + + )} + + + +
+ + + + + + + + + + + +
+
+
+
) }