tailwind cssでガラスっぽいダイアログを実装してみる[Next.js TailwindCSS]

# はじめに
こちらの方の記事でガラスっぽいデザインを`tailwind css`で実装されている方がいらっしゃり、オシャレだ！真似しよう！と思ったので、今回ガラス張りのダイアログを`tailwind css`で実装してみました。

https://qiita.com/ruiiixiii/items/37fa4a6e3a7745b2437d

# 実装結果
自分のポートフォリオサイトで下のように実装してみました。

![ダイアログ.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3766230/2514f8d9-1f5a-fc1d-29cd-69613e0a055b.gif)

# 実装方法

### ダイアログのテンプレート
ガラスのダイアログをコンポーネント化したコードは以下のようになっています。
先ほど紹介した方の記事からガラスのような見た目になるデザインを引っ張ってきつつ、`Framer-motion`でフェードインとフェードアウトを実装しています。
>`Framer-motion`は[別の記事](https://qiita.com/maooz4426/items/21fbc1f02eab1d76061e)で紹介しているので、よかったらご覧ください。

```typescript
export const DialogTemplate: React.FC<DialogTemplateProps> = (props) =>{
     return (
         <div className="fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 scale-125">
                 <motion.div
                     initial={{opacity: 0}}
                     animate={{opacity: 1}}
                     exit={{opacity: 0}}
                     transition={{duration: 1, delay: 0.5}}
                     className=" backdrop-blur-lg rounded-md border border-gray-200/30 shadow-lg grid grid-cols-2"
                 >
                    <Image
                        src={props.src}
                        alt="dialog img"
                        width={500}
                        height={500}
                        className="m-0.5"
                    />
                     <div>
                         {props.children}
                     </div>
                 </motion.div>
             </div>
     )
}
```

真ん中にダイアログを設置するように、`fixed`で画面上にダイアログを固定しつつ、`top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2`で真ん中に設定しています。
```typescript
<div className="fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 scale-125">
```

`motion.div`でフェードイン、フェードアウトを設定しています。

:::note
フェードアウトを実現してる`exit`オプションは`AnimatePresence`で囲う必要があります。
:::

`tailwind css`では、先ほど[ご紹介した記事](https://qiita.com/ruiiixiii/items/37fa4a6e3a7745b2437d)の`backdrop-blur-lg`で背景をぼかしたバージョンを使用しています。
```typescript
 <motion.div
    initial={{opacity: 0}}
    animate={{opacity: 1}}
    exit={{opacity: 0}}
    transition={{duration: 1, delay: 0.5}}
    className=" backdrop-blur-lg rounded-md border border-gray-200/30 shadow-lg grid grid-cols-2"
>
```




### ガラスダイアログコンポーネントを使ってみる

以下のコードに、ガラスコンポーネントの具体的な使い方のコードを書いています。
```typescript
const BASE_PATH = nextConfig.basePath || "";
export default function WorkPage (){
    enum DialogType{
        None,
        Dialog1,
        Dialog2
    }
    const [dialogType, setDialogType] = React.useState(DialogType.None);
    const dialogRef = React.useRef<HTMLDivElement | null>(null);

    const handleCloseDialog =(e:MouseEvent) =>{
        if((dialogRef.current as any).contains(e.target)) return;
        setDialogType(DialogType.None);
    }

    React.useEffect(()=>{
        document.addEventListener("click", handleCloseDialog);
        return ()=>{
            document.removeEventListener("click", handleCloseDialog);
        }
    })

  return(
      <div>
          <AnimatePresence>
          {dialogType === DialogType.Dialog1 && (
              <div className="fixed inset-0 bg-black bg-opacity-50">
                  <div ref={dialogRef}>
                      <DialogTemplate
                          src={`${BASE_PATH}/<publicに置いたパスを書く}`}
                      >
                          <div className="mx-2">
                              <h1 className="flex items-center justify-center underline">Dialog1</h1>
                              <p>Dialog1の本文</p>
                          </div>
                      </DialogTemplate>
                  </div>
              </div>
          )}
          </AnimatePresence>

          <motion.div
              initial={{opacity: 0}}
              animate={{opacity: 1}}
              transition={{duration: 1, delay: 2}}
              onClick={() => setDialogType(DialogType.Report)}
              className="my-10 flex flex-col items-center justify-center">
              <button>
                  <Image
                      src={`${BASE_PATH}/<publicに置いたパスを書く}`}
                      alt="plateau-sdk"
                      width={500}
                      height={400}
                  />
              </button>
              <h1 className="text-2xl my-2 underline">Dialog1</h1>
          </motion.div>
          </div>

          <div className="dialog">
          </div>
      </div>
)
}
```

下のコードで、enumで状態(state)を管理しています。enumで複数の状態を列挙しておくことで、どのダイアログが表示されているかを管理することができます。
```typescript
 enum DialogType{
        None,
        Dialog1,
        Dialog2
    }
    const [dialogType, setDialogType] = React.useState(DialogType.None);
```

以下のコードでダイアログの外側をタップしたらダイアログを閉じるようにしています。
`useRef`を使うことでHTMLのdiv要素をメモ化することが可能で、ダイアログの部分だけを指定します。
そうすることで、その外側をクリックしたらDialogのstateを`None`に変え、ダイアログが表示される前の画面にするようにしています。
```typescript
    const dialogRef = React.useRef<HTMLDivElement | null>(null);

    const handleCloseDialog =(e:MouseEvent) =>{
        if((dialogRef.current as any).contains(e.target)) return;
        setDialogType(DialogType.None);
    }

    React.useEffect(()=>{
        document.addEventListener("click", handleCloseDialog);
        return ()=>{
            document.removeEventListener("click", handleCloseDialog);
        }
    })
```


以下のコードでダイアログのフェードインとフェードアウトを行っています。
`dialogRef`をdivタグの`ref`オプションで指定することで、どの範囲の`HTMLDiv`要素をダイアログとするかを指定しています。

`{dialogType === DialogType.Dialog1 && `を記述することで条件付きレンダリングを行うことが可能になります。こうすることでstateの状態によって表示するダイアログを変化させています。

また、`DialogTemplate`コンポーネント内で`motion.div`の`exit`オプションで指定したフェードアウトを機能させるために、`AnimatePresene`で`DialogTemplate`を囲うことでフェードアウトを行えるようにしています。

```typescript
 <AnimatePresence>
          {dialogType === DialogType.Dialog1 && (
              <div className="fixed inset-0 bg-black bg-opacity-50">
                  <div ref={dialogRef}>
                      <DialogTemplate
                          src={`${BASE_PATH}/<publicに置いたパスを書く}`}
                      >
                          <div className="mx-2">
                              <h1 className="flex items-center justify-center underline">Dialog1</h1>
                              <p>Dialog1の本文</p>
                          </div>
                      </DialogTemplate>
                  </div>
              </div>
          )}
</AnimatePresence>

```

# 最後に
デザインセンスのない自分ですが、`Framer-motion`とqiitaの記事を参考にして結構オシャレなダイアログが作れたのではと感じています。
ただ、これはモバイル端末に合わせて作っていないのでいつか調整したいです。