add: theme functionality

This commit is contained in:
Mahyar Khanbolooki
2025-08-12 18:07:00 +03:30
parent 9035a03f19
commit e92c33963d
4 changed files with 101 additions and 3 deletions
+3 -1
View File
@@ -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;
@@ -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<HTMLBodyElementEventMap>
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<boolean>('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 (
<div className="h-svh overflow-hidden pt-10 flex flex-col pb-4 xl:pt-12">
+31
View File
@@ -0,0 +1,31 @@
import { useState, useEffect } from 'react';
import { usePreferencesStore } from '@/zustand/preferencesStore';
function usePreference<T>(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;
+59
View File
@@ -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<PreferenceStore>()(
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: () => { },
};
}),
}
)
);