1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

React/Next.jsを使用して、よくあるテーマカラーの切り替えボタンの実装をめちゃ簡単に解説する

Last updated at Posted at 2024-04-17

1. 概要

簡単なアニメーションの挙動
https://x.com/Nao8epicmotion/status/1780411806277919134

ソースコード
https://github.com/Kroro1208/nextjs-portfolio-website.git

React/Next.jsを使用してwebsiteの開発を行う際に、よく出てくるライトモードとダークモードの切り替えを解説します。

もうこれはパターン化できるもので、一度覚えてしまえば使いまわしたりして表現できる幅も広がると思います。この際に覚えちゃいましょう。

2. 必要なコンポーネントとファイル

今回のテーマに関係ある部分だけ解説させていただきます。
iconはReact iconを使用してます。

app/layout.jsx

ここで後に作成するThemeProviderコンポーネントでレンダリングされる部分を囲います。

import { Inter } from "next/font/google";
import "./globals.css";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import { ThemeProvider } from "@/components/ThemeProvider";

const inter = Inter({ subsets: ["latin"] });

export const metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({ children }) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body className={inter.className}>
        <ThemeProvider attribute="class" defaultTheme="light">
          <Header />
          {children}
          <Footer />
        </ThemeProvider>
      </body>
    </html>
  );
}

app/Home.jsx

import About from "@/components/About";
import Cta from "@/components/Cta";
import Hero from "@/components/Hero";
import Reviews from "@/components/Reviews";
import Services from "@/components/Services";
import Works from "@/components/Works";

export default function Home() {
  return (
    <main>
      <Hero />
      <About />
      <Services />
      <Works />
      <Reviews />
      <Cta />
    </main>
  );
}

components/Header.jsx

※ここのuseEffectはスクロールアニメーションに関係するので今回は関係ありません。
動画にあるスクールの時に、遅れてニュルっと出てくるオレンジのBarのやつです。

"use client";
import { useEffect, useState } from "react";
import Logo from "./Logo";
import MobileNav from "./MobileNav";
import Nav from "./Nav";
import ThemeToggle from "./ThemeToggle"
import { usePathname } from "next/navigation";

const Header = () => {
   const [header, setHeader] = useState(false);
   const pathname = usePathname();

   useEffect(() => {
       const scrollYPos = window.addEventListener('scroll', () => {
           window.scrollY > 50 ? setHeader(true) : setHeader(false);
       });

       return () => window.removeEventListener('scroll', scrollYPos);
   });

   return (
       <header className={`${header ? "py-4 bg-gray-200 shadow-lg dark:bg-accent" : "py-6 dark:bg-transparent"}
       sticky top-0 z-30 transition-all ${pathname === "/" && "bg-sky-200"}`}>
           <div className="container mx-auto">
               <div className="flex justify-between items-center">
                   <Logo />
                   <div className="flex items-center gap-x-6">
                       <Nav
                           containerStyles="hidden xl:flex gap-x-8 items-center"
                           linkStyles="relative hover:text-primary transition-all"
                           underlineStyles="absolute left-0 top-full h-[2px] bg-primary w-full"
                       />
                       <ThemeToggle />
                       <div className="xl:hidden">
                           <MobileNav />
                       </div>
                   </div>
               </div>
           </div>
       </header>
   )
}

export default Header

components/ThemeProvider.jsx

ここでライブラリをインポートしてNextThemeProviderとして返します。
この使い方がちょっとだけ難しいかもですが、...propsにはlayout.jsxに記載している<ThemeProvider>内のattribute="class" defaultTheme="light"が渡ってきます。
この二つのpropsはThemeProviderを使用するときの書き方みたいなものです。

"use client";
import { ThemeProvider as NextThemeProvider } from "next-themes";

export function ThemeProvider({ children, ...props }) {
   return <NextThemeProvider {...props}>{children}</NextThemeProvider>;
}

components/ThemeToggle.jsx

rotateとscaleを使用してアイコンをクリックしたときに回転させてちょっとオシャレに。
ボタンにvariant="outline"を指定して、枠線だけが表示させ中身が透明な「アウトラインスタイル」を持たせてます。

"use client";
import { Button } from "./ui/button";
import { MoonIcon, SunIcon } from "@radix-ui/react-icons";
import { useTheme } from "next-themes";

const ThemeToggle = () => {
   const { theme, setTheme } = useTheme();
   return (
       <Button variant="outline" size="icon" onClick={() => setTheme(theme === "dark" ? "light" : "dark")}>
           <SunIcon className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
           <MoonIcon className="absolute h-[1.2rem] w-[1.2rem] rotate-0 scale-0 transition-all dark:rotate-0 dark:scale-100" />
       </Button>
   )
}

export default ThemeToggle

app/global.css

ここでデフォルトカラーとdarkモード時に必要となるカラーを定義する

@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
 @layer base {
   :root {
     --background: 0 0% 100%;
     --primary: 8 99% 67%;
     --primary-foreground: 300 0% 100%;
     --secondary: 240 19% 16%;
     --secondary-foreground: 300 0% 100%;
     --border: 8 82% 92%;
     --input: 8 82% 92%;
     --ring: 9 99% 67%;
     
     他の設定
   }
   .dark {
     --background: 237 22% 20%;
     --primary: 8 99% 67%;
     --primary-foreground: 300 0% 100%;
     --secondary: 240 19% 16%;
     --secondary-foreground: 300 0% 100%;
     --border: 237 22% 23%;
     --input: 237 22% 23%;
     --ring: 8 99% 67%;
     
     他の設定
   }
 }
}



tailwind.config.js

/** @type {import('tailwindcss').Config} */
module.exports = {
 darkMode: ["class"],
 content: [
   './pages/**/*.{js,jsx}',
   './components/**/*.{js,jsx}',
   './app/**/*.{js,jsx}',
   './src/**/*.{js,jsx}',
 ],
 prefix: "",
 theme: {
   container: {
     center: true,
     padding: "2rem",
     screens: {
       "sm": "640px",
       "md": "768px",
       "lg": "1024px",
       "xl": "1400px",
     },
   },
   extend: {
     colors: {
       border: "hsl(var(--border))",
       input: "hsl(var(--input))",
       ring: "hsl(var(--ring))",
       background: "hsl(var(--background))",
       primary: {
         DEFAULT: "hsl(var(--primary))",
         foreground: "hsl(var(--primary-foreground))",
       },
       secondary: {
         DEFAULT: "hsl(var(--secondary))",
         foreground: "hsl(var(--secondary-foreground))",
       },
     },
   },
 },
 plugins: [require("tailwindcss-animate")],
}

以上です!!

次回は、スクロール時のニュルっと出てくるやつを解説します!

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?