Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export const EVENTS = {
show: 'fui-toast-show',
dismiss: 'fui-toast-dismiss',
dismissAll: 'fui-toast-dismiss-all',
update: 'fui-toast-update',
} as const;
18 changes: 13 additions & 5 deletions packages/react-components/react-toast/src/state/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { EVENTS } from './constants';

export type ToastId = string;
export type ToasterId = string;

export type ToastPosition = 'top-right' | 'top-center' | 'top-left' | 'bottom-right' | 'bottom-center' | 'bottom-left';

Expand All @@ -11,12 +12,13 @@ export interface ToastOptions {
timeout: number;
pauseOnWindowBlur: boolean;
pauseOnHover: boolean;
toasterId: ToasterId | undefined;
}

export interface ToasterOptions
extends Pick<ToastOptions, 'position' | 'timeout' | 'pauseOnWindowBlur' | 'pauseOnHover'> {
offset?: number[];
toasterId?: string;
toasterId?: ToasterId;
}

export interface Toast extends ToastOptions {
Expand All @@ -25,18 +27,24 @@ export interface Toast extends ToastOptions {
updateId: number;
}

export interface ShowToastEventDetail extends Partial<ToastOptions> {
export interface CommonToastDetail {
toasterId?: ToasterId;
}

export interface ShowToastEventDetail extends Partial<ToastOptions>, CommonToastDetail {
toastId: ToastId;
}

export interface UpdateToastEventDetail extends Partial<ToastOptions> {
export interface UpdateToastEventDetail extends Partial<ToastOptions>, CommonToastDetail {
toastId: ToastId;
}

export interface DismissToastEventDetail {
toastId: ToastId | undefined;
export interface DismissToastEventDetail extends CommonToastDetail {
toastId: ToastId;
}

export interface DismissAllToastsEventDetail extends CommonToastDetail {}

type EventListener<TDetail> = (e: CustomEvent<TDetail>) => void;

export type ToastListenerMap = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts
import {
dispatchToast as dispatchToastVanilla,
dismissToast as dismissToastVanilla,
dismissAllToasts as dismissAllToastsVanilla,
updateToast as updateToastVanilla,
} from './vanilla';
import { ToastId, ToastOptions, UpdateToastEventDetail } from './types';
import { ToastId, ToastOptions, ToasterId, UpdateToastEventDetail } from './types';

const noop = () => undefined;

Expand All @@ -17,6 +18,7 @@ export function useToastController() {
return {
dispatchToast: noop,
dismissToast: noop,
dismissAllToasts: noop,
updateToast: noop,
};
}
Expand All @@ -25,8 +27,11 @@ export function useToastController() {
dispatchToast: (content: React.ReactNode, options?: Partial<ToastOptions>) => {
dispatchToastVanilla(content, options, targetDocument);
},
dismissToast: (toastId?: Partial<ToastId>) => {
dismissToastVanilla(toastId, targetDocument);
dismissToast: (toastId: ToastId, toasterId?: ToasterId) => {
dismissToastVanilla(toastId, toasterId, targetDocument);
},
dismissAllToasts: (toasterId?: ToasterId) => {
dismissAllToastsVanilla(toasterId, targetDocument);
},
updateToast: (options: UpdateToastEventDetail) => {
updateToastVanilla(options, targetDocument);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import { Toast, ToastPosition, ToasterOptions } from './types';
import { ToasterProps } from '../components/Toaster';

export function useToaster<TElement extends HTMLElement>(options: ToasterProps = {}) {
const { toasterId, ...rest } = options;
const forceRender = useForceUpdate();
const defaultToastOptions = useToastOptions(options);
const defaultToastOptions = useToastOptions(rest);
const toasterRef = React.useRef<TElement>(null);
const [toaster] = React.useState(() => new Toaster());
const [toaster] = React.useState(() => new Toaster(toasterId));

React.useEffect(() => {
if (toasterRef.current) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { EVENTS } from '../constants';
import { DismissAllToastsEventDetail, ToasterId } from '../types';

export function dismissAllToasts(toasterId: ToasterId | undefined = undefined, targetDocument: Document) {
const event = new CustomEvent<DismissAllToastsEventDetail>(EVENTS.dismissAll, {
bubbles: false,
cancelable: false,
detail: { toasterId },
});
targetDocument.dispatchEvent(event);
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { EVENTS } from '../constants';
import { DismissToastEventDetail, ToastId } from '../types';
import { DismissToastEventDetail, ToastId, ToasterId } from '../types';

export function dismissToast(toastId: ToastId | undefined = undefined, targetDocument: Document) {
export function dismissToast(toastId: ToastId, toasterId: ToasterId | undefined = undefined, targetDocument: Document) {
const event = new CustomEvent<DismissToastEventDetail>(EVENTS.dismiss, {
bubbles: false,
cancelable: false,
detail: { toastId },
detail: { toastId, toasterId },
});
targetDocument.dispatchEvent(event);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './dispatchToast';
export * from './dismissToast';
export * from './dismissAllToasts';
export * from './updateToast';
export * from './toast';
export * from './toaster';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
import { Toast, ToasterOptions, ToastId, ToastOptions, ToastListenerMap, UpdateToastEventDetail } from '../types';
import {
Toast,
ToasterOptions,
ToastId,
ToastOptions,
ToastListenerMap,
UpdateToastEventDetail,
ToasterId,
ShowToastEventDetail,
CommonToastDetail,
} from '../types';
import { EVENTS } from '../constants';

function assignDefined<T extends object>(a: Partial<T>, b: Partial<T>) {
Expand All @@ -18,10 +28,12 @@ export class Toaster {
public onUpdate: () => void;
private toasterElement?: HTMLElement;
private toasterOptions: ToasterOptions;
private toasterId?: ToastId;

private listeners = new Map<keyof ToastListenerMap, ToastListenerMap[keyof ToastListenerMap]>();

constructor() {
constructor(toasterId?: ToasterId) {
this.toasterId = toasterId;
this.toasts = new Map<ToastId, Toast>();
this.visibleToasts = new Set<ToastId>();
this.onUpdate = () => null;
Expand Down Expand Up @@ -50,17 +62,12 @@ export class Toaster {

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) {
this._dismissToast(toastId);
} else {
this._dismissAllToasts();
}
};
const dismissToast: ToastListenerMap[typeof EVENTS.dismiss] = e => this._dismissToast(e.detail.toastId);
const dismissAllToasts: ToastListenerMap[typeof EVENTS.dismissAll] = e => this._dismissAllToasts();

this._addEventListener(EVENTS.show, buildToast);
this._addEventListener(EVENTS.dismiss, dismissToast);
this._addEventListener(EVENTS.dismissAll, dismissAllToasts);
this._addEventListener(EVENTS.update, updateToast);
}

Expand All @@ -73,9 +80,17 @@ export class Toaster {
return;
}

this.listeners.set(eventType, callback);
const listener: ToastListenerMap[TType] = (e: CustomEvent<CommonToastDetail>) => {
if (e.detail.toasterId !== this.toasterId) {
return;
}

callback(e as CustomEvent<ShowToastEventDetail>);
};

this.listeners.set(eventType, listener);
const targetDocument = this.toasterElement?.ownerDocument;
targetDocument.addEventListener(eventType, callback as () => void);
targetDocument.addEventListener(eventType, listener as () => void);
}

private _removeEventListener<TType extends keyof ToastListenerMap>(
Expand Down Expand Up @@ -113,7 +128,7 @@ export class Toaster {
}

private _buildToast(toastOptions: Partial<ToastOptions> & { toastId: ToastId }) {
const { toastId, content } = toastOptions;
const { toastId, content, toasterId } = toastOptions;

if (this.toasts.has(toastId)) {
return;
Expand All @@ -136,6 +151,7 @@ export class Toaster {
toastId,
content,
updateId: 0,
toasterId,
};

assignDefined<Toast>(toast, toastOptions);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import * as React from 'react';
import { Toaster, useToastController } from '@fluentui/react-toast';
import { useId } from '@fluentui/react-components';

export const CustomTimeout = () => {
const toasterId = useId('toaster');
const { dispatchToast } = useToastController();
const notify = () => dispatchToast('This is a toast', { timeout: 1000 });
const notify = () => dispatchToast('This is a toast', { timeout: 1000, toasterId });

return (
<>
<Toaster />
<Toaster toasterId={toasterId} />
<button onClick={notify}>Make toast</button>
</>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import * as React from 'react';
import { Toaster, useToastController } from '@fluentui/react-toast';
import { useId } from '@fluentui/react-components';

export const Default = () => {
const toasterId = useId('toaster');
const { dispatchToast } = useToastController();
const notify = () => dispatchToast('This is a toast');
const notify = () => dispatchToast('This is a toast', { toasterId });

return (
<>
<Toaster />
<Toaster toasterId={toasterId} />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is usage of toasterId mandatory? If no, why all examples were updated?

<button onClick={notify}>Make toast</button>
</>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import * as React from 'react';
import { Toaster, useToastController } from '@fluentui/react-toast';
import { useId } from '@fluentui/react-components';

export const DefaultToastOptions = () => {
const toasterId = useId('toaster');
const { dispatchToast } = useToastController();
const notify = () => dispatchToast('This is a toast');
const notify = () => dispatchToast('This is a toast', { toasterId });

return (
<>
<Toaster position="top-right" pauseOnHover pauseOnWindowBlur timeout={1000} />
<Toaster toasterId={toasterId} position="top-right" pauseOnHover pauseOnWindowBlur timeout={1000} />
<button onClick={notify}>Make toast</button>
</>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import * as React from 'react';
import { Toaster, useToastController } from '@fluentui/react-toast';
import { useId } from '@fluentui/react-components';

export const DismissAll = () => {
const { dispatchToast, dismissToast } = useToastController();
const notify = () => dispatchToast('This is a toast');
const toasterId = useId('toaster');
const { dispatchToast, dismissAllToasts } = useToastController();
const notify = () => dispatchToast('This is a toast', { toasterId });
const dismissAll = () => dismissAllToasts(toasterId);

return (
<>
<Toaster />
<button onClick={() => notify()}>Make toast</button>
<button onClick={() => dismissToast()}>Dismiss all</button>
<Toaster toasterId={toasterId} />
<button onClick={notify}>Make toast</button>
<button onClick={dismissAll}>Dismiss all</button>
</>
);
};
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import * as React from 'react';
import { Toaster, useToastController, ToastId } from '@fluentui/react-toast';
import { Toaster, useToastController } from '@fluentui/react-toast';
import { useId } from '@fluentui/react-components';

export const DismissToast = () => {
const toasterId = useId('toaster');
const toastId = useId('example');
const { dispatchToast, dismissToast } = useToastController();
const notify = (id: ToastId) => dispatchToast('This is a toast', { toastId: id });
const notify = () => dispatchToast('This is a toast', { toastId, toasterId });
const dismiss = () => dismissToast(toastId, toasterId);

return (
<>
<Toaster />
<button onClick={() => notify('example')}>Make toast</button>
<button onClick={() => dismissToast('example')}>Dismiss all</button>
<Toaster toasterId={toasterId} />
<button onClick={notify}>Make toast</button>
<button onClick={dismiss}>Dismiss toast</button>
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import * as React from 'react';
import { Toaster, useToastController } from '@fluentui/react-toast';
import { useId } from '@fluentui/react-components';

export const MultipeToasters = () => {
const first = useId('toaster-1');
const second = useId('toaster-2');
const { dispatchToast } = useToastController();
const notifyFirst = () => dispatchToast('Toaster first', { toasterId: first });
const notifySecond = () => dispatchToast('Toaster second', { toasterId: second });

return (
<>
<Toaster toasterId={first} position="bottom-right" />
<Toaster toasterId={second} position="top-right" />
<button onClick={notifyFirst}>Toaster first</button>
<button onClick={notifySecond}>Toaster second</button>
</>
);
};
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import * as React from 'react';
import { Toaster, useToastController } from '@fluentui/react-toast';
import { useId } from '@fluentui/react-components';

export const PauseOnHover = () => {
const toasterId = useId('toaster');
const { dispatchToast } = useToastController();
const notify = () => dispatchToast('Hover me!', { pauseOnHover: true });
const notify = () => dispatchToast('Hover me!', { pauseOnHover: true, toasterId });

return (
<>
<Toaster />
<Toaster toasterId={toasterId} />
<button onClick={notify}>Make toast</button>
</>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import * as React from 'react';
import { Toaster, useToastController } from '@fluentui/react-toast';
import { useId } from '@fluentui/react-components';

export const PauseOnWindowBlur = () => {
const toasterId = useId('toaster');
const { dispatchToast } = useToastController();
const notify = () => dispatchToast('Click on another window!', { pauseOnWindowBlur: true });
const notify = () => dispatchToast('Click on another window!', { pauseOnWindowBlur: true, toasterId });

return (
<>
<Toaster />
<Toaster toasterId={toasterId} />
<button onClick={notify}>Make toast</button>
</>
);
Expand Down
Loading