Примитивы
32

Toast

Стопка исчезающих сообщений и первое в библиотеке, у чего есть настоящий уход. Область — это live-регион, и она рендерится даже пустой: регион, добавленный на страницу одновременно с первым сообщением, — это регион, за которым вспомогательные технологии не следили, и сообщение просто теряется.

чистая разметка — библиотеки на странице нет
отрендерил aura-react
отрендерил aura-vue
отрендерил aura-svelte
собрано из классов самого aura-angular
Saved
Your changes are live.
Could not reach the server
Saved
Your changes are live.
Could not reach the server
Saved
Your changes are live.
Could not reach the server
Saved
Your changes are live.
Could not reach the server
Saved
Your changes are live.
Could not reach the server

Код

index.html
<div class="aura-toast__viewport" aria-live="polite" aria-atomic="false">
  <div class="aura-toast" data-state="open" aria-live="polite">
    <div class="aura-toast__body">
      <div class="aura-toast__title">Saved</div>
      <div class="aura-toast__description">Your changes are live.</div>
    </div>
    <button type="button" class="aura-icon-btn aura-icon-btn--sm" aria-label="Dismiss Saved">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" aria-hidden="true">
        <path d="M6 6l12 12M18 6L6 18"></path>
      </svg>
    </button>
  </div>
  <div class="aura-toast aura-toast--danger" data-state="open" aria-live="assertive">
    <div class="aura-toast__body">
      <div class="aura-toast__title">Could not reach the server</div>
    </div>
    <button type="button" class="aura-icon-btn aura-icon-btn--sm" aria-label="Dismiss Could not reach the server">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" aria-hidden="true">
        <path d="M6 6l12 12M18 6L6 18"></path>
      </svg>
    </button>
  </div>
</div>
Demo.tsx
import { ToastViewport } from '@ilomee/aura-react';

/**
 * Отрисовано с `duration: 0`, поэтому тосты никуда не деваются: демо, содержимое которого исчезает
 * через пять секунд после загрузки, — это демо пустого угла. Закрыть тост по-прежнему можно, и уход
 * по-прежнему проигрывается — ради этого всё и показано.
 */
const TOASTS = [
  { id: 'a', title: 'Saved', description: 'Your changes are live.', duration: 0 },
  { id: 'b', title: 'Could not reach the server', tone: 'danger' as const, duration: 0 },
];

export default function Demo() {
  return <ToastViewport toasts={TOASTS} onDismiss={() => {}} />;
}
Demo.vue
<script setup lang="ts">
import { ToastViewport, createToaster, provideToaster } from '@ilomee/aura-vue';

// Или: вынесите createToaster() в собственный модуль и зовите откуда угодно.
const toaster = provideToaster();
</script>

<template>
  <button type="button" @click="toaster.success('Saved', { description: 'Your changes are live.' })">
    Save
  </button>
  <ToastViewport :toasts="toaster.toasts.value" @dismiss="toaster.dismiss" />
</template>

У Vue есть и сама очередь: createToaster() возвращает { toasts, show, success, info, warning, danger, dismiss, clear }, так что тридцать строк под этим компонентом больше не нужно писать заново. Фабрика, а не синглтон: модульное состояние на сервере общее для всех запросов процесса, поэтому где держать инстанс — решает приложение. Свой модуль зовётся откуда угодно, в том числе из перехватчика или обработчика ошибок, где inject() не работает; provideToaster() + useToast() — по одному на приложение и безопасно при SSR. Методы названы по тонам (danger, а не error), чтобы словарь остался один.

Demo.svelte
<script lang="ts">
  import { ToastViewport } from '@ilomee/aura-svelte';
  import { dismissToast } from '@ilomee/aura-core/toast';

  let toasts = $state([{ id: 'a', title: 'Saved', description: 'Your changes are live.' }]);
</script>

<ToastViewport {toasts} ondismiss={(id) => (toasts = dismissToast(toasts, id))} />

Используется как aura-toast-viewport.

demo.component.ts
<aura-toast-viewport [toasts]="toasts" (dismiss)="toasts = dismissToast(toasts, $event)" />

Пропсы

У чистой разметки нет пропсов — классы ниже и есть весь API. Четыре пакета не делают ничего сверх того, что проставляют вам те же самые классы.
ИмяТипПо умолчаниюПримечания
renderToast(toast: AuraToastItem) => ReactNodeОтрисуйте тело тоста сами. Оболочка — live-регион, таймер, пауза, уход — остаётся у области.
action{ label, onAction }У самого тоста: одно действие кнопкой под текстом. Ставьте вместе с duration: 0 — пяти секунд не хватает, чтобы прочитать, решить и дотянуться.
toastsAuraToastItem[] | nullСписок на экране. Состояние принадлежит вызывающей стороне: addToast и dismissToast из @ilomee/aura-core/toast — две чистые операции над ним.
onDismiss*событие(id: string) => voidСрабатывает после анимации ухода, а не по клику: элемент должен задержаться в DOM достаточно, чтобы из него уйти.
closeLabelstring'Dismiss {title}'{title} заменяется на заголовок тоста. Именно шаблон: в разных языках существительное стоит в разных местах.
ИмяТипПо умолчаниюПримечания
toastслот{ toast: AuraToastItem }
toastsAuraToastItem[] | null
dismissсобытие(id: string) => void
closeLabelstring'Dismiss {title}'
ИмяТипПо умолчаниюПримечания
toastслотSnippet<[AuraToastItem]>
toastsAuraToastItem[] | null
ondismiss*событие(id: string) => void
closeLabelstring'Dismiss {title}'
ИмяТипПо умолчаниюПримечания
toastTemplateTemplateRef<{ $implicit: AuraToastItem }>
toastsAuraToastItem[] | null
dismissсобытие(id: string) => void
closeLabelstring'Dismiss {title}'

Какие классы отдаёт

Все фреймворки отдают одни и те же классы — именно поэтому четыре библиотеки выглядят одинаково. Их можно использовать напрямую, вообще без библиотеки компонентов.

КлассРольПримечания
.aura-toast__viewportэлемент
.aura-toastбазаОбязательный. Без него модификатор не работает.
.aura-toast__bodyэлемент
.aura-toast__titleэлемент
.aura-toast__descriptionэлемент

Токены и правила

components.css
слой
var(--z-toast) — minted for this and unused until now
ширина
min(360px, calc(100vw - 2 * var(--space-4)))
край
the safe-area insets, so it clears the home indicator