@liveblocks/react-ui
Version:
A set of React pre-built components for the Liveblocks products. Liveblocks is the all-in-one toolkit to build collaborative products like Figma, Notion, and more.
503 lines (499 loc) • 17.5 kB
JavaScript
"use client";
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
import { assertNever, console } from '@liveblocks/core';
import { useMarkInboxNotificationAsRead, useDeleteInboxNotification, useInboxNotificationThread, useRoomInfo } from '@liveblocks/react';
import { Slot } from '@radix-ui/react-slot';
import { TooltipProvider } from '@radix-ui/react-tooltip';
import { forwardRef, useState, useCallback, useMemo } from 'react';
import { useComponents } from '../components.js';
import { CheckIcon } from '../icons/Check.js';
import { DeleteIcon } from '../icons/Delete.js';
import { EllipsisIcon } from '../icons/Ellipsis.js';
import { WarningIcon } from '../icons/Warning.js';
import { useOverrides } from '../overrides.js';
import { Timestamp } from '../primitives/Timestamp.js';
import { useCurrentUserId } from '../shared.js';
import { classNames } from '../utils/class-names.js';
import { generateURL } from '../utils/url.js';
import { Avatar } from './internal/Avatar.js';
import { Button } from './internal/Button.js';
import { Dropdown, DropdownItem } from './internal/Dropdown.js';
import { generateInboxNotificationThreadContents, InboxNotificationComment, INBOX_NOTIFICATION_THREAD_MAX_COMMENTS } from './internal/InboxNotificationThread.js';
import { List } from './internal/List.js';
import { Room } from './internal/Room.js';
import { Tooltip } from './internal/Tooltip.js';
import { User } from './internal/User.js';
import { DropdownMenuTrigger } from '@radix-ui/react-dropdown-menu';
const InboxNotificationLayout = forwardRef(
({
inboxNotification,
children,
aside,
title,
date,
unread,
markAsReadOnClick,
onClick,
href,
showActions,
overrides,
components,
className,
asChild,
...props
}, forwardedRef) => {
const $ = useOverrides(overrides);
const { Anchor } = useComponents(components);
const Component = asChild ? Slot : Anchor;
const [isMoreActionOpen, setMoreActionOpen] = useState(false);
const markInboxNotificationAsRead = useMarkInboxNotificationAsRead();
const deleteInboxNotification = useDeleteInboxNotification();
const handleClick = useCallback(
(event) => {
onClick?.(event);
const shouldMarkAsReadOnClick = markAsReadOnClick ?? Boolean(href);
if (unread && shouldMarkAsReadOnClick) {
markInboxNotificationAsRead(inboxNotification.id);
}
},
[
href,
inboxNotification.id,
markAsReadOnClick,
markInboxNotificationAsRead,
onClick,
unread
]
);
const stopPropagation = useCallback((event) => {
event.stopPropagation();
}, []);
const preventDefaultAndStopPropagation = useCallback(
(event) => {
event.preventDefault();
event.stopPropagation();
},
[]
);
const handleMoreClick = useCallback((event) => {
event.preventDefault();
event.stopPropagation();
setMoreActionOpen((open) => !open);
}, []);
const handleMarkAsRead = useCallback(() => {
markInboxNotificationAsRead(inboxNotification.id);
}, [inboxNotification.id, markInboxNotificationAsRead]);
const handleDelete = useCallback(() => {
deleteInboxNotification(inboxNotification.id);
}, [inboxNotification.id, deleteInboxNotification]);
return /* @__PURE__ */ jsx(TooltipProvider, {
children: /* @__PURE__ */ jsxs(Component, {
className: classNames(
"lb-root lb-inbox-notification",
showActions === "hover" && "lb-inbox-notification:show-actions-hover",
isMoreActionOpen && "lb-inbox-notification:action-open",
className
),
dir: $.dir,
"data-unread": unread ? "" : void 0,
"data-kind": inboxNotification.kind,
onClick: handleClick,
href,
...props,
ref: forwardedRef,
children: [
aside && /* @__PURE__ */ jsx("div", {
className: "lb-inbox-notification-aside",
children: aside
}),
/* @__PURE__ */ jsxs("div", {
className: "lb-inbox-notification-content",
children: [
/* @__PURE__ */ jsxs("div", {
className: "lb-inbox-notification-header",
children: [
/* @__PURE__ */ jsx("span", {
className: "lb-inbox-notification-title",
children: title
}),
/* @__PURE__ */ jsx("div", {
className: "lb-inbox-notification-details",
children: /* @__PURE__ */ jsxs("span", {
className: "lb-inbox-notification-details-labels",
children: [
/* @__PURE__ */ jsx(Timestamp, {
locale: $.locale,
date,
className: "lb-date lb-inbox-notification-date"
}),
unread && /* @__PURE__ */ jsx("span", {
className: "lb-inbox-notification-unread-indicator",
role: "presentation"
})
]
})
}),
showActions && /* @__PURE__ */ jsx("div", {
className: "lb-inbox-notification-actions",
children: /* @__PURE__ */ jsx(Dropdown, {
open: isMoreActionOpen,
onOpenChange: setMoreActionOpen,
align: "end",
content: /* @__PURE__ */ jsxs(Fragment, {
children: [
unread ? /* @__PURE__ */ jsx(DropdownItem, {
onSelect: handleMarkAsRead,
onClick: stopPropagation,
icon: /* @__PURE__ */ jsx(CheckIcon, {}),
children: $.INBOX_NOTIFICATION_MARK_AS_READ
}) : null,
/* @__PURE__ */ jsx(DropdownItem, {
onSelect: handleDelete,
onClick: stopPropagation,
icon: /* @__PURE__ */ jsx(DeleteIcon, {}),
children: $.INBOX_NOTIFICATION_DELETE
})
]
}),
children: /* @__PURE__ */ jsx(Tooltip, {
content: $.INBOX_NOTIFICATION_MORE,
children: /* @__PURE__ */ jsx(DropdownMenuTrigger, {
asChild: true,
children: /* @__PURE__ */ jsx(Button, {
className: "lb-inbox-notification-action",
onClick: handleMoreClick,
onPointerDown: preventDefaultAndStopPropagation,
onPointerUp: preventDefaultAndStopPropagation,
"aria-label": $.INBOX_NOTIFICATION_MORE,
icon: /* @__PURE__ */ jsx(EllipsisIcon, {})
})
})
})
})
})
]
}),
/* @__PURE__ */ jsx("div", {
className: "lb-inbox-notification-body",
children
})
]
})
]
})
});
}
);
function InboxNotificationIcon({
className,
...props
}) {
return /* @__PURE__ */ jsx("div", {
className: classNames("lb-inbox-notification-icon", className),
...props
});
}
function InboxNotificationAvatar({
className,
...props
}) {
return /* @__PURE__ */ jsx(Avatar, {
className: classNames("lb-inbox-notification-avatar", className),
...props
});
}
const InboxNotificationThread = forwardRef(
({
inboxNotification,
href,
showRoomName = true,
showReactions = true,
showAttachments = true,
showActions = "hover",
overrides,
...props
}, forwardedRef) => {
const $ = useOverrides(overrides);
const thread = useInboxNotificationThread(inboxNotification.id);
const currentUserId = useCurrentUserId();
const { info } = useRoomInfo(inboxNotification.roomId);
const contents = useMemo(() => {
const contents2 = generateInboxNotificationThreadContents(
inboxNotification,
thread,
currentUserId ?? ""
);
if (contents2.comments.length === 0 || contents2.userIds.length === 0) {
return null;
}
switch (contents2.type) {
case "comments": {
const reversedUserIds = [...contents2.userIds].reverse();
const firstUserId = reversedUserIds[0];
const aside2 = /* @__PURE__ */ jsx(InboxNotificationAvatar, {
userId: firstUserId
});
const title2 = $.INBOX_NOTIFICATION_THREAD_COMMENTS_LIST(
/* @__PURE__ */ jsx(List, {
values: reversedUserIds.map((userId) => /* @__PURE__ */ jsx(User, {
userId,
replaceSelf: true
}, userId)),
formatRemaining: $.LIST_REMAINING_USERS,
truncate: INBOX_NOTIFICATION_THREAD_MAX_COMMENTS - 1,
locale: $.locale
}),
showRoomName ? /* @__PURE__ */ jsx(Room, {
roomId: thread.roomId
}) : void 0,
reversedUserIds.length
);
const content2 = /* @__PURE__ */ jsx("div", {
className: "lb-inbox-notification-comments",
children: contents2.comments.map((comment) => /* @__PURE__ */ jsx(InboxNotificationComment, {
comment,
showHeader: contents2.comments.length > 1,
showAttachments,
showReactions,
overrides
}, comment.id))
});
return {
unread: contents2.unread,
date: contents2.date,
aside: aside2,
title: title2,
content: content2,
threadId: thread.id,
commentId: contents2.comments[contents2.comments.length - 1].id
};
}
case "mention": {
const mentionUserId = contents2.userIds[0];
const mentionComment = contents2.comments[0];
const aside2 = /* @__PURE__ */ jsx(InboxNotificationAvatar, {
userId: mentionUserId
});
const title2 = $.INBOX_NOTIFICATION_THREAD_MENTION(
/* @__PURE__ */ jsx(User, {
userId: mentionUserId
}, mentionUserId),
showRoomName ? /* @__PURE__ */ jsx(Room, {
roomId: thread.roomId
}) : void 0
);
const content2 = /* @__PURE__ */ jsx("div", {
className: "lb-inbox-notification-comments",
children: /* @__PURE__ */ jsx(InboxNotificationComment, {
comment: mentionComment,
showHeader: false,
showAttachments,
showReactions,
overrides
}, mentionComment.id)
});
return {
unread: contents2.unread,
date: contents2.date,
aside: aside2,
title: title2,
content: content2,
threadId: thread.id,
commentId: mentionComment.id
};
}
default:
return assertNever(
contents2,
"Unexpected thread inbox notification type"
);
}
}, [
$,
currentUserId,
inboxNotification,
overrides,
showRoomName,
showAttachments,
showReactions,
thread
]);
const resolvedHref = useMemo(() => {
const resolvedHref2 = href ?? info?.url;
return resolvedHref2 ? generateURL(resolvedHref2, void 0, contents?.commentId) : void 0;
}, [contents?.commentId, href, info?.url]);
if (!contents) {
return null;
}
const { aside, title, content, date, unread } = contents;
return /* @__PURE__ */ jsx(InboxNotificationLayout, {
inboxNotification,
aside,
title,
date,
unread,
overrides,
href: resolvedHref,
showActions,
markAsReadOnClick: false,
...props,
ref: forwardedRef,
children: content
});
}
);
const InboxNotificationTextMention = forwardRef(
({
inboxNotification,
showActions = "hover",
showRoomName = true,
href,
overrides,
...props
}, ref) => {
const $ = useOverrides(overrides);
const { info } = useRoomInfo(inboxNotification.roomId);
const resolvedHref = useMemo(() => {
const resolvedHref2 = href ?? info?.url;
return resolvedHref2 ? generateURL(resolvedHref2) : void 0;
}, [href, info?.url]);
const unread = useMemo(() => {
return !inboxNotification.readAt || inboxNotification.notifiedAt > inboxNotification.readAt;
}, [inboxNotification.notifiedAt, inboxNotification.readAt]);
return /* @__PURE__ */ jsx(InboxNotificationLayout, {
inboxNotification,
aside: /* @__PURE__ */ jsx(InboxNotificationAvatar, {
userId: inboxNotification.createdBy
}),
title: $.INBOX_NOTIFICATION_TEXT_MENTION(
/* @__PURE__ */ jsx(User, {
userId: inboxNotification.createdBy
}, inboxNotification.createdBy),
showRoomName ? /* @__PURE__ */ jsx(Room, {
roomId: inboxNotification.roomId
}) : void 0
),
date: inboxNotification.notifiedAt,
unread,
overrides,
showActions,
href: resolvedHref,
...props,
ref
});
}
);
const InboxNotificationCustom = forwardRef(
({
inboxNotification,
showActions = "hover",
title,
aside,
children,
overrides,
...props
}, forwardedRef) => {
const unread = useMemo(() => {
return !inboxNotification.readAt || inboxNotification.notifiedAt > inboxNotification.readAt;
}, [inboxNotification.notifiedAt, inboxNotification.readAt]);
return /* @__PURE__ */ jsx(InboxNotificationLayout, {
inboxNotification,
aside,
title,
date: inboxNotification.notifiedAt,
unread,
overrides,
showActions,
...props,
ref: forwardedRef,
children
});
}
);
const InboxNotificationCustomMissing = forwardRef(({ inboxNotification, ...props }, forwardedRef) => {
return /* @__PURE__ */ jsxs(InboxNotificationCustom, {
inboxNotification,
...props,
title: /* @__PURE__ */ jsxs(Fragment, {
children: [
"Custom notification kind ",
/* @__PURE__ */ jsx("code", {
children: inboxNotification.kind
}),
" is not handled"
]
}),
aside: /* @__PURE__ */ jsx(InboxNotificationIcon, {
children: /* @__PURE__ */ jsx(WarningIcon, {})
}),
ref: forwardedRef,
"data-missing": "",
children: [
"Notifications of this kind won\u2019t be displayed in production. Use the",
" ",
/* @__PURE__ */ jsx("code", {
children: "kinds"
}),
" prop to define how they should be rendered."
]
});
});
const inboxNotificationKindsWarnings = /* @__PURE__ */ new Set();
const InboxNotification = Object.assign(
forwardRef(
({ inboxNotification, kinds, ...props }, forwardedRef) => {
switch (inboxNotification.kind) {
case "thread": {
const ResolvedInboxNotificationThread = kinds?.thread ?? InboxNotificationThread;
return /* @__PURE__ */ jsx(ResolvedInboxNotificationThread, {
inboxNotification,
...props,
ref: forwardedRef
});
}
case "textMention": {
const ResolvedInboxNotificationTextMention = kinds?.textMention ?? InboxNotificationTextMention;
return /* @__PURE__ */ jsx(ResolvedInboxNotificationTextMention, {
inboxNotification,
...props,
ref: forwardedRef
});
}
default: {
const ResolvedInboxNotificationCustom = kinds?.[inboxNotification.kind];
if (!ResolvedInboxNotificationCustom) {
if (process.env.NODE_ENV !== "production") {
if (!inboxNotificationKindsWarnings.has(inboxNotification.kind)) {
inboxNotificationKindsWarnings.add(inboxNotification.kind);
console.warn(
`Custom notification kind "${inboxNotification.kind}" is not handled so notifications of this kind will not be displayed in production. Use the kinds prop to define how they should be rendered.`
);
}
return /* @__PURE__ */ jsx(InboxNotificationCustomMissing, {
inboxNotification,
...props,
ref: forwardedRef
});
} else {
return null;
}
}
return /* @__PURE__ */ jsx(ResolvedInboxNotificationCustom, {
inboxNotification,
...props,
ref: forwardedRef
});
}
}
}
),
{
Thread: InboxNotificationThread,
TextMention: InboxNotificationTextMention,
Custom: InboxNotificationCustom,
Icon: InboxNotificationIcon,
Avatar: InboxNotificationAvatar
}
);
export { InboxNotification };
//# sourceMappingURL=InboxNotification.js.map