Как сообщить странице React/Next.js, что темный режим включен?

Я разрабатываю свое первое простое приложение Next.js+Tailwind. Он имеет фиксированный заголовок и боковую панель. Боковая панель содержит кнопку для переключения между темным и светлым режимом. Я использую next-theme, и с Tailwind до сих пор это было довольно просто.

Теперь одна из главных страниц контента содержит ориентированный граф. Сейчас я использую для этого Cytoscape, это в целом отличная библиотека. Моя главная проблема сейчас заключается в том, что Cytoscape не поддерживает стиль, подобный Tailwind. Создание графика Cytoscape требует ввода следующим образом:

const stylesheet = [
    {
    selector: 'node',
    style: {
        'backgroundColor': "#33FF66", // this color should change in dark mode
    },
    {
    selector: "edge",
    style: {
        'curve-style': 'bezier',
        'line-color': '#333333', // this color should change in dark mode
    },
    },
  ];

Другими словами, я бы хотел, чтобы цвета моего графика менялись при переключении темного режима. В настоящее время я рассматриваю необходимость создания таблиц стилей (например, stylesheetLight и stylesheetDark), а затем переключения между обоими листами при нажатии кнопки переключения. Однако я понятия не имею, как прослушивать кнопку переключения, расположенную на боковой панели страницы, содержащей график. Мой RootLayout выглядит следующим образом:

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  const [sidebarOpen, setSidebarOpen] = useState(false);
  return (
    <html lang = "en">
    <body className = "${inter.className} bg-slate-50 dark:bg-slate-800">
        <div className = "grid min-h-screen grid-rows-header">
        <div>
            <Header onMenuButtonClick = {() => setSidebarOpen((prev) => !prev)} />
        </div>
        <div className = "flex min-h-screen">
            <div className = "">
            <Sidebar open = {sidebarOpen} setOpen = {setSidebarOpen} />
            </div>
            <div className = "mt-[50px] flex-1">
                <main>{children}</main>
            </div>
        </div>
        </div>
    </body>
    </html>
  );
}

Как я могу это сделать? Есть ли у меня доступ к кнопке переключения на странице с графиком для добавления прослушивателя событий?

Или это вообще правильный подход?

ОБНОВЛЕНИЕ 1: Следуя ответам @ayex @Ganesh, я изменил свой RootLayout следующим образом.

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  const [sidebarOpen, setSidebarOpen] = useState(false);
  return (
    <html lang = "en">
      <body className = "${inter.className} bg-slate-50 dark:bg-slate-800">
      <ThemeProvider attribute = "class" defaultTheme = "system" enableSystem disableTransitionOnChange>
          <div className = "grid min-h-screen grid-rows-header">
          <div>
            <Header onMenuButtonClick = {() => setSidebarOpen((prev) => !prev)} />
          </div>
          <div className = "flex min-h-screen">
            <div className = "">
              <Sidebar open = {sidebarOpen} setOpen = {setSidebarOpen} />
            </div>
              <div className = "mt-[50px] flex-1">
                <main>{children}</main>
              </div>
          </div>
        </div>
        </ThemeProvider>
      </body>
    </html>
  );
}

Изменение заключается в том, что я переместил ThemeProvider из компонента боковой панели в RootLayout. Кажется, это помогает, поскольку теперь я могу «видеть» изменение темы на моей странице графика, которая по своей сути выглядит следующим образом:

export default function GraphPage() {
  const { theme, setTheme } = useTheme()
  const [topicGraph, setTopicGraph] = useState<any[] | null>(null);
  const [stylesheet, setStylesheet] = useState<any | null>(null)

  // GOOD: Prints every time I toggle the dark mode
  console.info("Theme:", theme)
  
  // BAD: this causes "Too Many Re-renders" error
  //if (theme === "light") {
  //  setStylesheet(stylesheetLight);
  //} else {
  //  setStylesheet(stylesheetDark);
  //}

  useEffect(() => {
    getGraph().then((jsonResponse) => {

      // THIS WORKS!
      if (theme === "light") {
        setStylesheet(stylesheetLight);
      } else {
        setStylesheet(stylesheetDark);
      }

      //...Call API to fetch graph data...
      setTopicGraph(graph);
    });
  }, [theme]);

  return (
    <div>
      <CytoscapeComponent
        elements = {topicGraph}
        style = {{ width: 'w-full', height: 'calc(100vh - 50px)' }}
        stylesheet = {stylesheet}
        layout = {defaultLayout}
        wheelSensitivity = {0.15}
        cy = {(cy) => {
          setListeners(cy);
        }}
      />      
    </div>
  )
}

Как отмечено в приведенном выше фрагменте кода, оператор console.info теперь всегда печатает текущий режим (темный или светлый). Однако, если я теперь использую там setStylesheet, я получаю ошибку «слишком много повторных рендерингов». Как мне этого избежать?

ОБНОВЛЕНИЕ 2: я заставил его работать, переместив setStylesheet в блок useEffect — я редактирую приведенный выше фрагмент кода. Важно, что theme — это зависимость.

Поведение ключевого слова "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
399
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

если вы используете темы next, то это просто: крючок useTheme дает вам состояние темы, и в зависимости от этого вы можете управлять стилем, применяемым к графику, как вы упомянули. пока, кстати, я не думаю, что вы еще использовали темы next, потому что, насколько я вижу, в вашем корневом макете они не настроены. вот как вы можете его использовать, настраивайте по своему усмотрению.

РутЛайаут.tsx

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  const [sidebarOpen, setSidebarOpen] = useState(false);
  return (
    <html lang = "en">
    <body className = "${inter.className} bg-slate-50 dark:bg-slate-800">
<ThemeContextProvider>
        <div className = "grid min-h-screen grid-rows-header">
        <div>
            <Header onMenuButtonClick = {() => setSidebarOpen((prev) => !prev)} />
        </div>
        <div className = "flex min-h-screen">
            <div className = "">
            <Sidebar open = {sidebarOpen} setOpen = {setSidebarOpen} />
            </div>
            <div className = "mt-[50px] flex-1">
                <main>{children}</main>
            </div>
        </div>
        </div>
</ThemeContextProvider>
    </body>
    </html>
  );
}

контекст/тема-context.tsx

"use client";

import { ThemeProvider } from "next-themes";

type ThemeContextProviderProps = {
  children: React.ReactNode;
};

export default function ThemeContextProvider({
  children,
}: ThemeContextProviderProps) {
  return <ThemeProvider attribute = "class">{children}</ThemeProvider>;
}

Боковая панель.tsx

"use client";
import { useTheme } from "next-themes";
import React from "react";
import { BsMoon, BsSun } from "react-icons/bs";

export default function Sidebar() {
  const { theme, setTheme } = useTheme();
  
  const toggleTheme = () => {
    if (theme === "light") {
      setTheme("dark");
    } else {
      setTheme("light");
    }
  };

  return (
    <button
      className = "fixed bottom-5 right-5 bg-white w-[3rem] h-[3rem]
    bg-opacity-80 backdrop-blur-lg border border-white 
    border-opacity-40 shadow-2xl rounded-full flex 
    justify-center items-center hover:scale-105 active:scale-105 
    hover:backdrop-blur-none hover:bg-opacity-100 transition-all dark:bg-gray-950/80
    dark:backdrop-blur-lg dark:hover:bg-gray-950 dark:border-dark/40"
      onClick = {toggleTheme}
    >
      {theme === "light" ? <BsSun /> : <BsMoon />}
    </button>
  );
}

Итак, на вашем Sidebar.tsx вы можете сделать что-то вроде theme==='light'?stylesheetLight:stylesheetDark подробнее об этом можно прочитать здесь и если вы используете shadcn-ui: библиотеку компонентов на основе Tailwindcss, у них есть собственная документация о том, как установить темный режим и использовать его с темами next здесь

Спасибо! Да, перемещение ThemeProvider из боковой панели в RootLayout уже привело к большим изменениям. Однако я не уверен, как теперь правильно обработать это на странице графика. Я «вижу» изменение theme на странице графика каждый раз, когда переключаюсь с помощью кнопки на боковой панели, но моя попытка обработать приводит к ошибке «слишком много повторных рендерингов»; см. обновление в моем исходном сообщении с фрагментами кода.

Christian 24.05.2024 11:02

приятно знать, что ты понял это @Christian.

ayex 24.05.2024 15:42

@Кристиан, выбрав ответ в качестве ответа, будет воодушевлен, если это вам немного поможет, спасибо.

ayex 28.05.2024 09:27
context/theme-context.tsx

return <ThemeProvider attribute = "class">{children}</ThemeProvider>;


replace above line by below line

return <ThemeProvider attribute = "class" defaultTheme = "dark">{children}</ThemeProvider>; 

you forgot to add defaultTheme = "dark" that's why your code is not working...

дайте мне знать, если проблема не решена.

Ganesh Negi 24.05.2024 10:13

Я использую defaultTheme = "system" в своем коде. Я переместил ThemeProvider из боковой панели в RootLayout, что теперь помогает «прослушивать» любые изменения в theme на странице графика. Однако использование этого для обновления таблицы стилей графика приводит к ошибке «слишком много повторных рендерингов»; см. обновленный исходный пост.

Christian 24.05.2024 11:06

покажите мне код, в котором вы вызываете компонент Graph

Ganesh Negi 24.05.2024 11:52

Я заставил это работать; смотрите мое последнее обновление. Еще раз спасибо!

Christian 24.05.2024 12:06

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