refactor: optimize the use of notification center (#3621)

This commit is contained in:
JimmFly
2023-08-09 03:00:44 +08:00
committed by GitHub
parent 73272e266b
commit 5842619206
5 changed files with 72 additions and 46 deletions

View File

@@ -223,6 +223,21 @@ export const Basic = () => {
gif gif
</button> </button>
</div> </div>
<div>
<button
onClick={() => {
const key = id++;
push({
title: `${key} title`,
type: 'info',
theme: 'default',
timeout: 3000,
});
}}
>
default message
</button>
</div>
<NotificationCenter /> <NotificationCenter />
</> </>
); );

View File

@@ -177,21 +177,20 @@ export const notificationContentStyle = style({
width: '380px', width: '380px',
borderRadius: '8px', borderRadius: '8px',
boxShadow: 'var(--affine-shadow-1)', boxShadow: 'var(--affine-shadow-1)',
border: '1px solid var(--affine-border-color)', border: '1px solid var(--affine-black-10)',
background: 'var(--affine-white)', background: 'var(--affine-white)',
transition: 'all 0.3s', transition: 'all 0.3s',
}); });
export const notificationTitleContactStyle = style({ export const notificationTitleContactStyle = style({
marginRight: '22px', marginRight: '22px',
width: '200px', width: '200px',
whiteSpace: 'nowrap', overflow: 'wrap',
textOverflow: 'ellipsis', lineHeight: '24px',
overflow: 'hidden', fontSize: 'var(--affine-font-base)',
lineHeight: '1.5',
}); });
export const notificationTitleStyle = style({ export const notificationTitleStyle = style({
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'flex-start',
width: '100%', width: '100%',
justifyContent: 'flex-start', justifyContent: 'flex-start',
}); });
@@ -199,6 +198,7 @@ export const notificationDescriptionStyle = style({
fontSize: 'var(--affine-font-sm)', fontSize: 'var(--affine-font-sm)',
color: 'var(--affine-text-secondary-color)', color: 'var(--affine-text-secondary-color)',
marginBottom: '4px', marginBottom: '4px',
lineHeight: '22px',
}); });
export const notificationTimeStyle = style({ export const notificationTimeStyle = style({
fontSize: 'var(--affine-font-sm)', fontSize: 'var(--affine-font-sm)',
@@ -234,7 +234,7 @@ export const closeButtonWithMediaStyle = style({
}, },
}); });
export const closeButtonColorStyle = style({ export const closeButtonColorStyle = style({
color: 'var(--affine-white)', color: 'var(--affine-text-primary-color)',
}); });
export const undoButtonStyle = style({ export const undoButtonStyle = style({
fontSize: 'var(--affine-font-sm)', fontSize: 'var(--affine-font-sm)',
@@ -296,10 +296,10 @@ export const lightWarningStyle = style({
borderRadius: '8px', borderRadius: '8px',
}); });
export const darkColorStyle = style({ export const darkColorStyle = style({
color: 'var(--affine-white)', color: 'var(--affine-pure-white)',
}); });
export const lightInfoIconStyle = style({ export const lightInfoIconStyle = style({
color: 'var(--affine-processing-color)', color: 'var(--affine-icon-color)',
}); });
export const defaultCollapseStyle = styleVariants({ export const defaultCollapseStyle = styleVariants({
secondary: { secondary: {

View File

@@ -1,11 +1,12 @@
import { uuidv4 } from '@blocksuite/store';
import { atom } from 'jotai'; import { atom } from 'jotai';
export type Notification = { export type Notification = {
key: string; key?: string;
title: string; title: string;
message: string; message?: string;
type: 'success' | 'error' | 'warning' | 'info'; type: 'success' | 'error' | 'warning' | 'info';
theme?: 'light' | 'dark'; theme?: 'light' | 'dark' | 'default';
timeout?: number; timeout?: number;
progressingBar?: boolean; progressingBar?: boolean;
multimedia?: React.ReactNode | JSX.Element | HTMLElement; multimedia?: React.ReactNode | JSX.Element | HTMLElement;
@@ -41,6 +42,7 @@ export const removeNotificationAtom = atom(null, (_, set, key: string) => {
export const pushNotificationAtom = atom<null, [Notification], void>( export const pushNotificationAtom = atom<null, [Notification], void>(
null, null,
(_, set, newNotification) => { (_, set, newNotification) => {
newNotification.key = newNotification.key || uuidv4();
const key = newNotification.key; const key = newNotification.key;
const removeNotification = () => const removeNotification = () =>
set(notificationsBaseAtom, notifications => set(notificationsBaseAtom, notifications =>

View File

@@ -2,7 +2,7 @@
// License on the MIT // License on the MIT
// https://github.com/emilkowalski/sonner/blob/5cb703edc108a23fd74979235c2f3c4005edd2a7/src/index.tsx // https://github.com/emilkowalski/sonner/blob/5cb703edc108a23fd74979235c2f3c4005edd2a7/src/index.tsx
import { CloseIcon, InformationIcon } from '@blocksuite/icons'; import { CloseIcon, InformationFillDuotoneIcon } from '@blocksuite/icons';
import * as Toast from '@radix-ui/react-toast'; import * as Toast from '@radix-ui/react-toast';
import { IconButton } from '@toeverything/components/button'; import { IconButton } from '@toeverything/components/button';
import clsx from 'clsx'; import clsx from 'clsx';
@@ -33,7 +33,7 @@ export {
}; };
type Height = { type Height = {
height: number; height: number;
notificationKey: number | string; notificationKey: number | string | undefined;
}; };
export type NotificationCardProps = { export type NotificationCardProps = {
notification: Notification; notification: Notification;
@@ -46,24 +46,29 @@ const typeColorMap = {
info: { info: {
light: styles.lightInfoStyle, light: styles.lightInfoStyle,
dark: styles.darkInfoStyle, dark: styles.darkInfoStyle,
default: '',
}, },
success: { success: {
light: styles.lightSuccessStyle, light: styles.lightSuccessStyle,
dark: styles.darkSuccessStyle, dark: styles.darkSuccessStyle,
default: '',
}, },
warning: { warning: {
light: styles.lightWarningStyle, light: styles.lightWarningStyle,
dark: styles.darkWarningStyle, dark: styles.darkWarningStyle,
default: '',
}, },
error: { error: {
light: styles.lightErrorStyle, light: styles.lightErrorStyle,
dark: styles.darkErrorStyle, dark: styles.darkErrorStyle,
default: '',
}, },
}; };
function NotificationCard(props: NotificationCardProps): ReactElement { function NotificationCard(props: NotificationCardProps): ReactElement {
const removeNotification = useSetAtom(removeNotificationAtom); const removeNotification = useSetAtom(removeNotificationAtom);
const { notification, notifications, setHeights, heights, index } = props; const { notification, notifications, setHeights, heights, index } = props;
const [expand, setExpand] = useAtom(expandNotificationCenterAtom); const [expand, setExpand] = useAtom(expandNotificationCenterAtom);
// const setNotificationRemoveAnimation = useSetAtom(notificationRemoveAnimationAtom); // const setNotificationRemoveAnimation = useSetAtom(notificationRemoveAnimationAtom);
const [mounted, setMounted] = useState<boolean>(false); const [mounted, setMounted] = useState<boolean>(false);
@@ -89,6 +94,7 @@ function NotificationCard(props: NotificationCardProps): ReactElement {
const duration = notification.timeout || 3000; const duration = notification.timeout || 3000;
const offset = useRef(0); const offset = useRef(0);
const pointerStartYRef = useRef<number | null>(null); const pointerStartYRef = useRef<number | null>(null);
const notificationsHeightBefore = useMemo(() => { const notificationsHeightBefore = useMemo(() => {
return heights.reduce((prev, curr, reducerIndex) => { return heights.reduce((prev, curr, reducerIndex) => {
// Calculate offset up until current notification // Calculate offset up until current notification
@@ -149,7 +155,7 @@ function NotificationCard(props: NotificationCardProps): ReactElement {
}, [notification.title, notification.key, mounted, setHeights]); }, [notification.title, notification.key, mounted, setHeights]);
const typeStyle = const typeStyle =
typeColorMap[notification.type][notification.theme || 'light']; typeColorMap[notification.type][notification.theme || 'dark'];
const onClickRemove = useCallback(() => { const onClickRemove = useCallback(() => {
// Save the offset for the exit swipe animation // Save the offset for the exit swipe animation
@@ -159,6 +165,9 @@ function NotificationCard(props: NotificationCardProps): ReactElement {
h.filter(height => height.notificationKey !== notification.key) h.filter(height => height.notificationKey !== notification.key)
); );
setTimeout(() => { setTimeout(() => {
if (!notification.key) {
return;
}
removeNotification(notification.key); removeNotification(notification.key);
}, 200); }, 200);
}, [setHeights, notification.key, removeNotification, offset]); }, [setHeights, notification.key, removeNotification, offset]);
@@ -291,7 +300,7 @@ function NotificationCard(props: NotificationCardProps): ReactElement {
> >
<div <div
className={clsx({ className={clsx({
[typeStyle]: notification.theme, [typeStyle]: notification.theme !== 'default',
[styles.hasMediaStyle]: notification.multimedia, [styles.hasMediaStyle]: notification.multimedia,
[styles.notificationContentStyle]: !notification.multimedia, [styles.notificationContentStyle]: !notification.multimedia,
})} })}
@@ -306,16 +315,20 @@ function NotificationCard(props: NotificationCardProps): ReactElement {
) : null} ) : null}
<Toast.Title <Toast.Title
className={clsx(styles.notificationTitleStyle, { className={clsx(styles.notificationTitleStyle, {
[styles.darkColorStyle]: notification.theme === 'dark', [styles.darkColorStyle]:
notification.theme !== 'light' &&
notification.theme !== 'default',
})} })}
> >
<div <div
className={clsx(styles.notificationIconStyle, { className={clsx(styles.notificationIconStyle, {
[styles.darkColorStyle]: notification.theme === 'dark', [styles.darkColorStyle]:
[styles.lightInfoIconStyle]: notification.theme !== 'dark', notification.theme !== 'light' &&
notification.theme !== 'default',
[styles.lightInfoIconStyle]: notification.theme === 'light',
})} })}
> >
<InformationIcon /> <InformationFillDuotoneIcon />
</div> </div>
<div className={styles.notificationTitleContactStyle}> <div className={styles.notificationTitleContactStyle}>
{notification.title} {notification.title}
@@ -323,7 +336,9 @@ function NotificationCard(props: NotificationCardProps): ReactElement {
{notification.undo && ( {notification.undo && (
<div <div
className={clsx(styles.undoButtonStyle, { className={clsx(styles.undoButtonStyle, {
[styles.darkColorStyle]: notification.theme === 'dark', [styles.darkColorStyle]:
notification.theme !== 'light' &&
notification.theme !== 'default',
[styles.undoButtonWithMediaStyle]: notification.multimedia, [styles.undoButtonWithMediaStyle]: notification.multimedia,
})} })}
onClick={onClickUndo} onClick={onClickUndo}
@@ -338,9 +353,10 @@ function NotificationCard(props: NotificationCardProps): ReactElement {
})} })}
style={{ style={{
color: color:
notification.theme === 'dark' notification.theme !== 'light' &&
? 'var(--affine-white)' notification.theme !== 'default'
: 'var(--affine-icon-color)', ? 'var(--affine-pure-white)'
: 'var(--affine-text-primary-color)',
}} }}
> >
<CloseIcon onClick={onClickRemove} /> <CloseIcon onClick={onClickRemove} />
@@ -349,7 +365,9 @@ function NotificationCard(props: NotificationCardProps): ReactElement {
</Toast.Title> </Toast.Title>
<Toast.Description <Toast.Description
className={clsx(styles.messageStyle, { className={clsx(styles.messageStyle, {
[styles.darkColorStyle]: notification.theme === 'dark', [styles.darkColorStyle]:
notification.theme !== 'light' &&
notification.theme !== 'default',
})} })}
> >
{notification.message} {notification.message}
@@ -410,16 +428,18 @@ export function NotificationCenter(): ReactElement {
if (!notifications.length) return <></>; if (!notifications.length) return <></>;
return ( return (
<Toast.Provider swipeDirection="right"> <Toast.Provider swipeDirection="right">
{notifications.map((notification, index) => ( {notifications.map((notification, index) =>
<NotificationCard notification.key ? (
notification={notification} <NotificationCard
index={index} notification={notification}
key={notification.key} index={index}
notifications={notifications} key={notification.key}
heights={heights} notifications={notifications}
setHeights={setHeights} heights={heights}
/> setHeights={setHeights}
))} />
) : null
)}
<Toast.Viewport <Toast.Viewport
tabIndex={-1} tabIndex={-1}
ref={listRef} ref={listRef}

View File

@@ -9,7 +9,6 @@ import {
ExportToPdfIcon, ExportToPdfIcon,
ExportToPngIcon, ExportToPngIcon,
} from '@blocksuite/icons'; } from '@blocksuite/icons';
import { uuidv4 } from '@blocksuite/store';
import { useSetAtom } from 'jotai'; import { useSetAtom } from 'jotai';
import { useCallback } from 'react'; import { useCallback } from 'react';
@@ -41,7 +40,6 @@ export const ExportToPdfMenuItem = ({
.then(() => { .then(() => {
onSelect?.({ type: 'pdf' }); onSelect?.({ type: 'pdf' });
setPushNotification({ setPushNotification({
key: uuidv4(),
title: t['com.affine.export.success.title'](), title: t['com.affine.export.success.title'](),
message: t['com.affine.export.success.message'](), message: t['com.affine.export.success.message'](),
type: 'success', type: 'success',
@@ -50,7 +48,6 @@ export const ExportToPdfMenuItem = ({
.catch(err => { .catch(err => {
console.error(err); console.error(err);
setPushNotification({ setPushNotification({
key: uuidv4(),
title: t['com.affine.export.error.title'](), title: t['com.affine.export.error.title'](),
message: t['com.affine.export.error.message'](), message: t['com.affine.export.error.message'](),
type: 'error', type: 'error',
@@ -64,7 +61,6 @@ export const ExportToPdfMenuItem = ({
.then(() => { .then(() => {
onSelect?.({ type: 'pdf' }); onSelect?.({ type: 'pdf' });
setPushNotification({ setPushNotification({
key: uuidv4(),
title: t['com.affine.export.success.title'](), title: t['com.affine.export.success.title'](),
message: t['com.affine.export.success.message'](), message: t['com.affine.export.success.message'](),
type: 'success', type: 'success',
@@ -73,7 +69,6 @@ export const ExportToPdfMenuItem = ({
.catch(err => { .catch(err => {
console.error(err); console.error(err);
setPushNotification({ setPushNotification({
key: uuidv4(),
title: t['com.affine.export.error.title'](), title: t['com.affine.export.error.title'](),
message: t['com.affine.export.error.message'](), message: t['com.affine.export.error.message'](),
type: 'error', type: 'error',
@@ -110,7 +105,6 @@ export const ExportToHtmlMenuItem = ({
.then(() => { .then(() => {
onSelect?.({ type: 'html' }); onSelect?.({ type: 'html' });
setPushNotification({ setPushNotification({
key: uuidv4(),
title: t['com.affine.export.success.title'](), title: t['com.affine.export.success.title'](),
message: t['com.affine.export.success.message'](), message: t['com.affine.export.success.message'](),
type: 'success', type: 'success',
@@ -119,7 +113,6 @@ export const ExportToHtmlMenuItem = ({
.catch(err => { .catch(err => {
console.error(err); console.error(err);
setPushNotification({ setPushNotification({
key: uuidv4(),
title: t['com.affine.export.error.title'](), title: t['com.affine.export.error.title'](),
message: t['com.affine.export.error.message'](), message: t['com.affine.export.error.message'](),
type: 'error', type: 'error',
@@ -159,7 +152,6 @@ export const ExportToPngMenuItem = ({
.then(() => { .then(() => {
onSelect?.({ type: 'png' }); onSelect?.({ type: 'png' });
setPushNotification({ setPushNotification({
key: uuidv4(),
title: t['com.affine.export.success.title'](), title: t['com.affine.export.success.title'](),
message: t['com.affine.export.success.message'](), message: t['com.affine.export.success.message'](),
type: 'success', type: 'success',
@@ -168,7 +160,6 @@ export const ExportToPngMenuItem = ({
.catch(err => { .catch(err => {
console.error(err); console.error(err);
setPushNotification({ setPushNotification({
key: uuidv4(),
title: t['com.affine.export.error.title'](), title: t['com.affine.export.error.title'](),
message: t['com.affine.export.error.message'](), message: t['com.affine.export.error.message'](),
type: 'error', type: 'error',
@@ -206,7 +197,6 @@ export const ExportToMarkdownMenuItem = ({
.then(() => { .then(() => {
onSelect?.({ type: 'markdown' }); onSelect?.({ type: 'markdown' });
setPushNotification({ setPushNotification({
key: uuidv4(),
title: t['com.affine.export.success.title'](), title: t['com.affine.export.success.title'](),
message: t['com.affine.export.success.message'](), message: t['com.affine.export.success.message'](),
type: 'success', type: 'success',
@@ -215,7 +205,6 @@ export const ExportToMarkdownMenuItem = ({
.catch(err => { .catch(err => {
console.error(err); console.error(err);
setPushNotification({ setPushNotification({
key: uuidv4(),
title: t['com.affine.export.error.title'](), title: t['com.affine.export.error.title'](),
message: t['com.affine.export.error.message'](), message: t['com.affine.export.error.message'](),
type: 'error', type: 'error',