132 lines
4.8 KiB
TypeScript
132 lines
4.8 KiB
TypeScript
import { useColorMode } from '@chakra-ui/react';
|
|
import { useEffect, useRef } from 'react';
|
|
import { useDispatch } from 'react-redux';
|
|
import { ThemeType } from '../types/theme';
|
|
import {
|
|
LIGHT_THEME,
|
|
DARK_THEME,
|
|
PINK_THEME,
|
|
BLUE_THEME,
|
|
GREEN_THEME,
|
|
PURPLE_THEME,
|
|
THEMES
|
|
} from '../utils/themes';
|
|
import { useAppSelector } from '../__data__/store';
|
|
import {
|
|
setTheme,
|
|
cycleNextTheme as cycleTheme,
|
|
selectCurrentTheme,
|
|
selectIsLightVariant,
|
|
selectIsDarkVariant
|
|
} from '../__data__/slices/theme';
|
|
|
|
// Маппинг тем к базовым режимам Chakra UI
|
|
const themeToColorMode: Record<ThemeType, 'light' | 'dark'> = {
|
|
[LIGHT_THEME]: 'light',
|
|
[DARK_THEME]: 'dark',
|
|
[PINK_THEME]: 'light',
|
|
[BLUE_THEME]: 'light',
|
|
[GREEN_THEME]: 'light',
|
|
[PURPLE_THEME]: 'dark'
|
|
};
|
|
|
|
export const useThemeManager = () => {
|
|
// Получаем базовый функционал переключения темы из Chakra UI
|
|
const { colorMode, setColorMode } = useColorMode();
|
|
|
|
// Используем Redux для управления темой
|
|
const dispatch = useDispatch();
|
|
const currentTheme = useAppSelector(selectCurrentTheme);
|
|
const isLightVariant = useAppSelector(selectIsLightVariant);
|
|
const isDarkVariant = useAppSelector(selectIsDarkVariant);
|
|
|
|
// Ref для хранения observer
|
|
const observerRef = useRef<MutationObserver | null>(null);
|
|
|
|
// Функция для применения классов и атрибутов темы
|
|
const applyThemeToDOM = () => {
|
|
if (typeof document === 'undefined') return;
|
|
|
|
// Удаляем все классы тем
|
|
document.documentElement.classList.remove(...THEMES);
|
|
|
|
// Добавляем класс текущей темы
|
|
document.documentElement.classList.add(currentTheme);
|
|
|
|
// Также устанавливаем data-theme атрибут для использования в CSS
|
|
document.documentElement.setAttribute('data-theme', currentTheme);
|
|
|
|
// Устанавливаем специальный флаг, что тема установлена нами
|
|
document.documentElement.setAttribute('data-custom-theme-source', 'redux');
|
|
};
|
|
|
|
// Эффект для применения дополнительных классов к документу в зависимости от выбранной темы
|
|
// и создания MutationObserver для отслеживания изменений
|
|
useEffect(() => {
|
|
if (typeof document === 'undefined' || typeof window === 'undefined') return;
|
|
|
|
// Применяем тему к DOM
|
|
applyThemeToDOM();
|
|
|
|
// Синхронизируем с Chakra UI для светлой/темной темы
|
|
const requiredColorMode = themeToColorMode[currentTheme];
|
|
if (colorMode !== requiredColorMode) {
|
|
setColorMode(requiredColorMode);
|
|
}
|
|
|
|
// Создаем MutationObserver для отслеживания изменений атрибута data-theme
|
|
if (!observerRef.current) {
|
|
observerRef.current = new MutationObserver((mutations) => {
|
|
mutations.forEach((mutation) => {
|
|
if (
|
|
mutation.type === 'attributes' &&
|
|
mutation.attributeName === 'data-theme' &&
|
|
document.documentElement.getAttribute('data-theme') !== currentTheme
|
|
) {
|
|
// Если атрибут был изменен не нами, восстанавливаем его
|
|
applyThemeToDOM();
|
|
}
|
|
});
|
|
});
|
|
|
|
// Начинаем наблюдение за изменениями атрибута data-theme
|
|
observerRef.current.observe(document.documentElement, {
|
|
attributes: true,
|
|
attributeFilter: ['data-theme']
|
|
});
|
|
}
|
|
|
|
// Для дополнительной защиты устанавливаем интервал для проверки и восстановления атрибута
|
|
const intervalId = setInterval(() => {
|
|
if (document.documentElement.getAttribute('data-theme') !== currentTheme) {
|
|
applyThemeToDOM();
|
|
}
|
|
}, 500);
|
|
|
|
// Отключаем observer и интервал при размонтировании компонента
|
|
return () => {
|
|
if (observerRef.current) {
|
|
observerRef.current.disconnect();
|
|
}
|
|
clearInterval(intervalId);
|
|
};
|
|
}, [currentTheme, setColorMode, colorMode]);
|
|
|
|
// Функция для изменения темы
|
|
const changeTheme = (theme: ThemeType) => {
|
|
dispatch(setTheme(theme));
|
|
};
|
|
|
|
// Функция для последовательного циклического переключения тем
|
|
const cycleNextTheme = () => {
|
|
dispatch(cycleTheme());
|
|
};
|
|
|
|
return {
|
|
currentTheme,
|
|
changeTheme,
|
|
cycleNextTheme,
|
|
isLightVariant,
|
|
isDarkVariant,
|
|
};
|
|
};
|