diff --git a/src/app/globals.css b/src/app/globals.css index 8d533c5..d8246e5 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -2,12 +2,14 @@ @import "../../public/assets/css/fonts.css"; @import "tw-animate-css"; -@custom-variant dark (&:is(.dark *)); +/* @custom-variant dark (&:is(.dark *)); */ +@custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *)); * { box-sizing: border-box; } + html, body { @apply h-full bg-background font-irancell font-normal text-foreground; diff --git a/src/components/wrapper/ClientMenuRouteWrapper.tsx.tsx b/src/components/wrapper/ClientMenuRouteWrapper.tsx similarity index 83% rename from src/components/wrapper/ClientMenuRouteWrapper.tsx.tsx rename to src/components/wrapper/ClientMenuRouteWrapper.tsx index 35bea60..b6c059d 100644 --- a/src/components/wrapper/ClientMenuRouteWrapper.tsx.tsx +++ b/src/components/wrapper/ClientMenuRouteWrapper.tsx @@ -6,14 +6,15 @@ import SideMenu from '../menu/SideMenu' import BottomNavBar from '../navigation/BottomNavBar' import useToggle from '@/hooks/helpers/useToggle'; import { usePathname } from 'next/navigation'; +import usePreference from '@/hooks/helpers/usePreference'; type Props = {} & React.AllHTMLAttributes function ClientMenuRouteWrapper({ children }: Props) { const { state: profileDrop, toggle: _toggleProfileDrop } = useToggle(false); - const { state: nightMode, toggle: _toggleNightMode } = useToggle(false); const { state: menuState, toggle: _toggleMenuState, set: setMenuState } = useToggle(false); const { state: searchModal, toggle: _toggleSearchModal } = useToggle(false); + const { state: nightMode, toggle: _toggleNightMode } = usePreference('night-mode', false); const toggleProfileDrop = () => { _toggleProfileDrop(); @@ -37,9 +38,14 @@ function ClientMenuRouteWrapper({ children }: Props) { if (menuState) { setMenuState(false) } - // eslint-disable-next-line react-hooks/exhaustive-deps + // eslint-disable-next-line react-hooks/exhaustive-deps }, [location]) + useEffect(() => { + const theme = nightMode || window.matchMedia("(prefers-color-scheme: dark)").matches; + document.documentElement.setAttribute("data-theme", theme ? "dark" : "light"); + }, [nightMode]); + return (
diff --git a/src/hooks/helpers/usePreference.tsx b/src/hooks/helpers/usePreference.tsx new file mode 100644 index 0000000..7b81578 --- /dev/null +++ b/src/hooks/helpers/usePreference.tsx @@ -0,0 +1,31 @@ +import { useState, useEffect } from 'react'; +import { usePreferencesStore } from '@/zustand/preferencesStore'; + +function usePreference(key: string, initial?: T) { + const value = usePreferencesStore((s) => s.items[key]?.value as T); + const setValue = usePreferencesStore((s) => s.set); + const toggleValue = usePreferencesStore((s) => s.toggle); + const [hydrated, setHydrated] = useState(false); + + useEffect(() => { + setHydrated(true); + }, []); + + useEffect(() => { + if (hydrated && value === undefined) { + setValue(key, initial ?? null); + } + }, [hydrated, value, key, initial, setValue]); + + const toggle = (e?: React.MouseEvent | null) => { + if (e) { + e.preventDefault(); + e.stopPropagation(); + } + toggleValue(key); + }; + + return { state: value, toggle, set: setValue, hydrated }; +} + +export default usePreference; diff --git a/src/zustand/preferencesStore.ts b/src/zustand/preferencesStore.ts new file mode 100644 index 0000000..9018521 --- /dev/null +++ b/src/zustand/preferencesStore.ts @@ -0,0 +1,59 @@ +// zustand/receiptStore.ts +import { create } from 'zustand'; +import { createJSONStorage, persist } from 'zustand/middleware'; + +type PreferenceItemMap = { + [key: string]: { + value: unknown; + }; +}; + +type PreferenceStore = { + items: PreferenceItemMap; + toggle: (key: string) => void; + set: (key: string, value: unknown) => void; + reset: () => void; +}; + +export const usePreferencesStore = create()( + persist( + (set) => ({ + items: {}, + reset: () => set({ items: {} }), + toggle: (key) => { + return set((state) => { + const current = state.items[key]?.value || false; + return { + items: { + ...state.items, + [key]: { value: !current } + } + } + }) + }, + set: (key: string, value: unknown) => { + return set((state) => { + return { + items: { + ...state.items, + [key]: { value: value } + } + } + }) + }, + }), + { + name: 'preference-storage', + storage: createJSONStorage(() => { + if (typeof window !== 'undefined') { + return localStorage; + } + return { + getItem: () => null, + setItem: () => { }, + removeItem: () => { }, + }; + }), + } + ) +);