diff --git a/addon/components/pix-toast-container.hbs b/addon/components/pix-toast-container.hbs new file mode 100644 index 000000000..11c2f818e --- /dev/null +++ b/addon/components/pix-toast-container.hbs @@ -0,0 +1,9 @@ +
+ {{#if this.pixToast.content}} + + {{/if}} +
\ No newline at end of file diff --git a/addon/components/pix-toast-container.js b/addon/components/pix-toast-container.js new file mode 100644 index 000000000..6ea4a5490 --- /dev/null +++ b/addon/components/pix-toast-container.js @@ -0,0 +1,6 @@ +import Component from '@glimmer/component'; +import { service } from '@ember/service'; + +export default class PixToastContainer extends Component { + @service pixToast; +} diff --git a/addon/components/pix-toast.hbs b/addon/components/pix-toast.hbs new file mode 100644 index 000000000..ac01771f6 --- /dev/null +++ b/addon/components/pix-toast.hbs @@ -0,0 +1,17 @@ +
+
+ +
+

+ {{@message}} +

+
+ +
+
\ No newline at end of file diff --git a/addon/components/pix-toast.js b/addon/components/pix-toast.js new file mode 100644 index 000000000..bf3ac3c66 --- /dev/null +++ b/addon/components/pix-toast.js @@ -0,0 +1,37 @@ +import Component from '@glimmer/component'; +import { service } from '@ember/service'; +import { action } from '@ember/object'; +import { warn } from '@ember/debug'; +const TYPE_SUCCESS = 'success'; +const TYPE_ERROR = 'error'; +const TYPE_INFORMATION = 'information'; +const TYPE_WARNING = 'warning'; + +export default class PixToast extends Component { + @service pixToast; + + get type() { + const correctTypes = [TYPE_SUCCESS, TYPE_ERROR, TYPE_INFORMATION, TYPE_WARNING]; + warn('PixToast: you need to provide a type', [correctTypes].includes(this.args.type), { + id: 'pix-ui.toast.type.not-provided', + }); + return this.args.type ?? 'success'; + } + + get iconClass() { + const classes = { + [TYPE_SUCCESS]: 'circle-check', + [TYPE_ERROR]: 'circle-exclamation', + [TYPE_INFORMATION]: 'circle-info', + [TYPE_WARNING]: 'triangle-exclamation', + }; + return classes[this.type]; + } + + @action + removeNotification(event) { + event.preventDefault(); + event.stopPropagation(); + this.pixToast.removeNotification(this.pixToast.content); + } +} diff --git a/addon/services/pix-toast.js b/addon/services/pix-toast.js new file mode 100644 index 000000000..f0669ec0b --- /dev/null +++ b/addon/services/pix-toast.js @@ -0,0 +1,63 @@ +import Service from '@ember/service'; +import EmberObject from '@ember/object'; +import { tracked } from '@glimmer/tracking'; + +export default class ToastService extends Service { + @tracked content = undefined; + + addNotification({ message, ariaLabel, type }) { + if (!message || !ariaLabel) { + throw new Error('Mandatory attributes are missing: message and ariaLabel'); + } + + const toast = EmberObject.create({ + ariaLabel, + message, + type: type || 'success', + }); + + this.content = toast; + + return toast; + } + + sendErrorNotification({ message, ariaLabel }) { + return this.addNotification({ + ariaLabel, + message, + type: 'error', + }); + } + + sendSuccessNotification({ message, ariaLabel }) { + return this.addNotification({ + ariaLabel, + message, + type: 'success', + }); + } + + sendInformationNotification({ message, ariaLabel }) { + return this.addNotification({ + ariaLabel, + message, + type: 'information', + }); + } + + sendWarningNotification({ message, ariaLabel }) { + return this.addNotification({ + ariaLabel, + message, + type: 'warning', + }); + } + + removeNotification(toast) { + if (!toast) { + return; + } + + this.content = undefined; + } +} diff --git a/addon/styles/_pix-toast.scss b/addon/styles/_pix-toast.scss new file mode 100644 index 000000000..2ad4ed0f8 --- /dev/null +++ b/addon/styles/_pix-toast.scss @@ -0,0 +1,138 @@ +.pix-toast { + display: flex; + max-width: 400px; + margin: 0 auto; + border-radius: 5px; + transition: all 0.3s ease-in-out; + + &--error { + color: var(--pix-error-700); + background-color: var(--pix-error-50); + border: 1.5px solid var(--pix-error-700); + } + + &--success { + color: var(--pix-success-700); + background-color: var(--pix-success-50); + border: 1.5px solid var(--pix-success-700); + } + + &--information { + color: var(--pix-info-700); + background-color: var(--pix-info-50); + border: 1.5px solid var(--pix-info-700); + } + + &--warning { + color: var(--pix-warning-700); + background-color: var(--pix-warning-50); + border: 1.5px solid var(--pix-warning-700); + } + + > div, p { + @extend %pix-body-m; + + display: flex; + align-items: center; + justify-content: center; + } +} + +.pix-toast__icon { + flex: none; + width: 40px; + + &--error { + color: var(--pix-neutral-0); + background-color: var(--pix-error-700); + } + + &--success { + color: var(--pix-neutral-0); + background-color: var(--pix-success-700); + } + + &--information { + color: var(--pix-neutral-0); + background-color: var(--pix-info-700); + } + + &--warning { + color: var(--pix-neutral-0); + background-color: var(--pix-warning-700); + } +} + +.pix-toast__content { + padding: 8px 12px; +} + +.pix-toast__close-button-container { + flex: none; + width: 40px; +} + +.pix-toast__close-button { + &--error { + color: var(--pix-error-700); + + &:hover:enabled, + &:active:enabled, { + color: var(--pix-neutral-0); + background-color: var(--pix-error-700); + } + + &:focus:enabled { + background-color: var(--pix-error-900); + } + } + + &--success { + color: var(--pix-success-700); + + &:hover:enabled, + &:active:enabled, { + color: var(--pix-neutral-0); + background-color: var(--pix-success-700); + } + + &:focus:enabled { + background-color: var(--pix-success-900); + } + } + + &--information { + color: var(--pix-info-700); + + &:hover:enabled, + &:active:enabled, { + color: var(--pix-neutral-0); + background-color: var(--pix-info-700); + } + + &:focus:enabled { + background-color: var(--pix-info-900); + } + } + + &--warning { + color: var(--pix-warning-700); + + &:hover:enabled, + &:active:enabled, { + color: var(--pix-neutral-0); + background-color: var(--pix-warning-700); + } + + &:focus:enabled { + background-color: var(--pix-warning-900); + } + } +} + +.pix-toast-container { + position: fixed; + right: 12px; + bottom: 30px; + z-index: 1000; +} diff --git a/addon/styles/addon.scss b/addon/styles/addon.scss index 36b371f04..ebee38f1d 100644 --- a/addon/styles/addon.scss +++ b/addon/styles/addon.scss @@ -34,6 +34,7 @@ @import 'pix-indicator-card'; @import 'trap-focus'; @import 'pix-search-input'; +@import 'pix-toast'; // at the end so it can override it's children scss @import 'pix-filterable-and-searchable-select'; diff --git a/app/components/pix-toast-container.js b/app/components/pix-toast-container.js new file mode 100644 index 000000000..9d5b68fe1 --- /dev/null +++ b/app/components/pix-toast-container.js @@ -0,0 +1 @@ +export { default } from '@1024pix/pix-ui/components/pix-toast-container'; diff --git a/app/components/pix-toast.js b/app/components/pix-toast.js new file mode 100644 index 000000000..cdd0751b2 --- /dev/null +++ b/app/components/pix-toast.js @@ -0,0 +1 @@ +export { default } from '@1024pix/pix-ui/components/pix-toast'; diff --git a/app/services/pix-toast.js b/app/services/pix-toast.js new file mode 100644 index 000000000..a955cfaa8 --- /dev/null +++ b/app/services/pix-toast.js @@ -0,0 +1 @@ +export { default } from '@1024pix/pix-ui/services/pix-toast'; diff --git a/app/stories/pix-toast.mdx b/app/stories/pix-toast.mdx new file mode 100644 index 000000000..eb0e15731 --- /dev/null +++ b/app/stories/pix-toast.mdx @@ -0,0 +1,47 @@ +import { Meta, Story, ArgTypes } from '@storybook/blocks'; +import * as ComponentStories from './pix-toast.stories'; + + + +# Pix Toast + +Une notification avec icône et un bouton de fermeture. + +## Success + +La notification en cas de succès. + + + +## Error + +La notification en cas d'erreur. + + + +## Information + +La notification pour afficher une information. + + + +## Warning + +La notification pour afficher un avertissement. + + + + +## Usage + +```html + +``` + +## Arguments + + diff --git a/app/stories/pix-toast.stories.js b/app/stories/pix-toast.stories.js new file mode 100644 index 000000000..988f599a1 --- /dev/null +++ b/app/stories/pix-toast.stories.js @@ -0,0 +1,61 @@ +import { hbs } from 'ember-cli-htmlbars'; + +export default { + title: 'Notification/Toast', + render: (args) => ({ + template: hbs``, + context: args, + }), + argTypes: { + type: { + name: 'type', + description: 'Type de la notification : success, error, information, warning', + type: { name: 'string', required: true }, + }, + ariaLabelForCloseButton: { + name: 'ariaLabelForCloseButton', + description: 'Aria-label pour le bouton de fermeture de la notification', + type: { name: 'string', required: true }, + }, + message: { + name: 'message', + description: 'Contenu de la notification', + type: { name: 'message', required: true }, + }, + }, +}; + +export const success = { + args: { + type: 'success', + message: + 'ed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,\n totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae\n dicta sunt explicabo.', + ariaLabelForCloseButton: 'Fermer', + }, +}; +export const error = { + args: { + type: 'error', + message: + 'ed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,\n totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae\n dicta sunt explicabo.', + ariaLabelForCloseButton: 'Fermer', + }, +}; + +export const information = { + args: { + type: 'information', + message: + 'ed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,\n totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae\n dicta sunt explicabo.', + ariaLabelForCloseButton: 'Fermer', + }, +}; + +export const warning = { + args: { + type: 'warning', + message: + 'ed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,\n totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae\n dicta sunt explicabo.', + ariaLabelForCloseButton: 'Fermer', + }, +};