@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.
345 lines (338 loc) • 14.4 kB
JavaScript
"use client";
'use strict';
var jsxRuntime = require('react/jsx-runtime');
var core = require('@liveblocks/core');
var _private = require('@liveblocks/react/_private');
var TogglePrimitive = require('@radix-ui/react-toggle');
var react = require('react');
var ArrowDown = require('../icons/ArrowDown.cjs');
var Bell = require('../icons/Bell.cjs');
var BellCrossed = require('../icons/BellCrossed.cjs');
var CheckCircle = require('../icons/CheckCircle.cjs');
var CheckCircleFill = require('../icons/CheckCircleFill.cjs');
var overrides = require('../overrides.cjs');
var cn = require('../utils/cn.cjs');
var Comment = require('./Comment.cjs');
var Composer = require('./Composer.cjs');
var Button = require('./internal/Button.cjs');
var Tooltip = require('./internal/Tooltip.cjs');
var TooltipPrimitive = require('@radix-ui/react-tooltip');
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 Thread = react.forwardRef(
({
thread,
indentCommentContent = true,
showActions = "hover",
showDeletedComments,
showResolveAction = true,
showReactions = true,
showComposer = "collapsed",
showAttachments = true,
showComposerFormattingControls = true,
maxVisibleComments,
commentDropdownItems,
onResolvedChange,
onCommentEdit,
onCommentDelete,
onThreadDelete,
onAuthorClick,
onMentionClick,
onAttachmentClick,
onComposerSubmit,
blurComposerOnSubmit,
overrides: overrides$1,
components,
className,
...props
}, forwardedRef) => {
const markThreadAsResolved = _private.useMarkRoomThreadAsResolved(thread.roomId);
const markThreadAsUnresolved = _private.useMarkRoomThreadAsUnresolved(thread.roomId);
const $ = overrides.useOverrides(overrides$1);
const [showAllComments, setShowAllComments] = react.useState(false);
const firstCommentIndex = react.useMemo(() => {
return showDeletedComments ? 0 : thread.comments.findIndex((comment) => comment.body);
}, [showDeletedComments, thread.comments]);
const lastCommentIndex = react.useMemo(() => {
return showDeletedComments ? thread.comments.length - 1 : core.findLastIndex(thread.comments, (comment) => Boolean(comment.body));
}, [showDeletedComments, thread.comments]);
const hiddenComments = react.useMemo(() => {
const maxVisibleCommentsCount = typeof maxVisibleComments === "number" ? maxVisibleComments : maxVisibleComments?.max;
const visibleCommentsShow = (typeof maxVisibleComments === "object" ? maxVisibleComments?.show : void 0) ?? "newest";
if (showAllComments || maxVisibleCommentsCount === void 0) {
return;
}
const comments = thread.comments.map((comment, index) => ({ comment, index })).filter(({ comment }) => showDeletedComments || comment.body);
if (comments.length <= Math.max(maxVisibleCommentsCount, 2)) {
return;
}
const firstVisibleComment = comments[0];
const lastVisibleComment = comments[comments.length - 1];
if (maxVisibleCommentsCount <= 2) {
const firstHiddenCommentIndex = comments[1]?.index ?? firstVisibleComment.index;
const lastHiddenCommentIndex = comments[comments.length - 2]?.index ?? lastVisibleComment.index;
return {
firstIndex: firstHiddenCommentIndex,
lastIndex: lastHiddenCommentIndex,
count: comments.slice(1, comments.length - 1).length
};
}
const remainingVisibleCommentsCount = maxVisibleCommentsCount - 2;
const beforeVisibleCommentsCount = visibleCommentsShow === "oldest" ? remainingVisibleCommentsCount : visibleCommentsShow === "newest" ? 0 : Math.floor(remainingVisibleCommentsCount / 2);
const afterVisibleCommentsCount = visibleCommentsShow === "oldest" ? 0 : visibleCommentsShow === "newest" ? remainingVisibleCommentsCount : Math.ceil(remainingVisibleCommentsCount / 2);
const firstHiddenComment = comments[1 + beforeVisibleCommentsCount];
const lastHiddenComment = comments[comments.length - 2 - afterVisibleCommentsCount];
if (!firstHiddenComment || !lastHiddenComment || firstHiddenComment.index > lastHiddenComment.index) {
return;
}
return {
firstIndex: firstHiddenComment.index,
lastIndex: lastHiddenComment.index,
count: thread.comments.slice(firstHiddenComment.index, lastHiddenComment.index + 1).filter((comment) => showDeletedComments || comment.body).length
};
}, [
maxVisibleComments,
showAllComments,
showDeletedComments,
thread.comments
]);
const {
status: subscriptionStatus,
unreadSince,
subscribe,
unsubscribe
} = _private.useRoomThreadSubscription(thread.roomId, thread.id);
const unreadIndex = react.useMemo(() => {
if (subscriptionStatus !== "subscribed") {
return;
}
if (unreadSince === null) {
return firstCommentIndex;
}
const unreadIndex2 = thread.comments.findIndex(
(comment) => (showDeletedComments ? true : comment.body) && comment.createdAt > unreadSince
);
return unreadIndex2 >= 0 && unreadIndex2 < thread.comments.length ? unreadIndex2 : void 0;
}, [
firstCommentIndex,
showDeletedComments,
subscriptionStatus,
thread.comments,
unreadSince
]);
const [newIndex, setNewIndex] = react.useState();
const newIndicatorIndex = newIndex === void 0 ? unreadIndex : newIndex;
react.useEffect(() => {
if (unreadIndex) {
setNewIndex(
(persistedUnreadIndex) => Math.min(persistedUnreadIndex ?? Infinity, unreadIndex)
);
}
}, [unreadIndex]);
const permissions = _private.useRoomPermissions(thread.roomId);
const canComment = permissions.size > 0 ? permissions.has(core.Permission.CommentsWrite) || permissions.has(core.Permission.Write) : true;
const stopPropagation = react.useCallback((event) => {
event.stopPropagation();
}, []);
const handleResolvedChange = react.useCallback(
(resolved) => {
onResolvedChange?.(resolved);
if (resolved) {
markThreadAsResolved(thread.id);
} else {
markThreadAsUnresolved(thread.id);
}
},
[
markThreadAsResolved,
markThreadAsUnresolved,
onResolvedChange,
thread.id
]
);
const handleCommentDelete = react.useCallback(
(comment) => {
onCommentDelete?.(comment);
const filteredComments = thread.comments.filter(
(comment2) => comment2.body
);
if (filteredComments.length <= 1) {
onThreadDelete?.(thread);
}
},
[onCommentDelete, onThreadDelete, thread]
);
const handleSubscribeChange = react.useCallback(() => {
if (subscriptionStatus === "subscribed") {
unsubscribe();
} else {
subscribe();
}
}, [subscriptionStatus, subscribe, unsubscribe]);
return /* @__PURE__ */ jsxRuntime.jsx(TooltipPrimitive.TooltipProvider, { children: /* @__PURE__ */ jsxRuntime.jsxs(
"div",
{
className: cn.cn(
"lb-root lb-thread",
showActions === "hover" && "lb-thread:show-actions-hover",
className
),
"data-resolved": thread.resolved ? "" : void 0,
"data-unread": unreadIndex !== void 0 ? "" : void 0,
dir: $.dir,
...props,
ref: forwardedRef,
children: [
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "lb-thread-comments", children: thread.comments.map((comment, index) => {
const isFirstComment = index === firstCommentIndex;
const isUnread = unreadIndex !== void 0 && index >= unreadIndex;
const isHidden = hiddenComments && index >= hiddenComments.firstIndex && index <= hiddenComments.lastIndex;
const isFirstHiddenComment = isHidden && index === hiddenComments.firstIndex;
if (isFirstHiddenComment) {
return /* @__PURE__ */ jsxRuntime.jsx(
"div",
{
className: "lb-thread-show-more",
children: /* @__PURE__ */ jsxRuntime.jsx(
Button.Button,
{
variant: "ghost",
className: "lb-thread-show-more-button",
onClick: () => setShowAllComments(true),
children: $.THREAD_SHOW_MORE_COMMENTS(hiddenComments.count)
}
)
},
`${comment.id}-show-more`
);
}
if (isHidden) {
return null;
}
const children = /* @__PURE__ */ jsxRuntime.jsx(
Comment.Comment,
{
overrides: overrides$1,
className: "lb-thread-comment",
"data-unread": isUnread ? "" : void 0,
comment,
indentContent: indentCommentContent,
showDeleted: showDeletedComments,
showActions,
showReactions,
showAttachments,
showComposerFormattingControls,
onCommentEdit,
onCommentDelete: handleCommentDelete,
onAuthorClick,
onMentionClick,
onAttachmentClick,
components,
autoMarkReadThreadId: index === lastCommentIndex && isUnread ? thread.id : void 0,
actionsClassName: isFirstComment ? "lb-thread-actions" : void 0,
actions: isFirstComment && showResolveAction ? /* @__PURE__ */ jsxRuntime.jsx(
Tooltip.Tooltip,
{
content: thread.resolved ? $.THREAD_UNRESOLVE : $.THREAD_RESOLVE,
children: /* @__PURE__ */ jsxRuntime.jsx(
TogglePrimitive__namespace.Root,
{
pressed: thread.resolved,
onPressedChange: handleResolvedChange,
asChild: true,
children: /* @__PURE__ */ jsxRuntime.jsx(
Button.Button,
{
className: "lb-comment-action",
onClick: stopPropagation,
"aria-label": thread.resolved ? $.THREAD_UNRESOLVE : $.THREAD_RESOLVE,
icon: thread.resolved ? /* @__PURE__ */ jsxRuntime.jsx(CheckCircleFill.CheckCircleFillIcon, {}) : /* @__PURE__ */ jsxRuntime.jsx(CheckCircle.CheckCircleIcon, {}),
disabled: !canComment
}
)
}
)
}
) : null,
dropdownItems: ({ children: children2 }) => {
const threadDropdownItems = isFirstComment ? /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: /* @__PURE__ */ jsxRuntime.jsx(
Comment.Comment.DropdownItem,
{
onSelect: handleSubscribeChange,
icon: subscriptionStatus === "subscribed" ? /* @__PURE__ */ jsxRuntime.jsx(BellCrossed.BellCrossedIcon, {}) : /* @__PURE__ */ jsxRuntime.jsx(Bell.BellIcon, {}),
children: subscriptionStatus === "subscribed" ? $.THREAD_UNSUBSCRIBE : $.THREAD_SUBSCRIBE
}
) }) : null;
if (typeof commentDropdownItems === "function") {
return commentDropdownItems({
children: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
threadDropdownItems,
children2
] }),
comment
});
}
return threadDropdownItems || commentDropdownItems || children2 ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
threadDropdownItems,
children2,
commentDropdownItems
] }) : null;
}
},
comment.id
);
return index === newIndicatorIndex && newIndicatorIndex !== firstCommentIndex && newIndicatorIndex <= lastCommentIndex ? /* @__PURE__ */ jsxRuntime.jsxs(react.Fragment, { children: [
/* @__PURE__ */ jsxRuntime.jsx(
"div",
{
className: "lb-thread-new-indicator",
"aria-label": $.THREAD_NEW_INDICATOR_DESCRIPTION,
children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "lb-thread-new-indicator-label", children: [
/* @__PURE__ */ jsxRuntime.jsx(ArrowDown.ArrowDownIcon, { className: "lb-thread-new-indicator-label-icon" }),
$.THREAD_NEW_INDICATOR
] })
}
),
children
] }, comment.id) : children;
}) }),
showComposer && /* @__PURE__ */ jsxRuntime.jsx(
Composer.Composer,
{
className: "lb-thread-composer",
threadId: thread.id,
defaultCollapsed: showComposer === "collapsed" ? true : void 0,
showAttachments,
showFormattingControls: showComposerFormattingControls,
onComposerSubmit,
blurOnSubmit: blurComposerOnSubmit,
overrides: {
COMPOSER_PLACEHOLDER: $.THREAD_COMPOSER_PLACEHOLDER,
COMPOSER_SEND: $.THREAD_COMPOSER_SEND,
...overrides$1
},
roomId: thread.roomId
}
)
]
}
) });
}
);
exports.Thread = Thread;
//# sourceMappingURL=Thread.cjs.map