This commit is contained in:
chamikaJ
2025-04-17 18:28:54 +05:30
parent f583291d8a
commit 8825b0410a
2837 changed files with 241385 additions and 127578 deletions

View File

@@ -0,0 +1,28 @@
// ThemeSelector.tsx
import { Button } from 'antd';
import React from 'react';
import { useAppDispatch } from '@/hooks/useAppDispatch';
import { useAppSelector } from '@/hooks/useAppSelector';
import { toggleTheme } from './themeSlice';
import { MoonOutlined, SunOutlined } from '@ant-design/icons';
const ThemeSelector = () => {
const themeMode = useAppSelector(state => state.themeReducer.mode);
const dispatch = useAppDispatch();
const handleDarkModeToggle = () => {
dispatch(toggleTheme());
};
return (
<Button
type={themeMode === 'dark' ? 'primary' : 'default'}
icon={themeMode === 'dark' ? <SunOutlined /> : <MoonOutlined />}
shape="circle"
onClick={handleDarkModeToggle}
className="transition-all duration-300" // Optional: add smooth transition
/>
);
};
export default ThemeSelector;

View File

@@ -0,0 +1,77 @@
import { ConfigProvider, theme } from 'antd';
import React, { useEffect, useRef } from 'react';
import { useAppSelector } from '@/hooks/useAppSelector';
import { useAppDispatch } from '@/hooks/useAppDispatch';
import { initializeTheme } from './themeSlice';
import { colors } from '../../styles/colors';
type ChildrenProp = {
children: React.ReactNode;
};
const ThemeWrapper = ({ children }: ChildrenProp) => {
const dispatch = useAppDispatch();
const themeMode = useAppSelector(state => state.themeReducer.mode);
const isInitialized = useAppSelector(state => state.themeReducer.isInitialized);
const configRef = useRef<HTMLDivElement>(null);
// Initialize theme after mount
useEffect(() => {
if (!isInitialized) {
dispatch(initializeTheme());
}
}, [dispatch, isInitialized]);
// Listen for system theme changes
useEffect(() => {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
const handleChange = (e: MediaQueryListEvent) => {
if (!localStorage.getItem('theme')) {
dispatch(initializeTheme());
}
};
mediaQuery.addEventListener('change', handleChange);
return () => mediaQuery.removeEventListener('change', handleChange);
}, [dispatch]);
// Add CSS transition classes to prevent flash
useEffect(() => {
if (configRef.current) {
configRef.current.style.transition = 'background-color 0.3s ease';
}
}, []);
return (
<div ref={configRef} className={`theme-${themeMode}`}>
<ConfigProvider
theme={{
algorithm: themeMode === 'dark' ? theme.darkAlgorithm : theme.defaultAlgorithm,
components: {
Layout: {
colorBgLayout: themeMode === 'dark' ? colors.darkGray : colors.white,
headerBg: themeMode === 'dark' ? colors.darkGray : colors.white,
},
Menu: {
colorBgContainer: colors.transparent,
},
Table: {
rowHoverBg: themeMode === 'dark' ? '#000' : '#edebf0',
},
Select: {
controlHeight: 32,
},
},
token: {
borderRadius: 4,
},
}}
>
{children}
</ConfigProvider>
</div>
);
};
export default ThemeWrapper;

View File

@@ -0,0 +1,88 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
type ThemeType = 'light' | 'dark';
interface ThemeState {
mode: ThemeType;
isInitialized: boolean;
}
const isBrowser = typeof window !== 'undefined';
const getPreloadedTheme = (): ThemeType =>
!isBrowser ? 'light' : (window as any).__THEME_STATE__ || 'light';
const getSystemTheme = (): ThemeType =>
!isBrowser
? 'light'
: window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light';
const getThemeModeFromLocalStorage = (): ThemeType => {
if (!isBrowser) return 'light';
try {
return (localStorage.getItem('theme') as ThemeType) || getSystemTheme();
} catch {
return 'light';
}
};
const updateDocumentTheme = (themeMode: ThemeType): void => {
if (!isBrowser) return;
const root = document.documentElement;
const oppositeTheme = themeMode === 'dark' ? 'light' : 'dark';
const themeColor = themeMode === 'dark' ? '#181818' : '#ffffff';
[root, document.body].forEach(element => {
element.classList.remove(oppositeTheme);
element.classList.add(themeMode);
});
root.style.colorScheme = themeMode;
document.querySelector('meta[name="theme-color"]')?.setAttribute('content', themeColor);
(window as any).__THEME_STATE__ = themeMode;
};
const saveThemeModeToLocalStorage = (themeMode: ThemeType): void => {
if (!isBrowser) return;
try {
localStorage.setItem('theme', themeMode);
updateDocumentTheme(themeMode);
} catch (error) {
console.error('Failed to save theme to localStorage:', error);
}
};
const initialState: ThemeState = {
mode: getPreloadedTheme(),
isInitialized: false,
};
const themeSlice = createSlice({
name: 'themeReducer',
initialState,
reducers: {
toggleTheme: (state: ThemeState) => {
state.mode = state.mode === 'light' ? 'dark' : 'light';
saveThemeModeToLocalStorage(state.mode);
},
setTheme: (state: ThemeState, action: PayloadAction<ThemeType>) => {
state.mode = action.payload;
saveThemeModeToLocalStorage(state.mode);
},
initializeTheme: (state: ThemeState) => {
if (!state.isInitialized) {
state.mode = getThemeModeFromLocalStorage();
state.isInitialized = true;
updateDocumentTheme(state.mode);
}
},
},
});
export const { toggleTheme, setTheme, initializeTheme } = themeSlice.actions;
export default themeSlice.reducer;