Vue3 + DaisyUIで通知を作成

## はじめに
ユーザにエラーなどを通知する際のトーストみたいなものです。
DaisyUI に [Alertコンポーネント](https://daisyui.com/components/alert/) があるのですが、それをただ表示するのでは味気ないので横からスライドして入ってくるものを作りたかった感じです。

調べるとVueには [TransitionGroup](https://ja.vuejs.org/guide/built-ins/transition-group) という組み込みコンポーネントが用意されており、
これを利用すると　`v-for`　でのリスト表示時、リストに**入ったとき**、**出たとき**にクラスが付与されてアニメーションが設定できるようです。

## 作ったもの
![画面収録-2024-07-21-19.10.33.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178346/aa44e1ff-4c75-56c8-8682-dd5ad58fc954.gif)
なんかgifにしたらクソ遅いですけど、実際はもっと早いです

## 実装
### Alertコンポーネント
```vuejs:components/alert/Alert.vue
<template>
  <transition-group name="alert" tag="ul" class="alert-wrapper md:w-1/2 w-full">
    <li v-for="alert in alerts" :key="alert.key" class="my-2 w-full">
      <div role="alert" class="alert flex justify-between" :class="getAlertColorClass(alert.color)" >
        <div class="flex gap-1.5">
          <svg
            xmlns="http://www.w3.org/2000/svg"
            fill="none"
            viewBox="0 0 24 24"
            class="stroke-current h-6 w-6 shrink-0">
            <path
              stroke-linecap="round"
              stroke-linejoin="round"
              stroke-width="2"
              d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
          </svg>
          <span>
            {{ alert.message }}
          </span>
        </div>

        <button class="btn btn-sm btn-ghost" v-if="alert.closeable" @click="closeAlert(alert)">
          <span class="icon-[ic--baseline-close]" style="width: 20px; height: 20px;"></span>
        </button>
      </div>
    </li>
  </transition-group>
</template>

<script setup lang="ts">
import { alerts, closeAlert } from '@/composables/alert';

function getAlertColorClass(color: string = 'default') {
  let className: string | null | undefined = null;

  switch (color) {
    case 'default':
      className = '';
      break;
    case 'info':
      className = 'alert-info';
      break;
    case 'success':
      className = 'alert-success';
      break;
    case 'warning':
      className = 'alert-warning';
      break;
    case 'error':
      className = 'alert-error';
      break;
  }

  return className;
}
</script>

<style>
.alert-wrapper {
  position: absolute;
  right: 0;
  bottom: 0;
  z-index: 1000000;
  padding: 20px;
  overflow: hidden;
}

.alert-move, /* 移動する要素にトランジションを適用 */
.alert-enter-active,
.alert-leave-active {
  transition: all 0.8s ease;
}

.alert-enter-from,
.alert-leave-to {
  opacity: 0;
  transform: translateX(50%);
}

/* leave する項目をレイアウトフローから外すことで
   アニメーションが正しく計算されるようになる */
.alert-leave-active {
  position: absolute;
}
</style>
```
template部分は DaisyUI のコンポーネントを利用する形で、Alertのタイプによってクラスを変更している形です。
スタイルはVueのドキュメントほぼそのままです。
`.alert-wrapper` は通知形式に表示する用の設定です。(ルートの要素に `position: relative;` も必要)
**閉じるアイコンなどは外部のライブラリを利用しています**

### 通知の表示ロジック
```typescript:composables/alert.ts
import { ref, type Ref } from 'vue';

interface Alert {
  key?: string,
  /**
   * アラートのタイプ
   */
  color?: string | 'default' | 'info' | 'success' | 'warning' | 'error',
  /**
   * 表示するメッセージ
   */
  message?: string,
  /**
   * 閉じるボタンを表示するかどうか
   */
  closeable?: boolean,
  /**
   * 閉じるまでの秒数
   * 0以下で無効 その場合closeableが必要
   * clseableも無効の場合は５秒後に削除
   */
  close_at?: number,
};

const alerts: Ref<Alert[]> = ref<Alert[]>([]);

const pushAlert = ({ message, color = 'default', closeable = false, close_at = 0}: Alert) => {
  close_at = close_at > 0 ? close_at : 0;
  const alert: Alert = {
    key: crypto.randomUUID(),
    color,
    message,
    closeable,
    close_at,
  };

  alerts.value.push(alert);

  // 閉じるボタン非表示かつ、閉じる秒数も設定されていない場合は、強制的に5秒後に削除
  if (!closeable && !close_at) {
    close_at = 5;
  }

  if (close_at) {
    /**
     * close_at秒後に消す
     */
    setTimeout(() => {
      closeAlert(alert);
    }, close_at * 1000);
  }

};

const closeAlert = (alert: Alert) => {
  alerts.value = alerts.value.filter((item) => item.key !== alert.key);
}

export {
  type Alert,
  pushAlert,
  closeAlert,
  alerts,
};
```
ここでは通知のリストを配列として保持し、配列に出し入れする処理を関数としてエクスポートしています。
通知の要素としては
* key:  配列で管理する際の key
* color: 通知するアラートのタイプ
* message:  通知するメッセージ
* closeable: 閉じるボタンを表示するかどうか
* close_at: x秒後に非表示

を用意して、通知を表示する際に指定する形にしています。(keyは自動で設定)
通知の表示非表示は、リスト（配列)の要素があるかないかで表現される感じです。

### 利用
* ルートのコンポーネントで Alert コンポーネントを呼び出し (ここは teleport などが使えるかもですが、未検証)
* 通知の表示は以下のように†コンポーザブルをインポートして†()、pushAlertを呼び出す形です

```test.ts
import { pushAlert } from '@/composables/alert'

pushAlert({/** 省略 */});
```

## 締め
外部のライブラリなどを使わずにいい感じに通知アニメーションが作れて良いですね。
TypeScript初心者なので色々使い方など間違ってるかもしれませんが...