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 54701f3fc974c..763885a437ce3 100644 --- a/packages/react-components/react-toast/etc/react-toast.api.md +++ b/packages/react-components/react-toast/etc/react-toast.api.md @@ -9,12 +9,16 @@ import * as React_2 from 'react'; // @public (undocumented) export const Toaster: React_2.FC; +// @public (undocumented) +export type ToastId = string; + // @public (undocumented) export type ToastPosition = 'top-right' | 'top-center' | 'top-left' | 'bottom-right' | 'bottom-center' | 'bottom-left'; // @public (undocumented) export function useToastController(): { dispatchToast: (content: React_2.ReactNode, options?: ToastOptions | undefined) => void; + dismissToast: (toastId?: string | undefined) => void; }; // (No @packageDocumentation comment for this package) diff --git a/packages/react-components/react-toast/src/index.ts b/packages/react-components/react-toast/src/index.ts index d5a5e178359db..ac38826fe7182 100644 --- a/packages/react-components/react-toast/src/index.ts +++ b/packages/react-components/react-toast/src/index.ts @@ -1,4 +1,4 @@ export { Toaster } from './components/Toaster'; export { useToastController } from './state'; -export type { ToastPosition } from './state'; +export type { ToastPosition, ToastId } from './state'; diff --git a/packages/react-components/react-toast/src/state/constants.ts b/packages/react-components/react-toast/src/state/constants.ts index bfaa28528c6f3..9ca243fc8dcc6 100644 --- a/packages/react-components/react-toast/src/state/constants.ts +++ b/packages/react-components/react-toast/src/state/constants.ts @@ -1,3 +1,4 @@ export const EVENTS = { show: 'fui-toast-show', + dismiss: 'fui-toast-dismiss', } 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 fa985d5925f79..d5a359e2f5ade 100644 --- a/packages/react-components/react-toast/src/state/types.ts +++ b/packages/react-components/react-toast/src/state/types.ts @@ -16,10 +16,14 @@ export interface Toast extends Required> { remove: () => void; } +export interface DismissToastEventDetail { + toastId: ToastId | undefined; +} + export interface ToastEventMap { [EVENTS.show]: CustomEvent; + [EVENTS.dismiss]: CustomEvent; } export type ToastEventListenerGeneric = (e: ToastEventMap[K]) => void; -export type ToastShowEventListener = ToastEventListenerGeneric; -export type ToastEventListener = ToastShowEventListener; +export type ToastEventListener = (e: ToastEventMap[K]) => void; diff --git a/packages/react-components/react-toast/src/state/useToastController.ts b/packages/react-components/react-toast/src/state/useToastController.ts index 59af3fd369438..0baab5b3e1728 100644 --- a/packages/react-components/react-toast/src/state/useToastController.ts +++ b/packages/react-components/react-toast/src/state/useToastController.ts @@ -1,7 +1,7 @@ import { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts'; -import { dispatchToast as dispatchToastVanilla } from './vanilla/dispatchToast'; +import { dispatchToast as dispatchToastVanilla, dismissToast as dismissToastVanilla } from './vanilla'; import * as React from 'react'; -import { ToastOptions } from './types'; +import { ToastId, ToastOptions } from './types'; export function useToastController() { const { targetDocument } = useFluent(); @@ -15,7 +15,17 @@ export function useToastController() { [targetDocument], ); + const dismissToast = React.useCallback( + (toastId?: ToastId) => { + if (targetDocument) { + dismissToastVanilla(toastId, targetDocument); + } + }, + [targetDocument], + ); + return { dispatchToast, + dismissToast, }; } diff --git a/packages/react-components/react-toast/src/state/vanilla/dismissToast.ts b/packages/react-components/react-toast/src/state/vanilla/dismissToast.ts new file mode 100644 index 0000000000000..4ef480f581184 --- /dev/null +++ b/packages/react-components/react-toast/src/state/vanilla/dismissToast.ts @@ -0,0 +1,11 @@ +import { EVENTS } from '../constants'; +import { DismissToastEventDetail, ToastId } from '../types'; + +export function dismissToast(toastId: ToastId | undefined = undefined, targetDocument: Document) { + const event = new CustomEvent(EVENTS.dismiss, { + bubbles: false, + cancelable: false, + detail: { toastId }, + }); + targetDocument.dispatchEvent(event); +} diff --git a/packages/react-components/react-toast/src/state/vanilla/dispatchToast.ts b/packages/react-components/react-toast/src/state/vanilla/dispatchToast.ts index fa0f4e504a3ce..74a6cfac970a0 100644 --- a/packages/react-components/react-toast/src/state/vanilla/dispatchToast.ts +++ b/packages/react-components/react-toast/src/state/vanilla/dispatchToast.ts @@ -5,6 +5,10 @@ let counter = 0; export function dispatchToast(content: unknown, options: ToastOptions = {}, targetDocument: Document) { options.toastId ??= (counter++).toString(); - const event = new CustomEvent(EVENTS.show, { bubbles: false, cancelable: false, detail: { ...options, content } }); + const event = new CustomEvent(EVENTS.show, { + bubbles: false, + cancelable: false, + detail: { ...options, content }, + }); targetDocument.dispatchEvent(event); } 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 671fab78b6b05..78e6c1e54e5b2 100644 --- a/packages/react-components/react-toast/src/state/vanilla/index.ts +++ b/packages/react-components/react-toast/src/state/vanilla/index.ts @@ -1,4 +1,5 @@ export * from './dispatchToast'; +export * from './dismissToast'; 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 f4fe62a7c4ebc..64fbb9d721711 100644 --- a/packages/react-components/react-toast/src/state/vanilla/toaster.ts +++ b/packages/react-components/react-toast/src/state/vanilla/toaster.ts @@ -33,10 +33,20 @@ export class Toaster { private _initEvents() { const buildToast: ToastEventListener = e => this._buildToast(e.detail); + const dismissToast: ToastEventListener = e => { + const { toastId } = e.detail; + if (toastId) { + this._dismissToast(toastId); + } else { + this._dismissAllToasts(); + } + }; this.listeners.set(EVENTS.show, buildToast); + this.listeners.set(EVENTS.dismiss, dismissToast); this._addEventListener(EVENTS.show, buildToast); + this._addEventListener(EVENTS.dismiss, dismissToast); } private _addEventListener( @@ -53,6 +63,16 @@ export class Toaster { this.targetDocument.removeEventListener(eventType, callback as () => void); } + private _dismissToast(toastId: ToastId) { + this.visibleToasts.delete(toastId); + this.onUpdate(); + } + + private _dismissAllToasts() { + this.visibleToasts.clear(); + this.onUpdate(); + } + private _buildToast(toastOptions: ToastOptions) { const { toastId = '', position = 'bottom-right', timeout = 3000, content = '' } = toastOptions; if (this.toasts.has(toastId)) { diff --git a/packages/react-components/react-toast/stories/Toast/DismissAll.stories.tsx b/packages/react-components/react-toast/stories/Toast/DismissAll.stories.tsx new file mode 100644 index 0000000000000..d0738cc4ceaf7 --- /dev/null +++ b/packages/react-components/react-toast/stories/Toast/DismissAll.stories.tsx @@ -0,0 +1,15 @@ +import * as React from 'react'; +import { Toaster, useToastController } from '@fluentui/react-toast'; + +export const DismissAll = () => { + const { dispatchToast, dismissToast } = useToastController(); + const notify = () => dispatchToast('This is a toast'); + + return ( + <> + + + + + ); +}; diff --git a/packages/react-components/react-toast/stories/Toast/DismissToast.stories.tsx b/packages/react-components/react-toast/stories/Toast/DismissToast.stories.tsx new file mode 100644 index 0000000000000..ba76d996ebd60 --- /dev/null +++ b/packages/react-components/react-toast/stories/Toast/DismissToast.stories.tsx @@ -0,0 +1,15 @@ +import * as React from 'react'; +import { Toaster, useToastController, ToastId } from '@fluentui/react-toast'; + +export const DismissToast = () => { + const { dispatchToast, dismissToast } = useToastController(); + const notify = (id: ToastId) => dispatchToast('This is a toast', { toastId: id }); + + 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 8ed02a592f383..d1a749f4a62e0 100644 --- a/packages/react-components/react-toast/stories/Toast/index.stories.tsx +++ b/packages/react-components/react-toast/stories/Toast/index.stories.tsx @@ -1,6 +1,8 @@ export { Default } from './Default.stories'; export { CustomTimeout } from './CustomTimeout.stories'; export { ToastPositions } from './ToastPositions.stories'; +export { DismissToast } from './DismissToast.stories'; +export { DismissAll } from './DismissAll.stories'; export default { title: 'Preview Components/Toast',