Files
journal.pl/src/hooks/useThemeManager.ts
T
Primakov Alexandr Alexandrovich 9cbc5910ef
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

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