ToastHost.vue 2.08 KB
<script setup>
/**
 * ToastHost —— 应用根挂载一次,渲染 use-toast 的受控队列。只读队列、不写入;
 * 写入一律走 `useToast()`。每条 toast 可手动关闭(点 × 调 dismiss)。
 */

import { useToast, useToastQueue } from '../composables/use-toast.js'

const toasts = useToastQueue()
const { dismiss } = useToast()
</script>

<template>
  <Teleport to="body">
    <div
      class="drama-toast-host"
      role="region"
      aria-live="polite"
    >
      <div
        v-for="t in toasts"
        :key="t.id"
        class="drama-toast"
        :class="`drama-toast--${t.type}`"
        role="status"
      >
        <span class="drama-toast__msg">{{ t.message }}</span>
        <button
          class="drama-toast__close"
          type="button"
          aria-label="Dismiss"
          @click="dismiss(t.id)"
        >
          ×
        </button>
      </div>
    </div>
  </Teleport>
</template>

<style scoped>
.drama-toast-host {
  position: fixed;
  top: var(--drama-space-lg);
  right: var(--drama-space-lg);
  display: flex;
  flex-direction: column;
  gap: var(--drama-space-sm);
  z-index: 1100;
  pointer-events: none;
}

.drama-toast {
  display: flex;
  align-items: center;
  gap: var(--drama-space-md);
  min-width: 240px;
  max-width: 360px;
  padding: var(--drama-space-md) var(--drama-space-lg);
  border-radius: var(--drama-radius-md);
  border-left: 4px solid var(--drama-color-border);
  background-color: var(--drama-color-surface);
  color: var(--drama-color-text);
  box-shadow: var(--drama-shadow-md);
  font-family: var(--drama-font-family-body);
  font-size: var(--drama-font-size-sm);
  pointer-events: auto;
}

.drama-toast--success {
  border-left-color: var(--drama-color-success);
}

.drama-toast--error {
  border-left-color: var(--drama-color-danger);
}

.drama-toast--info {
  border-left-color: var(--drama-color-info);
}

.drama-toast__msg {
  flex: 1;
}

.drama-toast__close {
  background: transparent;
  border: none;
  color: var(--drama-color-text-muted);
  font-size: var(--drama-font-size-lg);
  line-height: 1;
  cursor: pointer;
}
</style>