From e36f211b7c09f144bbf6c4a5a6cec9d97d329319 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Fri, 12 May 2023 13:11:46 +0100 Subject: [PATCH 1/3] feat: Update existing toasts Creates a new `updateToast` controller to content or any options of an existing toast. --- .../react-toast/src/components/Toast.tsx | 4 +- .../react-toast/src/state/constants.ts | 1 + .../react-toast/src/state/types.ts | 6 +++ .../src/state/useToastController.ts | 51 ++++++++++--------- .../react-toast/src/state/vanilla/index.ts | 1 + .../react-toast/src/state/vanilla/toaster.ts | 21 ++++++-- .../src/state/vanilla/updateToast.ts | 12 +++++ .../stories/Toast/UpdateToast.stories.tsx | 16 ++++++ .../stories/Toast/index.stories.tsx | 1 + 9 files changed, 84 insertions(+), 29 deletions(-) create mode 100644 packages/react-components/react-toast/src/state/vanilla/updateToast.ts create mode 100644 packages/react-components/react-toast/stories/Toast/UpdateToast.stories.tsx diff --git a/packages/react-components/react-toast/src/components/Toast.tsx b/packages/react-components/react-toast/src/components/Toast.tsx index 19c538935f37a0..77f89beeeb8511 100644 --- a/packages/react-components/react-toast/src/components/Toast.tsx +++ b/packages/react-components/react-toast/src/components/Toast.tsx @@ -77,7 +77,7 @@ const useStyles = makeStyles({ export const Toast: React.FC = props => { const styles = useStyles(); - const { visible, children, close, remove, ...toastOptions } = props; + const { visible, children, close, remove, updateId, ...toastOptions } = props; const { timeout } = toastOptions; const { play, running, toastRef } = useToast({ ...toastOptions, content: children }); @@ -99,7 +99,7 @@ export const Toast: React.FC = props => {
{children} - +
); diff --git a/packages/react-components/react-toast/src/state/constants.ts b/packages/react-components/react-toast/src/state/constants.ts index 9ca243fc8dcc6d..003bad5391867e 100644 --- a/packages/react-components/react-toast/src/state/constants.ts +++ b/packages/react-components/react-toast/src/state/constants.ts @@ -1,4 +1,5 @@ export const EVENTS = { show: 'fui-toast-show', dismiss: 'fui-toast-dismiss', + update: 'fui-toast-update', } as const; diff --git a/packages/react-components/react-toast/src/state/types.ts b/packages/react-components/react-toast/src/state/types.ts index a9161b84757cb3..86c419864ecf49 100644 --- a/packages/react-components/react-toast/src/state/types.ts +++ b/packages/react-components/react-toast/src/state/types.ts @@ -22,12 +22,17 @@ export interface ToasterOptions export interface Toast extends ToastOptions { close: () => void; remove: () => void; + updateId: number; } export interface ShowToastEventDetail extends Partial { toastId: ToastId; } +export interface UpdateToastEventDetail extends Partial { + toastId: ToastId; +} + export interface DismissToastEventDetail { toastId: ToastId | undefined; } @@ -37,4 +42,5 @@ type EventListener = (e: CustomEvent) => void; export type ToastListenerMap = { [EVENTS.show]: EventListener; [EVENTS.dismiss]: EventListener; + [EVENTS.update]: EventListener; }; diff --git a/packages/react-components/react-toast/src/state/useToastController.ts b/packages/react-components/react-toast/src/state/useToastController.ts index fbd64588edace3..5eb4c33ae377cc 100644 --- a/packages/react-components/react-toast/src/state/useToastController.ts +++ b/packages/react-components/react-toast/src/state/useToastController.ts @@ -1,31 +1,36 @@ -import { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts'; -import { dispatchToast as dispatchToastVanilla, dismissToast as dismissToastVanilla } from './vanilla'; import * as React from 'react'; -import { ToastId, ToastOptions } from './types'; +import { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts'; +import { + dispatchToast as dispatchToastVanilla, + dismissToast as dismissToastVanilla, + updateToast as updateToastVanilla, +} from './vanilla'; +import { ToastId, ToastOptions, UpdateToastEventDetail } from './types'; + +const noop = () => undefined; export function useToastController() { const { targetDocument } = useFluent(); - const dispatchToast = React.useCallback( - (content: React.ReactNode, options?: Partial) => { - if (targetDocument) { - dispatchToastVanilla(content, options, targetDocument); - } - }, - [targetDocument], - ); + return React.useMemo(() => { + if (!targetDocument) { + return { + dispatchToast: noop, + dismissToast: noop, + updateToast: noop, + }; + } - const dismissToast = React.useCallback( - (toastId?: ToastId) => { - if (targetDocument) { + return { + dispatchToast: (content: React.ReactNode, options?: Partial) => { + dispatchToastVanilla(content, options, targetDocument); + }, + dismissToast: (toastId?: Partial) => { dismissToastVanilla(toastId, targetDocument); - } - }, - [targetDocument], - ); - - return { - dispatchToast, - dismissToast, - }; + }, + updateToast: (options: UpdateToastEventDetail) => { + updateToastVanilla(options, targetDocument); + }, + }; + }, [targetDocument]); } diff --git a/packages/react-components/react-toast/src/state/vanilla/index.ts b/packages/react-components/react-toast/src/state/vanilla/index.ts index 78e6c1e54e5b22..c7a2e618930799 100644 --- a/packages/react-components/react-toast/src/state/vanilla/index.ts +++ b/packages/react-components/react-toast/src/state/vanilla/index.ts @@ -1,5 +1,6 @@ export * from './dispatchToast'; export * from './dismissToast'; +export * from './updateToast'; export * from './toast'; export * from './toaster'; export * from './getPositionStyles'; diff --git a/packages/react-components/react-toast/src/state/vanilla/toaster.ts b/packages/react-components/react-toast/src/state/vanilla/toaster.ts index 38d327e638b9ac..24314a84e76f15 100644 --- a/packages/react-components/react-toast/src/state/vanilla/toaster.ts +++ b/packages/react-components/react-toast/src/state/vanilla/toaster.ts @@ -1,4 +1,4 @@ -import { Toast, ToasterOptions, ToastId, ToastOptions, ToastListenerMap } from '../types'; +import { Toast, ToasterOptions, ToastId, ToastOptions, ToastListenerMap, UpdateToastEventDetail } from '../types'; import { EVENTS } from '../constants'; function assignDefined(a: Partial, b: Partial) { @@ -49,6 +49,7 @@ export class Toaster { assignDefined(this.toasterOptions, options); const buildToast: ToastListenerMap[typeof EVENTS.show] = e => this._buildToast(e.detail); + const updateToast: ToastListenerMap[typeof EVENTS.update] = e => this._updateToast(e.detail); const dismissToast: ToastListenerMap[typeof EVENTS.dismiss] = e => { const { toastId } = e.detail; if (toastId) { @@ -58,11 +59,9 @@ export class Toaster { } }; - this.listeners.set(EVENTS.show, buildToast); - this.listeners.set(EVENTS.dismiss, dismissToast); - this._addEventListener(EVENTS.show, buildToast); this._addEventListener(EVENTS.dismiss, dismissToast); + this._addEventListener(EVENTS.update, updateToast); } public isToastVisible = (toastId: ToastId) => { @@ -74,6 +73,7 @@ export class Toaster { return; } + this.listeners.set(eventType, callback); const targetDocument = this.toasterElement?.ownerDocument; targetDocument.addEventListener(eventType, callback as () => void); } @@ -90,6 +90,18 @@ export class Toaster { targetDocument.removeEventListener(eventType, callback as () => void); } + private _updateToast(toastOptions: UpdateToastEventDetail) { + const { toastId } = toastOptions; + const toastToUpdate = this.toasts.get(toastId); + if (!toastToUpdate) { + return; + } + + Object.assign(toastToUpdate, toastOptions); + toastToUpdate.updateId++; + this.onUpdate(); + } + private _dismissToast(toastId: ToastId) { this.visibleToasts.delete(toastId); this.onUpdate(); @@ -123,6 +135,7 @@ export class Toaster { remove, toastId, content, + updateId: 0, }; assignDefined(toast, toastOptions); diff --git a/packages/react-components/react-toast/src/state/vanilla/updateToast.ts b/packages/react-components/react-toast/src/state/vanilla/updateToast.ts new file mode 100644 index 00000000000000..e708df31823ef4 --- /dev/null +++ b/packages/react-components/react-toast/src/state/vanilla/updateToast.ts @@ -0,0 +1,12 @@ +import { UpdateToastEventDetail } from '../types'; +import { EVENTS } from '../constants'; + +export function updateToast(options: UpdateToastEventDetail, targetDocument: Document) { + const event = new CustomEvent(EVENTS.update, { + bubbles: false, + cancelable: false, + detail: options, + }); + + targetDocument.dispatchEvent(event); +} diff --git a/packages/react-components/react-toast/stories/Toast/UpdateToast.stories.tsx b/packages/react-components/react-toast/stories/Toast/UpdateToast.stories.tsx new file mode 100644 index 00000000000000..ecfd3559e2b75a --- /dev/null +++ b/packages/react-components/react-toast/stories/Toast/UpdateToast.stories.tsx @@ -0,0 +1,16 @@ +import * as React from 'react'; +import { Toaster, useToastController, ToastId } from '@fluentui/react-toast'; + +export const UpdateToast = () => { + const { dispatchToast, updateToast } = useToastController(); + const notify = (id: ToastId) => dispatchToast('This toast never closes', { toastId: id, timeout: -1 }); + const update = (id: ToastId) => updateToast({ content: 'This toast will close soon', toastId: id, timeout: 1000 }); + + return ( + <> + + + + + ); +}; diff --git a/packages/react-components/react-toast/stories/Toast/index.stories.tsx b/packages/react-components/react-toast/stories/Toast/index.stories.tsx index 96353ca5bdbd81..67513a4be27e20 100644 --- a/packages/react-components/react-toast/stories/Toast/index.stories.tsx +++ b/packages/react-components/react-toast/stories/Toast/index.stories.tsx @@ -6,6 +6,7 @@ export { DismissToast } from './DismissToast.stories'; export { DismissAll } from './DismissAll.stories'; export { PauseOnWindowBlur } from './PauseOnWindowBlur.stories'; export { PauseOnHover } from './PauseOnHover.stories'; +export { UpdateToast } from './UpdateToast.stories'; export default { title: 'Preview Components/Toast', From 8d6579f2da0f1caef6cf44a6b1a075d5a65fa354 Mon Sep 17 00:00:00 2001 From: ling1726 Date: Fri, 12 May 2023 14:44:11 +0100 Subject: [PATCH 2/3] Update packages/react-components/react-toast/stories/Toast/UpdateToast.stories.tsx Co-authored-by: Oleksandr Fediashov --- .../react-toast/stories/Toast/UpdateToast.stories.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-components/react-toast/stories/Toast/UpdateToast.stories.tsx b/packages/react-components/react-toast/stories/Toast/UpdateToast.stories.tsx index ecfd3559e2b75a..b20ab45a4ec5e0 100644 --- a/packages/react-components/react-toast/stories/Toast/UpdateToast.stories.tsx +++ b/packages/react-components/react-toast/stories/Toast/UpdateToast.stories.tsx @@ -9,8 +9,8 @@ export const UpdateToast = () => { return ( <> - - + + ); }; From c6a236b829aa8a40c849b6973ccd0e43980d30f8 Mon Sep 17 00:00:00 2001 From: Lingfan Gao Date: Fri, 12 May 2023 13:23:07 +0100 Subject: [PATCH 3/3] update md --- packages/react-components/react-toast/etc/react-toast.api.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react-components/react-toast/etc/react-toast.api.md b/packages/react-components/react-toast/etc/react-toast.api.md index 04a9c293cea649..212a5f8126af5a 100644 --- a/packages/react-components/react-toast/etc/react-toast.api.md +++ b/packages/react-components/react-toast/etc/react-toast.api.md @@ -19,6 +19,7 @@ export type ToastPosition = 'top-right' | 'top-center' | 'top-left' | 'bottom-ri export function useToastController(): { dispatchToast: (content: React_2.ReactNode, options?: Partial | undefined) => void; dismissToast: (toastId?: string | undefined) => void; + updateToast: (options: UpdateToastEventDetail) => void; }; // (No @packageDocumentation comment for this package)