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
-
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