journal.pl/src/hooks/useThemeManager.ts
Primakov Alexandr Alexandrovich 9cbc5910ef
All checks were successful
platform/bro-js/journal.pl/pipeline/head This commit looks good
vibe themes
2025-04-24 17:24:07 +03:00

132 lines
4.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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,
};
};