Как сделать кастомные темы?

Написал hook useTheme и у нас будет стандартные темы: dark и light. Но также я хочу добавить кнопка "Создать тему" и при клике на неё добавлять новую тему в custom-themes, по дефолту все темы которые мы добавляем - выключены. Если мы включили нашу custom-тему, нужно не добавлять атрибут data-theme, а просто получить все то, что находится в details и передать в setProperty. Ключ - это у нас тип --background, --color, а значение - цвет. Можете пожалуйста помочь с реализацией?))

Я предполагаю, что для каждой нашей темы нужно добавлять active: false и при клике на отпрядённую тему менять состояние и проверять весь список наших тем, дабы определить какая включена и поменять.

use-theme.js

export const useTheme = () => {
    // Получить системную тему пользователя
    const getSystemTheme = () => window?.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';

    const [theme, setTheme] = useState(localStorage.getItem('app-theme') || getSystemTheme());

    // Получить кастомные темы пользователя
    const getCustomThemes = () => JSON.parse(localStorage.getItem('custom-themes')) || [];

    // Добавить обьект темы в LocalStorage custom-themes
    // Example: addCustomTheme({name: 'Название тема', details: [background: 'orange']})
    const addCustomTheme = obj => {
        if (getCustomThemes().find(t => t.name === obj.name)) return false;

        localStorage.setItem(
            'custom-themes', 
            JSON.stringify(
                [
                    ...getCustomThemes(), 
                    { ...obj, active: false }
                ]
            )
        );
    }

    useLayoutEffect(() => {
        document.documentElement.setAttribute('data-theme', theme);
        localStorage.setItem('theme', theme);
    }, [theme]);

    return { currentTheme: theme, setTheme, addCustomTheme };
}

Ответы (1 шт):

Автор решения: Андрей

Вам нужно добавить функцию toggleCustomTheme:

import { useState, useLayoutEffect } from 'react';

export const useTheme = () => {
    // Получить системную тему пользователя
    const getSystemTheme = () => 
        window?.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';

    const [theme, setTheme] = useState(localStorage.getItem('app-theme') || getSystemTheme());
    const [customThemes, setCustomThemes] = useState(getCustomThemes());

    // Получить кастомные темы пользователя
    const getCustomThemes = () => JSON.parse(localStorage.getItem('custom-themes')) || [];

    // Добавить обьект темы в LocalStorage custom-themes
    const addCustomTheme = obj => {
        if (customThemes.find(t => t.name === obj.name)) return false;

        const newThemes = [
            ...customThemes,
            { ...obj, active: false }
        ];
        
        localStorage.setItem('custom-themes', JSON.stringify(newThemes));
        setCustomThemes(newThemes); // Обновить состояние на основе локального хранилища
    };

    // Включить кастомную тему
    const toggleCustomTheme = (name) => {
        const updatedThemes = customThemes.map(theme => 
            theme.name === name ? { ...theme, active: !theme.active } : theme
        );

        localStorage.setItem('custom-themes', JSON.stringify(updatedThemes));
        setCustomThemes(updatedThemes);
        
        // Установить CSS-переменные для активной темы
        const activeTheme = updatedThemes.find(t => t.active);
        if (activeTheme) {
            Object.entries(activeTheme.details).forEach(([key, value]) => {
                document.documentElement.style.setProperty(key, value);
            });
            setTheme('custom'); // Устанавливаем специальное значение для темы
        } else {
            setTheme(getSystemTheme()); // Устанавливаем систему или прочую тему
        }
    };

    useLayoutEffect(() => {
        const defaultActiveTheme = customThemes.find(t => t.active);
        if (defaultActiveTheme) {
            Object.entries(defaultActiveTheme.details).forEach(([key, value]) => {
                document.documentElement.style.setProperty(key, value);
            });
            setTheme('custom'); // Устанавливаем специальное значение для темы
        } else {
            document.documentElement.setAttribute('data-theme', theme);
            localStorage.setItem('theme', theme);
        }
    }, [theme, customThemes]);

    return { currentTheme: theme, setTheme, addCustomTheme, toggleCustomTheme, customThemes };
}
→ Ссылка