diff --git a/src/lib/components/toast/ng-package.json b/src/lib/components/toast/ng-package.json new file mode 100644 index 0000000..ecdf8fe --- /dev/null +++ b/src/lib/components/toast/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} + diff --git a/src/lib/components/toast/provide-toast.ts b/src/lib/components/toast/provide-toast.ts new file mode 100644 index 0000000..639e9f5 --- /dev/null +++ b/src/lib/components/toast/provide-toast.ts @@ -0,0 +1,17 @@ +import { EnvironmentProviders, makeEnvironmentProviders } from '@angular/core'; +import { MessageService } from 'primeng/api'; + +/** + * Регистрирует зависимости, необходимые для работы `ExtraToastService` и ``. + * Вызывать один раз в `ApplicationConfig.providers` или в `bootstrapApplication`. + * + * @example + * // app.config.ts + * export const appConfig: ApplicationConfig = { + * providers: [provideExtraToast()], + * }; + */ +export function provideExtraToast(): EnvironmentProviders { + return makeEnvironmentProviders([MessageService]); +} + diff --git a/src/lib/components/toast/public_api.ts b/src/lib/components/toast/public_api.ts new file mode 100644 index 0000000..b4c6642 --- /dev/null +++ b/src/lib/components/toast/public_api.ts @@ -0,0 +1,6 @@ +export * from './toast.component'; +export * from './toast.service'; +export * from './provide-toast'; + + + diff --git a/src/lib/components/toast/toast.component.ts b/src/lib/components/toast/toast.component.ts new file mode 100644 index 0000000..6fedad9 --- /dev/null +++ b/src/lib/components/toast/toast.component.ts @@ -0,0 +1,50 @@ +import { Component, Input } from '@angular/core'; +import { Toast } from 'primeng/toast'; +import { SharedModule } from 'primeng/api'; + +export type ToastSeverity = 'success' | 'info' | 'warn' | 'error' | 'secondary' | 'contrast'; +export type ToastPosition = + | 'top-right' + | 'top-left' + | 'top-center' + | 'bottom-right' + | 'bottom-left' + | 'bottom-center' + | 'center'; + +const SEVERITY_ICONS: Record = { + info: 'ti ti-info-circle', + success: 'ti ti-circle-check', + warn: 'ti ti-alert-triangle', + error: 'ti ti-alert-circle', +}; + +@Component({ + selector: 'extra-toast', + standalone: true, + imports: [Toast, SharedModule], + template: ` + + +
+ +
+ {{ message.summary }} + @if (message.detail) { +
{{ message.detail }}
+ } +
+
+
+ `, +}) +export class ExtraToastComponent { + @Input() position: ToastPosition = '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'; + } +} diff --git a/src/lib/components/toast/toast.service.ts b/src/lib/components/toast/toast.service.ts new file mode 100644 index 0000000..eac20f4 --- /dev/null +++ b/src/lib/components/toast/toast.service.ts @@ -0,0 +1,28 @@ +import { Injectable } from '@angular/core'; +import { MessageService } from 'primeng/api'; +import { ToastSeverity } from './toast.component'; + +export interface ExtraToastMessage { + key?: string; + severity?: ToastSeverity; + summary?: string; + detail?: string; + life?: number; + icon?: string; + closable?: boolean; + data?: unknown; +} + +@Injectable({ providedIn: 'root' }) +export class ExtraToastService { + + constructor(private readonly messageService: MessageService) {} + + add(message: ExtraToastMessage): void { + this.messageService.add(message); + } + + clear(key?: string): void { + this.messageService.clear(key); + } +} diff --git a/src/lib/providers/prime-preset/tokens/components/toast.ts b/src/lib/providers/prime-preset/tokens/components/toast.ts new file mode 100644 index 0000000..adc6430 --- /dev/null +++ b/src/lib/providers/prime-preset/tokens/components/toast.ts @@ -0,0 +1,144 @@ +export const toastCss = ({ dt }: { dt: (token: string) => string }): string => ` + /* Основной контейнер toast-сообщения */ + .p-toast-message { + width: ${dt('toast.root.width')}; + overflow: hidden; + border-width: ${dt('toast.root.borderWidth')}; + border-radius: ${dt('toast.root.borderRadius')}; + box-shadow: ${dt('toast.colorScheme.light.info.shadow')}; + position: relative; + } + + /* border-radius для контента toast-сообщения */ + .p-toast .p-toast-message .p-toast-message-content { + border-radius: ${dt('toast.root.borderRadius')}; + } + + /* Текстовый блок toast */ + .p-toast-message-text { + flex: 1; + display: flex; + flex-direction: column; + gap: ${dt('toast.text.gap')}; + } + + /* Заголовок toast */ + .p-toast-summary { + font-family: ${dt('fonts.fontFamily.base')}; + line-height: ${dt('fonts.lineHeight.250')}; + } + + /* Детальное описание toast */ + .p-toast-message .p-toast-detail { + font-family: ${dt('fonts.fontFamily.base')}; + line-height: ${dt('fonts.lineHeight.250')}; + } + + /* Кнопка закрытия toast-сообщения */ + .p-toast-message .p-toast-message-content .p-toast-close-button { + margin: 0; + padding: 0; + right: 0; + } + + /* Общие стили border для кнопки закрытия всех типов toast */ + .p-toast-message-info .p-toast-close-button, + .p-toast-message-success .p-toast-close-button, + .p-toast-message-warn .p-toast-close-button, + .p-toast-message-error .p-toast-close-button { + border: ${dt('toast.extend.extCloseButton.width')} solid; + } + + /* Общие стили для акцентной линии всех типов toast */ + .p-toast-message-info .p-toast-accent-line, + .p-toast-message-success .p-toast-accent-line, + .p-toast-message-warn .p-toast-accent-line, + .p-toast-message-error .p-toast-accent-line { + width: ${dt('toast.extend.extAccentLine.width')}; + position: absolute; + left: 0; + top: 0; + bottom: 0; + border-radius: ${dt('toast.root.borderRadius')} 0 0 ${dt('toast.root.borderRadius')}; + } + + /* Стили для toast типа Info */ + .p-toast-message-info .p-toast-message-icon { + color: ${dt('toast.extend.extInfo.color')}; + } + + .p-toast-message-info .p-toast-close-button { + color: ${dt('toast.extend.extInfo.closeButton.color')}; + border-color: ${dt('toast.extend.extInfo.closeButton.borderColor')}; + } + + .p-toast-message.p-toast-message-info .p-toast-close-button.p-button-text:not(:disabled):hover { + background: ${dt('toast.colorScheme.light.info.closeButton.hoverBackground')}; + border-color: ${dt('toast.extend.extInfo.closeButton.borderColor')}; + color: ${dt('toast.extend.extInfo.closeButton.color')}; + } + + .p-toast-message-info .p-toast-accent-line { + background: ${dt('toast.extend.extInfo.color')}; + } + + /* Стили для toast типа Success */ + .p-toast-message-success .p-toast-message-icon { + color: ${dt('toast.extend.extSuccess.color')}; + } + + .p-toast-message-success .p-toast-close-button { + color: ${dt('toast.extend.extSuccess.closeButton.color')}; + border-color: ${dt('toast.extend.extSuccess.closeButton.borderColor')}; + } + + .p-toast-message.p-toast-message-success .p-toast-close-button.p-button-text:not(:disabled):hover { + background: ${dt('toast.colorScheme.light.success.closeButton.hoverBackground')}; + border-color: ${dt('toast.extend.extSuccess.closeButton.borderColor')}; + color: ${dt('toast.extend.extSuccess.closeButton.color')}; + } + + .p-toast-message-success .p-toast-accent-line { + background: ${dt('toast.extend.extSuccess.color')}; + } + + /* Стили для toast типа Warn */ + .p-toast-message-warn .p-toast-message-icon { + color: ${dt('toast.extend.extWarn.color')}; + } + + .p-toast-message-warn .p-toast-close-button { + color: ${dt('toast.extend.extWarn.closeButton.color')}; + border-color: ${dt('toast.extend.extWarn.closeButton.borderColor')}; + } + + .p-toast-message.p-toast-message-warn .p-toast-close-button.p-button-text:not(:disabled):hover { + background: ${dt('toast.colorScheme.light.warn.closeButton.hoverBackground')}; + border-color: ${dt('toast.extend.extWarn.closeButton.borderColor')}; + color: ${dt('toast.extend.extWarn.closeButton.color')}; + } + + .p-toast-message-warn .p-toast-accent-line { + background: ${dt('toast.extend.extWarn.color')}; + } + + /* Стили для toast типа Error */ + .p-toast-message-error .p-toast-message-icon { + color: ${dt('toast.extend.extError.color')}; + } + + .p-toast-message-error .p-toast-close-button { + color: ${dt('toast.extend.extError.closeButton.color')}; + border-color: ${dt('toast.extend.extError.closeButton.borderColor')}; + } + + .p-toast-message.p-toast-message-error .p-toast-close-button.p-button-text:not(:disabled):hover { + background: ${dt('toast.colorScheme.light.error.closeButton.hoverBackground')}; + border-color: ${dt('toast.extend.extError.closeButton.borderColor')}; + color: ${dt('toast.extend.extError.closeButton.color')}; + } + + .p-toast-message-error .p-toast-accent-line { + background: ${dt('toast.extend.extError.color')}; + } +`; diff --git a/src/stories/components/toast/examples/toast-position.component.ts b/src/stories/components/toast/examples/toast-position.component.ts new file mode 100644 index 0000000..bed9bfa --- /dev/null +++ b/src/stories/components/toast/examples/toast-position.component.ts @@ -0,0 +1,120 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { Button } from 'primeng/button'; +import { ExtraToastComponent } from '../../../../lib/components/toast/toast.component'; +import { ExtraToastService } from '../../../../lib/components/toast/toast.service'; + +const POSITIONS = [ + { position: 'top-left', label: 'Вверх слева', key: 'pos-top-left' }, + { position: 'top-center', label: 'Вверх по центру', key: 'pos-top-center' }, + { position: 'top-right', label: 'Вверх справа', key: 'pos-top-right' }, + { position: 'bottom-left', label: 'Вниз слева', key: 'pos-bottom-left' }, + { position: 'bottom-center', label: 'Вниз по центру', key: 'pos-bottom-center' }, + { position: 'bottom-right', label: 'Вниз справа', key: 'pos-bottom-right' }, +] as const; + +const template = ` +@for (p of positions; track p.key) { + +} + +
+ @for (p of positions; track p.key) { + + } +
+`; +const styles = ''; + +@Component({ + selector: 'app-toast-position', + standalone: true, + imports: [ExtraToastComponent, Button], + template, + styles, +}) +export class ToastPositionComponent { + readonly positions = POSITIONS; + + constructor(private readonly toastService: ExtraToastService) {} + + show(key: string, position: string): void { + this.toastService.add({ + key, + severity: 'info', + summary: 'Сообщение', + detail: 'Позиция: ' + position, + life: 3000, + icon: 'ti ti-info-circle', + }); + } +} + +export const Position: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Расположение тоста задаётся через `position` и `key`.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraToastComponent, ExtraToastService } from '@cdek-it/angular-ui-kit'; +import { Button } from 'primeng/button'; + +const POSITIONS = [ + { position: 'top-left', label: 'Вверх слева', key: 'pos-top-left' }, + { position: 'top-center', label: 'Вверх по центру', key: 'pos-top-center' }, + { position: 'top-right', label: 'Вверх справа', key: 'pos-top-right' }, + { position: 'bottom-left', label: 'Вниз слева', key: 'pos-bottom-left' }, + { position: 'bottom-center', label: 'Вниз по центру', key: 'pos-bottom-center' }, + { position: 'bottom-right', label: 'Вниз справа', key: 'pos-bottom-right' }, +] as const; + +@Component({ + selector: 'app-example', + standalone: true, + imports: [ExtraToastComponent, Button], + template: \` + @for (p of positions; track p.key) { + + } + +
+ @for (p of positions; track p.key) { + + } +
+ \`, +}) +export class ExampleComponent { + readonly positions = POSITIONS; + + constructor(private toastService: ExtraToastService) {} + + show(key: string, position: string): void { + this.toastService.add({ + key, + severity: 'info', + summary: 'Сообщение', + detail: 'Позиция: ' + 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 new file mode 100644 index 0000000..a7bb19b --- /dev/null +++ b/src/stories/components/toast/examples/toast-severities.component.ts @@ -0,0 +1,129 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { Button } from 'primeng/button'; +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) { +
+
+
+ +
+ Сообщение +
Подпись
+
+
+
+ } +
+ +
+ @for (s of severities; track s.type) { + + } +
+`; +const styles = ''; + +@Component({ + selector: 'app-toast-severities', + standalone: true, + imports: [ExtraToastComponent, Button], + template, + styles, +}) +export class ToastSeveritiesComponent { + readonly severities = SEVERITIES; + + constructor(private readonly toastService: ExtraToastService) {} + + show(severity: string, icon: string): void { + this.toastService.add({ + key: 'severities', + severity: severity as any, + summary: 'Сообщение', + detail: 'Подпись', + life: 5000, + icon, + }); + } +} + +export const Severities: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Четыре типа уведомлений: информация, успех, предупреждение, ошибка.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraToastComponent, ExtraToastService } from '@cdek-it/angular-ui-kit'; +import { Button } from 'primeng/button'; + +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, + imports: [ExtraToastComponent, Button], + template: \` + + +
+ @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: 'severities', + severity: severity as any, + summary: 'Сообщение', + detail: 'Подпись', + life: 5000, + icon, + }); + } +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/toast/examples/toast-width.component.ts b/src/stories/components/toast/examples/toast-width.component.ts new file mode 100644 index 0000000..9af4f71 --- /dev/null +++ b/src/stories/components/toast/examples/toast-width.component.ts @@ -0,0 +1,142 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { Button } from 'primeng/button'; +import { ExtraToastComponent } from '../../../../lib/components/toast/toast.component'; +import { ExtraToastService } from '../../../../lib/components/toast/toast.service'; + +const SIZES = [ + { key: 'sm', label: 'Small (20rem)', width: '20rem', cssVar: '20rem' }, + { key: 'base', label: 'Base (25rem)', width: '25rem', cssVar: '25rem' }, + { key: 'lg', label: 'Large (30rem)', width: '30rem', cssVar: '30rem' }, + { key: 'xlg', label: 'X-Large (45rem)', width: '45rem', cssVar: '45rem' }, +] as const; + +const template = ` + + +
+ @for (s of sizes; track s.key) { +
+
+
+ +
+ Сообщение +
Ширина {{ s.key }}: {{ s.width }}
+
+
+
+ } +
+ +
+ @for (s of sizes; track s.key) { + + } +
+`; +const styles = ''; + +@Component({ + selector: 'app-toast-width', + standalone: true, + imports: [ExtraToastComponent, Button], + template, + styles, +}) +export class ToastWidthComponent { + readonly sizes = SIZES; + currentWidth = '25rem'; + + constructor(private readonly toastService: ExtraToastService) {} + + show(cssVar: string): void { + this.currentWidth = cssVar; + this.toastService.clear('width-preview'); + this.toastService.add({ + key: 'width-preview', + severity: 'info', + summary: 'Сообщение', + detail: 'Ширина: ' + cssVar, + life: 3000, + icon: 'ti ti-info-circle', + }); + } +} + +export const Width: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Ширина задаётся через CSS-переменную `--p-toast-width` с помощью пропа `pt`.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraToastComponent, ExtraToastService } from '@cdek-it/angular-ui-kit'; +import { Button } from 'primeng/button'; + +const SIZES = [ + { key: 'sm', label: 'Small (20rem)', cssVar: '20rem' }, + { key: 'base', label: 'Base (25rem)', cssVar: '25rem' }, + { key: 'lg', label: 'Large (30rem)', cssVar: '30rem' }, + { key: 'xlg', label: 'X-Large (45rem)', cssVar: '45rem' }, +] as const; + +@Component({ + selector: 'app-example', + standalone: true, + imports: [ExtraToastComponent, Button], + template: \` + + +
+ @for (s of sizes; track s.key) { + + } +
+ \`, +}) +export class ExampleComponent { + readonly sizes = SIZES; + currentWidth = '25rem'; + + constructor(private toastService: ExtraToastService) {} + + show(cssVar: string): void { + this.currentWidth = cssVar; + this.toastService.clear('width-preview'); + this.toastService.add({ + key: 'width-preview', + severity: 'info', + summary: 'Сообщение', + detail: 'Ширина: ' + 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 new file mode 100644 index 0000000..39ae896 --- /dev/null +++ b/src/stories/components/toast/examples/toast-with-close-button.component.ts @@ -0,0 +1,137 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { Button } from 'primeng/button'; +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) { +
+
+
+ +
+ Сообщение +
Подпись
+
+ +
+
+ } +
+ +
+ @for (s of severities; track s.type) { + + } +
+`; +const styles = ''; + +@Component({ + selector: 'app-toast-with-close-button', + standalone: true, + imports: [ExtraToastComponent, Button], + template, + styles, +}) +export class ToastWithCloseButtonComponent { + readonly severities = SEVERITIES; + + constructor(private readonly toastService: ExtraToastService) {} + + show(severity: string, icon: string): void { + this.toastService.add({ + key: 'with-close', + severity: severity as any, + summary: 'Сообщение', + detail: 'Подпись', + life: 5000, + icon, + closable: true, + }); + } +} + +export const WithCloseButton: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Уведомления с кнопкой закрытия (closable: true).' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraToastComponent, ExtraToastService } from '@cdek-it/angular-ui-kit'; +import { Button } from 'primeng/button'; + +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, + imports: [ExtraToastComponent, Button], + template: \` + + +
+ @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-close', + severity: severity as any, + summary: 'Сообщение', + detail: 'Подпись', + life: 5000, + icon, + closable: 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 new file mode 100644 index 0000000..c8dea34 --- /dev/null +++ b/src/stories/components/toast/examples/toast-with-content.component.ts @@ -0,0 +1,159 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { Button } from 'primeng/button'; +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, Button], + 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 { Component } from '@angular/core'; +import { ExtraToastComponent, ExtraToastService } from '@cdek-it/angular-ui-kit'; +import { Button } from 'primeng/button'; + +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, Button], + 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 new file mode 100644 index 0000000..e349a19 --- /dev/null +++ b/src/stories/components/toast/toast.stories.ts @@ -0,0 +1,100 @@ +import { Meta, applicationConfig, moduleMetadata } from '@storybook/angular'; +import { ExtraToastComponent } from '../../../lib/components/toast/toast.component'; +import { provideExtraToast } from '../../../lib/components/toast/provide-toast'; +import { ToastSeveritiesComponent, Severities } 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 { ToastWidthComponent, Width } from './examples/toast-width.component'; +import { ToastPositionComponent, Position } from './examples/toast-position.component'; + +const meta: Meta = { + title: 'Components/Feedback/Toast', + component: ExtraToastComponent, + tags: ['autodocs'], + decorators: [ + applicationConfig({ providers: [provideExtraToast()] }), + moduleMetadata({ + imports: [ + ExtraToastComponent, + ToastSeveritiesComponent, + ToastWithCloseButtonComponent, + ToastWithContentComponent, + ToastWidthComponent, + ToastPositionComponent, + ], + }), + ], + parameters: { + designTokens: { prefix: '--p-toast' }, + docs: { + description: { + component: `Компонент для отображения всплывающих уведомлений поверх интерфейса. + +## Подключение + +Добавьте \`provideExtraToast()\` в провайдеры приложения: + +\`\`\`typescript +// app.config.ts +import { provideExtraToast } from '@cdek-it/angular-ui-kit'; + +export const appConfig: ApplicationConfig = { + providers: [provideExtraToast()], +}; +\`\`\` + +## Использование + +\`\`\`typescript +import { ExtraToastComponent, ExtraToastService } from '@cdek-it/angular-ui-kit'; + +@Component({ + imports: [ExtraToastComponent], + template: \`\`, +}) +export class AppComponent { + private toast = inject(ExtraToastService); + + show() { + this.toast.add({ severity: 'success', summary: 'Готово', detail: 'Операция выполнена успешно' }); + } +} +\`\`\``, + }, + }, + }, + argTypes: { + position: { + control: 'select', + options: ['top-right', 'top-left', 'top-center', 'bottom-right', 'bottom-left', 'bottom-center', 'center'], + description: 'Позиция тоста на экране.', + table: { + category: 'Props', + defaultValue: { summary: 'top-right' }, + type: { summary: "'top-right' | 'top-left' | 'top-center' | 'bottom-right' | 'bottom-left' | 'bottom-center' | 'center'" }, + }, + }, + key: { + table: { disable: true }, + }, + life: { + control: 'number', + description: 'Время (мс) до автоматического закрытия тоста.', + table: { + category: 'Props', + defaultValue: { summary: '5000' }, + type: { summary: 'number' }, + }, + }, + }, + args: { + position: 'top-right', + key: undefined, + life: 5000, + }, +}; + +export default meta; + +// ── Re-exports from example components ──────────────────────────────────── +export { Severities as Default, WithCloseButton, WithContent, Width, Position };