Skip to content

Commit

Permalink
feat(component): basic notification adaptation for mobile (toeverythi…
Browse files Browse the repository at this point in the history
  • Loading branch information
CatsJuice committed Oct 11, 2024
1 parent db4d8dd commit 1c59eda
Show file tree
Hide file tree
Showing 11 changed files with 315 additions and 63 deletions.
Original file line number Diff line number Diff line change
@@ -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<HTMLDivElement> {
notification: Notification;
}

export const NotificationCard = ({ notification }: NotificationCardProps) => {
export const DesktopNotificationCard = ({
notification,
}: NotificationCardProps) => {
const {
theme = 'info',
style = 'normal',
Expand All @@ -43,14 +33,7 @@ export const NotificationCard = ({ notification }: NotificationCardProps) => {

return (
<div
style={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),
})}
style={getCardVars(style, theme, iconColor)}
data-with-icon={icon ? '' : undefined}
{...rootAttrs}
className={clsx(styles.card, rootAttrs?.className)}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { assignInlineVars } from '@vanilla-extract/dynamic';
import { type CSSProperties, useMemo } from 'react';
import { Toaster } from 'sonner';

import type { NotificationCenterProps } from '../types';

export function DesktopNotificationCenter({
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 toastOptions = useMemo(
() => ({
style: {
width: '100%',
},
}),
[]
);

return (
<Toaster
className="affine-notification-center"
style={style}
toastOptions={toastOptions}
/>
);
}
2 changes: 1 addition & 1 deletion packages/frontend/component/src/ui/notification/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export * from './notification-center';
export * from './notify';
export type { Notification } from './types';
Original file line number Diff line number Diff line change
@@ -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 = <InformationFillDuotoneIcon />,
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 ? (
<MobileNotifyDetail
notification={notification}
onClose={handleHideDetail}
/>
) : (
<div
data-animated={animated}
onClick={handleShowDetail}
className={styles.toastRoot}
style={getCardVars(style, theme, iconColor)}
>
<span className={styles.toastIcon}>{icon}</span>
<span className={styles.toastLabel}>{notification.title}</span>
</div>
);
}
const MobileNotifyDetail = ({
notification,
onClose,
}: NotificationCardProps & {
onClose: () => void;
}) => {
const {
theme = 'info',
style = 'normal',
icon = <InformationFillDuotoneIcon />,
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 (
<Modal
open
withoutCloseButton
width="100%"
minHeight={60}
animation="slideBottom"
onOpenChange={handleOpenChange}
contentWrapperStyle={getCardVars(style, theme, iconColor)}
contentOptions={{ style: { padding: '12px 0' } }}
>
<div className={styles.detailCard} onClick={e => e.stopPropagation()}>
<header className={styles.detailHeader}>
<span className={styles.detailIcon}>{icon}</span>
<span className={styles.detailLabel}>{title}</span>
<IconButton onClick={onClose} icon={<CloseIcon />} />
</header>
<main className={styles.detailContent}>{message}</main>
{/* actions */}
<div className={styles.detailActions}>
{action ? (
<Button onClick={onActionClicked} {...action.buttonProps}>
{action.label}
</Button>
) : null}
{footer}
</div>
</div>
</Modal>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Toaster } from 'sonner';

export function MobileNotificationCenter() {
return (
<Toaster
visibleToasts={1}
position="top-center"
style={{
width: '100%',
top: 'calc(env(safe-area-inset-top) + 16px)',
pointerEvents: 'auto',
}}
/>
);
}
108 changes: 108 additions & 0 deletions packages/frontend/component/src/ui/notification/mobile/styles.css.ts
Original file line number Diff line number Diff line change
@@ -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',
});
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<Toaster
className="affine-notification-center"
style={style}
toastOptions={toastOptions}
/>
);
}
export { NotificationCenter };

/**
*
Expand Down
Loading

0 comments on commit 1c59eda

Please sign in to comment.