🐡
【NextJs14】NextJs14 と 便利なライブラリ【#7DarkMode】
【#7DarkMode】
YouTube: https://youtu.be/sBxCYwxkjM4
今回はアプリケーションにダークモードのテーマを実装します。
こちらはshadcn-uiに限らずtailwindを使用している
アプリで共通の実装方法になります。
まずはこちらをインストールします。
npm install next-themes
次にプロバイダーを作成します。
components/theme-provider.tsx
"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>;
}
プロバイダーをレイアウトに反映します。
app/layout.tsx
import type { Metadata } from "next";
import { ClerkProvider } from "@clerk/nextjs";
import { Inter } from "next/font/google";
import "./globals.css";
import { ThemeProvider } from "@/components/theme-provider";
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en" suppressHydrationWarning>
<ClerkProvider>
<body className={`${inter.className} bg-white dark:bg-slate-800`}>
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
{children}
</ThemeProvider>
</body>
</ClerkProvider>
</html>
);
}
次にモードを切り替えるボタンを作成します。
まずは、shadcn-uiからdropdown-menuをインストールします。
npx shadcn-ui@latest add dropdown-menu
components/mode-toggle-button.tsx
"use client";
import * as React from "react";
import { Moon, Sun } from "lucide-react";
import { useTheme } from "next-themes";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
export function ModeToggle() {
const { setTheme } = useTheme();
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
size="icon"
className="dark:bg-slate-800 focus-visible:ring-0 focus-visible:ring-offset-0"
>
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Toggle theme</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setTheme("light")}>
Light
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("dark")}>
Dark
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("system")}>
System
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}
今回は上記ボタンを(main)でグループされたページで使用しますので
(main)にlayout.tsxを作成します。
app/(main)/layout.tsx
import React from "react";
import { MainNavbar } from "./_components/main-navbar";
const MainLayout = ({ children }: { children: React.ReactNode }) => {
return (
<div>
<MainNavbar />
{children}
</div>
);
};
export default MainLayout;
ボタンを設置している上記のMainNavbarコンポーネントは
(main)のグループだけで使用しますので、
そのような場合には(main)の直下に「_components」フォルダを作成して
その中でコンポーネントファイルを作成します。
app/(main)/_components/main-navbar.tsx
import { ModeToggle } from "@/components/mode-toggle-button";
import { UserButton } from "@clerk/nextjs";
export const MainNavbar = () => {
return (
<nav className="w-full h-auto px-4 py-3 flex items-center justify-between bg-slate-200 dark:bg-slate-900">
<div>Main Page</div>
<div className="flex items-center justify-center gap-x-2">
<ModeToggle />
<UserButton afterSignOutUrl="/" />
</div>
</nav>
);
};
レイアウト追加に伴いプロテクトページの変更を行います。
app/(main)/protected/page.tsx
import { UserButton, auth, currentUser, UserProfile } from "@clerk/nextjs";
import Image from "next/image";
const ProtectedPage = async () => {
const { userId } = auth();
const user = await currentUser();
return (
<div className="flex flex-col p-10">
<div className="flex items-center gap-x-2">
<UserButton afterSignOutUrl="/" />
<p>User ID: {userId}</p>
</div>
<ul className="flex flex-col p-6">
<li>
User Name: {user?.lastName} {user?.firstName}
</li>
<li>User Email: {user?.emailAddresses?.[0].emailAddress}</li>
<li className="flex gap-x-2">
User Image:{" "}
<Image src={user?.imageUrl!} width={30} height={30} alt="User" />
</li>
</ul>
<UserProfile />
</div>
);
};
export default ProtectedPage;
Discussion