@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.
683 lines (676 loc) • 25.7 kB
JavaScript
"use client";
'use strict';
var jsxRuntime = require('react/jsx-runtime');
var _private = require('@liveblocks/react/_private');
var TogglePrimitive = require('@radix-ui/react-toggle');
var react = require('react');
var Check = require('../icons/Check.cjs');
var Cross = require('../icons/Cross.cjs');
var Delete = require('../icons/Delete.cjs');
var Edit = require('../icons/Edit.cjs');
var Ellipsis = require('../icons/Ellipsis.cjs');
var EmojiPlus = require('../icons/EmojiPlus.cjs');
var overrides = require('../overrides.cjs');
var index = require('../primitives/Comment/index.cjs');
var index$1 = require('../primitives/Composer/index.cjs');
var Timestamp = require('../primitives/Timestamp.cjs');
var shared = require('../shared.cjs');
var mentions = require('../slate/plugins/mentions.cjs');
var classNames = require('../utils/class-names.cjs');
var download = require('../utils/download.cjs');
var useRefs = require('../utils/use-refs.cjs');
var useVisible = require('../utils/use-visible.cjs');
var useWindowFocus = require('../utils/use-window-focus.cjs');
var Composer = require('./Composer.cjs');
var Attachment = require('./internal/Attachment.cjs');
var Avatar = require('./internal/Avatar.cjs');
var Button = require('./internal/Button.cjs');
var Dropdown = require('./internal/Dropdown.cjs');
var Emoji = require('./internal/Emoji.cjs');
var EmojiPicker = require('./internal/EmojiPicker.cjs');
var List = require('./internal/List.cjs');
var Tooltip = require('./internal/Tooltip.cjs');
var User = require('./internal/User.cjs');
var TooltipPrimitive = require('@radix-ui/react-tooltip');
var PopoverPrimitive = require('@radix-ui/react-popover');
var DropdownMenuPrimitive = require('@radix-ui/react-dropdown-menu');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var TogglePrimitive__namespace = /*#__PURE__*/_interopNamespaceDefault(TogglePrimitive);
const REACTIONS_TRUNCATE = 5;
function CommentMention({
userId,
className,
...props
}) {
const currentId = shared.useCurrentUserId();
return /* @__PURE__ */ jsxRuntime.jsxs(index.Mention, {
className: classNames.classNames("lb-comment-mention", className),
"data-self": userId === currentId ? "" : void 0,
...props,
children: [
mentions.MENTION_CHARACTER,
/* @__PURE__ */ jsxRuntime.jsx(User.User, {
userId
})
]
});
}
function CommentLink({
href,
children,
className,
...props
}) {
return /* @__PURE__ */ jsxRuntime.jsx(index.Link, {
className: classNames.classNames("lb-comment-link", className),
href,
...props,
children
});
}
function CommentNonInteractiveLink({
href: _href,
children,
className,
...props
}) {
return /* @__PURE__ */ jsxRuntime.jsx("span", {
className: classNames.classNames("lb-comment-link", className),
...props,
children
});
}
const CommentReactionButton = react.forwardRef(({ reaction, overrides: overrides$1, className, ...props }, forwardedRef) => {
const $ = overrides.useOverrides(overrides$1);
return /* @__PURE__ */ jsxRuntime.jsxs(Button.CustomButton, {
className: classNames.classNames("lb-comment-reaction", className),
variant: "outline",
"aria-label": $.COMMENT_REACTION_DESCRIPTION(
reaction.emoji,
reaction.users.length
),
...props,
ref: forwardedRef,
children: [
/* @__PURE__ */ jsxRuntime.jsx(Emoji.Emoji, {
className: "lb-comment-reaction-emoji",
emoji: reaction.emoji
}),
/* @__PURE__ */ jsxRuntime.jsx("span", {
className: "lb-comment-reaction-count",
children: reaction.users.length
})
]
});
});
const CommentReaction = react.forwardRef(({ comment, reaction, overrides: overrides$1, disabled, ...props }, forwardedRef) => {
const addReaction = _private.useAddRoomCommentReaction(comment.roomId);
const removeReaction = _private.useRemoveRoomCommentReaction(comment.roomId);
const currentId = shared.useCurrentUserId();
const isActive = react.useMemo(() => {
return reaction.users.some((users) => users.id === currentId);
}, [currentId, reaction]);
const $ = overrides.useOverrides(overrides$1);
const tooltipContent = react.useMemo(
() => /* @__PURE__ */ jsxRuntime.jsx("span", {
children: $.COMMENT_REACTION_LIST(
/* @__PURE__ */ jsxRuntime.jsx(List.List, {
values: reaction.users.map((users) => /* @__PURE__ */ jsxRuntime.jsx(User.User, {
userId: users.id,
replaceSelf: true
}, users.id)),
formatRemaining: $.LIST_REMAINING_USERS,
truncate: REACTIONS_TRUNCATE,
locale: $.locale
}),
reaction.emoji,
reaction.users.length
)
}),
[$, reaction]
);
const stopPropagation = react.useCallback((event) => {
event.stopPropagation();
}, []);
const handlePressedChange = react.useCallback(
(isPressed) => {
if (isPressed) {
addReaction({
threadId: comment.threadId,
commentId: comment.id,
emoji: reaction.emoji
});
} else {
removeReaction({
threadId: comment.threadId,
commentId: comment.id,
emoji: reaction.emoji
});
}
},
[addReaction, comment.threadId, comment.id, reaction.emoji, removeReaction]
);
return /* @__PURE__ */ jsxRuntime.jsx(Tooltip.Tooltip, {
content: tooltipContent,
multiline: true,
className: "lb-comment-reaction-tooltip",
children: /* @__PURE__ */ jsxRuntime.jsx(TogglePrimitive__namespace.Root, {
asChild: true,
pressed: isActive,
onPressedChange: handlePressedChange,
onClick: stopPropagation,
disabled,
ref: forwardedRef,
children: /* @__PURE__ */ jsxRuntime.jsx(CommentReactionButton, {
"data-self": isActive ? "" : void 0,
reaction,
overrides: overrides$1,
...props
})
})
});
});
const CommentNonInteractiveReaction = react.forwardRef(({ reaction, overrides, ...props }, forwardedRef) => {
const currentId = shared.useCurrentUserId();
const isActive = react.useMemo(() => {
return reaction.users.some((users) => users.id === currentId);
}, [currentId, reaction]);
return /* @__PURE__ */ jsxRuntime.jsx(CommentReactionButton, {
disableable: false,
"data-self": isActive ? "" : void 0,
reaction,
overrides,
...props,
ref: forwardedRef
});
});
function openAttachment({ attachment, url }) {
if (attachment.mimeType === "application/pdf" || attachment.mimeType.startsWith("image/") || attachment.mimeType.startsWith("video/") || attachment.mimeType.startsWith("audio/")) {
window.open(url, "_blank");
} else {
download.download(url, attachment.name);
}
}
function CommentMediaAttachment({
attachment,
onAttachmentClick,
roomId,
className,
overrides,
...props
}) {
const { url } = _private.useRoomAttachmentUrl(attachment.id, roomId);
const handleClick = react.useCallback(
(event) => {
if (!url) {
return;
}
const args = { attachment, url };
onAttachmentClick?.(args, event);
if (event.isDefaultPrevented()) {
return;
}
openAttachment(args);
},
[attachment, onAttachmentClick, url]
);
return /* @__PURE__ */ jsxRuntime.jsx(Attachment.MediaAttachment, {
className: classNames.classNames("lb-comment-attachment", className),
...props,
attachment,
overrides,
onClick: url ? handleClick : void 0,
roomId
});
}
function CommentFileAttachment({
attachment,
onAttachmentClick,
roomId,
className,
overrides,
...props
}) {
const { url } = _private.useRoomAttachmentUrl(attachment.id, roomId);
const handleClick = react.useCallback(
(event) => {
if (!url) {
return;
}
const args = { attachment, url };
onAttachmentClick?.(args, event);
if (event.isDefaultPrevented()) {
return;
}
openAttachment(args);
},
[attachment, onAttachmentClick, url]
);
return /* @__PURE__ */ jsxRuntime.jsx(Attachment.FileAttachment, {
className: classNames.classNames("lb-comment-attachment", className),
...props,
attachment,
overrides,
onClick: url ? handleClick : void 0,
roomId
});
}
function CommentNonInteractiveFileAttachment({
className,
...props
}) {
return /* @__PURE__ */ jsxRuntime.jsx(Attachment.FileAttachment, {
className: classNames.classNames("lb-comment-attachment", className),
allowMediaPreview: false,
...props
});
}
function AutoMarkReadThreadIdHandler({
threadId,
roomId,
commentRef
}) {
const markThreadAsRead = _private.useMarkRoomThreadAsRead(roomId);
const isWindowFocused = useWindowFocus.useWindowFocus();
useVisible.useVisibleCallback(
commentRef,
() => {
markThreadAsRead(threadId);
},
{
enabled: isWindowFocused
}
);
return null;
}
const Comment = react.forwardRef(
({
comment,
indentContent = true,
showDeleted,
showActions = "hover",
showReactions = true,
showAttachments = true,
showComposerFormattingControls = true,
onAuthorClick,
onMentionClick,
onAttachmentClick,
onCommentEdit,
onCommentDelete,
overrides: overrides$1,
className,
additionalActions,
additionalActionsClassName,
autoMarkReadThreadId,
...props
}, forwardedRef) => {
const ref = react.useRef(null);
const mergedRefs = useRefs.useRefs(forwardedRef, ref);
const currentUserId = shared.useCurrentUserId();
const deleteComment = _private.useDeleteRoomComment(comment.roomId);
const editComment = _private.useEditRoomComment(comment.roomId);
const addReaction = _private.useAddRoomCommentReaction(comment.roomId);
const removeReaction = _private.useRemoveRoomCommentReaction(comment.roomId);
const $ = overrides.useOverrides(overrides$1);
const [isEditing, setEditing] = react.useState(false);
const [isTarget, setTarget] = react.useState(false);
const [isMoreActionOpen, setMoreActionOpen] = react.useState(false);
const [isReactionActionOpen, setReactionActionOpen] = react.useState(false);
const { mediaAttachments, fileAttachments } = react.useMemo(() => {
return Attachment.separateMediaAttachments(comment.attachments);
}, [comment.attachments]);
const stopPropagation = react.useCallback((event) => {
event.stopPropagation();
}, []);
const handleEdit = react.useCallback(() => {
setEditing(true);
}, []);
const handleEditCancel = react.useCallback(
(event) => {
event.stopPropagation();
setEditing(false);
},
[]
);
const handleEditSubmit = react.useCallback(
({ body, attachments }, event) => {
onCommentEdit?.(comment);
if (event.isDefaultPrevented()) {
return;
}
event.stopPropagation();
event.preventDefault();
setEditing(false);
editComment({
commentId: comment.id,
threadId: comment.threadId,
body,
attachments
});
},
[comment, editComment, onCommentEdit]
);
const handleDelete = react.useCallback(() => {
onCommentDelete?.(comment);
deleteComment({
commentId: comment.id,
threadId: comment.threadId
});
}, [comment, deleteComment, onCommentDelete]);
const handleAuthorClick = react.useCallback(
(event) => {
onAuthorClick?.(comment.userId, event);
},
[comment.userId, onAuthorClick]
);
const handleReactionSelect = react.useCallback(
(emoji) => {
const reactionIndex = comment.reactions.findIndex(
(reaction) => reaction.emoji === emoji
);
if (reactionIndex >= 0 && currentUserId && comment.reactions[reactionIndex]?.users.some(
(user) => user.id === currentUserId
)) {
removeReaction({
threadId: comment.threadId,
commentId: comment.id,
emoji
});
} else {
addReaction({
threadId: comment.threadId,
commentId: comment.id,
emoji
});
}
},
[
addReaction,
comment.id,
comment.reactions,
comment.threadId,
removeReaction,
currentUserId
]
);
react.useEffect(() => {
const isWindowDefined = typeof window !== "undefined";
if (!isWindowDefined)
return;
const hash = window.location.hash;
const commentId = hash.slice(1);
if (commentId === comment.id) {
setTarget(true);
}
}, []);
if (!showDeleted && !comment.body) {
return null;
}
return /* @__PURE__ */ jsxRuntime.jsxs(TooltipPrimitive.TooltipProvider, {
children: [
autoMarkReadThreadId && /* @__PURE__ */ jsxRuntime.jsx(AutoMarkReadThreadIdHandler, {
commentRef: ref,
threadId: autoMarkReadThreadId,
roomId: comment.roomId
}),
/* @__PURE__ */ jsxRuntime.jsxs("div", {
id: comment.id,
className: classNames.classNames(
"lb-root lb-comment",
indentContent && "lb-comment:indent-content",
showActions === "hover" && "lb-comment:show-actions-hover",
(isMoreActionOpen || isReactionActionOpen) && "lb-comment:action-open",
className
),
"data-deleted": !comment.body ? "" : void 0,
"data-editing": isEditing ? "" : void 0,
"data-target": isTarget ? "" : void 0,
dir: $.dir,
...props,
ref: mergedRefs,
children: [
/* @__PURE__ */ jsxRuntime.jsxs("div", {
className: "lb-comment-header",
children: [
/* @__PURE__ */ jsxRuntime.jsxs("div", {
className: "lb-comment-details",
children: [
/* @__PURE__ */ jsxRuntime.jsx(Avatar.Avatar, {
className: "lb-comment-avatar",
userId: comment.userId,
onClick: handleAuthorClick
}),
/* @__PURE__ */ jsxRuntime.jsxs("span", {
className: "lb-comment-details-labels",
children: [
/* @__PURE__ */ jsxRuntime.jsx(User.User, {
className: "lb-comment-author",
userId: comment.userId,
onClick: handleAuthorClick
}),
/* @__PURE__ */ jsxRuntime.jsxs("span", {
className: "lb-comment-date",
children: [
/* @__PURE__ */ jsxRuntime.jsx(Timestamp.Timestamp, {
locale: $.locale,
date: comment.createdAt,
className: "lb-date lb-comment-date-created"
}),
comment.editedAt && comment.body && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, {
children: [
" ",
/* @__PURE__ */ jsxRuntime.jsx("span", {
className: "lb-comment-date-edited",
children: $.COMMENT_EDITED
})
]
})
]
})
]
})
]
}),
showActions && !isEditing && /* @__PURE__ */ jsxRuntime.jsxs("div", {
className: classNames.classNames(
"lb-comment-actions",
additionalActionsClassName
),
children: [
additionalActions ?? null,
showReactions && /* @__PURE__ */ jsxRuntime.jsx(EmojiPicker.EmojiPicker, {
onEmojiSelect: handleReactionSelect,
onOpenChange: setReactionActionOpen,
children: /* @__PURE__ */ jsxRuntime.jsx(Tooltip.Tooltip, {
content: $.COMMENT_ADD_REACTION,
children: /* @__PURE__ */ jsxRuntime.jsx(PopoverPrimitive.PopoverTrigger, {
asChild: true,
children: /* @__PURE__ */ jsxRuntime.jsx(Button.Button, {
className: "lb-comment-action",
onClick: stopPropagation,
"aria-label": $.COMMENT_ADD_REACTION,
icon: /* @__PURE__ */ jsxRuntime.jsx(EmojiPlus.EmojiPlusIcon, {})
})
})
})
}),
comment.userId === currentUserId && /* @__PURE__ */ jsxRuntime.jsx(Dropdown.Dropdown, {
open: isMoreActionOpen,
onOpenChange: setMoreActionOpen,
align: "end",
content: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, {
children: [
/* @__PURE__ */ jsxRuntime.jsx(Dropdown.DropdownItem, {
onSelect: handleEdit,
onClick: stopPropagation,
icon: /* @__PURE__ */ jsxRuntime.jsx(Edit.EditIcon, {}),
children: $.COMMENT_EDIT
}),
/* @__PURE__ */ jsxRuntime.jsx(Dropdown.DropdownItem, {
onSelect: handleDelete,
onClick: stopPropagation,
icon: /* @__PURE__ */ jsxRuntime.jsx(Delete.DeleteIcon, {}),
children: $.COMMENT_DELETE
})
]
}),
children: /* @__PURE__ */ jsxRuntime.jsx(Tooltip.Tooltip, {
content: $.COMMENT_MORE,
children: /* @__PURE__ */ jsxRuntime.jsx(DropdownMenuPrimitive.DropdownMenuTrigger, {
asChild: true,
children: /* @__PURE__ */ jsxRuntime.jsx(Button.Button, {
className: "lb-comment-action",
disabled: !comment.body,
onClick: stopPropagation,
"aria-label": $.COMMENT_MORE,
icon: /* @__PURE__ */ jsxRuntime.jsx(Ellipsis.EllipsisIcon, {})
})
})
})
})
]
})
]
}),
/* @__PURE__ */ jsxRuntime.jsx("div", {
className: "lb-comment-content",
children: isEditing ? /* @__PURE__ */ jsxRuntime.jsx(Composer.Composer, {
className: "lb-comment-composer",
onComposerSubmit: handleEditSubmit,
defaultValue: comment.body,
defaultAttachments: comment.attachments,
autoFocus: true,
showAttribution: false,
showAttachments,
showFormattingControls: showComposerFormattingControls,
actions: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, {
children: [
/* @__PURE__ */ jsxRuntime.jsx(Tooltip.Tooltip, {
content: $.COMMENT_EDIT_COMPOSER_CANCEL,
"aria-label": $.COMMENT_EDIT_COMPOSER_CANCEL,
children: /* @__PURE__ */ jsxRuntime.jsx(Button.Button, {
className: "lb-composer-action",
onClick: handleEditCancel,
icon: /* @__PURE__ */ jsxRuntime.jsx(Cross.CrossIcon, {})
})
}),
/* @__PURE__ */ jsxRuntime.jsx(Tooltip.ShortcutTooltip, {
content: $.COMMENT_EDIT_COMPOSER_SAVE,
shortcut: "Enter",
children: /* @__PURE__ */ jsxRuntime.jsx(index$1.Submit, {
asChild: true,
children: /* @__PURE__ */ jsxRuntime.jsx(Button.Button, {
variant: "primary",
className: "lb-composer-action",
onClick: stopPropagation,
"aria-label": $.COMMENT_EDIT_COMPOSER_SAVE,
icon: /* @__PURE__ */ jsxRuntime.jsx(Check.CheckIcon, {})
})
})
})
]
}),
overrides: {
COMPOSER_PLACEHOLDER: $.COMMENT_EDIT_COMPOSER_PLACEHOLDER
},
roomId: comment.roomId
}) : comment.body ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, {
children: [
/* @__PURE__ */ jsxRuntime.jsx(index.Body, {
className: "lb-comment-body",
body: comment.body,
components: {
Mention: ({ userId }) => /* @__PURE__ */ jsxRuntime.jsx(CommentMention, {
userId,
onClick: (event) => onMentionClick?.(userId, event)
}),
Link: CommentLink
}
}),
showAttachments && (mediaAttachments.length > 0 || fileAttachments.length > 0) ? /* @__PURE__ */ jsxRuntime.jsxs("div", {
className: "lb-comment-attachments",
children: [
mediaAttachments.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", {
className: "lb-attachments",
children: mediaAttachments.map((attachment) => /* @__PURE__ */ jsxRuntime.jsx(CommentMediaAttachment, {
attachment,
overrides: overrides$1,
onAttachmentClick,
roomId: comment.roomId
}, attachment.id))
}) : null,
fileAttachments.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", {
className: "lb-attachments",
children: fileAttachments.map((attachment) => /* @__PURE__ */ jsxRuntime.jsx(CommentFileAttachment, {
attachment,
overrides: overrides$1,
onAttachmentClick,
roomId: comment.roomId
}, attachment.id))
}) : null
]
}) : null,
showReactions && comment.reactions.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", {
className: "lb-comment-reactions",
children: [
comment.reactions.map((reaction) => /* @__PURE__ */ jsxRuntime.jsx(CommentReaction, {
comment,
reaction,
overrides: overrides$1
}, reaction.emoji)),
/* @__PURE__ */ jsxRuntime.jsx(EmojiPicker.EmojiPicker, {
onEmojiSelect: handleReactionSelect,
children: /* @__PURE__ */ jsxRuntime.jsx(Tooltip.Tooltip, {
content: $.COMMENT_ADD_REACTION,
children: /* @__PURE__ */ jsxRuntime.jsx(PopoverPrimitive.PopoverTrigger, {
asChild: true,
children: /* @__PURE__ */ jsxRuntime.jsx(Button.Button, {
className: "lb-comment-reaction lb-comment-reaction-add",
variant: "outline",
onClick: stopPropagation,
"aria-label": $.COMMENT_ADD_REACTION,
icon: /* @__PURE__ */ jsxRuntime.jsx(EmojiPlus.EmojiPlusIcon, {})
})
})
})
})
]
})
]
}) : /* @__PURE__ */ jsxRuntime.jsx("div", {
className: "lb-comment-body",
children: /* @__PURE__ */ jsxRuntime.jsx("p", {
className: "lb-comment-deleted",
children: $.COMMENT_DELETED
})
})
})
]
})
]
});
}
);
exports.Comment = Comment;
exports.CommentLink = CommentLink;
exports.CommentMention = CommentMention;
exports.CommentNonInteractiveFileAttachment = CommentNonInteractiveFileAttachment;
exports.CommentNonInteractiveLink = CommentNonInteractiveLink;
exports.CommentNonInteractiveReaction = CommentNonInteractiveReaction;
exports.CommentReaction = CommentReaction;
//# sourceMappingURL=Comment.cjs.map