はじめに
ユーザにエラーなどを通知する際のトーストみたいなものです。
DaisyUI に Alertコンポーネント があるのですが、それをただ表示するのでは味気ないので横からスライドして入ってくるものを作りたかった感じです。
調べるとVueには TransitionGroup という組み込みコンポーネントが用意されており、
これを利用すると v-for
でのリスト表示時、リストに入ったとき、出たときにクラスが付与されてアニメーションが設定できるようです。
作ったもの
実装
Alertコンポーネント
<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;
も必要)
閉じるアイコンなどは外部のライブラリを利用しています
通知の表示ロジック
import { ref, type Ref } from 'vue';
interface Alert {
key?: string,
/**
* アラートのタイプ
*/
color?: string | 'default' | 'info' | 'success' | 'warning' | 'error',
/**
* 表示するメッセージ
*/
message?: string,
/**
* 閉じるボタンを表示するかどうか
*/
closeable?: boolean,
/**
* 閉じるまでの秒数
* 0以下で無効 その場合closeableが必要
* clseableも無効の場合は5秒後に削除
*/
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を呼び出す形です
import { pushAlert } from '@/composables/alert'
pushAlert({/** 省略 */});
締め
外部のライブラリなどを使わずにいい感じに通知アニメーションが作れて良いですね。
TypeScript初心者なので色々使い方など間違ってるかもしれませんが...