mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-13 21:05:19 +00:00
refactor: optimize the use of notification center (#3621)
This commit is contained in:
@@ -177,21 +177,20 @@ export const notificationContentStyle = style({
|
||||
width: '380px',
|
||||
borderRadius: '8px',
|
||||
boxShadow: 'var(--affine-shadow-1)',
|
||||
border: '1px solid var(--affine-border-color)',
|
||||
border: '1px solid var(--affine-black-10)',
|
||||
background: 'var(--affine-white)',
|
||||
transition: 'all 0.3s',
|
||||
});
|
||||
export const notificationTitleContactStyle = style({
|
||||
marginRight: '22px',
|
||||
width: '200px',
|
||||
whiteSpace: 'nowrap',
|
||||
textOverflow: 'ellipsis',
|
||||
overflow: 'hidden',
|
||||
lineHeight: '1.5',
|
||||
overflow: 'wrap',
|
||||
lineHeight: '24px',
|
||||
fontSize: 'var(--affine-font-base)',
|
||||
});
|
||||
export const notificationTitleStyle = style({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
alignItems: 'flex-start',
|
||||
width: '100%',
|
||||
justifyContent: 'flex-start',
|
||||
});
|
||||
@@ -199,6 +198,7 @@ export const notificationDescriptionStyle = style({
|
||||
fontSize: 'var(--affine-font-sm)',
|
||||
color: 'var(--affine-text-secondary-color)',
|
||||
marginBottom: '4px',
|
||||
lineHeight: '22px',
|
||||
});
|
||||
export const notificationTimeStyle = style({
|
||||
fontSize: 'var(--affine-font-sm)',
|
||||
@@ -234,7 +234,7 @@ export const closeButtonWithMediaStyle = style({
|
||||
},
|
||||
});
|
||||
export const closeButtonColorStyle = style({
|
||||
color: 'var(--affine-white)',
|
||||
color: 'var(--affine-text-primary-color)',
|
||||
});
|
||||
export const undoButtonStyle = style({
|
||||
fontSize: 'var(--affine-font-sm)',
|
||||
@@ -296,10 +296,10 @@ export const lightWarningStyle = style({
|
||||
borderRadius: '8px',
|
||||
});
|
||||
export const darkColorStyle = style({
|
||||
color: 'var(--affine-white)',
|
||||
color: 'var(--affine-pure-white)',
|
||||
});
|
||||
export const lightInfoIconStyle = style({
|
||||
color: 'var(--affine-processing-color)',
|
||||
color: 'var(--affine-icon-color)',
|
||||
});
|
||||
export const defaultCollapseStyle = styleVariants({
|
||||
secondary: {
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { uuidv4 } from '@blocksuite/store';
|
||||
import { atom } from 'jotai';
|
||||
|
||||
export type Notification = {
|
||||
key: string;
|
||||
key?: string;
|
||||
title: string;
|
||||
message: string;
|
||||
message?: string;
|
||||
type: 'success' | 'error' | 'warning' | 'info';
|
||||
theme?: 'light' | 'dark';
|
||||
theme?: 'light' | 'dark' | 'default';
|
||||
timeout?: number;
|
||||
progressingBar?: boolean;
|
||||
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>(
|
||||
null,
|
||||
(_, set, newNotification) => {
|
||||
newNotification.key = newNotification.key || uuidv4();
|
||||
const key = newNotification.key;
|
||||
const removeNotification = () =>
|
||||
set(notificationsBaseAtom, notifications =>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// License on the MIT
|
||||
// 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 { IconButton } from '@toeverything/components/button';
|
||||
import clsx from 'clsx';
|
||||
@@ -33,7 +33,7 @@ export {
|
||||
};
|
||||
type Height = {
|
||||
height: number;
|
||||
notificationKey: number | string;
|
||||
notificationKey: number | string | undefined;
|
||||
};
|
||||
export type NotificationCardProps = {
|
||||
notification: Notification;
|
||||
@@ -46,24 +46,29 @@ const typeColorMap = {
|
||||
info: {
|
||||
light: styles.lightInfoStyle,
|
||||
dark: styles.darkInfoStyle,
|
||||
default: '',
|
||||
},
|
||||
success: {
|
||||
light: styles.lightSuccessStyle,
|
||||
dark: styles.darkSuccessStyle,
|
||||
default: '',
|
||||
},
|
||||
warning: {
|
||||
light: styles.lightWarningStyle,
|
||||
dark: styles.darkWarningStyle,
|
||||
default: '',
|
||||
},
|
||||
error: {
|
||||
light: styles.lightErrorStyle,
|
||||
dark: styles.darkErrorStyle,
|
||||
default: '',
|
||||
},
|
||||
};
|
||||
|
||||
function NotificationCard(props: NotificationCardProps): ReactElement {
|
||||
const removeNotification = useSetAtom(removeNotificationAtom);
|
||||
const { notification, notifications, setHeights, heights, index } = props;
|
||||
|
||||
const [expand, setExpand] = useAtom(expandNotificationCenterAtom);
|
||||
// const setNotificationRemoveAnimation = useSetAtom(notificationRemoveAnimationAtom);
|
||||
const [mounted, setMounted] = useState<boolean>(false);
|
||||
@@ -89,6 +94,7 @@ function NotificationCard(props: NotificationCardProps): ReactElement {
|
||||
const duration = notification.timeout || 3000;
|
||||
const offset = useRef(0);
|
||||
const pointerStartYRef = useRef<number | null>(null);
|
||||
|
||||
const notificationsHeightBefore = useMemo(() => {
|
||||
return heights.reduce((prev, curr, reducerIndex) => {
|
||||
// Calculate offset up until current notification
|
||||
@@ -149,7 +155,7 @@ function NotificationCard(props: NotificationCardProps): ReactElement {
|
||||
}, [notification.title, notification.key, mounted, setHeights]);
|
||||
|
||||
const typeStyle =
|
||||
typeColorMap[notification.type][notification.theme || 'light'];
|
||||
typeColorMap[notification.type][notification.theme || 'dark'];
|
||||
|
||||
const onClickRemove = useCallback(() => {
|
||||
// Save the offset for the exit swipe animation
|
||||
@@ -159,6 +165,9 @@ function NotificationCard(props: NotificationCardProps): ReactElement {
|
||||
h.filter(height => height.notificationKey !== notification.key)
|
||||
);
|
||||
setTimeout(() => {
|
||||
if (!notification.key) {
|
||||
return;
|
||||
}
|
||||
removeNotification(notification.key);
|
||||
}, 200);
|
||||
}, [setHeights, notification.key, removeNotification, offset]);
|
||||
@@ -291,7 +300,7 @@ function NotificationCard(props: NotificationCardProps): ReactElement {
|
||||
>
|
||||
<div
|
||||
className={clsx({
|
||||
[typeStyle]: notification.theme,
|
||||
[typeStyle]: notification.theme !== 'default',
|
||||
[styles.hasMediaStyle]: notification.multimedia,
|
||||
[styles.notificationContentStyle]: !notification.multimedia,
|
||||
})}
|
||||
@@ -306,16 +315,20 @@ function NotificationCard(props: NotificationCardProps): ReactElement {
|
||||
) : null}
|
||||
<Toast.Title
|
||||
className={clsx(styles.notificationTitleStyle, {
|
||||
[styles.darkColorStyle]: notification.theme === 'dark',
|
||||
[styles.darkColorStyle]:
|
||||
notification.theme !== 'light' &&
|
||||
notification.theme !== 'default',
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={clsx(styles.notificationIconStyle, {
|
||||
[styles.darkColorStyle]: notification.theme === 'dark',
|
||||
[styles.lightInfoIconStyle]: notification.theme !== 'dark',
|
||||
[styles.darkColorStyle]:
|
||||
notification.theme !== 'light' &&
|
||||
notification.theme !== 'default',
|
||||
[styles.lightInfoIconStyle]: notification.theme === 'light',
|
||||
})}
|
||||
>
|
||||
<InformationIcon />
|
||||
<InformationFillDuotoneIcon />
|
||||
</div>
|
||||
<div className={styles.notificationTitleContactStyle}>
|
||||
{notification.title}
|
||||
@@ -323,7 +336,9 @@ function NotificationCard(props: NotificationCardProps): ReactElement {
|
||||
{notification.undo && (
|
||||
<div
|
||||
className={clsx(styles.undoButtonStyle, {
|
||||
[styles.darkColorStyle]: notification.theme === 'dark',
|
||||
[styles.darkColorStyle]:
|
||||
notification.theme !== 'light' &&
|
||||
notification.theme !== 'default',
|
||||
[styles.undoButtonWithMediaStyle]: notification.multimedia,
|
||||
})}
|
||||
onClick={onClickUndo}
|
||||
@@ -338,9 +353,10 @@ function NotificationCard(props: NotificationCardProps): ReactElement {
|
||||
})}
|
||||
style={{
|
||||
color:
|
||||
notification.theme === 'dark'
|
||||
? 'var(--affine-white)'
|
||||
: 'var(--affine-icon-color)',
|
||||
notification.theme !== 'light' &&
|
||||
notification.theme !== 'default'
|
||||
? 'var(--affine-pure-white)'
|
||||
: 'var(--affine-text-primary-color)',
|
||||
}}
|
||||
>
|
||||
<CloseIcon onClick={onClickRemove} />
|
||||
@@ -349,7 +365,9 @@ function NotificationCard(props: NotificationCardProps): ReactElement {
|
||||
</Toast.Title>
|
||||
<Toast.Description
|
||||
className={clsx(styles.messageStyle, {
|
||||
[styles.darkColorStyle]: notification.theme === 'dark',
|
||||
[styles.darkColorStyle]:
|
||||
notification.theme !== 'light' &&
|
||||
notification.theme !== 'default',
|
||||
})}
|
||||
>
|
||||
{notification.message}
|
||||
@@ -410,16 +428,18 @@ export function NotificationCenter(): ReactElement {
|
||||
if (!notifications.length) return <></>;
|
||||
return (
|
||||
<Toast.Provider swipeDirection="right">
|
||||
{notifications.map((notification, index) => (
|
||||
<NotificationCard
|
||||
notification={notification}
|
||||
index={index}
|
||||
key={notification.key}
|
||||
notifications={notifications}
|
||||
heights={heights}
|
||||
setHeights={setHeights}
|
||||
/>
|
||||
))}
|
||||
{notifications.map((notification, index) =>
|
||||
notification.key ? (
|
||||
<NotificationCard
|
||||
notification={notification}
|
||||
index={index}
|
||||
key={notification.key}
|
||||
notifications={notifications}
|
||||
heights={heights}
|
||||
setHeights={setHeights}
|
||||
/>
|
||||
) : null
|
||||
)}
|
||||
<Toast.Viewport
|
||||
tabIndex={-1}
|
||||
ref={listRef}
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
ExportToPdfIcon,
|
||||
ExportToPngIcon,
|
||||
} from '@blocksuite/icons';
|
||||
import { uuidv4 } from '@blocksuite/store';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
@@ -41,7 +40,6 @@ export const ExportToPdfMenuItem = ({
|
||||
.then(() => {
|
||||
onSelect?.({ type: 'pdf' });
|
||||
setPushNotification({
|
||||
key: uuidv4(),
|
||||
title: t['com.affine.export.success.title'](),
|
||||
message: t['com.affine.export.success.message'](),
|
||||
type: 'success',
|
||||
@@ -50,7 +48,6 @@ export const ExportToPdfMenuItem = ({
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
setPushNotification({
|
||||
key: uuidv4(),
|
||||
title: t['com.affine.export.error.title'](),
|
||||
message: t['com.affine.export.error.message'](),
|
||||
type: 'error',
|
||||
@@ -64,7 +61,6 @@ export const ExportToPdfMenuItem = ({
|
||||
.then(() => {
|
||||
onSelect?.({ type: 'pdf' });
|
||||
setPushNotification({
|
||||
key: uuidv4(),
|
||||
title: t['com.affine.export.success.title'](),
|
||||
message: t['com.affine.export.success.message'](),
|
||||
type: 'success',
|
||||
@@ -73,7 +69,6 @@ export const ExportToPdfMenuItem = ({
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
setPushNotification({
|
||||
key: uuidv4(),
|
||||
title: t['com.affine.export.error.title'](),
|
||||
message: t['com.affine.export.error.message'](),
|
||||
type: 'error',
|
||||
@@ -110,7 +105,6 @@ export const ExportToHtmlMenuItem = ({
|
||||
.then(() => {
|
||||
onSelect?.({ type: 'html' });
|
||||
setPushNotification({
|
||||
key: uuidv4(),
|
||||
title: t['com.affine.export.success.title'](),
|
||||
message: t['com.affine.export.success.message'](),
|
||||
type: 'success',
|
||||
@@ -119,7 +113,6 @@ export const ExportToHtmlMenuItem = ({
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
setPushNotification({
|
||||
key: uuidv4(),
|
||||
title: t['com.affine.export.error.title'](),
|
||||
message: t['com.affine.export.error.message'](),
|
||||
type: 'error',
|
||||
@@ -159,7 +152,6 @@ export const ExportToPngMenuItem = ({
|
||||
.then(() => {
|
||||
onSelect?.({ type: 'png' });
|
||||
setPushNotification({
|
||||
key: uuidv4(),
|
||||
title: t['com.affine.export.success.title'](),
|
||||
message: t['com.affine.export.success.message'](),
|
||||
type: 'success',
|
||||
@@ -168,7 +160,6 @@ export const ExportToPngMenuItem = ({
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
setPushNotification({
|
||||
key: uuidv4(),
|
||||
title: t['com.affine.export.error.title'](),
|
||||
message: t['com.affine.export.error.message'](),
|
||||
type: 'error',
|
||||
@@ -206,7 +197,6 @@ export const ExportToMarkdownMenuItem = ({
|
||||
.then(() => {
|
||||
onSelect?.({ type: 'markdown' });
|
||||
setPushNotification({
|
||||
key: uuidv4(),
|
||||
title: t['com.affine.export.success.title'](),
|
||||
message: t['com.affine.export.success.message'](),
|
||||
type: 'success',
|
||||
@@ -215,7 +205,6 @@ export const ExportToMarkdownMenuItem = ({
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
setPushNotification({
|
||||
key: uuidv4(),
|
||||
title: t['com.affine.export.error.title'](),
|
||||
message: t['com.affine.export.error.message'](),
|
||||
type: 'error',
|
||||
|
||||
Reference in New Issue
Block a user