Blog

Next UIでモーダルを追加する

Cover Image for Next UIでモーダルを追加する

モーダルを追加したい

このブログのカバー画像はAIで作ってるんですけど、カバー画像だけをギャラリー形式で見れるページを作ることにしました。

グリッド形式で画像を見せるところまではTailwindのギャラリーのサンプルを真似してできたのだけど、画像をクリックして拡大して見せるのは別途モーダルを作らないといけないらしい。

フロント音痴のワイ、DOMいじったりスタイリングするだけでヒーヒー言ってるのにモーダル作るなんて無理です。(大昔業務で作ったこともあったけど大変だった記憶)

ライブラリを探してみると、Next UIという、Next.js専用でTailwindとも統合してるいい感じのライブラリがあったので、モーダルだけNext UIを使ってみることにしました。

Next UIのパッケージをインストール

最初にnpm install @nextui-org/reactでパッケージを追加しようとすると、以下のエラーでつっかかってしまいました。

npm install @nextui-org/react
npm WARN ERESOLVE overriding peer dependency
npm WARN ERESOLVE overriding peer dependency
npm WARN ERESOLVE overriding peer dependency
npm WARN ERESOLVE overriding peer dependency
npm WARN ERESOLVE overriding peer dependency
npm WARN ERESOLVE overriding peer dependency
npm WARN ERESOLVE overriding peer dependency
npm WARN ERESOLVE overriding peer dependency
npm WARN ERESOLVE overriding peer dependency
npm WARN ERESOLVE overriding peer dependency
npm WARN ERESOLVE overriding peer dependency
npm WARN ERESOLVE overriding peer dependency
npm WARN ERESOLVE overriding peer dependency
npm WARN ERESOLVE overriding peer dependency
npm WARN ERESOLVE overriding peer dependency
npm WARN ERESOLVE overriding peer dependency
npm WARN ERESOLVE overriding peer dependency
npm WARN ERESOLVE overriding peer dependency
npm WARN ERESOLVE overriding peer dependency
npm WARN ERESOLVE overriding peer dependency
npm WARN ERESOLVE overriding peer dependency
npm WARN ERESOLVE overriding peer dependency
npm WARN ERESOLVE overriding peer dependency
npm ERR! code ERESOLVE
npm ERR! ERESOLVE could not resolve
npm ERR! 
npm ERR! While resolving: framer-motion@11.15.0
npm ERR! Found: react@19.0.0-rc-02c0e824-20241028
npm ERR! node_modules/react
npm ERR!   peer react@">=18" from @nextui-org/form@2.1.7
npm ERR!   node_modules/@nextui-org/react/node_modules/@nextui-org/form
npm ERR!     @nextui-org/form@"2.1.7" from @nextui-org/react@2.6.10
npm ERR!     node_modules/@nextui-org/react
npm ERR!       @nextui-org/react@"*" from the root project
npm ERR! 
npm ERR! Could not resolve dependency:
npm ERR! peerOptional react@"^18.0.0 || ^19.0.0" from framer-motion@11.15.0
npm ERR! node_modules/framer-motion
npm ERR!   peer framer-motion@">=11.5.6 || >=12.0.0-alpha.1" from @nextui-org/react@2.6.10
npm ERR!   node_modules/@nextui-org/react
npm ERR!     @nextui-org/react@"*" from the root project
npm ERR!   peer framer-motion@">=11.5.6 || >=12.0.0-alpha.1" from @nextui-org/accordion@2.2.6
npm ERR!   node_modules/@nextui-org/accordion
npm ERR!     @nextui-org/accordion@"2.2.6" from @nextui-org/react@2.6.10
npm ERR!     node_modules/@nextui-org/react
npm ERR!       @nextui-org/react@"*" from the root project
npm ERR!   17 more (@nextui-org/system, @nextui-org/autocomplete, ...)
npm ERR! 
npm ERR! Conflicting peer dependency: react@19.0.0
npm ERR! node_modules/react
npm ERR!   peerOptional react@"^18.0.0 || ^19.0.0" from framer-motion@11.15.0
npm ERR!   node_modules/framer-motion
npm ERR!     peer framer-motion@">=11.5.6 || >=12.0.0-alpha.1" from @nextui-org/react@2.6.10
npm ERR!     node_modules/@nextui-org/react
npm ERR!       @nextui-org/react@"*" from the root project
npm ERR!     peer framer-motion@">=11.5.6 || >=12.0.0-alpha.1" from @nextui-org/accordion@2.2.6
npm ERR!     node_modules/@nextui-org/accordion
npm ERR!       @nextui-org/accordion@"2.2.6" from @nextui-org/react@2.6.10
npm ERR!       node_modules/@nextui-org/react
npm ERR!         @nextui-org/react@"*" from the root project
npm ERR!     17 more (@nextui-org/system, @nextui-org/autocomplete, ...)
npm ERR! 
npm ERR! Fix the upstream dependency conflict, or retry
npm ERR! this command with --force or --legacy-peer-deps
npm ERR! to accept an incorrect (and potentially broken) dependency resolution.
npm ERR! 
npm ERR! 
npm ERR! For a full report see:
npm ERR! /Users/nitta/.npm/_logs/2025-01-05T10_24_55_348Z-eresolve-report.txt

npm ERR! A complete log of this run can be found in: /Users/nitta/.npm/_logs/2025-01-05T10_24_55_348Z-debug-0.log

どうも、このNext.jsアプリケーションでインストールされてるReactのバージョンが高すぎるらしい。

npm install react@18 react-dom@18

でReactをダウングレードしてからもう一度Next UIをインストールすると上手くいきました。

Next UIのCLIをインストール

npm install -g nextui-cli

これでnextuiコマンドが使えるようになりました。使わなくてもいいんじゃないかと思うけど公式が推してるので一応入れました。

モーダルをインストール

nextui add modal

これでpackage.jsonにモーダルのパッケージが追加されました。

ルートのDOMをNext UI Providerでラップ

これがNext.js初心者的に「??」な部分だったんだけど、Next UI ProviderっていうReactコンポーネントでDOMをラップする必要があります。多分、ライブラリを適用したいコンポーネントだけラップしてもいいんだと思うけど、公式が推してるようにルートのDOMをラップしておけばサイト全体で使えるので私もそうしました。App routerの場合、src/app/layout.tsxがルートのDOMなので、そのbodyタグの中身の子要素をラップしました。(コメントしてる部分が既存のlayout.tsxから書き換えた部分)

"use client"; ← これがないとダメだった

import Footer from "@/app/_components/footer";
...
import { NextUIProvider } from "@nextui-org/react"; ← Next UI Providerをインポート

import "./globals.css";

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

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <head>
        ...
      </head>
        <body
          className={cn(inter.className, "dark:bg-slate-900 dark:text-slate-400")}
        >
          <ThemeSwitcher />
          <NextUIProvider><div className="min-h-screen">{children}</div></NextUIProvider> ← 子要素をラップ
          <Footer /> 
        </body>
    </html>
  );
}

use clientはいまいちよくわかってないので時間があるときに調べたい。

モーダル用のコンポーネントを作成

Next UI Modalの公式ドキュメントを読みつつ、最初はGalleryページapp/gallery/page.tsxにそのままModalコンポーネントを入れようとしたんですが、Modal使用時に必要な"use client";が既存のコードと喧嘩してうまくいかなかったので、app/_components/gallery-modal.tsxにコンポーネントを作り、app/gallery/page.tsxから呼び出すようにするとうまくいきました。やっぱuse clientは闇ですね。

モーダルコンポーネント app/_components/gallery-modal.tsx

"use client";

import {
    Modal,
    ModalContent,
    ModalHeader,
    ModalBody,
    ModalFooter,
    Button,
    useDisclosure,
} from "@nextui-org/react";

type Props = {
    src: string;
}

export default function GalleryModal({ src }: Props) {
    const { isOpen, onOpen, onOpenChange } = useDisclosure();

    return (
        <>
            <Button onPress={onOpen}><img className="h-auto max-w-full rounded-lg" src={`${src}`} /></Button>
            <Modal
                isOpen={isOpen}
                onOpenChange={onOpenChange}
                size="5xl"
                hideCloseButton={true}
            >
                <ModalContent>
                    {(onClose) => (
                        <ModalBody>
                            <img className="h-auto max-w-full rounded-lg" src={`${src}`} />
                        </ModalBody>
                    )}
                </ModalContent>
            </Modal>
        </>
    );
}

呼び出し側 app/gallery/page.tsx

import Container from "@/app/_components/container";
import Header from "@/app/_components/header";
import PageTitle from "@/app/_components/page-title";
import fs from "fs";
import { join } from "path";
import GalleryModal from "@/app/_components/gallery-modal";

const coverImagesDirectory = join(process.cwd(), "public/assets/cover_images");

export default function Gallery() {
  const files = fs.readdirSync(coverImagesDirectory);

  return (
    <main>
      <Container>
        <Header />
        <PageTitle title="Gallery" />
        <div className="grid grid-cols-2 md:grid-cols-3 gap-4">
          {files.map((item) => (
            <GalleryModal key={item} src={`/assets/cover_images/${item}`}/>
          ))}
        </div>
      </Container>
    </main>
  );
}

出来上がり!

これで画像クリックで拡大表示できるギャラリーができました。

画像をクリックすると...↓

ぱっ!!(ワァァァ〜〜)

実際のギャラリーはこちらになります。→ Gallery

これはモーダル、誰がどう見ても立派なモーダルです。やってやりました。バックエンド界のフロント音痴史に残る快挙と言えるでしょう。これは調子に乗ってなんかいろいろ作りたくなりますね。画面動かすのって面白いかも??