feat(core): adjust notification style (#11296)

This commit is contained in:
EYHN
2025-03-31 08:38:28 +00:00
parent 6850871bfb
commit 73c7815a6d
5 changed files with 156 additions and 40 deletions

View File

@@ -1,6 +1,6 @@
import { cssVar } from '@toeverything/theme';
import { cssVarV2 } from '@toeverything/theme/v2';
import { style } from '@vanilla-extract/css';
import { keyframes, style } from '@vanilla-extract/css';
export const containerScrollViewport = style({
maxHeight: '272px',
@@ -14,10 +14,39 @@ export const itemList = style({
});
export const listEmpty = style({
color: cssVarV2('text/placeholder'),
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
gap: '2px',
height: '184px',
padding: '16px 45px',
});
export const listEmptyIconContainer = style({
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: '40px',
height: '40px',
marginBottom: '14px',
borderRadius: '50%',
backgroundColor: cssVarV2('layer/background/secondary'),
color: cssVarV2('icon/primary'),
});
export const listEmptyTitle = style({
color: cssVarV2('text/primary'),
fontSize: '14px',
lineHeight: '22px',
padding: '4px 2px',
textAlign: 'center',
});
export const listEmptyDescription = style({
color: cssVarV2('text/secondary'),
fontSize: '14px',
lineHeight: '20px',
textAlign: 'center',
});
export const error = style({
@@ -36,12 +65,37 @@ export const itemContainer = style({
gap: '8px',
cursor: 'pointer',
selectors: {
[`&:hover:not([data-disabled="true"])`]: {
[`&:hover:not([data-disabled="true"],:has(button:hover))`]: {
backgroundColor: cssVarV2('layer/background/hoverOverlay'),
},
},
});
export const itemSkeletonContainer = style({
opacity: 0,
animation: `${keyframes({
'0%': { opacity: 0 },
'100%': { opacity: 1 },
})} 500ms ease forwards 1s`,
});
export const itemDeleteButton = style({
position: 'absolute',
right: '10px',
bottom: '8px',
width: '20px',
height: '20px',
backgroundColor: cssVarV2('button/iconButtonSolid'),
border: `0.5px solid ${cssVarV2('layer/insideBorder/border')}`,
boxShadow: cssVar('buttonShadow'),
opacity: 0,
selectors: {
[`${itemContainer}:hover &`]: {
opacity: 1,
},
},
});
export const itemMain = style({
display: 'flex',
flexDirection: 'column',
@@ -62,29 +116,10 @@ export const itemNotSupported = style({
lineHeight: '22px',
});
export const itemDeleteButton = style({
position: 'absolute',
right: '10px',
bottom: '8px',
width: '20px',
height: '20px',
backgroundColor: cssVarV2('button/iconButtonSolid'),
border: `0.5px solid ${cssVarV2('layer/insideBorder/border')}`,
boxShadow: cssVar('buttonShadow'),
opacity: 0,
selectors: {
[`${itemContainer}:hover &`]: {
opacity: 1,
},
},
});
export const itemNameLabel = style({
fontWeight: 'bold',
fontWeight: '500',
color: cssVarV2('text/primary'),
display: 'inline-flex',
alignItems: 'center',
gap: '4px',
display: 'inline',
verticalAlign: 'top',
selectors: {
[`&[data-inactived="true"]`]: {
@@ -95,4 +130,11 @@ export const itemNameLabel = style({
export const itemActionButton = style({
width: 'fit-content',
borderRadius: '4px',
});
export const itemNameLabelIcon = style({
verticalAlign: 'top',
marginRight: '4px',
color: cssVarV2('icon/primary'),
});

View File

@@ -13,6 +13,7 @@ import {
NotificationType,
} from '@affine/core/modules/notification';
import { WorkspacesService } from '@affine/core/modules/workspace';
import { extractEmojiIcon } from '@affine/core/utils';
import { UserFriendlyError } from '@affine/error';
import type {
InvitationAcceptedNotificationBodyType,
@@ -24,15 +25,21 @@ import type {
MentionNotificationBodyType,
} from '@affine/graphql';
import { i18nTime, Trans, useI18n } from '@affine/i18n';
import { CollaborationIcon, DeleteIcon } from '@blocksuite/icons/rc';
import {
CollaborationIcon,
DeleteIcon,
EdgelessIcon,
NotificationIcon,
PageIcon,
} from '@blocksuite/icons/rc';
import { useLiveData, useService } from '@toeverything/infra';
import clsx from 'clsx';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useNavigateHelper } from '../hooks/use-navigate-helper';
import * as styles from './list.style.css';
export const NotificationList = () => {
const t = useI18n();
const notificationListService = useService(NotificationListService);
const notifications = useLiveData(notificationListService.notifications$);
const isLoading = useLiveData(notificationListService.isLoading$);
@@ -84,9 +91,7 @@ export const NotificationList = () => {
) : userFriendlyError ? (
<div className={styles.error}>{userFriendlyError.message}</div>
) : (
<div className={styles.listEmpty}>
{t['com.affine.notification.empty']()}
</div>
<NotificationListEmpty />
)}
</Scrollable.Viewport>
<Scrollable.Scrollbar />
@@ -94,10 +99,31 @@ export const NotificationList = () => {
);
};
const NotificationListEmpty = () => {
const t = useI18n();
return (
<div className={styles.listEmpty}>
<div className={styles.listEmptyIconContainer}>
<NotificationIcon width={24} height={24} />
</div>
<div className={styles.listEmptyTitle}>
{t['com.affine.notification.empty']()}
</div>
<div className={styles.listEmptyDescription}>
{t['com.affine.notification.empty.description']()}
</div>
</div>
);
};
const NotificationItemSkeleton = () => {
return Array.from({ length: 3 }).map((_, i) => (
// oxlint-disable-next-line no-array-index-key
<div key={i} className={styles.itemContainer} data-disabled="true">
<div
// oxlint-disable-next-line no-array-index-key
key={i}
className={clsx(styles.itemContainer, styles.itemSkeletonContainer)}
data-disabled="true"
>
<Skeleton variant="circular" width={22} height={22} />
<div className={styles.itemMain}>
<Skeleton variant="text" width={150} />
@@ -183,7 +209,7 @@ const MentionNotificationItem = ({
data-inactived={memberInactived}
/>
),
2: <b className={styles.itemNameLabel} />,
2: <DocNameWithIcon mode={body.doc.mode} />,
}}
values={{
username:
@@ -419,12 +445,7 @@ const InvitationAcceptedNotificationItem = ({
<Trans
i18nKey={'com.affine.notification.invitation-accepted'}
components={{
1: (
<b
className={styles.itemNameLabel}
data-inactived={memberInactived}
/>
),
1: <WorkspaceNameWithIcon data-inactived={memberInactived} />,
}}
values={{
username:
@@ -657,8 +678,50 @@ const WorkspaceNameWithIcon = ({
}: React.PropsWithChildren<React.HTMLAttributes<HTMLSpanElement>>) => {
return (
<b className={styles.itemNameLabel} {...props}>
<CollaborationIcon width={20} height={20} />
<CollaborationIcon
className={styles.itemNameLabelIcon}
width={20}
height={20}
/>
{children}
</b>
);
};
const DocNameWithIcon = ({
children,
mode,
...props
}: React.PropsWithChildren<
React.HTMLAttributes<HTMLSpanElement> & { mode: 'page' | 'edgeless' }
>) => {
const { emoji, rest: titleWithoutEmoji } = useMemo(() => {
if (typeof children === 'string') {
return extractEmojiIcon(children);
}
if (
children instanceof Array &&
children.length === 1 &&
typeof children[0] === 'string'
) {
return extractEmojiIcon(children[0]);
}
return { rest: children, emoji: null };
}, [children]);
return (
<b className={styles.itemNameLabel} {...props}>
{emoji ? (
<span className={styles.itemNameLabelIcon}>{emoji}</span>
) : mode === 'page' ? (
<PageIcon className={styles.itemNameLabelIcon} width={20} height={20} />
) : (
<EdgelessIcon
className={styles.itemNameLabelIcon}
width={20}
height={20}
/>
)}
{titleWithoutEmoji}
</b>
);
};

View File

@@ -60,6 +60,7 @@ export const NotificationSettings = () => {
<>
<SettingHeader
title={t['com.affine.setting.notifications.header.title']()}
subtitle={t['com.affine.setting.notifications.header.description']()}
/>
<SettingWrapper
title={t['com.affine.setting.notifications.email.title']()}