- Published on
ทำ Dark mode ใน Next.js แบบ (App Directory)
อันยองงง หลังจากที่ห่างหายจากการเขียนบทความไปประมาณ 1 เดือน เนื่องจากอาการเหนื่อยๆ และสารพัดข้ออ้าง
วันนี้ฤกษ์งามยามดี เลยมานั่งเขียนบทความดีกว่า
บทความวันนี้เจมส์จะมาเขียนเกี่ยวกับการทำ Dark mode ใน Next.js ที่เป็นแบบ App Directory และใช้ TailwindCSS ด้วยครับ คิดว่าคงจะมีประโยชน์กับผู้อ่านทุกท่านที่ติดปัญหาเกี่ยวกับการทำ มาเริ่มกันเลยดีกว่า
มาเริ่มกันเลย
ขั้นแรกเดี๋ยวเราจะมาสร้างโปรเจค Next.js แบบ App Directory และใช้ TailwindCSS ขึ้นมาก่อนเน้อครับ เจมส์จะสร้างโปรเจคชื่อ next-darkmode
เน้อครับ
npx create-next-app next-darkmode
เจมส์ตั้งค่าตามนี้เน้อครับ
✔ Would you like to use TypeScript? … Yes
✔ Would you like to use ESLint? … No
✔ Would you like to use Tailwind CSS? … Yes
✔ Would you like to use `src/` directory? … Yes
✔ Would you like to use App Router? (recommended) … Yes
✔ Would you like to customize the default import alias (@/*)? … Yes
✔ What import alias would you like configured? … @/*
หลังจากที่เราสร้าง Project เปล่า ๆ ขึ้นมาเรียบร้อยแล้ว เดี๋ยวเรามาลง node_modules ชื่อ next-themes
เพิ่มกันหน่อยเน้อครับ
ให้ cd เข้าไปใน project ที่สร้าง
cd next-darkmode
ติดตั้ง next-themes
สำหรับเปลี่ยนเป็น Dark mode หรือ Light mode
npm install --save next-themes
ติดตั้ง @radix-ui/react-icons
เพื่อจะใช้ Icon เพื่อเพิ่มความสวยงาม ในบทความนี้เจมส์จะทำให้กดที่ Icon รูปพระอาทิตย์ แล้วจะแสดงเป็น Light mode และ Icon จะเปลี่ยนเป็นรูปพระจันทร์ เมื่อกดที่ Icon ปุ่มพระจันทร์ จะเปลี่ยนเป็น Dark mode และ Icon จะเปลี่ยนเป็นรูปพระอาทิตย์
npm install --save @radix-ui/react-icons
ขั้นต่อไปเดี๋ยวเราปรับไฟล์ src/app/globals.css
ให้เหลือแค่
@tailwind base;
@tailwind components;
@tailwind utilities;
จากนั้นให้ปรับไฟล์ tailwind.config.ts
โดยเพิ่ม darkMode: "class"
เข้าไป
import type { Config } from "tailwindcss";
const config: Config = {
content: [
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
extend: {
backgroundImage: {
"gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
"gradient-conic":
"conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
},
},
},
plugins: [],
darkMode: "class",
};
export default config;
จากนั้นเราจะมาเพิ่มไฟล์ src/components/SwitchTheme.tsx
เพื่อเราจะใส่ปุ่มสำหรับเปลี่ยน Dark mode และ Light mode ให้กับเว็บไซต์ของเราครับ โดยจะมีโค้ดดังนี้ครับ
"use client";
import { useTheme } from "next-themes";
import { useEffect, useState } from "react";
import { MoonIcon, SunIcon } from "@radix-ui/react-icons";
const SwitchTheme = () => {
const [mounted, setMounted] = useState(false);
const { theme, setTheme } = useTheme();
useEffect(() => setMounted(true));
if (!mounted) {
return null;
}
const handleSwitchTheme = () => {
if (theme === "dark") {
setTheme("light");
} else {
setTheme("dark");
}
};
return (
<button
className="border relative mt-4 h-[1.2rem] w-[1.2rem] p-4 rounded-md hover:opacity-70"
onClick={handleSwitchTheme}
>
<SunIcon className="absolute top-2 left-2 scale-0 dark:scale-100 " />
<MoonIcon className="absolute top-2 left-2 h-[1.2rem] w-[1.2rem] scale-100 dark:scale-0" />
</button>
);
};
export default SwitchTheme;
โค้ดในส่วนนี้จะ Run ในฝั่ง Client เท่านั้นครับ โดยถ้าหากยังไม่ Run ฝั่ง client จะ return null กลับไป ซึ่งในส่วนของ Icon ที่แสดงจะอยู่ใน button โดยถ้าหากเป็น dark theme จะแสดง SunIcon
แต่ถ้าหากเป็น light theme จะแสดง MoonIcon
เมื่อคลิกที่ button จะเช็คว่าถ้าหาก theme ปัจจุบันเป็น dark
จะ setTheme ใหม่เป็น ligth
ขั้นต่อมาให้เรามาเพิ่มไฟล์ชื่อ ThemeProvider.tsx
ใน src/app
โดยเพิ่มโค้ดลงไปดังนี้ครับ
"use client";
import * as React from "react";
import { ThemeProvider as NextThemesProvider } from "next-themes";
import { type ThemeProviderProps } from "next-themes/dist/types";
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
}
ในส่วนนี้เราจะเรียกใช้ ThemeProvider ของ next-themes
ซึ่งเดี๋ยวเราจะเอาไปเรียกใช้ใน layout.tsx
กันครับ
ปรับโค้ด layout.tsx
ให้เป็นดังนี้ครับ
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import { ThemeProvider } from "./ThemeProvider";
import SwitchTheme from "@/components/SwitchTheme";
import "./globals.css";
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en" suppressHydrationWarning>
<body className={inter.className}>
<div className="container px-4">
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
<SwitchTheme />
{children}
</ThemeProvider>
</div>
</body>
</html>
);
}
ก็คือเราเอา ThemeProvider
ที่สร้างมาใส่ และ SwitchTheme
ที่เป็นปุ่มเปลี่ยน Theme มาใส่ ซึ่งใน ThemeProvider เราจะส่ง Props เข้าไปดังนี้ครับคือ
attribute="class"
คือ config ให้ในส่วนของ tag html ใช้class
แทนdata-theme
เวลาเราปรับ themedefaultTheme="system"
และenableSystem
ให้ค่าเริ่มต้นของ theme เป็นsystem
disableTransitionOnChange
คือ ปรับให้ไม่ต้องมี transition ในระหว่างการเปลี่ยน theme
ในส่วน
suppressHydrationWarning
ที่เราเพิ่มเข้ามาในส่วนของ html คือเพื่อบอกไม่ได้แจ้งเตือน error เนื่องจากข้อมูลใน client และ server ไม่ตรงกัน
จากนั้นเราจะมาปรับในส่วนของหน้า page.tsx
ให้เป็นดังนี้ครับ
export default function Home() {
return (
<div className="pt-4">
<div>Hello world</div>
<div className="dark:text-green-200 text-blue-800">Custom color</div>
</div>
);
}
โค้ดในที่นี้คือในส่วนของ Hello world จะแสดงตามค่า default ของ tailwind ที่เป็น dark mode และ light mode แต่ในส่วนของ text ที่เป็นคำว่า Custom color
เราจะปรับในส่วนของ dark mode ให้แสดงสี text-green-200
แต่ถ้าเป็น light mode จะให้แสดงสี text-blue-800
ดังรูปด้านล่างครับ
เรียบร้อยแล้วครับ หวังว่าบทความนี้จะมีประโยชน์กับคุณผู้อ่าน ไม่มากก็น้อยเน้อครับ ถ้าหากบทความนี้มีส่วนไหนผิดพลาดประการใด ก็ขออภัยมา ณ ที่นี้ด้วยเน้อครับ