From ff8133c48d8ad99d25c0bbe96e6ee9069cd18cc9 Mon Sep 17 00:00:00 2001 From: Raouf Date: Wed, 27 Nov 2024 17:53:48 +0100 Subject: [PATCH 1/7] feat(pie-toast-provider): DSW-2222 add priorty order --- .changeset/tiny-eels-play.md | 9 + .../data/tag-variants-to-statuses-map.ts | 6 +- .../stories/pie-toast-provider.stories.ts | 104 ++++++++++- .../pie-toast-provider/package.json | 1 + .../components/pie-toast-provider/src/defs.ts | 53 +++++- .../pie-toast-provider/src/index.ts | 175 ++++++++++++++++-- .../src/toast-provider.scss | 14 ++ packages/components/pie-toast/src/toast.scss | 9 - yarn.lock | 1 + 9 files changed, 337 insertions(+), 35 deletions(-) create mode 100644 .changeset/tiny-eels-play.md diff --git a/.changeset/tiny-eels-play.md b/.changeset/tiny-eels-play.md new file mode 100644 index 0000000000..67002a14d6 --- /dev/null +++ b/.changeset/tiny-eels-play.md @@ -0,0 +1,9 @@ +--- +"@justeattakeaway/pie-toast-provider": minor +"@justeattakeaway/pie-toast": minor +"@justeattakeaway/pie-webc": minor +"pie-storybook": minor +"pie-monorepo": minor +--- + +[Added] - priority order for the toast provider diff --git a/apps/pie-storybook/data/tag-variants-to-statuses-map.ts b/apps/pie-storybook/data/tag-variants-to-statuses-map.ts index e72b29accc..6f0d5e70f7 100644 --- a/apps/pie-storybook/data/tag-variants-to-statuses-map.ts +++ b/apps/pie-storybook/data/tag-variants-to-statuses-map.ts @@ -1,9 +1,9 @@ import { type TagVariantToStatusMap } from '../interfaces/tag-variant-to-status-map'; export const tagVariantToStatusMap: TagVariantToStatusMap = { - alpha: 'brand-05', - beta: 'brand-05', + alpha: 'brand-02', + beta: 'brand-02', deprecated: 'error', removed: 'error', - stable: 'success', + stable: 'information', }; diff --git a/apps/pie-storybook/stories/pie-toast-provider.stories.ts b/apps/pie-storybook/stories/pie-toast-provider.stories.ts index f7e026cc80..2608058173 100644 --- a/apps/pie-storybook/stories/pie-toast-provider.stories.ts +++ b/apps/pie-storybook/stories/pie-toast-provider.stories.ts @@ -1,19 +1,47 @@ import { html } from 'lit'; import { type Meta } from '@storybook/web-components'; +import { action } from '@storybook/addon-actions'; -import '@justeattakeaway/pie-toast-provider'; -import { type ToastProviderProps } from '@justeattakeaway/pie-toast-provider'; +import { toaster } from '@justeattakeaway/pie-toast-provider'; +import { type ToastProviderProps, defaultProps } from '@justeattakeaway/pie-toast-provider'; +import '@justeattakeaway/pie-button'; +import '@justeattakeaway/pie-tag'; import { createStory } from '../utilities'; type ToastProviderStoryMeta = Meta; -const defaultArgs: ToastProviderProps = {}; +const onQueueUpdate = (event: CustomEvent) => { + action('pie-toast-provider-queue-update')(event.detail); + + const queueLengthTag = document.querySelector('#queue-length-tag') as HTMLElement; + if (queueLengthTag) { + queueLengthTag.textContent = `Toast Queue Length: ${event.detail.length}`; + } +}; +const defaultArgs: ToastProviderProps = { + ...defaultProps, + options: { + duration: 3000, + isDismissible: true, + onPieToastOpen: action('onPieToastOpen'), + onPieToastClose: action('onPieToastClose'), + onPieToastLeadingActionClick: action('onPieToastLeadingActionClick'), + }, +}; const toastProviderStoryMeta: ToastProviderStoryMeta = { title: 'Toast Provider', component: 'pie-toast-provider', - argTypes: {}, + argTypes: { + options: { + description: 'Global options to configure default toast behavior.', + control: 'object', + defaultValue: { + summary: defaultProps.options, + }, + }, + }, args: defaultArgs, parameters: { design: { @@ -25,10 +53,70 @@ const toastProviderStoryMeta: ToastProviderStoryMeta = { export default toastProviderStoryMeta; -// TODO: remove the eslint-disable rule when props are added -// eslint-disable-next-line no-empty-pattern -const Template = ({}: ToastProviderProps) => html` - +const Template = ({ options }: ToastProviderProps) => html` + + + + + Toast Queue Length: 0 + + +
+ { + toaster.create({ + message: 'Low Priority Info Toast', + variant: 'info', + duration: null, + leadingAction: { + text: 'Confirm', + }, + }); +}}> + Trigger Info Toast (Low Priority) + + + { + toaster.create({ + message: 'Medium Priority Warning Toast', + variant: 'warning', + }); +}}> + Trigger Warning Toast (Medium Priority) + + + { + toaster.create({ + message: 'High Priority Error Toast', + variant: 'error', + }); +}}> + Trigger Error Toast (High Priority) + + + { + toaster.create({ + message: 'Actionable Info Toast', + variant: 'info', + leadingAction: { text: 'Retry' }, + }); +}}> + Trigger Actionable Info Toast + + + { + toaster.clearAll(); +}}> + Clear All Toasts + +
`; export const Default = createStory(Template, defaultArgs)(); diff --git a/packages/components/pie-toast-provider/package.json b/packages/components/pie-toast-provider/package.json index b19ba8a56b..b1a28326dd 100644 --- a/packages/components/pie-toast-provider/package.json +++ b/packages/components/pie-toast-provider/package.json @@ -41,6 +41,7 @@ "cem-plugin-module-file-extensions": "0.0.5" }, "dependencies": { + "@justeattakeaway/pie-toast": "0.5.0", "@justeattakeaway/pie-webc-core": "0.24.2" }, "volta": { diff --git a/packages/components/pie-toast-provider/src/defs.ts b/packages/components/pie-toast-provider/src/defs.ts index e9a5c00337..5989db056d 100644 --- a/packages/components/pie-toast-provider/src/defs.ts +++ b/packages/components/pie-toast-provider/src/defs.ts @@ -1,3 +1,50 @@ -// TODO - please remove the eslint disable comment below when you add props to this interface -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface ToastProviderProps {} +import { type ToastProps } from '@justeattakeaway/pie-toast'; + +export const PRIORITY_ORDER: { [x: string]: number } = { + 'error-actionable': 1, + error: 2, + 'warning-actionable': 3, + 'positive-actionable': 4, + 'info-actionable': 5, + 'neutral-actionable': 6, + warning: 7, + positive: 8, + info: 9, + neutral: 10, +}; + +export interface ExtendedToastProps extends ToastProps { + /** + * Callback for when the toast is closed. + */ + onPieToastClose?: () => void; + + /** + * Callback for when the toast is opened. + */ + onPieToastOpen?: () => void; + + /** + * Callback for when the leading action is clicked. + */ + onPieToastLeadingActionClick?: (event: Event) => void; +} + +export interface ToastProviderProps { + /** + * Default options for all toasts. + */ + options?: Partial; +} + +export const defaultProps: ToastProviderProps = { + options: {}, +}; + +/** + * Event name for when the chip is closed. + * + * @constant + */ + +export const ON_TOAST_PROVIDER_QUEUE_UPDATE_EVENT = 'pie-toast-provider-queue-update'; diff --git a/packages/components/pie-toast-provider/src/index.ts b/packages/components/pie-toast-provider/src/index.ts index b9bc7e79fa..00d1276d82 100644 --- a/packages/components/pie-toast-provider/src/index.ts +++ b/packages/components/pie-toast-provider/src/index.ts @@ -1,8 +1,25 @@ -import { LitElement, html, unsafeCSS } from 'lit'; -import { RtlMixin, defineCustomElement } from '@justeattakeaway/pie-webc-core'; - +import { + LitElement, + html, + nothing, + unsafeCSS, + type PropertyValues, +} from 'lit'; +import { state, property } from 'lit/decorators.js'; +import { ifDefined } from 'lit/directives/if-defined.js'; +import { + RtlMixin, + defineCustomElement, + dispatchCustomEvent, +} from '@justeattakeaway/pie-webc-core'; import styles from './toast-provider.scss?inline'; -import { type ToastProviderProps } from './defs'; +import { + defaultProps, + PRIORITY_ORDER, + type ToastProviderProps, + type ExtendedToastProps, + ON_TOAST_PROVIDER_QUEUE_UPDATE_EVENT, +} from './defs'; // Valid values available to consumers export * from './defs'; @@ -11,20 +28,154 @@ const componentSelector = 'pie-toast-provider'; /** * @tagname pie-toast-provider + * @event {CustomEvent} pie-toast-provider-queue-update - when a toast is added or removed from the queue. */ export class PieToastProvider extends RtlMixin(LitElement) implements ToastProviderProps { - render () { - return html`

Hello world!

`; - } + @state() + private _toasts: ExtendedToastProps[] = []; + + @state() + private _currentToast: ExtendedToastProps | null = null; + + @property({ type: Object }) + public options = defaultProps.options; + + updated (changedProperties: PropertyValues): void { + if (changedProperties.has('_toasts' as keyof PieToastProvider)) { + this._dispatchQueueUpdateEvent(); + } + } + + private _dispatchQueueUpdateEvent () : void { + dispatchCustomEvent( + this, ON_TOAST_PROVIDER_QUEUE_UPDATE_EVENT, + this._toasts, + ); + } + + /** + * Get the priority for a toast. + * @param {string} type - The variant type of the toast. + * @param {boolean} hasAction - Whether the toast has an action. + * @returns {number} - The priority based on the variant and action. + */ + private getPriority (type: ExtendedToastProps['variant'], hasAction: boolean): number { + const key = `${type}${hasAction ? '-actionable' : ''}`; + return PRIORITY_ORDER[key]; + } + + /** + * Handles the dismissal of the current toast and displays the next one in the queue (if any). + */ + private _dismissToast () { + this._currentToast?.onPieToastClose?.(); + this._currentToast = null; + requestAnimationFrame(() => { this._showNextToast(); }); + } + + /** + * Displays the next toast in the queue, if available. + */ + private _showNextToast () { + if (this._toasts.length > 0) { + const [nextToast, ...remainingToasts] = this._toasts; + this._currentToast = nextToast; + this._toasts = remainingToasts; + } else { + this._currentToast = null; + } + } + + /** + * Adds a new toast to the queue and triggers its display if no toast is currently active. + * @param {ToastProps} toast - The toast props to display. + */ + public createToast (toast: ExtendedToastProps) { + const newToast = { ...this.options, ...toast }; + + this._toasts = [...this._toasts, newToast].sort((a, b) => { + const priorityB = this.getPriority(b.variant, !!b.leadingAction?.text); + const priorityA = this.getPriority(a.variant, !!a.leadingAction?.text); + + return priorityA - priorityB; + }); + + if (!this._currentToast) { + this._showNextToast(); + } + } - // Renders a `CSSResult` generated from SCSS by Vite - static styles = unsafeCSS(styles); + /** + * + * Clears all toasts from the queue and dismisses the currently visible toast. + */ + public clearToasts () { + this._toasts = []; + this._currentToast = null; + } + + render () { + const { _currentToast, _dismissToast } = this; + + return html` +
+ ${_currentToast + ? html` + + + ` + : nothing} +
+ `; + } + + // Renders a `CSSResult` generated from SCSS by Vite + static styles = unsafeCSS(styles); } defineCustomElement(componentSelector, PieToastProvider); declare global { - interface HTMLElementTagNameMap { - [componentSelector]: PieToastProvider; - } + interface HTMLElementTagNameMap { + [componentSelector]: PieToastProvider; + } } + +/** + * Singleton toaster interface for global access. + */ +export const toaster = { + _getToastProvider (): PieToastProvider | null { + const toastProvider = document.querySelector(componentSelector) as PieToastProvider; + + if (!toastProvider) { + console.error('ToastProvider is not initialized.'); + return null; + } + + return toastProvider; + }, + create (toast: ExtendedToastProps) { + const toastProvider = this._getToastProvider(); + if (!toastProvider) return; + + toastProvider.createToast(toast); + }, + clearAll () { + const toastProvider = this._getToastProvider(); + if (!toastProvider) return; + + toastProvider.clearToasts(); + }, +}; + diff --git a/packages/components/pie-toast-provider/src/toast-provider.scss b/packages/components/pie-toast-provider/src/toast-provider.scss index 6ffaedad64..fa5c53d152 100644 --- a/packages/components/pie-toast-provider/src/toast-provider.scss +++ b/packages/components/pie-toast-provider/src/toast-provider.scss @@ -1 +1,15 @@ @use '@justeattakeaway/pie-css/scss' as p; +@use '@justeattakeaway/pie-css/scss/settings' as *; + + +.c-toast-provider { + --toast-provider-offset: var(--dt-spacing-d); + + position: absolute; + inset-inline-start: var(--toast-provider-offset); + inset-block-end: var(--toast-provider-offset); + + @include media('>md') { + --toast-offset: var(--dt-spacing-e); + } +} \ No newline at end of file diff --git a/packages/components/pie-toast/src/toast.scss b/packages/components/pie-toast/src/toast.scss index 050e162fdc..46b3324060 100644 --- a/packages/components/pie-toast/src/toast.scss +++ b/packages/components/pie-toast/src/toast.scss @@ -1,5 +1,4 @@ @use '@justeattakeaway/pie-css/scss' as p; -@use '@justeattakeaway/pie-css/scss/settings' as *; .c-toast { --toast-border-radius: var(--dt-radius-rounded-b); @@ -8,13 +7,9 @@ --toast-font-size: #{p.font-size(--dt-font-body-s-size)}; --toast-line-height: #{p.line-height(--dt-font-body-s-line-height)}; --toast-icon-fill: var(--dt-color-content-default); - --toast-offset: var(--dt-spacing-d); --toast-translate-start: -100%; --toast-translate-end: 0; - position: absolute; - inset-inline-start: var(--toast-offset); - inset-block-end: var(--toast-offset); display: flex; flex-direction: column; justify-content: center; @@ -33,10 +28,6 @@ transition-property: all; transition-duration: var(--dt-motion-timing-100); transition-timing-function: var(--dt-motion-easing-in); - - @include media('>md') { - --toast-offset: var(--dt-spacing-e); - } } .c-toast--rtl { diff --git a/yarn.lock b/yarn.lock index ef099cd74d..8746c9f673 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5181,6 +5181,7 @@ __metadata: "@custom-elements-manifest/analyzer": 0.9.0 "@justeattakeaway/pie-components-config": 0.18.0 "@justeattakeaway/pie-css": 0.13.1 + "@justeattakeaway/pie-toast": 0.5.0 "@justeattakeaway/pie-webc-core": 0.24.2 cem-plugin-module-file-extensions: 0.0.5 languageName: unknown From 6881f24684a72b7dec89edeeb7a4c20ce8f6191f Mon Sep 17 00:00:00 2001 From: Raouf Date: Fri, 29 Nov 2024 15:53:03 +0100 Subject: [PATCH 2/7] feat(pie-toast-provider): DSW-2222 add tests --- .../toast/overview/priority-queue.json | 4 +- .../stories/pie-toast-provider.stories.ts | 81 +++++----- .../components/pie-toast-provider/src/defs.ts | 20 ++- .../pie-toast-provider/src/index.ts | 32 +--- .../pie-toast-provider/src/toaster.ts | 30 ++++ .../test/component/pie-toast-provider.spec.ts | 148 +++++++++++++++++- 6 files changed, 238 insertions(+), 77 deletions(-) create mode 100644 packages/components/pie-toast-provider/src/toaster.ts diff --git a/apps/pie-docs/src/components/toast/overview/priority-queue.json b/apps/pie-docs/src/components/toast/overview/priority-queue.json index e47860064e..647d8a4838 100644 --- a/apps/pie-docs/src/components/toast/overview/priority-queue.json +++ b/apps/pie-docs/src/components/toast/overview/priority-queue.json @@ -18,7 +18,7 @@ ], [ "4", - "Positive - actionable" + "Success - actionable" ], [ "5", @@ -34,7 +34,7 @@ ], [ "8", - "Positive" + "Success" ], [ "9", diff --git a/apps/pie-storybook/stories/pie-toast-provider.stories.ts b/apps/pie-storybook/stories/pie-toast-provider.stories.ts index 2608058173..7455fbb86f 100644 --- a/apps/pie-storybook/stories/pie-toast-provider.stories.ts +++ b/apps/pie-storybook/stories/pie-toast-provider.stories.ts @@ -11,14 +11,6 @@ import { createStory } from '../utilities'; type ToastProviderStoryMeta = Meta; -const onQueueUpdate = (event: CustomEvent) => { - action('pie-toast-provider-queue-update')(event.detail); - - const queueLengthTag = document.querySelector('#queue-length-tag') as HTMLElement; - if (queueLengthTag) { - queueLengthTag.textContent = `Toast Queue Length: ${event.detail.length}`; - } -}; const defaultArgs: ToastProviderProps = { ...defaultProps, options: { @@ -35,7 +27,7 @@ const toastProviderStoryMeta: ToastProviderStoryMeta = { component: 'pie-toast-provider', argTypes: { options: { - description: 'Global options to configure default toast behavior.', + description: 'Default options for all toasts; accepts all toast props.', control: 'object', defaultValue: { summary: defaultProps.options, @@ -53,7 +45,15 @@ const toastProviderStoryMeta: ToastProviderStoryMeta = { export default toastProviderStoryMeta; -const Template = ({ options }: ToastProviderProps) => html` +const Template = ({ options }: ToastProviderProps) => { + const onQueueUpdate = (event: CustomEvent) => { + const queueLength = document.querySelector('#queue-length-tag') as HTMLElement; + if (queueLength) { + queueLength.textContent = `Toast Queue Length: ${event.detail.length}`; + } + }; + + return html` @@ -66,57 +66,64 @@ const Template = ({ options }: ToastProviderProps) => html`
{ - toaster.create({ - message: 'Low Priority Info Toast', - variant: 'info', - duration: null, - leadingAction: { - text: 'Confirm', - }, - }); -}}> + toaster.create({ + message: 'Low Priority Info', + variant: 'info', + }); + }}> Trigger Info Toast (Low Priority) { - toaster.create({ - message: 'Medium Priority Warning Toast', - variant: 'warning', - }); -}}> + toaster.create({ + message: 'Medium Priority Warning Toast', + variant: 'warning', + }); + }}> Trigger Warning Toast (Medium Priority) { - toaster.create({ - message: 'High Priority Error Toast', - variant: 'error', - }); -}}> + toaster.create({ + message: 'High Priority Error Toast', + variant: 'error', + }); + }}> Trigger Error Toast (High Priority) { - toaster.create({ - message: 'Actionable Info Toast', - variant: 'info', - leadingAction: { text: 'Retry' }, - }); -}}> + toaster.create({ + message: 'Actionable Info Toast', + variant: 'info', + leadingAction: { text: 'Retry' }, + }); + }}> Trigger Actionable Info Toast + { + toaster.create({ + message: 'Persistent Toast', + duration: null, + }); + }}> + Trigger Persistent Toast + + { - toaster.clearAll(); -}}> + toaster.clearAll(); + }}> Clear All Toasts
`; +}; export const Default = createStory(Template, defaultArgs)(); diff --git a/packages/components/pie-toast-provider/src/defs.ts b/packages/components/pie-toast-provider/src/defs.ts index 5989db056d..f4475c7b98 100644 --- a/packages/components/pie-toast-provider/src/defs.ts +++ b/packages/components/pie-toast-provider/src/defs.ts @@ -1,48 +1,52 @@ import { type ToastProps } from '@justeattakeaway/pie-toast'; +import { type ComponentDefaultProps } from '@justeattakeaway/pie-webc-core'; + export const PRIORITY_ORDER: { [x: string]: number } = { 'error-actionable': 1, error: 2, 'warning-actionable': 3, - 'positive-actionable': 4, + 'success-actionable': 4, 'info-actionable': 5, 'neutral-actionable': 6, warning: 7, - positive: 8, + success: 8, info: 9, neutral: 10, }; export interface ExtendedToastProps extends ToastProps { /** - * Callback for when the toast is closed. + * Triggered when the user interacts with the close icon or when the toast auto dismiss. */ onPieToastClose?: () => void; /** - * Callback for when the toast is opened. + * Triggered when the toast is opened. */ onPieToastOpen?: () => void; /** - * Callback for when the leading action is clicked. + * Triggered when the user interacts with the leading action. */ onPieToastLeadingActionClick?: (event: Event) => void; } export interface ToastProviderProps { /** - * Default options for all toasts. + * Default options for all toasts; accepts all toast props. */ options?: Partial; } -export const defaultProps: ToastProviderProps = { +export type DefaultProps = ComponentDefaultProps; + +export const defaultProps: DefaultProps = { options: {}, }; /** - * Event name for when the chip is closed. + * Event name for when the toast provider queue is updated. * * @constant */ diff --git a/packages/components/pie-toast-provider/src/index.ts b/packages/components/pie-toast-provider/src/index.ts index 00d1276d82..00fd7a065c 100644 --- a/packages/components/pie-toast-provider/src/index.ts +++ b/packages/components/pie-toast-provider/src/index.ts @@ -12,6 +12,7 @@ import { defineCustomElement, dispatchCustomEvent, } from '@justeattakeaway/pie-webc-core'; +import '@justeattakeaway/pie-toast'; import styles from './toast-provider.scss?inline'; import { defaultProps, @@ -23,6 +24,7 @@ import { // Valid values available to consumers export * from './defs'; +export { toaster } from './toaster'; const componentSelector = 'pie-toast-provider'; @@ -118,7 +120,7 @@ export class PieToastProvider extends RtlMixin(LitElement) implements ToastProvi const { _currentToast, _dismissToast } = this; return html` -
+
${_currentToast ? html` { const toastProvider = page.locator(componentSelector); // Assert - expect(toastProvider).toBeVisible(); + expect(toastProvider).toBeDefined(); + }); + + test.describe('Priority Order Tests', () => { + test('should handle toast priority correctly', async ({ page, mount }) => { + // Arrange + let toasts: ExtendedToastProps[] = []; + await mount(PieToastProvider, { + on: { + [ON_TOAST_PROVIDER_QUEUE_UPDATE_EVENT]: (queue: ExtendedToastProps[]) => { + toasts = queue; + }, + }, + }); + + // Act + await page.evaluate(() => { + const toastProvider = document.querySelector('pie-toast-provider') as PieToastProvider; + + toastProvider.createToast({ + message: 'Neutral toast (Priority 10)', + variant: 'neutral', + }); + + toastProvider.createToast({ + message: 'Success toast with action (Priority 4)', + variant: 'success', + leadingAction: { text: 'Action' }, + }); + + toastProvider.createToast({ + message: 'Info toast with action (5)', + variant: 'info', + leadingAction: { text: 'Action' }, + }); + + toastProvider.createToast({ + message: 'Error toast (Priority 2)', + variant: 'error', + }); + }); + + // Assert + const queueVariants = toasts.map((toast: ExtendedToastProps) => `${toast.variant}${toast.leadingAction ? '-actionable' : ''}`); + for (let i = 1; i < queueVariants.length; i++) { + const prevPriority = PRIORITY_ORDER[queueVariants[i - 1]]; + const currPriority = PRIORITY_ORDER[queueVariants[i]]; + expect(currPriority).toBeGreaterThanOrEqual(prevPriority); // Ensure the current has a higher priority + } + }); + + test('should clear all toasts when clearToasts is called', async ({ page, mount }) => { + // Arrange + let toasts: ExtendedToastProps[] = []; + await mount(PieToastProvider, { + on: { + [ON_TOAST_PROVIDER_QUEUE_UPDATE_EVENT]: (queue: ExtendedToastProps[]) => { + toasts = queue; + }, + }, + }); + + // Act + await page.evaluate(() => { + const toastProvider = document.querySelector('pie-toast-provider') as PieToastProvider; + + toastProvider.createToast({ + message: 'Toast 1', + variant: 'neutral', + }); + + toastProvider.createToast({ + message: 'Toast 2', + variant: 'success', + }); + }); + + // Act + await page.evaluate(() => { + const toastProvider = document.querySelector('pie-toast-provider') as PieToastProvider; + toastProvider.clearToasts(); + }); + + // Assert + expect(toasts.length).toBe(0); + }); + }); + + test.describe('Props', () => { + test.describe('options', () => { + test('should apply global options to all toasts when options are passed', async ({ page, mount }) => { + let toasts: ExtendedToastProps[] = []; + await mount(PieToastProvider, { + props: { + options: { + variant: 'neutral', + isDismissible: true, + }, + } as PieToastProvider, + on: { + [ON_TOAST_PROVIDER_QUEUE_UPDATE_EVENT]: (queue: ExtendedToastProps[]) => { + toasts = queue; + }, + }, + }); + + // Act + await page.evaluate(() => { + const toastProvider = document.querySelector('pie-toast-provider') as PieToastProvider; + toastProvider.createToast({ message: 'Toast 1' }); + toastProvider.createToast({ message: 'Toast 2' }); + }); + + // Assert + toasts.forEach((toast) => { + expect(toast.isDismissible).toBeTruthy(); + expect(toast.variant).toBe('neutral'); + }); + }); + + test('should respect individual toast overrides when provided', async ({ page, mount }) => { + const toasts: ExtendedToastProps[] = []; + await mount(PieToastProvider, { + props: { + options: { duration: null }, + } as PieToastProvider, + + }); + + // Act + await page.evaluate(() => { + const toastProvider = document.querySelector('pie-toast-provider') as PieToastProvider; + toastProvider.createToast({ message: 'Toast 1' }); + toastProvider.createToast({ message: 'Toast 2' }); + toastProvider.createToast({ message: 'Toast 3', isDismissible: false }); + }); + + // Assert + expect(toasts[0].isDismissible).toBeTruthy(); // Global option should apply + expect(toasts[1].isDismissible).toBeFalsy(); // Override should take precedence + }); + }); }); }); From 56336556506d6db5a00297f0578ba18f44965d3a Mon Sep 17 00:00:00 2001 From: Raouf Date: Fri, 29 Nov 2024 16:01:06 +0100 Subject: [PATCH 3/7] feat(pie-toast-provider): DSW-2222 update yarn --- yarn.lock | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/yarn.lock b/yarn.lock index 8746c9f673..a3b638999d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4876,18 +4876,6 @@ __metadata: languageName: unknown linkType: soft -"@justeattakeaway/pie-foo@0.0.0, @justeattakeaway/pie-foo@workspace:packages/components/pie-foo": - version: 0.0.0-use.local - resolution: "@justeattakeaway/pie-foo@workspace:packages/components/pie-foo" - dependencies: - "@custom-elements-manifest/analyzer": 0.9.0 - "@justeattakeaway/pie-components-config": 0.18.0 - "@justeattakeaway/pie-css": 0.13.1 - "@justeattakeaway/pie-webc-core": 0.24.2 - cem-plugin-module-file-extensions: 0.0.5 - languageName: unknown - linkType: soft - "@justeattakeaway/pie-form-label@0.14.4, @justeattakeaway/pie-form-label@workspace:packages/components/pie-form-label": version: 0.0.0-use.local resolution: "@justeattakeaway/pie-form-label@workspace:packages/components/pie-form-label" @@ -5235,7 +5223,6 @@ __metadata: "@justeattakeaway/pie-components-config": 0.18.0 "@justeattakeaway/pie-cookie-banner": 1.2.0 "@justeattakeaway/pie-divider": 1.0.0 - "@justeattakeaway/pie-foo": 0.0.0 "@justeattakeaway/pie-form-label": 0.14.4 "@justeattakeaway/pie-icon-button": 1.0.0 "@justeattakeaway/pie-link": 1.0.0 @@ -22841,7 +22828,6 @@ __metadata: "@justeattakeaway/pie-cookie-banner": 1.2.0 "@justeattakeaway/pie-css": 0.13.1 "@justeattakeaway/pie-divider": 1.0.0 - "@justeattakeaway/pie-foo": 0.0.0 "@justeattakeaway/pie-form-label": 0.14.4 "@justeattakeaway/pie-icon-button": 1.0.0 "@justeattakeaway/pie-icons-configs": 4.5.1 From 37e17ffdbf8eb0b69ca85f6bb6acdc779323cb2a Mon Sep 17 00:00:00 2001 From: Raouf Date: Fri, 29 Nov 2024 16:02:38 +0100 Subject: [PATCH 4/7] feat(pie-toast-provider): DSW-2222 update yarn --- apps/pie-storybook/data/tag-variants-to-statuses-map.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/pie-storybook/data/tag-variants-to-statuses-map.ts b/apps/pie-storybook/data/tag-variants-to-statuses-map.ts index 6f0d5e70f7..e72b29accc 100644 --- a/apps/pie-storybook/data/tag-variants-to-statuses-map.ts +++ b/apps/pie-storybook/data/tag-variants-to-statuses-map.ts @@ -1,9 +1,9 @@ import { type TagVariantToStatusMap } from '../interfaces/tag-variant-to-status-map'; export const tagVariantToStatusMap: TagVariantToStatusMap = { - alpha: 'brand-02', - beta: 'brand-02', + alpha: 'brand-05', + beta: 'brand-05', deprecated: 'error', removed: 'error', - stable: 'information', + stable: 'success', }; From 6e96ada70716e9963b01f218ec23e0e503ce5b71 Mon Sep 17 00:00:00 2001 From: Raouf Date: Fri, 29 Nov 2024 16:09:36 +0100 Subject: [PATCH 5/7] fix(pie-toast-provider): DSW-2222 update formating --- .../pie-toast-provider/src/index.ts | 180 +++++++++--------- 1 file changed, 90 insertions(+), 90 deletions(-) diff --git a/packages/components/pie-toast-provider/src/index.ts b/packages/components/pie-toast-provider/src/index.ts index 00fd7a065c..5c0f284b7b 100644 --- a/packages/components/pie-toast-provider/src/index.ts +++ b/packages/components/pie-toast-provider/src/index.ts @@ -33,96 +33,96 @@ const componentSelector = 'pie-toast-provider'; * @event {CustomEvent} pie-toast-provider-queue-update - when a toast is added or removed from the queue. */ export class PieToastProvider extends RtlMixin(LitElement) implements ToastProviderProps { - @state() + @state() private _toasts: ExtendedToastProps[] = []; - @state() - private _currentToast: ExtendedToastProps | null = null; - - @property({ type: Object }) - public options = defaultProps.options; - - updated (changedProperties: PropertyValues): void { - if (changedProperties.has('_toasts' as keyof PieToastProvider)) { - this._dispatchQueueUpdateEvent(); - } - } - - private _dispatchQueueUpdateEvent () : void { - dispatchCustomEvent( - this, ON_TOAST_PROVIDER_QUEUE_UPDATE_EVENT, - this._toasts, - ); - } - - /** - * Get the priority for a toast. - * @param {string} type - The variant type of the toast. - * @param {boolean} hasAction - Whether the toast has an action. - * @returns {number} - The priority based on the variant and action. + @state() + private _currentToast: ExtendedToastProps | null = null; + + @property({ type: Object }) + public options = defaultProps.options; + + updated (changedProperties: PropertyValues): void { + if (changedProperties.has('_toasts' as keyof PieToastProvider)) { + this._dispatchQueueUpdateEvent(); + } + } + + private _dispatchQueueUpdateEvent (): void { + dispatchCustomEvent( + this, ON_TOAST_PROVIDER_QUEUE_UPDATE_EVENT, + this._toasts, + ); + } + + /** + * Get the priority for a toast. + * @param {string} type - The variant type of the toast. + * @param {boolean} hasAction - Whether the toast has an action. + * @returns {number} - The priority based on the variant and action. + */ + private getPriority (type: ExtendedToastProps['variant'], hasAction: boolean): number { + const key = `${type}${hasAction ? '-actionable' : ''}`; + return PRIORITY_ORDER[key]; + } + + /** + * Handles the dismissal of the current toast and displays the next one in the queue (if any). + */ + private _dismissToast () { + this._currentToast?.onPieToastClose?.(); + this._currentToast = null; + requestAnimationFrame(() => { this._showNextToast(); }); + } + + /** + * Displays the next toast in the queue, if available. + */ + private _showNextToast () { + if (this._toasts.length > 0) { + const [nextToast, ...remainingToasts] = this._toasts; + this._currentToast = nextToast; + this._toasts = remainingToasts; + } else { + this._currentToast = null; + } + } + + /** + * Adds a new toast to the queue and triggers its display if no toast is currently active. + * @param {ToastProps} toast - The toast props to display. + */ + public createToast (toast: ExtendedToastProps) { + const newToast = { ...this.options, ...toast }; + + this._toasts = [...this._toasts, newToast].sort((a, b) => { + const priorityB = this.getPriority(b.variant, !!b.leadingAction?.text); + const priorityA = this.getPriority(a.variant, !!a.leadingAction?.text); + + return priorityA - priorityB; + }); + + if (!this._currentToast) { + this._showNextToast(); + } + } + + /** + * + * Clears all toasts from the queue and dismisses the currently visible toast. */ - private getPriority (type: ExtendedToastProps['variant'], hasAction: boolean): number { - const key = `${type}${hasAction ? '-actionable' : ''}`; - return PRIORITY_ORDER[key]; - } + public clearToasts () { + this._toasts = []; + this._currentToast = null; + } - /** - * Handles the dismissal of the current toast and displays the next one in the queue (if any). - */ - private _dismissToast () { - this._currentToast?.onPieToastClose?.(); - this._currentToast = null; - requestAnimationFrame(() => { this._showNextToast(); }); - } - - /** - * Displays the next toast in the queue, if available. - */ - private _showNextToast () { - if (this._toasts.length > 0) { - const [nextToast, ...remainingToasts] = this._toasts; - this._currentToast = nextToast; - this._toasts = remainingToasts; - } else { - this._currentToast = null; - } - } - - /** - * Adds a new toast to the queue and triggers its display if no toast is currently active. - * @param {ToastProps} toast - The toast props to display. - */ - public createToast (toast: ExtendedToastProps) { - const newToast = { ...this.options, ...toast }; - - this._toasts = [...this._toasts, newToast].sort((a, b) => { - const priorityB = this.getPriority(b.variant, !!b.leadingAction?.text); - const priorityA = this.getPriority(a.variant, !!a.leadingAction?.text); - - return priorityA - priorityB; - }); - - if (!this._currentToast) { - this._showNextToast(); - } - } - - /** - * - * Clears all toasts from the queue and dismisses the currently visible toast. - */ - public clearToasts () { - this._toasts = []; - this._currentToast = null; - } - - render () { - const { _currentToast, _dismissToast } = this; + render () { + const { _currentToast, _dismissToast } = this; - return html` + return html`
${_currentToast - ? html` + ? html` ` - : nothing} + : nothing}
`; - } + } - // Renders a `CSSResult` generated from SCSS by Vite - static styles = unsafeCSS(styles); + // Renders a `CSSResult` generated from SCSS by Vite + static styles = unsafeCSS(styles); } defineCustomElement(componentSelector, PieToastProvider); declare global { - interface HTMLElementTagNameMap { - [componentSelector]: PieToastProvider; - } + interface HTMLElementTagNameMap { + [componentSelector]: PieToastProvider; + } } From f667474326b8189656bcb299f6f15515b35b025d Mon Sep 17 00:00:00 2001 From: Raouf Date: Fri, 29 Nov 2024 17:05:15 +0100 Subject: [PATCH 6/7] fix(pie-toast-provider): DSW-2222 ensure duration is parsed correctly --- packages/components/pie-toast-provider/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/pie-toast-provider/src/index.ts b/packages/components/pie-toast-provider/src/index.ts index 5c0f284b7b..4083a3518b 100644 --- a/packages/components/pie-toast-provider/src/index.ts +++ b/packages/components/pie-toast-provider/src/index.ts @@ -130,7 +130,7 @@ export class PieToastProvider extends RtlMixin(LitElement) implements ToastProvi ?isDismissible="${_currentToast.isDismissible}" ?isMultiline="${_currentToast.isMultiline}" .leadingAction="${_currentToast.leadingAction}" - .duration="${_currentToast.duration}" + .duration="${typeof _currentToast.duration === 'undefined' ? nothing : _currentToast.duration}" @pie-toast-close="${_dismissToast}" @pie-toast-open="${_currentToast.onPieToastOpen}" @pie-toast-leading-action-click="${_currentToast.onPieToastLeadingActionClick}"> From 7b0a454bfe5a0440b8d2b64ea53341f1708c6ca0 Mon Sep 17 00:00:00 2001 From: Raouf Date: Mon, 2 Dec 2024 13:14:39 +0300 Subject: [PATCH 7/7] fix(pie-toast-provider): DSW-2222 update markup --- packages/components/pie-toast-provider/src/index.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/components/pie-toast-provider/src/index.ts b/packages/components/pie-toast-provider/src/index.ts index 4083a3518b..b0ca4ecb2b 100644 --- a/packages/components/pie-toast-provider/src/index.ts +++ b/packages/components/pie-toast-provider/src/index.ts @@ -121,8 +121,8 @@ export class PieToastProvider extends RtlMixin(LitElement) implements ToastProvi return html`
- ${_currentToast - ? html` + ${_currentToast && + html` - ` - : nothing} + `}
`; }