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',
+ },
+};