Я пытаюсь реализовать функциональность темного режима в своем компоненте заголовка, поэтому я создал контекстный API для его обработки, но он не работает

сначала у меня есть корневой компонент, который обрабатывает дерево компонентов,

RootApp.jsx

import { Suspense } from 'react';
import { HashRouter as Router } from 'react-router-dom';
import { Provider } from 'react-redux';
import store from '@/redux/store';
import PageLoader from '@/components/PageLoader';

import '@/style/app.css';
import '@/style/index.css';
import '@/style/tailwind.css'

import ERP_SODEOs from '@/apps/IdurarOs';
import { ThemeProvider } from '@/context/ThemeContext/ThemeContext'; // Import ThemeProvider

export default function RoutApp() {
  return (
    <Router>
      <Provider store = {store}>
        <ThemeProvider>
          <Suspense fallback = {<PageLoader />}>
            <ERP_SODEOs />
          </Suspense>
        </ThemeProvider>
      </Provider>
    </Router>
  );
}

в <ERP_SODEOs> имеется несколько компонентов, основным компонентом, обрабатывающим дочерний компонент, является ErpApp.jsx:


  return (
    <Layout hasSider>
      <Navigation onPathChange = {handlePathChange} />

      {isMobile ? (
        <Layout style = {{ marginLeft: 0 }}>
          <HeaderContent />
          <Content
            style = {{
              margin: '40px auto 30px',
              overflow: 'initial',
              width: '100%',
              padding: '0 25px',
              maxWidth: 'none',
            }}
          >
            <AppRouter />
          </Content>
        </Layout>
      ) : (
        <Layout style = {{ marginLeft: isNavMenuClose ? 100 : 220 }}>
          <HeaderContent currentPath = {currentPath} />
          <Content
            style = {{
              margin: '30px auto 30px',
              overflow: 'initial',
              width: '100%',
              padding: '0px 10px 0px 0px',
              maxWidth: isNavMenuClose ? 1700 : 1600,
            }}
          >
            <AppRouter />
          </Content>
        </Layout>
      )}
    </Layout>
  );
}

я пытаюсь реализовать темный режим внутри моего компонента API контекста, который не работает HeaderContent.jsx:


export default function HeaderContent() {
  const currentAdmin = useSelector(selectCurrentAdmin);
  const translate = useLanguage();
  const [hasPhotoprofile, setHasPhotoprofile] = useState(false);
  const [activeKey, setActiveKey] = useState(null);
  const [isScrolled, setIsScrolled] = useState(false);
  const isAdmin = currentAdmin?.role === 'admin';
  const { theme, toggleTheme } = useContext(ThemeContext);

  useEffect(() => {
    function handleScroll() {
      const scrollPosition = window.scrollY;
      setIsScrolled(scrollPosition > 0);
    }

    window.addEventListener('scroll', handleScroll);

    return () => {
      window.removeEventListener('scroll', handleScroll);
    };
  }, []);

  useEffect(() => {
    async function fetchData() {
      try {
        const result = await checkImage(BASE_URL + currentAdmin?.photo);
        setHasPhotoprofile(result);
      } catch (error) {
        console.error('Error checking image:', error);
      }
    }

    fetchData();
  }, [currentAdmin]);

  const srcImgProfile = hasPhotoprofile ? BASE_URL + currentAdmin?.photo : null;

  const ProfileDropdown = () => {
    const navigate = useNavigate();
    return (
      <div className = "profileDropdown" onClick = {() => navigate('/profile')}>
        <Avatar
          size = "large"
          className = "last"
          src = {srcImgProfile}
          style = {{ color: '#f56a00', backgroundColor: !hasPhotoprofile ? '#fde3cf' : '#f9fafc' }}
        >
          {currentAdmin?.fullname && currentAdmin?.fullname.charAt(0).toUpperCase()}
        </Avatar>
        <div className = "profileDropdownInfo">
          <p className='capitalize font-thin text-sm text-blue-600'>
            {currentAdmin?.fullname}
          </p>
          <p className='font-mono text-[11px] text-sm text-red-600 font-thin'>{currentAdmin?.username}</p>
        </div>
      </div>
    );
  };

  const DropdownMenu = ({ text }) => <span>{text}</span>;

  const items = isAdmin
    ? [
      {
        label: <ProfileDropdown className = "headerDropDownMenu" />,
        key: 'ProfileDropdown',
      },
      { type: 'divider' },
      {
        icon: <SettingOutlined />,
        key: 'settingProfile',
        label: (
          <Link to = {'/profile'}>
            <DropdownMenu text = {translate('profile_settings')} />
          </Link>
        ),
      },
      {
        icon: <SettingOutlined />,
        key: 'settingApp',
        label: <Link to = {'/settings'}>{translate('app_settings')}</Link>,
      },
      { type: 'divider' },
      {
        icon: <LogoutOutlined />,
        key: 'logout',
        label: <Link to = {'/logout'}>{translate('logout')}</Link>,
      }
    ]
    : [
      {
        icon: <SettingOutlined />,
        key: 'settingProfile',
        label: (
          <Link to = {'/profile'}>
            <DropdownMenu text = {translate('update_password')} />
          </Link>
        ),
      },
      {
        icon: <LogoutOutlined />,
        key: 'logout',
        label: <Link to = {'/logout'}>{translate('logout')}</Link>,
      }
    ];

  const darkModeIcons = [
    {
      icon: <BsMoonStars />,
      key: 'dark',
      label: 'Dark',
      onClick: () => toggleTheme('dark'),
    },
    {
      icon: <GrSun />,
      key: 'light',
      label: 'Light',
      onClick: () => toggleTheme('light'),
    },
    {
      icon: <MdOutlineComputer />,
      key: 'system',
      label: 'System',
      onClick: () => toggleTheme('system'),
    },
  ];

  const darkModeDropdown = (
    <Menu className='w-36' selectedKeys = {[activeKey]}>
      {darkModeIcons.map((item) => (
        <Menu.Item
          key = {item.key}
          onClick = {() => handleItemClick(item.key)}
          style = {{ display: 'flex', alignItems: 'center' }}
        >
          <div className='flex items-center gap-2.5'>
            {item.icon} <span>{item.label}</span>
          </div>
        </Menu.Item>
      ))}
    </Menu>
  );

  const handleItemClick = (key) => {
    setActiveKey(key);
  };

  return (
    <Header
      className = {`sticky top-0 z-50 ${isScrolled ? 'scrolled' : 'bg-white w-[100%] scroll-smooth'} ${theme === 'dark' ? 'bg-gray-900' : 'bg-white'}`}
      style = {{
        display: 'flex',
        justifyContent: 'space-between',
        alignItems: 'center',
      }}
    >
      <div className = {`text-[15px] font-thin ${theme === 'dark' ? 'text-white' : 'text-black'}`}>
        <span className='uppercase'>Dashboard</span>
      </div>
      <div className='flex flex-row-reverse gap-3 items-center'>
        <Dropdown
          overlay = {<Menu items = {items} />}
          trigger = {['click']}
          placement = "bottomRight"
          style = {{ width: '280px', float: 'right' }}
        >
          <Avatar
            className = "last"
            src = {srcImgProfile}
            style = {{
              color: '#f56a00',
              backgroundColor: !hasPhotoprofile ? '#fde3cf' : '#f9fafc',
              float: 'right',
            }}
            size = "large"
          >
            {currentAdmin?.fullname && currentAdmin?.fullname.charAt(0).toUpperCase()}
          </Avatar>
        </Dropdown>
        <Dropdown overlay = {darkModeDropdown} trigger = {['click']} placement='bottomRight'>
          <div>
            <BsMoonStars className = {`text-[20px] cursor-pointer ${theme === 'dark' ? 'text-white' : 'text-black'}`} />
          </div>
        </Dropdown>
        <SelectLanguage />
      </div>
    </Header>
  );
}

поскольку глобальные файлы CSS импортируются в корневой файл, я также пытался добавить собственный CSS в этот глобальный файл CSS, но он не работал.

в файле headercontent есть раскрывающаяся кнопка, в которой есть 3 варианта изменения темы приложения. Когда пользователь нажимает темную кнопку, цвет заголовка должен измениться. Я также хочу использовать созданный мной контекст, используемый в других компонентах как ну, я также хочу добавить, что я уже изменил файл tsconfig с помощью darkMode: 'class'

Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
0
0
71
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Чтобы реализовать функциональность темного режима с помощью Context API и обеспечить его работу во всем вашем приложении, в том числе в компоненте HeaderContent, вам необходимо выполнить несколько шагов, чтобы правильно настроить и использовать ваш ThemeContext. Вот подробный подход, который поможет вам устранить неполадки и реализовать эту функциональность:

Шаг 1. Создайте контекст темы Сначала создайте ThemeContext с поставщиком и настраиваемым хуком для упрощения использования.

Темеконтекст.js

import React, { createContext, useState, useContext, useEffect } from 'react';

const ThemeContext = createContext();

export const ThemeProvider = ({ children }) => {
  const [theme, setTheme] = useState(() => {
    // Retrieve the stored theme or default to 'light'
    const savedTheme = localStorage.getItem('theme');
    return savedTheme ? savedTheme : 'light';
  });

  const toggleTheme = (newTheme) => {
    setTheme(newTheme);
    localStorage.setItem('theme', newTheme);
  };

  useEffect(() => {
    // Apply the theme to the body class
    document.body.className = theme;
  }, [theme]);

  return (
    <ThemeContext.Provider value = {{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
};

export const useTheme = () => useContext(ThemeContext);

Шаг 2. Оберните свое приложение поставщиком тем Убедитесь, что ваш ThemeProvider обертывает ваше приложение, чтобы контекст был доступен всем компонентам.

RootApp.jsx

import { Suspense } from 'react';
import { HashRouter as Router } from 'react-router-dom';
import { Provider } from 'react-redux';
import store from '@/redux/store';
import PageLoader from '@/components/PageLoader';

import '@/style/app.css';
import '@/style/index.css';
import '@/style/tailwind.css';

import ERP_SODEOs from '@/apps/IdurarOs';
import { ThemeProvider } from '@/context/ThemeContext/ThemeContext'; // Import ThemeProvider

export default function RootApp() {
  return (
    <Router>
      <Provider store = {store}>
        <ThemeProvider>
          <Suspense fallback = {<PageLoader />}>
            <ERP_SODEOs />
          </Suspense>
        </ThemeProvider>
      </Provider>
    </Router>
  );
}

Шаг 3. Используйте контекст темы в HeaderContent Теперь вы можете использовать хук useTheme для доступа и изменения темы в вашем компоненте HeaderContent.

HeaderContent.jsx

import React, { useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import { Link, useNavigate } from 'react-router-dom';
import { Avatar, Dropdown, Menu, Layout } from 'antd';
import { SettingOutlined, LogoutOutlined } from '@ant-design/icons';
import { BsMoonStars } from 'react-icons/bs';
import { GrSun } from 'react-icons/gr';
import { MdOutlineComputer } from 'react-icons/md';
import { useTheme } from '@/context/ThemeContext/ThemeContext'; // Import useTheme
import { selectCurrentAdmin } from '@/redux/selectors';
import SelectLanguage from '@/components/SelectLanguage';
import checkImage from '@/utils/checkImage';

const { Header, Content } = Layout;

export default function HeaderContent() {
  const currentAdmin = useSelector(selectCurrentAdmin);
  const [hasPhotoprofile, setHasPhotoprofile] = useState(false);
  const [activeKey, setActiveKey] = useState(null);
  const [isScrolled, setIsScrolled] = useState(false);
  const { theme, toggleTheme } = useTheme(); // Use the custom hook to access theme context

  useEffect(() => {
    function handleScroll() {
      const scrollPosition = window.scrollY;
      setIsScrolled(scrollPosition > 0);
    }

    window.addEventListener('scroll', handleScroll);

    return () => {
      window.removeEventListener('scroll', handleScroll);
    };
  }, []);

  useEffect(() => {
    async function fetchData() {
      try {
        const result = await checkImage(BASE_URL + currentAdmin?.photo);
        setHasPhotoprofile(result);
      } catch (error) {
        console.error('Error checking image:', error);
      }
    }

    fetchData();
  }, [currentAdmin]);

  const srcImgProfile = hasPhotoprofile ? BASE_URL + currentAdmin?.photo : null;

  const ProfileDropdown = () => {
    const navigate = useNavigate();
    return (
      <div className = "profileDropdown" onClick = {() => navigate('/profile')}>
        <Avatar
          size = "large"
          className = "last"
          src = {srcImgProfile}
          style = {{ color: '#f56a00', backgroundColor: !hasPhotoprofile ? '#fde3cf' : '#f9fafc' }}
        >
          {currentAdmin?.fullname && currentAdmin?.fullname.charAt(0).toUpperCase()}
        </Avatar>
        <div className = "profileDropdownInfo">
          <p className='capitalize font-thin text-sm text-blue-600'>
            {currentAdmin?.fullname}
          </p>
          <p className='font-mono text-[11px] text-sm text-red-600 font-thin'>{currentAdmin?.username}</p>
        </div>
      </div>
    );
  };

  const DropdownMenu = ({ text }) => <span>{text}</span>;

  const items = currentAdmin?.role === 'admin'
    ? [
      {
        label: <ProfileDropdown className = "headerDropDownMenu" />,
        key: 'ProfileDropdown',
      },
      { type: 'divider' },
      {
        icon: <SettingOutlined />,
        key: 'settingProfile',
        label: (
          <Link to = {'/profile'}>
            <DropdownMenu text = {translate('profile_settings')} />
          </Link>
        ),
      },
      {
        icon: <SettingOutlined />,
        key: 'settingApp',
        label: <Link to = {'/settings'}>{translate('app_settings')}</Link>,
      },
      { type: 'divider' },
      {
        icon: <LogoutOutlined />,
        key: 'logout',
        label: <Link to = {'/logout'}>{translate('logout')}</Link>,
      }
    ]
    : [
      {
        icon: <SettingOutlined />,
        key: 'settingProfile',
        label: (
          <Link to = {'/profile'}>
            <DropdownMenu text = {translate('update_password')} />
          </Link>
        ),
      },
      {
        icon: <LogoutOutlined />,
        key: 'logout',
        label: <Link to = {'/logout'}>{translate('logout')}</Link>,
      }
    ];

  const darkModeIcons = [
    {
      icon: <BsMoonStars />,
      key: 'dark',
      label: 'Dark',
      onClick: () => toggleTheme('dark'),
    },
    {
      icon: <GrSun />,
      key: 'light',
      label: 'Light',
      onClick: () => toggleTheme('light'),
    },
    {
      icon: <MdOutlineComputer />,
      key: 'system',
      label: 'System',
      onClick: () => toggleTheme('system'),
    },
  ];

  const darkModeDropdown = (
    <Menu className='w-36' selectedKeys = {[activeKey]}>
      {darkModeIcons.map((item) => (
        <Menu.Item
          key = {item.key}
          onClick = {() => handleItemClick(item.key)}
          style = {{ display: 'flex', alignItems: 'center' }}
        >
          <div className='flex items-center gap-2.5'>
            {item.icon} <span>{item.label}</span>
          </div>
        </Menu.Item>
      ))}
    </Menu>
  );

  const handleItemClick = (key) => {
    setActiveKey(key);
  };

  return (
    <Header
      className = {`sticky top-0 z-50 ${isScrolled ? 'scrolled' : 'bg-white w-[100%] scroll-smooth'} ${theme === 'dark' ? 'bg-gray-900' : 'bg-white'}`}
      style = {{
        display: 'flex',
        justifyContent: 'space-between',
        alignItems: 'center',
      }}
    >
      <div className = {`text-[15px] font-thin ${theme === 'dark' ? 'text-white' : 'text-black'}`}>
        <span className='uppercase'>Dashboard</span>
      </div>
      <div className='flex flex-row-reverse gap-3 items-center'>
        <Dropdown
          overlay = {<Menu items = {items} />}
          trigger = {['click']}
          placement = "bottomRight"
          style = {{ width: '280px', float: 'right' }}
        >
          <Avatar
            className = "last"
            src = {srcImgProfile}
            style = {{
              color: '#f56a00',
              backgroundColor: !hasPhotoprofile ? '#fde3cf' : '#f9fafc',
              float: 'right',
            }}
            size = "large"
          >
            {currentAdmin?.fullname && currentAdmin?.fullname.charAt(0).toUpperCase()}
          </Avatar>
        </Dropdown>
        <Dropdown overlay = {darkModeDropdown} trigger = {['click']} placement='bottomRight'>
          <div>
            <BsMoonStars className = {`text-[20px] cursor-pointer ${theme === 'dark' ? 'text-white' : 'text-black'}`} />
          </div>
        </Dropdown>
        <SelectLanguage />
      </div>
    </Header>
  );
}

Шаг 4. Добавьте стили для темного режима Наконец, убедитесь, что ваш CSS имеет необходимые стили для темного и светлого режимов. Чтобы справиться с этим, вы можете использовать класс CSS в элементе body.

body.light {
  background-color: #ffffff;
  color: #000000;
}

body.dark {
  background-color: #1a1a1a;
  color: #ffffff;
}

При таком подходе функция toggleTheme устанавливает состояние theme и обновляет локальное хранилище. Хук useEffect применяет тему, изменяя класс body. Компонент HeaderContent использует хук useTheme для доступа к текущей теме и соответствующего применения стилей.

Это должно гарантировать, что функциональность темного режима работает должным образом во всем вашем приложении.

Другие вопросы по теме