🕌

【NextJs14】NextJs14 と 便利なライブラリ【#11Shadcn-ui Dialog 】

2023/12/23に公開

【#11Shadcn-ui Dialog 】

YouTube: https://youtu.be/qWfZRQRMAGM

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」を使用してモーダルの作成を行います。

https://ui.shadcn.com/docs/components/dialog

npx shadcn-ui@latest add dialog
npx shadcn-ui@latest add input
npx shadcn-ui@latest add label

そして上記ページのExamplesのコードをコピーして
新しく作成した以下のファイルに貼り付けます。

モーダルはアプリ全体で使用する可能性がありますので、
app直下のcomponentsフォルダに作成します。

モーダルにフォームの値をコピーする関数と
成功のポップアップが出るように追加をしています。

https://www.npmjs.com/package/sonner

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