@magicbell/magicbell-react
Version:
React components for building a notification inbox for your app
73 lines • 3.73 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react';
import { 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';
const actionableTags = new Set(['A', 'BUTTON', 'INPUT', 'TEXTAREA', 'SELECT']);
function isActionableElement(event) {
let el = event.target;
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
* <ClickableNotification notification={notification} onClick={openActionUrl} />
*/
export default function ClickableNotification({ notification: rawNotification, onClick, prose }) {
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 (_jsxs(StyledContainer, { role: "button", "aria-labelledby": `magicbell-notification-title-${notification.id}`, notification: notification, onClick: handleClick, children: [_jsx(NotificationState, { notification: notification }), _jsxs("div", { css: content, children: [_jsx(NotificationTitle, { notification: notification }), _jsx(NotificationContent, { notification: notification, prose: prose })] }), _jsxs("div", { css: actions, children: [notification.sentAt ? _jsx(Timestamp, { date: notification.sentAt, tooltipPlacement: "left" }) : _jsx("div", {}), _jsx(NotificationMenu, { notification: notification })] })] }));
}
//# sourceMappingURL=ClickableNotification.js.map