Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
Expand Up @@ -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<ToastOptions> | undefined) => void;
dismissToast: (toastId?: string | undefined) => void;
updateToast: (options: UpdateToastEventDetail) => void;
};

// (No @packageDocumentation comment for this package)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ const useStyles = makeStyles({

export const Toast: React.FC<ToastProps & { visible: boolean }> = 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<HTMLDivElement>({ ...toastOptions, content: children });

Expand All @@ -99,7 +99,7 @@ export const Toast: React.FC<ToastProps & { visible: boolean }> = props => {
<Transition in={visible} unmountOnExit mountOnEnter timeout={500} onExited={remove} nodeRef={toastRef}>
<div ref={toastRef} className={mergeClasses(styles.toast, visible && styles.slide, !visible && styles.fadeOut)}>
{children}
<Timer onTimeout={close} timeout={timeout ?? -1} running={running} />
<Timer key={updateId} onTimeout={close} timeout={timeout ?? -1} running={running} />
</div>
</Transition>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export const EVENTS = {
show: 'fui-toast-show',
dismiss: 'fui-toast-dismiss',
update: 'fui-toast-update',
} as const;
6 changes: 6 additions & 0 deletions packages/react-components/react-toast/src/state/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,17 @@ export interface ToasterOptions
export interface Toast extends ToastOptions {
close: () => void;
remove: () => void;
updateId: number;
}

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

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

export interface DismissToastEventDetail {
toastId: ToastId | undefined;
}
Expand All @@ -37,4 +42,5 @@ type EventListener<TDetail> = (e: CustomEvent<TDetail>) => void;
export type ToastListenerMap = {
[EVENTS.show]: EventListener<ShowToastEventDetail>;
[EVENTS.dismiss]: EventListener<DismissToastEventDetail>;
[EVENTS.update]: EventListener<UpdateToastEventDetail>;
};
Original file line number Diff line number Diff line change
@@ -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<ToastOptions>) => {
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<ToastOptions>) => {
dispatchToastVanilla(content, options, targetDocument);
},
dismissToast: (toastId?: Partial<ToastId>) => {
dismissToastVanilla(toastId, targetDocument);
}
},
[targetDocument],
);

return {
dispatchToast,
dismissToast,
};
},
updateToast: (options: UpdateToastEventDetail) => {
updateToastVanilla(options, targetDocument);
},
};
}, [targetDocument]);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './dispatchToast';
export * from './dismissToast';
export * from './updateToast';
export * from './toast';
export * from './toaster';
export * from './getPositionStyles';
Original file line number Diff line number Diff line change
@@ -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<T extends object>(a: Partial<T>, b: Partial<T>) {
Expand Down Expand Up @@ -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) {
Expand All @@ -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) => {
Expand All @@ -74,6 +73,7 @@ export class Toaster {
return;
}

this.listeners.set(eventType, callback);
const targetDocument = this.toasterElement?.ownerDocument;
targetDocument.addEventListener(eventType, callback as () => void);
}
Expand All @@ -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();
Expand Down Expand Up @@ -123,6 +135,7 @@ export class Toaster {
remove,
toastId,
content,
updateId: 0,
};

assignDefined<Toast>(toast, toastOptions);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { UpdateToastEventDetail } from '../types';
import { EVENTS } from '../constants';

export function updateToast(options: UpdateToastEventDetail, targetDocument: Document) {
const event = new CustomEvent<UpdateToastEventDetail>(EVENTS.update, {
bubbles: false,
cancelable: false,
detail: options,
});

targetDocument.dispatchEvent(event);
}
Original file line number Diff line number Diff line change
@@ -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 (
<>
<Toaster />
<button onClick={() => notify('EXAMPLE_ID')}>Make toast</button>
<button onClick={() => update('EXAMPLE_ID')}>Update toast</button>
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down