From 73c7815a6d6024ce421db33e4e504fe69a9a1c9e Mon Sep 17 00:00:00 2001 From: EYHN Date: Mon, 31 Mar 2025 08:38:28 +0000 Subject: [PATCH] feat(core): adjust notification style (#11296) --- .../components/notification/list.style.css.ts | 92 +++++++++++++----- .../core/src/components/notification/list.tsx | 93 ++++++++++++++++--- .../general-setting/notifications/index.tsx | 1 + packages/frontend/i18n/src/i18n.gen.ts | 8 ++ packages/frontend/i18n/src/resources/en.json | 2 + 5 files changed, 156 insertions(+), 40 deletions(-) diff --git a/packages/frontend/core/src/components/notification/list.style.css.ts b/packages/frontend/core/src/components/notification/list.style.css.ts index 49732ffe2..ee442003b 100644 --- a/packages/frontend/core/src/components/notification/list.style.css.ts +++ b/packages/frontend/core/src/components/notification/list.style.css.ts @@ -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'), }); diff --git a/packages/frontend/core/src/components/notification/list.tsx b/packages/frontend/core/src/components/notification/list.tsx index bfbb38f2d..7def1fa4c 100644 --- a/packages/frontend/core/src/components/notification/list.tsx +++ b/packages/frontend/core/src/components/notification/list.tsx @@ -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 ? (
{userFriendlyError.message}
) : ( -
- {t['com.affine.notification.empty']()} -
+ )} @@ -94,10 +99,31 @@ export const NotificationList = () => { ); }; +const NotificationListEmpty = () => { + const t = useI18n(); + return ( +
+
+ +
+
+ {t['com.affine.notification.empty']()} +
+
+ {t['com.affine.notification.empty.description']()} +
+
+ ); +}; + const NotificationItemSkeleton = () => { return Array.from({ length: 3 }).map((_, i) => ( - // oxlint-disable-next-line no-array-index-key -
+
@@ -183,7 +209,7 @@ const MentionNotificationItem = ({ data-inactived={memberInactived} /> ), - 2: , + 2: , }} values={{ username: @@ -419,12 +445,7 @@ const InvitationAcceptedNotificationItem = ({ - ), + 1: , }} values={{ username: @@ -657,8 +678,50 @@ const WorkspaceNameWithIcon = ({ }: React.PropsWithChildren>) => { return ( - + {children} ); }; + +const DocNameWithIcon = ({ + children, + mode, + ...props +}: React.PropsWithChildren< + React.HTMLAttributes & { 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 ( + + {emoji ? ( + {emoji} + ) : mode === 'page' ? ( + + ) : ( + + )} + {titleWithoutEmoji} + + ); +}; diff --git a/packages/frontend/core/src/desktop/dialogs/setting/general-setting/notifications/index.tsx b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/notifications/index.tsx index a88fb9d90..a37bb016d 100644 --- a/packages/frontend/core/src/desktop/dialogs/setting/general-setting/notifications/index.tsx +++ b/packages/frontend/core/src/desktop/dialogs/setting/general-setting/notifications/index.tsx @@ -60,6 +60,7 @@ export const NotificationSettings = () => { <> {{username}} mentioned you in <2>{{docTitle}}", "com.affine.notification.empty": "No new notifications", + "com.affine.notification.empty.description": "You'll be notified here for @mentions and workspace invites.", "com.affine.notification.invitation-accepted": "<1>{{username}} has accept your invitation", "com.affine.notification.invitation-review-request": "<1>{{username}} has requested to join <2>{{workspaceName}}", "com.affine.notification.invitation-review-declined": "<1>{{username}} has declined your request to join <2>{{workspaceName}}",