forked from toeverything/AFFiNE
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(component): basic notification adaptation for mobile (toeverythi…
- Loading branch information
Showing
11 changed files
with
315 additions
and
63 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
37 changes: 37 additions & 0 deletions
37
packages/frontend/component/src/ui/notification/desktop/notification-center.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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} | ||
/> | ||
); | ||
} |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'; |
110 changes: 110 additions & 0 deletions
110
packages/frontend/component/src/ui/notification/mobile/notification-card.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
}; |
15 changes: 15 additions & 0 deletions
15
packages/frontend/component/src/ui/notification/mobile/notification-center.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
108
packages/frontend/component/src/ui/notification/mobile/styles.css.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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', | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.