/** @jsxImportSource @emotion/react */ import { css } from '@emotion/react'; import { INotification, IRemoteNotification, useNotification } from '@magicbell/react-headless'; import { useTheme } from '../../context/MagicBellThemeContext.js'; import NotificationContent from '../NotificationContent/index.js'; import NotificationMenu from '../NotificationMenu/index.js'; import NotificationState from '../NotificationState/index.js'; import Timestamp from '../Timestamp/index.js'; import { openActionUrl } from './eventHandlers.js'; import NotificationTitle from './NotificationTitle.js'; import StyledContainer from './StyledContainer.js'; export interface Props { notification: IRemoteNotification; onClick?: (notification: INotification) => void | boolean; prose?: boolean; } const actionableTags = new Set(['A', 'BUTTON', 'INPUT', 'TEXTAREA', 'SELECT']); function isActionableElement(event: MouseEvent): [boolean, boolean] { let el = event.target as HTMLElement; while (el && el !== event.currentTarget) { if (actionableTags.has(el.tagName.toUpperCase())) { const isMenu = el.dataset.magicbellTarget === 'notification-menu'; return [true, isMenu]; } el = el.parentElement; } return [false, false]; } /** * Component that renders a notification. When the notification is clicked the * `onClick` callback is called and the notification is marked as read. * * @example * */ export default function ClickableNotification({ notification: rawNotification, onClick, prose }: Props) { const { notification: { default: theme }, } = useTheme(); const notification = useNotification(rawNotification); const handleClick = (event) => { // ignore event when user clicks inside the menu if (event.isDefaultPrevented()) return; const [isAction, isMenu] = isActionableElement(event); let markAsReadPromise; // don't mark as read when the user clicks the menu if (!isMenu) { markAsReadPromise = notification.markAsRead(); } // We don't want to invoke the action url when the user clicks a link or button inside the notification. // Notification content should take precedence. if (isAction) return; const onClickResult = onClick?.(notification); if (onClickResult === false) return; if (notification.actionUrl) { // We wait for the markAsRead before navigating to the new url, to prevent race conditions // between mark and read, and fetching new data on page reload. But let's not wait forever. const timeout = new Promise((resolve) => setTimeout(resolve, 1_000)); Promise.race([markAsReadPromise, timeout]).then(() => openActionUrl(notification)); } }; const content = css` margin: 0 8px !important; width: 100%; `; const actions = css` display: flex; padding: 0 5px !important; flex-direction: column; align-items: flex-end; margin-left: auto !important; font-size: ${theme.fontSize} !important; `; return (
{notification.sentAt ? :
}
); }