🕌
【NextJs14】NextJs14 と 便利なライブラリ【#11Shadcn-ui Dialog 】
【#11Shadcn-ui Dialog 】
YouTube: https://youtu.be/qWfZRQRMAGM
まずは前回動画内でLinkとliの
タグの位置が逆でしたので修正を行います。
app/(main)/_components/main-sidebar-item.tsx
"use client";
import { usePathname } from "next/navigation";
import Link from "next/link";
import { MenuItem } from "./main-sidebar";
import { cn } from "@/lib/utils";
interface MainSidebarItemProps {
item: MenuItem;
}
export const MainSidebarItem = ({ item }: MainSidebarItemProps) => {
const pathname = usePathname();
return (
<li
className={cn(
"group rounded-sm p-2 transition",
item.pathname === pathname
? "bg-slate-700 text-white"
: "text-neutral-600 dark:text-neutral-400 hover:bg-slate-700"
)}
>
<Link href={item.href} className="flex items-center">
{item.icon}
<span className="group-hover:text-white">{item.label}</span>
</Link>
</li>
);
};
今回は「Dialog」を使用してモーダルの作成を行います。
npx shadcn-ui@latest add dialog
npx shadcn-ui@latest add input
npx shadcn-ui@latest add label
そして上記ページのExamplesのコードをコピーして
新しく作成した以下のファイルに貼り付けます。
モーダルはアプリ全体で使用する可能性がありますので、
app直下のcomponentsフォルダに作成します。
モーダルにフォームの値をコピーする関数と
成功のポップアップが出るように追加をしています。
npm install sonner
app/components/modal/user-profile-modal.tsx
"use client";
import { Copy } from "lucide-react";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
interface UserProfileModalProps {
email: string;
}
export function UserProfileModal({ email }: UserProfileModalProps) {
const onClickCopy = () => {
navigator.clipboard.writeText(email);
toast.success("Copy Succeed!");
};
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">Share</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>Share link</DialogTitle>
<DialogDescription>
Anyone who has this link will be able to view this.
</DialogDescription>
</DialogHeader>
<div className="flex items-center space-x-2">
<div className="grid flex-1 gap-2">
<Label htmlFor="link" className="sr-only">
Link
</Label>
<Input id="link" defaultValue={email} readOnly />
</div>
<Button
onClick={onClickCopy}
type="button"
size="sm"
className="px-3"
>
<span className="sr-only">Copy</span>
<Copy className="h-4 w-4" />
</Button>
</div>
<DialogFooter className="sm:justify-start">
<DialogClose asChild>
<Button type="button" variant="secondary">
Close
</Button>
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
そして「sonner」を使用するために
app直下のlayout.tsxにToasterを追加します。
app/layout.tsx
import type { Metadata } from "next";
import { ClerkProvider } from "@clerk/nextjs";
import { Inter } from "next/font/google";
import { Toaster } from "sonner";
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
>
<Toaster />
{children}
</ThemeProvider>
</body>
</ClerkProvider>
</html>
);
}
ここまでできましたら、
プロテクトページにモーダルを追加します。
app/(main)/protected/page.tsx
import Image from "next/image";
import { currentUser, UserProfile } from "@clerk/nextjs";
import { dark } from "@clerk/themes";
import { UserProfileModal } from "@/components/modals/user-profile-modal";
const ProtectedPage = async () => {
const user = await currentUser();
return (
<div className="flex flex-col p-10">
<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>
<li>
<UserProfileModal
email={user?.emailAddresses?.[0].emailAddress || ""}
/>
</li>
</ul>
<UserProfile
appearance={{
baseTheme: dark,
elements: {
card: {
// boxShadow: "none",
},
},
}}
/>
</div>
);
};
export default ProtectedPage;
Discussion