Replies: 2 comments
-
I'll post the whole source code here which is related to Toast but might be too long to read. Forgive me that I cannot publish all my code into public since it may have some sensitive information. Source code
export enum NotificationActionType {
Request = 'request',
Navigate = 'navigate',
RequestWait = 'requestWait',
}
export interface INotificationAction<T extends NotificationActionType> {
type: T | `${T}`;
id: string;
text: string;
notificationId: string;
}
export type NotificationRequestAction = INotificationAction<
NotificationActionType.Request | NotificationActionType.RequestWait
> & {
endpoint: string;
method: 'POST' | 'GET' | 'PATCH' | 'DELETE';
searchParameters?: string;
bodyData?: string;
};
export type NotificationNavigateAction =
INotificationAction<NotificationActionType.Navigate> & {
url: string;
baseUrl?: string;
};
export type NotificationAction<T extends NotificationActionType> =
T extends NotificationActionType.Request
? NotificationRequestAction
: T extends NotificationActionType.RequestWait
? NotificationRequestAction
: NotificationNavigateAction;
export function isNotificationAction(
actionLike: any
): actionLike is NotificationAction<NotificationActionType> {
if (typeof actionLike !== 'object') {
return false;
}
if (Array.isArray(actionLike)) {
return false;
}
if (typeof actionLike.id !== 'string' || actionLike.id === '') {
return false;
}
if (
![
NotificationActionType.Navigate,
NotificationActionType.Request,
NotificationActionType.RequestWait,
].includes(actionLike.type)
) {
return false;
}
return true;
}
export function isNotificationRequestAction(
action: NotificationAction<NotificationActionType>
): action is NotificationRequestAction {
if (
action.type !== NotificationActionType.Request &&
action.type !== NotificationActionType.RequestWait
) {
return false;
}
return true;
}
export function isNotificationNavigateAction(
action: NotificationAction<NotificationActionType>
): action is NotificationNavigateAction {
if (action.type !== NotificationActionType.Navigate) {
return false;
}
return true;
}
export default NotificationAction;
import { ToastIntent } from '@fluentui/react-components';
import NotificationAction, {
NotificationActionType,
} from './NotificationAction';
type Notification<T extends NotificationActionType> = {
id: string;
content?: string;
title: string;
intent: ToastIntent;
subtitle?: string;
priority?: number;
titleAction?: NotificationAction<T>;
actions?: NotificationAction<T>[];
};
export default Notification;
import Notification from '@/models/Notification';
import { NotificationActionType } from '@/models/NotificationAction';
import { PropsWithChildren } from 'react';
export type NotificationProviderContextValues = {
notifications: Notification<NotificationActionType>[];
};
export type NotificationProviderProps = PropsWithChildren<{}>;
'use client';
import { createContext, useContext } from 'react';
import { NotificationProviderContextValues } from './NotificationProvider.types';
export const notificationProviderContext =
createContext<NotificationProviderContextValues>({ notifications: [] });
export function useNotifications() {
const notifications = useContext(notificationProviderContext);
return {
notifications: notifications.notifications,
count: notifications.notifications.length,
};
}
'use client';
import React from 'react';
import { NotificationProviderProps } from './NotificationProvider.types';
import Notification from '@/models/Notification';
import { notificationProviderContext } from './NotificationProvider.contexts';
import {
Link,
Toast,
ToastBody,
ToastFooter,
ToastTitle,
Toaster,
useId,
useToastController,
} from '@fluentui/react-components';
import NotificationAction, {
NotificationActionType,
isNotificationNavigateAction,
isNotificationRequestAction,
} from '@/models/NotificationAction';
import axios from 'axios';
import EnhancedLink from '@/components/EnhancedLink';
import { useSiteConfiguration } from '@/components/SiteConfigurationProvider';
export default function NotificationProvider({
children,
}: NotificationProviderProps) {
const [notifications, setNotifications] = React.useState<
Notification<NotificationActionType>[]
>([]);
const [newNotifications, setNewNotifications] = React.useState<
Notification<NotificationActionType>[]
>([]);
const toasterId = useId('toaster-');
const { dispatchToast, dismissToast, pauseToast } =
useToastController(toasterId);
const { host } = useSiteConfiguration();
const getToastActions = React.useCallback(
(actions: NotificationAction<NotificationActionType>[]) => {
return actions.map(action => {
if (isNotificationRequestAction(action)) {
return (
<Link
key={`action-${action.id}`}
onClick={e => {
e.preventDefault();
const url = new URL(action.endpoint);
url.search = action.searchParameters ?? '';
axios
.request({
url: url.toString(),
data: action.bodyData,
method: action.method,
})
.finally(() =>
dismissToast(`toast-${action.notificationId}`)
);
}}
>
{action.text}
</Link>
);
} else if (isNotificationNavigateAction(action)) {
const url = new URL(action.url, action.baseUrl ?? host);
return (
<EnhancedLink
key={`action-${action.id}`}
href={url.toString()}
onClick={() => dismissToast(`toast-${action.notificationId}`)}
externalIcon={null}
>
{action.text}
</EnhancedLink>
);
}
});
},
[dismissToast, host]
);
const notify = React.useCallback(
(notification: Notification<NotificationActionType>) => {
dispatchToast(
<Toast>
<ToastTitle
action={
notification.titleAction
? getToastActions([notification.titleAction])
: undefined
}
>
{notification.title}
</ToastTitle>
{(notification.subtitle || notification.content) && (
<ToastBody subtitle={notification.subtitle}>
{/** TODO: use markdown here */}
{notification.content}
</ToastBody>
)}
{notification.actions && (
<ToastFooter>{getToastActions(notification.actions)}</ToastFooter>
)}
</Toast>,
{
intent: notification.intent,
toastId: `toast-${notification.id}`,
priority: notification.priority,
pauseOnHover: true,
onStatusChange: (_e, data) => {
const waitAction = notification.actions?.findIndex(
action => action.type === NotificationActionType.RequestWait
);
if (
data.status === 'visible' &&
waitAction !== undefined &&
waitAction > -1
) {
pauseToast(`toast-${notification.id}`);
}
if (data.status === 'dismissed') {
setNotifications(current => [...current, notification]);
setNewNotifications(current => {
const index = current.findIndex(
newNotification => newNotification.id === notification.id
);
if (index !== undefined && index > -1) {
current.splice(index, 1);
return [...current];
}
return current;
});
}
},
}
);
},
[dispatchToast, getToastActions, pauseToast]
);
React.useEffect(() => {
if (newNotifications.length > 0) {
newNotifications.forEach(notification => notify(notification));
}
}, [newNotifications, notify]);
/** TODO: use websocket to get notifications from server. */
React.useEffect(() => {
setNewNotifications([
{
id: '0',
intent: 'info',
priority: 0,
title: 'Test 0',
content: 'Test 0 content',
titleAction: {
id: '0-0',
notificationId: '0',
text: 'Dismiss',
type: 'navigate',
url: '',
},
},
{
id: '1',
intent: 'error',
priority: 1,
title: 'Test 1',
subtitle: 'Test 1 subtitle',
actions: [
{
id: '1-0',
notificationId: '1',
text: 'Dismiss',
type: 'navigate',
url: '',
},
],
},
]);
}, []);
return (
<notificationProviderContext.Provider value={{ notifications }}>
<Toaster toasterId={toasterId} />
{children}
</notificationProviderContext.Provider>
);
} |
Beta Was this translation helpful? Give feedback.
0 replies
-
FYI @ling1726 If a component with a default usage triggers warnings from React ➡️ please create an issue, it's a bug. |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
ToastContainer has a
timer
slot, which renders a Timer component with initial props (rather than default props):fluentui/packages/react-components/react-toast/src/components/ToastContainer/useToastContainer.ts
Lines 217 to 219 in 25e02bd
I have 2 questions about this piece of code:
I am not sure how it appears since I cannot reproduce it in minimal sample, and thus I decided to publish a discussion rather than issue.
Beta Was this translation helpful? Give feedback.
All reactions