diff --git a/docs/components-api/toast.md b/docs/components-api/toast.md index 193f7bcb..97f98c18 100644 --- a/docs/components-api/toast.md +++ b/docs/components-api/toast.md @@ -2,8 +2,24 @@ # ExtraToast +> ✅ **Реализован**: `ExtraToastComponent` (`@cdek-it/angular-ui-kit`) соответствует спецификации. + Уведомление, отображаемое поверх интерфейса (overlay-вариант `ExtraMessage`; в потоке документа используется `ExtraMessage`). +Показ уведомлений устроен в два уровня: сам компонент `` — это контейнер, монтируемый +один раз, а показ каждого отдельного уведомления выполняется отдельным императивным вызовом. Поэтому +и свойства ниже разделены на два независимых набора. + +# Свойства компонента (``) + +| Свойство | Описание | Типизация | +| -------- | ---------------------------------------------------------------- | --------- | +| `position` | позиция группы уведомлений на экране | `top-right \| top-left \| top-center \| bottom-right \| bottom-left \| bottom-center \| center` | +| `key` | ключ контейнера; показывает только уведомления с тем же `key` (несколько независимых очередей) | `string` | +| `life` | время автозакрытия по умолчанию (мс) для уведомлений этого контейнера | `number` | + +# Свойства уведомления (передаются при вызове показа) + | Свойство | Описание | Типизация | | ------------ | ------------------------------------------------------------ | -------------------------------------- | | `severity` | тип сообщения | `info \| success \| warning \| danger` | @@ -15,6 +31,8 @@ # Слоты (content projection) +Относятся к уведомлению — так же передаются при вызове показа, а не проецируются в ``. + | Слот | Описание | | --------- | --------------------- | | `content` | контент после caption | @@ -22,6 +40,8 @@ # События +Относится к уведомлению — передаётся полем при вызове показа, а не эмитится самим ``. + | Событие | Описание | Типизация | | --------- | ------------------------------------ | ------------ | | `onClose` | срабатывает при закрытии уведомления | `() => void` | diff --git a/src/lib/components/toast/toast.component.ts b/src/lib/components/toast/toast.component.ts index a7a9e747..0cbc734f 100644 --- a/src/lib/components/toast/toast.component.ts +++ b/src/lib/components/toast/toast.component.ts @@ -1,8 +1,9 @@ import { Component, Input } from '@angular/core'; -import { Toast } from 'primeng/toast'; +import { NgTemplateOutlet } from '@angular/common'; +import { Toast, ToastCloseEvent } from 'primeng/toast'; import { SharedModule } from 'primeng/api'; -export type ExtraToastSeverity = 'success' | 'info' | 'warn' | 'error' | 'secondary' | 'contrast'; +export type ExtraToastSeverity = 'info' | 'success' | 'warning' | 'danger'; export type ExtraToastPosition = | 'top-right' | 'top-left' @@ -12,6 +13,16 @@ export type ExtraToastPosition = | 'bottom-center' | 'center'; +/** Внутренний словарь PrimeNG-совместимых значений severity — публично не используется. */ +type PrimeToastSeverity = 'success' | 'info' | 'warn' | 'error'; + +/** Маппинг собственного severity на PrimeNG; используется сервисом при отправке сообщения. */ +export function toPrimeToastSeverity(severity: ExtraToastSeverity): PrimeToastSeverity { + if (severity === 'warning') return 'warn'; + if (severity === 'danger') return 'error'; + return severity; +} + const SEVERITY_ICONS: Record = { info: 'ti ti-info-circle', success: 'ti ti-circle-check', @@ -22,9 +33,9 @@ const SEVERITY_ICONS: Record = { @Component({ selector: 'extra-toast', standalone: true, - imports: [Toast, SharedModule], + imports: [Toast, SharedModule, NgTemplateOutlet], template: ` - +
@@ -33,6 +44,14 @@ const SEVERITY_ICONS: Record = { @if (message.detail) {
{{ message.detail }}
} + @if (message.data?.content) { + + } + @if (message.data?.footer) { + + }
@@ -42,9 +61,13 @@ export class ExtraToastComponent { @Input() position: ExtraToastPosition = 'top-right'; @Input() key: string | undefined = undefined; @Input() life = 5000; - @Input() pt: Record | undefined = undefined; resolveIcon(message: { severity?: string; icon?: string }): string { - return message.icon ?? SEVERITY_ICONS[message.severity ?? 'info'] ?? 'ti ti-info-circle'; + return message.icon ?? SEVERITY_ICONS[message.severity ?? 'info'] ?? SEVERITY_ICONS['info']; + } + + /** Диспетчеризует пользовательский onClose-колбэк конкретного сообщения (клик по крестику или таймер). */ + onMessageClose(event: ToastCloseEvent): void { + (event.message?.data as { onClose?: () => void } | undefined)?.onClose?.(); } } diff --git a/src/lib/components/toast/toast.figma.md b/src/lib/components/toast/toast.figma.md index 57763c23..0d7d93e7 100644 --- a/src/lib/components/toast/toast.figma.md +++ b/src/lib/components/toast/toast.figma.md @@ -10,41 +10,55 @@ figma: componentKey: 'e62d7e276583e57fc42b31096f545e591ec18979' name: '' status: stable -updated: '2026-06-23' +updated: '2026-09-06' --- ## Overview -`ExtraToast` — контейнер всплывающих самозакрывающихся уведомлений поверх интерфейса. Сообщения не вставляются в шаблон напрямую: компонент монтируется один раз (обычно в корне приложения), а показ выполняется императивно через `ExtraToastService`. Оборачивает PrimeNG `p-toast` и подбирает иконку автоматически по `severity`. +`ExtraToast` — всплывающее самозакрывающееся уведомление поверх интерфейса, overlay-вариант `ExtraMessage` +(инлайн-сообщение в потоке страницы — см. [Message](../message/message.figma.md)). Сообщения не +вставляются в шаблон напрямую: контейнер `` монтируется один раз (обычно в корне +приложения), а показ каждого уведомления выполняется императивно через `ExtraToastService.add()`. +Свойства спецификации (`severity`/`timer`/`message`/`caption`/`icon`/`show-close`), слоты +(`content`/`footer`) и событие `onClose` относятся к КАЖДОМУ сообщению и передаются полем в объекте +`ExtraToastService.add({...})`, а не как `@Input()`/`@Output()` самого ``. Для работы требуется зарегистрировать провайдеры через `provideExtraToast()` в `ApplicationConfig.providers`. -Компонент соответствует Figma-узлу `` (nodeId `15953:4478`) — это всплывающее самозакрывающееся уведомление, в отличие от инлайн-компонента `` (nodeId `15963:3163`), встроенного в поток страницы. В ходе рефакторинга toast-сценарий был отделён от inline `` в самостоятельный Figma-узел ``. +Компонент соответствует Figma-узлу `` (nodeId `15953:4478`), задающему два свойства: +`severity` (`info | success | warning | danger`) и `timer` (`false | true`). ## Props mapping -Входные свойства задаются на самом контейнере `` и управляют отображением всех сообщений. +Контейнер `` принимает свойства, управляющие отображением ВСЕХ его сообщений +(вне спецификации — форм-обвязка/инфраструктура, аналог `disabled`/`invalid` у форм-контролов): | Свойство | Тип | По умолчанию | Описание | |----------|-----|--------------|---------| | `position` | `'top-right' \| 'top-left' \| 'top-center' \| 'bottom-right' \| 'bottom-left' \| 'bottom-center' \| 'center'` | `'top-right'` | Позиция группы уведомлений на экране | | `key` | `string \| undefined` | `undefined` | Ключ контейнера; показывают только сообщения с тем же `key` (несколько независимых очередей) | -| `life` | `number` | `5000` | Время автозакрытия в миллисекундах | -| `pt` | `Record \| undefined` | `undefined` | PrimeNG PassThrough для тонкой настройки внутренних элементов | - -Само сообщение передаётся в `ExtraToastService.add()` объектом `ExtraToastMessage`: - -| Поле | Тип | Описание | -|------|-----|---------| -| `severity` | `'success' \| 'info' \| 'warn' \| 'error' \| 'secondary' \| 'contrast'` | Семантический акцент; определяет цвет и иконку | -| `summary` | `string` | Заголовок уведомления | -| `detail` | `string` | Подробный текст под заголовком | -| `life` | `number` | Время автозакрытия в миллисекундах (переопределяет `life` контейнера) | -| `icon` | `string` | CSS-класс иконки; по умолчанию подбирается по `severity` — доступные иконки см. [icons.md](../../figma-code-connect/icons.md) | -| `closable` | `boolean` | Показывать кнопку закрытия | -| `key` | `string` | Ключ целевого контейнера `` | - -Контейнер монтируется в шаблоне без сообщений, а показ выполняется через сервис. +| `life` | `number` | `5000` | Дефолтное время автозакрытия (мс) для всех сообщений этого контейнера; переопределяется полем `life` конкретного сообщения | + +Само сообщение передаётся в `ExtraToastService.add()` объектом `ExtraToastMessage` — это и есть +публичный API спецификации: + +| Поле | Тип | По умолчанию | Описание | +|------|-----|--------------|---------| +| `severity` | `'info' \| 'success' \| 'warning' \| 'danger'` | `'info'` | Тип сообщения — соответствует спеке `severity` | +| `timer` | `boolean` | `true` | Таймер автоскрытия — соответствует спеке `timer`. `false` делает уведомление несгораемым (sticky) | +| `message` | `string` | — | Заголовок сообщения — соответствует спеке `message` | +| `caption` | `string` | — | Подробности сообщения — соответствует спеке `caption` | +| `icon` | `string` | — | Класс иконки tabler icon вместо стандартной для `severity` — соответствует спеке `icon` | +| `showClose` | `boolean` | `false` | Кнопка закрытия — соответствует спеке `show-close` | +| `content` | `TemplateRef` | — | Слот `content` — контент после `caption` | +| `footer` | `TemplateRef` | — | Слот `footer` — контент футера | +| `onClose` | `() => void` | — | Событие `onClose` — срабатывает при закрытии этого сообщения (крестиком или по таймеру) | +| `key` | `string \| undefined` | `undefined` | Ключ целевого контейнера ``; вне спеки, но необходим для нескольких независимых очередей | +| `life` | `number \| undefined` | `undefined` | Переопределяет длительность таймера (мс) для этого сообщения; вне спеки, дополнительная точная настройка поверх булева `timer` | + +`content`/`footer` — обычные `TemplateRef`, получаемые в компоненте-инициаторе через `@ViewChild` +(см. пример ниже); контент-проекция через `` здесь невозможна, так как сообщение не +является отдельным компонентом в шаблоне, а данными, переданными в сервис. ```html @@ -63,83 +77,91 @@ export class AppComponent { private readonly toast = inject(ExtraToastService); show(): void { - this.toast.add({ severity: 'success', summary: 'Готово', detail: 'Операция выполнена' }); + this.toast.add({ severity: 'success', message: 'Готово', caption: 'Операция выполнена' }); } } ``` ## Variants -Варианты различаются по `severity` сообщения. Контейнер один и тот же; меняется объект, переданный в `ExtraToastService.add()`. Figma-узел `` задаёт два свойства: `severity` (`info | success | warning | danger`) и `timer` (`false | true`). +Варианты различаются по `severity` сообщения. Контейнер один и тот же; меняется объект, переданный в +`ExtraToastService.add()`. Figma-узел `` задаёт два свойства: `severity` +(`info | success | warning | danger`) и `timer` (`false | true`). ### Info (информационное) Figma: `severity=info` -```html - -``` - ```ts -this.toast.add({ severity: 'info', summary: 'Информация', detail: 'Дополнительный текст' }); +this.toast.add({ severity: 'info', message: 'Информация', caption: 'Дополнительный текст' }); ``` ### Success (успех) Figma: `severity=success` -```html - -``` - ```ts -this.toast.add({ severity: 'success', summary: 'Готово', detail: 'Операция выполнена' }); +this.toast.add({ severity: 'success', message: 'Готово', caption: 'Операция выполнена' }); ``` -### Warn (предупреждение) +### Warning (предупреждение) Figma: `severity=warning` -```html - -``` - ```ts -this.toast.add({ severity: 'warn', summary: 'Внимание', detail: 'Проверьте данные' }); +this.toast.add({ severity: 'warning', message: 'Внимание', caption: 'Проверьте данные' }); ``` -### Error / danger (ошибка) +### Danger (ошибка) Figma: `severity=danger` -```html - +```ts +this.toast.add({ severity: 'danger', message: 'Ошибка', caption: 'Не удалось сохранить' }); ``` +### С кнопкой закрытия (show-close) + ```ts -this.toast.add({ severity: 'error', summary: 'Ошибка', detail: 'Не удалось сохранить' }); +this.toast.add({ severity: 'info', message: 'Сообщение', showClose: true }); ``` -### С кнопкой закрытия (closable) +### Несгораемое (timer=false) -```html - -``` +Figma: `timer=false` ```ts -this.toast.add({ severity: 'info', summary: 'Сообщение', closable: true }); +this.toast.add({ severity: 'success', message: 'Сохранено', timer: false }); ``` -### С увеличенным временем жизни (life) +### С контентом и футером (content / footer) -Figma: `timer=true` +```ts +import { Component, TemplateRef, ViewChild, inject } from '@angular/core'; +import { ExtraToastComponent, ExtraToastService } from '@cdek-it/angular-ui-kit'; -```html - -``` +@Component({ + imports: [ExtraToastComponent], + template: ` + + Дополнительный контент + Футер + `, +}) +export class ExampleComponent { + @ViewChild('extraContent') contentTpl!: TemplateRef; + @ViewChild('extraFooter') footerTpl!: TemplateRef; + private readonly toast = inject(ExtraToastService); -```ts -this.toast.add({ severity: 'success', summary: 'Сохранено', life: 10000 }); + show(): void { + this.toast.add({ + severity: 'info', + message: 'Сообщение', + content: this.contentTpl, + footer: this.footerTpl, + }); + } +} ``` ### Позиционирование (position) @@ -149,7 +171,7 @@ this.toast.add({ severity: 'success', summary: 'Сохранено', life: 10000 ``` ```ts -this.toast.add({ severity: 'info', summary: 'Уведомление снизу' }); +this.toast.add({ severity: 'info', message: 'Уведомление снизу' }); ``` ### Несколько очередей (key) @@ -159,16 +181,19 @@ this.toast.add({ severity: 'info', summary: 'Уведомление снизу' ``` ```ts -this.toast.add({ key: 'orders', severity: 'success', summary: 'Заказ создан' }); +this.toast.add({ key: 'orders', severity: 'success', message: 'Заказ создан' }); ``` ## Slots -Контент уведомления формируется из полей `summary` и `detail` объекта сообщения и не проектируется через ``. Структурные слоты не используются. +| Слот | Описание | +|------|----------| +| `content` | Контент после `caption`. Передаётся `TemplateRef` в поле `content` объекта `ExtraToastMessage` | +| `footer` | Контент футера. Передаётся `TemplateRef` в поле `footer` объекта `ExtraToastMessage` | ## Related -- [Message](../message/message.figma.md) — инлайн-вариант сообщения со статусом; покрывает Figma `` (`toast=false`) +- [Message](../message/message.figma.md) — инлайн-вариант того же уведомления, встроенный в поток страницы - [Button](../button/button.figma.md) — кнопка закрытия использует общий контракт - [Иконки](../../figma-code-connect/icons.md) — доступные иконки `severity` и `icon` - [Conventions](../../figma-code-connect/conventions.md) — соглашения маппинга Figma → Angular @@ -178,7 +203,7 @@ this.toast.add({ key: 'orders', severity: 'success', summary: 'Заказ соз **Do:** - Монтируйте один `` в корне приложения и показывайте сообщения через `ExtraToastService.add()` - Регистрируйте `provideExtraToast()` в `ApplicationConfig.providers` один раз -- Подбирайте `severity` по смыслу: `error` для ошибок, `warn` для предупреждений, `success` для подтверждений +- Подбирайте `severity` по смыслу: `danger` для ошибок, `warning` для предупреждений, `success` для подтверждений - Используйте `key` для независимых очередей уведомлений (например системные и контекстные) - Полагайтесь на автоподбор иконки по `severity`; переопределяйте `icon` только при необходимости — используйте справочник [icons.md](../../figma-code-connect/icons.md) @@ -186,4 +211,4 @@ this.toast.add({ key: 'orders', severity: 'success', summary: 'Заказ соз - Не используйте toast для устойчивого статуса рядом с формой или секцией — для инлайн-фидбэка предназначен [Message](../message/message.figma.md) - Не монтируйте несколько `` с одинаковым `position` и `key` — сообщения продублируются - Не инлайньте CSS-классы иконок вручную в обход справочника [icons.md](../../figma-code-connect/icons.md) -- Не задавайте слишком короткий `life` для текста, который нужно успеть прочитать +- Не делайте `timer: false` (несгораемым) без `showClose: true` — пользователю нечем будет закрыть уведомление diff --git a/src/lib/components/toast/toast.service.ts b/src/lib/components/toast/toast.service.ts index a8543613..4a6ac55e 100644 --- a/src/lib/components/toast/toast.service.ts +++ b/src/lib/components/toast/toast.service.ts @@ -1,16 +1,29 @@ -import { Injectable } from '@angular/core'; +import { Injectable, TemplateRef } from '@angular/core'; import { MessageService } from 'primeng/api'; -import { ExtraToastSeverity } from './toast.component'; +import { ExtraToastSeverity, toPrimeToastSeverity } from './toast.component'; export interface ExtraToastMessage { + /** Ключ целевого контейнера `` — для нескольких независимых очередей. */ key?: string; severity?: ExtraToastSeverity; - summary?: string; - detail?: string; - life?: number; + /** Заголовок сообщения. */ + message?: string; + /** Подробности сообщения. */ + caption?: string; + /** Класс иконки tabler icon; по умолчанию подбирается по `severity`. */ icon?: string; - closable?: boolean; - data?: unknown; + /** Показывать кнопку закрытия. */ + showClose?: boolean; + /** Таймер автоскрытия. По умолчанию `true` — тост скрывается сам; `false` делает его несгораемым (sticky). */ + timer?: boolean; + /** Переопределяет длительность таймера (мс) — учитывается только при `timer !== false`. */ + life?: number; + /** Срабатывает при закрытии этого сообщения (крестиком или по таймеру). */ + onClose?: () => void; + /** Слот `content` — контент после caption. */ + content?: TemplateRef; + /** Слот `footer` — контент футера. */ + footer?: TemplateRef; } @Injectable({ providedIn: 'root' }) @@ -18,7 +31,23 @@ export class ExtraToastService { constructor(private readonly messageService: MessageService) {} add(message: ExtraToastMessage): void { - this.messageService.add(message); + const timer = message.timer ?? true; + + this.messageService.add({ + key: message.key, + severity: toPrimeToastSeverity(message.severity ?? 'info'), + summary: message.message, + detail: message.caption, + icon: message.icon, + closable: message.showClose ?? false, + sticky: !timer, + life: message.life, + data: { + onClose: message.onClose, + content: message.content, + footer: message.footer + } + }); } clear(key?: string): void { diff --git a/src/stories/components/toast/examples/toast-position.component.ts b/src/stories/components/toast/examples/toast-position.component.ts index cf7e082b..9fe575f3 100644 --- a/src/stories/components/toast/examples/toast-position.component.ts +++ b/src/stories/components/toast/examples/toast-position.component.ts @@ -46,8 +46,8 @@ export class ToastPositionComponent { this.toastService.add({ key, severity: 'info', - summary: 'Сообщение', - detail: 'Позиция: ' + position, + message: 'Сообщение', + caption: 'Позиция: ' + position, life: 3000, icon: 'ti ti-info-circle' }); @@ -104,8 +104,8 @@ export class ExampleComponent { this.toastService.add({ key, severity: 'info', - summary: 'Сообщение', - detail: 'Позиция: ' + position, + message: 'Сообщение', + caption: 'Позиция: ' + position, life: 3000, icon: 'ti ti-info-circle', }); diff --git a/src/stories/components/toast/examples/toast-severities.component.ts b/src/stories/components/toast/examples/toast-severities.component.ts index 6f477370..6e4b0c3c 100644 --- a/src/stories/components/toast/examples/toast-severities.component.ts +++ b/src/stories/components/toast/examples/toast-severities.component.ts @@ -5,18 +5,18 @@ import { ExtraToastComponent } from '../../../../lib/components/toast/toast.comp import { ExtraToastService } from '../../../../lib/components/toast/toast.service'; const SEVERITIES = [ - { type: 'info', icon: 'ti ti-info-circle', label: 'Информация' }, - { type: 'success', icon: 'ti ti-circle-check', label: 'Успех' }, - { type: 'warn', icon: 'ti ti-alert-triangle', label: 'Предупреждение' }, - { type: 'error', icon: 'ti ti-alert-circle', label: 'Ошибка' } + { severity: 'info', primeClass: 'info', icon: 'ti ti-info-circle', label: 'Информация' }, + { severity: 'success', primeClass: 'success', icon: 'ti ti-circle-check', label: 'Успех' }, + { severity: 'warning', primeClass: 'warn', icon: 'ti ti-alert-triangle', label: 'Предупреждение' }, + { severity: 'danger', primeClass: 'error', icon: 'ti ti-alert-circle', label: 'Ошибка' } ] as const; const template = `
- @for (s of severities; track s.type) { -
+ @for (s of severities; track s.severity) { +
@@ -30,12 +30,12 @@ const template = `
- @for (s of severities; track s.type) { + @for (s of severities; track s.severity) { }
@@ -54,13 +54,12 @@ export class ToastSeveritiesComponent { constructor(private readonly toastService: ExtraToastService) {} - show(severity: string, icon: string): void { + show(severity: 'info' | 'success' | 'warning' | 'danger', icon: string): void { this.toastService.add({ key: 'severities', - severity: severity as any, - summary: 'Сообщение', - detail: 'Подпись', - life: 5000, + severity, + message: 'Сообщение', + caption: 'Подпись', icon }); } @@ -78,12 +77,7 @@ export const Severities: StoryObj = { code: ` import { ExtraButtonComponent, ExtraToastComponent, ExtraToastService } from '@cdek-it/angular-ui-kit'; -const SEVERITIES = [ - { type: 'info', icon: 'ti ti-info-circle', label: 'Информация' }, - { type: 'success', icon: 'ti ti-circle-check', label: 'Успех' }, - { type: 'warn', icon: 'ti ti-alert-triangle', label: 'Предупреждение' }, - { type: 'error', icon: 'ti ti-alert-circle', label: 'Ошибка' }, -] as const; +const SEVERITIES = ['info', 'success', 'warning', 'danger'] as const; @Component({ selector: 'app-example', @@ -93,12 +87,11 @@ const SEVERITIES = [
- @for (s of severities; track s.type) { + @for (severity of severities; track severity) { }
@@ -109,14 +102,12 @@ export class ExampleComponent { constructor(private toastService: ExtraToastService) {} - show(severity: string, icon: string): void { + show(severity: 'info' | 'success' | 'warning' | 'danger'): void { this.toastService.add({ key: 'severities', - severity: severity as any, - summary: 'Сообщение', - detail: 'Подпись', - life: 5000, - icon, + severity, + message: 'Сообщение', + caption: 'Подпись', }); } } diff --git a/src/stories/components/toast/examples/toast-slots.component.ts b/src/stories/components/toast/examples/toast-slots.component.ts new file mode 100644 index 00000000..5a91a557 --- /dev/null +++ b/src/stories/components/toast/examples/toast-slots.component.ts @@ -0,0 +1,140 @@ +import { Component, TemplateRef, ViewChild } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { ExtraButtonComponent } from '../../../../lib/components/button/button.component'; +import { ExtraToastComponent } from '../../../../lib/components/toast/toast.component'; +import { ExtraToastService } from '../../../../lib/components/toast/toast.service'; + +const template = ` + + + +
Контент после caption (слот content)
+
+ + +
+ + +
+
+ +
+ + + +
+`; + +@Component({ + selector: 'app-toast-slots', + standalone: true, + imports: [ExtraToastComponent, ExtraButtonComponent], + template +}) +export class ToastSlotsComponent { + @ViewChild('contentOnly') contentTpl!: TemplateRef; + @ViewChild('footerOnly') footerTpl!: TemplateRef; + + constructor(private readonly toastService: ExtraToastService) {} + + showContentOnly(): void { + this.toastService.add({ + key: 'slots', + severity: 'info', + message: 'Сообщение', + caption: 'caption', + timer: false, + showClose: true, + content: this.contentTpl + }); + } + + showFooterOnly(): void { + this.toastService.add({ + key: 'slots', + severity: 'danger', + message: 'Сообщение', + caption: 'caption', + timer: false, + showClose: true, + footer: this.footerTpl + }); + } + + showBoth(): void { + this.toastService.add({ + key: 'slots', + severity: 'success', + message: 'Сообщение', + caption: 'caption', + timer: false, + showClose: true, + content: this.contentTpl, + footer: this.footerTpl + }); + } +} + +export const Slots: StoryObj = { + render: () => ({ + template: `` + }), + parameters: { + docs: { + description: { + story: + 'Слот `content` — контент после `caption` (например, произвольный текст). Слот `footer` — контент футера (например, кнопки действий). Оба передаются как `TemplateRef` в `ExtraToastService.add()`.' + }, + source: { + language: 'ts', + code: ` +import { Component, TemplateRef, ViewChild } from '@angular/core'; +import { ExtraButtonComponent, ExtraToastComponent, ExtraToastService } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-example', + standalone: true, + imports: [ExtraToastComponent, ExtraButtonComponent], + template: \` + + + + +
Контент после caption
+
+ + + +
+ + +
+
+ + + \`, +}) +export class ExampleComponent { + @ViewChild('contentOnly') contentTpl!: TemplateRef; + @ViewChild('footerOnly') footerTpl!: TemplateRef; + + constructor(private toastService: ExtraToastService) {} + + show(): void { + this.toastService.add({ + key: 'slots', + severity: 'success', + message: 'Сообщение', + caption: 'caption', + timer: false, + showClose: true, + content: this.contentTpl, + footer: this.footerTpl, + }); + } +} + ` + } + } + } +}; diff --git a/src/stories/components/toast/examples/toast-width.component.ts b/src/stories/components/toast/examples/toast-width.component.ts index fe3ff21d..37395956 100644 --- a/src/stories/components/toast/examples/toast-width.component.ts +++ b/src/stories/components/toast/examples/toast-width.component.ts @@ -12,10 +12,7 @@ const SIZES = [ ] as const; const template = ` - +
@for (s of sizes; track s.key) { @@ -66,8 +63,8 @@ export class ToastWidthComponent { this.toastService.add({ key: 'width-preview', severity: 'info', - summary: 'Сообщение', - detail: 'Ширина: ' + cssVar, + message: 'Сообщение', + caption: 'Ширина: ' + cssVar, life: 3000, icon: 'ti ti-info-circle' }); @@ -80,7 +77,7 @@ export const Width: StoryObj = { }), parameters: { docs: { - description: { story: 'Ширина задаётся через CSS-переменную `--p-toast-width` с помощью пропа `pt`.' }, + description: { story: 'Ширина задаётся через CSS-переменную `--p-toast-width` прямо на теге ``.' }, source: { language: 'ts', code: ` @@ -98,10 +95,7 @@ const SIZES = [ standalone: true, imports: [ExtraToastComponent, ExtraButtonComponent], template: \` - +
@for (s of sizes; track s.key) { @@ -126,8 +120,8 @@ export class ExampleComponent { this.toastService.add({ key: 'width-preview', severity: 'info', - summary: 'Сообщение', - detail: 'Ширина: ' + cssVar, + message: 'Сообщение', + caption: 'Ширина: ' + cssVar, life: 3000, icon: 'ti ti-info-circle', }); diff --git a/src/stories/components/toast/examples/toast-with-close-button.component.ts b/src/stories/components/toast/examples/toast-with-close-button.component.ts index 00e27412..b1ab61bc 100644 --- a/src/stories/components/toast/examples/toast-with-close-button.component.ts +++ b/src/stories/components/toast/examples/toast-with-close-button.component.ts @@ -5,18 +5,18 @@ import { ExtraToastComponent } from '../../../../lib/components/toast/toast.comp import { ExtraToastService } from '../../../../lib/components/toast/toast.service'; const SEVERITIES = [ - { type: 'info', icon: 'ti ti-info-circle', label: 'Информация' }, - { type: 'success', icon: 'ti ti-circle-check', label: 'Успех' }, - { type: 'warn', icon: 'ti ti-alert-triangle', label: 'Предупреждение' }, - { type: 'error', icon: 'ti ti-alert-circle', label: 'Ошибка' } + { severity: 'info', primeClass: 'info', icon: 'ti ti-info-circle', label: 'Информация' }, + { severity: 'success', primeClass: 'success', icon: 'ti ti-circle-check', label: 'Успех' }, + { severity: 'warning', primeClass: 'warn', icon: 'ti ti-alert-triangle', label: 'Предупреждение' }, + { severity: 'danger', primeClass: 'error', icon: 'ti ti-alert-circle', label: 'Ошибка' } ] as const; const template = `
- @for (s of severities; track s.type) { -
+ @for (s of severities; track s.severity) { +
@@ -24,10 +24,7 @@ const template = ` Сообщение
Подпись
-
@@ -36,12 +33,12 @@ const template = `
- @for (s of severities; track s.type) { + @for (s of severities; track s.severity) { }
@@ -60,15 +57,14 @@ export class ToastWithCloseButtonComponent { constructor(private readonly toastService: ExtraToastService) {} - show(severity: string, icon: string): void { + show(severity: 'info' | 'success' | 'warning' | 'danger', icon: string): void { this.toastService.add({ key: 'with-close', - severity: severity as any, - summary: 'Сообщение', - detail: 'Подпись', - life: 5000, + severity, + message: 'Сообщение', + caption: 'Подпись', icon, - closable: true + showClose: true }); } } @@ -79,19 +75,12 @@ export const WithCloseButton: StoryObj = { }), parameters: { docs: { - description: { story: 'Уведомления с кнопкой закрытия (closable: true).' }, + description: { story: 'Уведомления с кнопкой закрытия (`showClose: true`).' }, source: { language: 'ts', code: ` import { ExtraButtonComponent, ExtraToastComponent, ExtraToastService } from '@cdek-it/angular-ui-kit'; -const SEVERITIES = [ - { type: 'info', icon: 'ti ti-info-circle', label: 'Информация' }, - { type: 'success', icon: 'ti ti-circle-check', label: 'Успех' }, - { type: 'warn', icon: 'ti ti-alert-triangle', label: 'Предупреждение' }, - { type: 'error', icon: 'ti ti-alert-circle', label: 'Ошибка' }, -] as const; - @Component({ selector: 'app-example', standalone: true, @@ -99,32 +88,19 @@ const SEVERITIES = [ template: \` -
- @for (s of severities; track s.type) { - - } -
+ \`, }) export class ExampleComponent { - readonly severities = SEVERITIES; - constructor(private toastService: ExtraToastService) {} - show(severity: string, icon: string): void { + show(): void { this.toastService.add({ key: 'with-close', - severity: severity as any, - summary: 'Сообщение', - detail: 'Подпись', - life: 5000, - icon, - closable: true, + severity: 'info', + message: 'Сообщение', + caption: 'Подпись', + showClose: true, }); } } diff --git a/src/stories/components/toast/examples/toast-with-content.component.ts b/src/stories/components/toast/examples/toast-with-content.component.ts deleted file mode 100644 index 12633f2e..00000000 --- a/src/stories/components/toast/examples/toast-with-content.component.ts +++ /dev/null @@ -1,157 +0,0 @@ -import { Component } from '@angular/core'; -import { StoryObj } from '@storybook/angular'; -import { ExtraButtonComponent } from '../../../../lib/components/button/button.component'; -import { ExtraToastComponent } from '../../../../lib/components/toast/toast.component'; -import { ExtraToastService } from '../../../../lib/components/toast/toast.service'; - -const SEVERITIES = [ - { type: 'info', icon: 'ti ti-info-circle', label: 'Информация' }, - { type: 'success', icon: 'ti ti-circle-check', label: 'Успех' }, - { type: 'warn', icon: 'ti ti-alert-triangle', label: 'Предупреждение' }, - { type: 'error', icon: 'ti ti-alert-circle', label: 'Ошибка' } -] as const; - -const template = ` - - -
- @for (s of severities; track s.type) { -
-
-
- -
- Сообщение -
Подпись
-
-
Дополнительный контент
-
-
-
Ячейка 1
-
Ячейка 2
-
-
- -
-
- } -
- -
- @for (s of severities; track s.type) { - - } -
-`; -const styles = ''; - -@Component({ - selector: 'app-toast-with-content', - standalone: true, - imports: [ExtraToastComponent, ExtraButtonComponent], - template, - styles -}) -export class ToastWithContentComponent { - readonly severities = SEVERITIES; - - constructor(private readonly toastService: ExtraToastService) {} - - show(severity: string, icon: string): void { - this.toastService.add({ - key: 'with-content', - severity: severity as any, - summary: 'Сообщение', - detail: 'Подпись', - life: 5000, - icon, - closable: true - }); - } -} - -export const WithContent: StoryObj = { - render: () => ({ - template: `` - }), - parameters: { - docs: { - description: { story: 'Уведомления с дополнительным контентом под заголовком.' }, - source: { - language: 'ts', - code: ` -import { ExtraButtonComponent, ExtraToastComponent, ExtraToastService } from '@cdek-it/angular-ui-kit'; - -const SEVERITIES = [ - { type: 'info', icon: 'ti ti-info-circle', label: 'Информация' }, - { type: 'success', icon: 'ti ti-circle-check', label: 'Успех' }, - { type: 'warn', icon: 'ti ti-alert-triangle', label: 'Предупреждение' }, - { type: 'error', icon: 'ti ti-alert-circle', label: 'Ошибка' }, -] as const; - -// Кастомный шаблон сообщения передаётся через ng-template -@Component({ - selector: 'app-example', - standalone: true, - imports: [ExtraToastComponent, ExtraButtonComponent], - template: \` - - -
- {{ message.summary }} -
{{ message.detail }}
-
-
Дополнительный контент
-
-
-
Ячейка 1
-
Ячейка 2
-
-
-
-
- -
- @for (s of severities; track s.type) { - - } -
- \`, -}) -export class ExampleComponent { - readonly severities = SEVERITIES; - - constructor(private toastService: ExtraToastService) {} - - show(severity: string, icon: string): void { - this.toastService.add({ - key: 'with-content', - severity: severity as any, - summary: 'Сообщение', - detail: 'Подпись', - life: 5000, - icon, - closable: true, - }); - } -} - ` - } - } - } -}; diff --git a/src/stories/components/toast/toast.stories.ts b/src/stories/components/toast/toast.stories.ts index 9b22ddf9..3875bd13 100644 --- a/src/stories/components/toast/toast.stories.ts +++ b/src/stories/components/toast/toast.stories.ts @@ -3,7 +3,7 @@ import { ExtraToastComponent } from '../../../lib/components/toast/toast.compone import { provideExtraToast } from '../../../lib/components/toast/provide-toast'; import { Severities, ToastSeveritiesComponent } from './examples/toast-severities.component'; import { ToastWithCloseButtonComponent, WithCloseButton } from './examples/toast-with-close-button.component'; -import { ToastWithContentComponent, WithContent } from './examples/toast-with-content.component'; +import { Slots, ToastSlotsComponent } from './examples/toast-slots.component'; import { ToastWidthComponent, Width } from './examples/toast-width.component'; import { Position, ToastPositionComponent } from './examples/toast-position.component'; @@ -18,7 +18,7 @@ const meta: Meta = { ExtraToastComponent, ToastSeveritiesComponent, ToastWithCloseButtonComponent, - ToastWithContentComponent, + ToastSlotsComponent, ToastWidthComponent, ToastPositionComponent ] @@ -28,11 +28,17 @@ const meta: Meta = { designTokens: { prefix: '--p-toast' }, docs: { description: { - component: `Компонент для отображения всплывающих уведомлений поверх интерфейса. + component: `Всплывающее уведомление поверх интерфейса — overlay-вариант \`ExtraMessage\`. -## Подключение +Реализован по спецификации [toast.md](https://github.com/cdek-it/angular-ui-kit/blob/main/docs/components-api/toast.md). -Добавьте \`provideExtraToast()\` в провайдеры приложения: +\`\`\`typescript +import { ExtraToastComponent, ExtraToastService } from '@cdek-it/angular-ui-kit'; +\`\`\` + +Контейнер \`\` монтируется один раз (обычно в корне приложения), а показ каждого уведомления выполняется императивно через \`ExtraToastService.add({...})\` — свойства спецификации (\`severity\`/\`timer\`/\`message\`/\`caption\`/\`icon\`/\`show-close\`), слоты (\`content\`/\`footer\`) и событие \`onClose\` передаются полем в этом объекте, а не как \`@Input()\`/\`@Output()\` самого контейнера. + +## Подключение \`\`\`typescript // app.config.ts @@ -56,7 +62,7 @@ export class AppComponent { private toast = inject(ExtraToastService); show() { - this.toast.add({ severity: 'success', summary: 'Готово', detail: 'Операция выполнена успешно' }); + this.toast.add({ severity: 'success', message: 'Готово', caption: 'Операция выполнена успешно' }); } } \`\`\`` @@ -67,7 +73,7 @@ export class AppComponent { position: { control: 'select', options: ['top-right', 'top-left', 'top-center', 'bottom-right', 'bottom-left', 'bottom-center', 'center'], - description: 'Позиция тоста на экране.', + description: 'Позиция группы уведомлений на экране', table: { category: 'Props', defaultValue: { summary: 'top-right' }, @@ -82,7 +88,7 @@ export class AppComponent { }, life: { control: 'number', - description: 'Время (мс) до автоматического закрытия тоста.', + description: 'Время (мс) до автоматического закрытия — общий дефолт контейнера, переопределяется полем `life` сообщения', table: { category: 'Props', defaultValue: { summary: '5000' }, @@ -100,4 +106,4 @@ export class AppComponent { export default meta; // ── Re-exports from example components ──────────────────────────────────── -export { Severities as Default, WithCloseButton, WithContent, Width, Position }; +export { Severities as Default, WithCloseButton, Slots, Width, Position };