From 1c59eda8b7c93506a430a788d43cb53b49960b51 Mon Sep 17 00:00:00 2001 From: CatsJuice Date: Fri, 11 Oct 2024 01:20:08 +0000 Subject: [PATCH] feat(component): basic notification adaptation for mobile (#8402) --- .../{ => desktop}/notification-card.tsx | 33 ++---- .../desktop/notification-center.tsx | 37 ++++++ .../notification/{ => desktop}/styles.css.ts | 0 .../component/src/ui/notification/index.ts | 2 +- .../notification/mobile/notification-card.tsx | 110 ++++++++++++++++++ .../mobile/notification-center.tsx | 15 +++ .../src/ui/notification/mobile/styles.css.ts | 108 +++++++++++++++++ .../notification-center.stories.tsx | 2 +- .../{notification-center.tsx => notify.tsx} | 50 +++----- .../component/src/ui/notification/types.ts | 4 + .../component/src/ui/notification/utils.ts | 17 +++ 11 files changed, 315 insertions(+), 63 deletions(-) rename packages/frontend/component/src/ui/notification/{ => desktop}/notification-card.tsx (66%) create mode 100644 packages/frontend/component/src/ui/notification/desktop/notification-center.tsx rename packages/frontend/component/src/ui/notification/{ => desktop}/styles.css.ts (100%) create mode 100644 packages/frontend/component/src/ui/notification/mobile/notification-card.tsx create mode 100644 packages/frontend/component/src/ui/notification/mobile/notification-center.tsx create mode 100644 packages/frontend/component/src/ui/notification/mobile/styles.css.ts rename packages/frontend/component/src/ui/notification/{notification-center.tsx => notify.tsx} (59%) diff --git a/packages/frontend/component/src/ui/notification/notification-card.tsx b/packages/frontend/component/src/ui/notification/desktop/notification-card.tsx similarity index 66% rename from packages/frontend/component/src/ui/notification/notification-card.tsx rename to packages/frontend/component/src/ui/notification/desktop/notification-card.tsx index 59d0971fc1..812aae7372 100644 --- a/packages/frontend/component/src/ui/notification/notification-card.tsx +++ b/packages/frontend/component/src/ui/notification/desktop/notification-card.tsx @@ -1,25 +1,15 @@ import { CloseIcon, InformationFillDuotoneIcon } from '@blocksuite/icons/rc'; -import { assignInlineVars } from '@vanilla-extract/dynamic'; import clsx from 'clsx'; -import { type HTMLAttributes, useCallback } from 'react'; +import { useCallback } from 'react'; -import { Button, IconButton } from '../button'; +import { Button, IconButton } from '../../button'; +import type { NotificationCardProps } from '../types'; +import { getCardVars } from '../utils'; import * as styles from './styles.css'; -import type { Notification } from './types'; -import { - getActionTextColor, - getCardBorderColor, - getCardColor, - getCardForegroundColor, - getCloseIconColor, - getIconColor, -} from './utils'; -export interface NotificationCardProps extends HTMLAttributes { - notification: Notification; -} - -export const NotificationCard = ({ notification }: NotificationCardProps) => { +export const DesktopNotificationCard = ({ + notification, +}: NotificationCardProps) => { const { theme = 'info', style = 'normal', @@ -43,14 +33,7 @@ export const NotificationCard = ({ notification }: NotificationCardProps) => { return (
{ + return { + ...assignInlineVars({ + // override css vars inside sonner + '--width': `${width}px`, + }), + // radix-ui will lock pointer-events when dialog is open + pointerEvents: 'auto', + } satisfies CSSProperties; + }, [width]); + + const toastOptions = useMemo( + () => ({ + style: { + width: '100%', + }, + }), + [] + ); + + return ( + + ); +} diff --git a/packages/frontend/component/src/ui/notification/styles.css.ts b/packages/frontend/component/src/ui/notification/desktop/styles.css.ts similarity index 100% rename from packages/frontend/component/src/ui/notification/styles.css.ts rename to packages/frontend/component/src/ui/notification/desktop/styles.css.ts diff --git a/packages/frontend/component/src/ui/notification/index.ts b/packages/frontend/component/src/ui/notification/index.ts index 25b23f28ee..9fab08c3bc 100644 --- a/packages/frontend/component/src/ui/notification/index.ts +++ b/packages/frontend/component/src/ui/notification/index.ts @@ -1,2 +1,2 @@ -export * from './notification-center'; +export * from './notify'; export type { Notification } from './types'; diff --git a/packages/frontend/component/src/ui/notification/mobile/notification-card.tsx b/packages/frontend/component/src/ui/notification/mobile/notification-card.tsx new file mode 100644 index 0000000000..c5c3649ea9 --- /dev/null +++ b/packages/frontend/component/src/ui/notification/mobile/notification-card.tsx @@ -0,0 +1,110 @@ +import { CloseIcon, InformationFillDuotoneIcon } from '@blocksuite/icons/rc'; +import { useCallback, useState } from 'react'; + +import { Button, IconButton } from '../../button'; +import { Modal } from '../../modal'; +import type { NotificationCardProps } from '../types'; +import { getCardVars } from '../utils'; +import * as styles from './styles.css'; + +export function MobileNotificationCard({ + notification, +}: NotificationCardProps) { + const { + theme = 'info', + style = 'normal', + icon = , + iconColor, + onDismiss, + } = notification; + + const [animated, setAnimated] = useState(false); + const [showDetail, setShowDetail] = useState(false); + + const handleShowDetail = useCallback(() => { + setAnimated(true); + setShowDetail(true); + }, []); + const handleHideDetail = useCallback(() => { + setShowDetail(false); + onDismiss?.(); + }, [onDismiss]); + + return showDetail ? ( + + ) : ( +
+ {icon} + {notification.title} +
+ ); +} +const MobileNotifyDetail = ({ + notification, + onClose, +}: NotificationCardProps & { + onClose: () => void; +}) => { + const { + theme = 'info', + style = 'normal', + icon = , + iconColor, + title, + message, + footer, + action, + } = notification; + + const handleOpenChange = useCallback( + (open: boolean) => { + if (!open) onClose(); + }, + [onClose] + ); + const onActionClicked = useCallback(() => { + action?.onClick()?.catch(console.error); + if (action?.autoClose !== false) { + onClose?.(); + } + }, [action, onClose]); + + return ( + +
e.stopPropagation()}> +
+ {icon} + {title} + } /> +
+
{message}
+ {/* actions */} +
+ {action ? ( + + ) : null} + {footer} +
+
+
+ ); +}; diff --git a/packages/frontend/component/src/ui/notification/mobile/notification-center.tsx b/packages/frontend/component/src/ui/notification/mobile/notification-center.tsx new file mode 100644 index 0000000000..bf8a583e71 --- /dev/null +++ b/packages/frontend/component/src/ui/notification/mobile/notification-center.tsx @@ -0,0 +1,15 @@ +import { Toaster } from 'sonner'; + +export function MobileNotificationCenter() { + return ( + + ); +} diff --git a/packages/frontend/component/src/ui/notification/mobile/styles.css.ts b/packages/frontend/component/src/ui/notification/mobile/styles.css.ts new file mode 100644 index 0000000000..5f065d5f34 --- /dev/null +++ b/packages/frontend/component/src/ui/notification/mobile/styles.css.ts @@ -0,0 +1,108 @@ +import { cssVar } from '@toeverything/theme'; +import { keyframes, style } from '@vanilla-extract/css'; + +import { + cardBorderColor, + cardColor, + cardForeground, + iconColor, +} from '../desktop/styles.css'; + +const expandIn = keyframes({ + from: { + maxWidth: 44, + }, + to: { + maxWidth: '100vw', + }, +}); +export const toastRoot = style({ + width: 'fit-content', + height: 44, + borderRadius: 22, + margin: '0px auto', + padding: 10, + backgroundColor: cardColor, + color: cardForeground, + border: `1px solid ${cardBorderColor}`, + boxShadow: cssVar('shadow1'), + + display: 'flex', + gap: 8, + alignItems: 'center', + + overflow: 'hidden', + transition: 'transform 0.1s', + + ':active': { + transform: 'scale(0.97)', + }, + + selectors: { + '&[data-animated="true"]': { + // sooner will apply the animation when leaving, hide it + visibility: 'hidden', + }, + '&[data-animated="false"]': { + maxWidth: 44, + animation: `${expandIn} 0.8s cubic-bezier(.27,.28,.13,.99)`, + animationFillMode: 'forwards', + }, + }, +}); + +export const toastIcon = style({ + fontSize: 24, + lineHeight: 0, + color: iconColor, +}); + +export const toastLabel = style({ + fontSize: 17, + fontWeight: 400, + lineHeight: '22px', + letterSpacing: -0.43, + whiteSpace: 'nowrap', +}); + +export const detailRoot = style({ + position: 'fixed', + top: 0, + left: 0, + width: '100%', + height: '100%', + display: 'flex', + alignItems: 'start', + padding: 16, + zIndex: 9999, + background: 'rgba(0,0,0,0.1)', +}); +export const detailCard = style({ + // backgroundColor: cardColor, + // color: cardForeground, +}); +export const detailHeader = style({ + padding: '0 20px', + display: 'flex', + alignItems: 'center', + gap: 8, +}); +export const detailContent = style({ + padding: '0 20px', + marginTop: 8, +}); +export const detailIcon = style([toastIcon, {}]); +export const detailLabel = style([ + toastLabel, + { + width: 0, + flex: 1, + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', + }, +]); +export const detailActions = style({ + display: 'flex', + flexDirection: 'column', +}); diff --git a/packages/frontend/component/src/ui/notification/notification-center.stories.tsx b/packages/frontend/component/src/ui/notification/notification-center.stories.tsx index 04a890a8be..00364b0fd0 100644 --- a/packages/frontend/component/src/ui/notification/notification-center.stories.tsx +++ b/packages/frontend/component/src/ui/notification/notification-center.stories.tsx @@ -5,7 +5,7 @@ import { type HTMLAttributes, useState } from 'react'; import { Button } from '../button'; import { Modal } from '../modal'; -import { NotificationCenter, notify } from './notification-center'; +import { NotificationCenter, notify } from '.'; import type { NotificationCustomRendererProps, NotificationStyle, diff --git a/packages/frontend/component/src/ui/notification/notification-center.tsx b/packages/frontend/component/src/ui/notification/notify.tsx similarity index 59% rename from packages/frontend/component/src/ui/notification/notification-center.tsx rename to packages/frontend/component/src/ui/notification/notify.tsx index c8765da3bd..718f8a5aba 100644 --- a/packages/frontend/component/src/ui/notification/notification-center.tsx +++ b/packages/frontend/component/src/ui/notification/notify.tsx @@ -2,46 +2,24 @@ import { InformationFillDuotoneIcon, SingleSelectSelectSolidIcon, } from '@blocksuite/icons/rc'; -import { assignInlineVars } from '@vanilla-extract/dynamic'; -import { type CSSProperties, type FC, useMemo } from 'react'; -import { type ExternalToast, toast, Toaster } from 'sonner'; +import type { FC } from 'react'; +import { type ExternalToast, toast } from 'sonner'; -import { NotificationCard } from './notification-card'; -import type { - Notification, - NotificationCenterProps, - NotificationCustomRendererProps, -} from './types'; +import { DesktopNotificationCard } from './desktop/notification-card'; +import { DesktopNotificationCenter } from './desktop/notification-center'; +import { MobileNotificationCard } from './mobile/notification-card'; +import { MobileNotificationCenter } from './mobile/notification-center'; +import type { Notification, NotificationCustomRendererProps } from './types'; -export function NotificationCenter({ width = 380 }: NotificationCenterProps) { - const style = useMemo(() => { - return { - ...assignInlineVars({ - // override css vars inside sonner - '--width': `${width}px`, - }), - // radix-ui will lock pointer-events when dialog is open - pointerEvents: 'auto', - } satisfies CSSProperties; - }, [width]); +const NotificationCard = BUILD_CONFIG.isMobileEdition + ? MobileNotificationCard + : DesktopNotificationCard; - const toastOptions = useMemo( - () => ({ - style: { - width: '100%', - }, - }), - [] - ); +const NotificationCenter = BUILD_CONFIG.isMobileEdition + ? MobileNotificationCenter + : DesktopNotificationCenter; - return ( - - ); -} +export { NotificationCenter }; /** * diff --git a/packages/frontend/component/src/ui/notification/types.ts b/packages/frontend/component/src/ui/notification/types.ts index adbc59deab..9344d0e8cb 100644 --- a/packages/frontend/component/src/ui/notification/types.ts +++ b/packages/frontend/component/src/ui/notification/types.ts @@ -44,3 +44,7 @@ export interface NotificationCenterProps { export interface NotificationCustomRendererProps { onDismiss?: () => void; } + +export interface NotificationCardProps extends HTMLAttributes { + notification: Notification; +} diff --git a/packages/frontend/component/src/ui/notification/utils.ts b/packages/frontend/component/src/ui/notification/utils.ts index 6d09597390..06216bc89e 100644 --- a/packages/frontend/component/src/ui/notification/utils.ts +++ b/packages/frontend/component/src/ui/notification/utils.ts @@ -1,5 +1,7 @@ import { cssVar } from '@toeverything/theme'; +import { assignInlineVars } from '@vanilla-extract/dynamic'; +import * as styles from './desktop/styles.css'; import type { NotificationStyle, NotificationTheme } from './types'; export const getCardColor = ( @@ -77,3 +79,18 @@ export const getCloseIconColor = (style: NotificationStyle) => { ? getCardForegroundColor(style) : cssVar('iconColor'); }; + +export const getCardVars = ( + style: NotificationStyle, + theme: NotificationTheme, + iconColor?: string +) => { + return assignInlineVars({ + [styles.cardColor]: getCardColor(style, theme), + [styles.cardBorderColor]: getCardBorderColor(style), + [styles.cardForeground]: getCardForegroundColor(style), + [styles.actionTextColor]: getActionTextColor(style, theme), + [styles.iconColor]: getIconColor(style, theme, iconColor), + [styles.closeIconColor]: getCloseIconColor(style), + }); +};