@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.
206 lines (202 loc) • 7.96 kB
JavaScript
"use client";
import { jsx, jsxs } from 'react/jsx-runtime';
import { useThreadSubscription } from '@liveblocks/react';
import { useMarkRoomThreadAsResolved, useMarkRoomThreadAsUnresolved } from '@liveblocks/react/_private';
import * as TogglePrimitive from '@radix-ui/react-toggle';
import { forwardRef, useMemo, useState, useEffect, useCallback, Fragment } from 'react';
import { ArrowDownIcon } from '../icons/ArrowDown.js';
import { ResolveIcon } from '../icons/Resolve.js';
import { ResolvedIcon } from '../icons/Resolved.js';
import { useOverrides } from '../overrides.js';
import { classNames } from '../utils/class-names.js';
import { findLastIndex } from '../utils/find-last-index.js';
import { Comment } from './Comment.js';
import { Composer } from './Composer.js';
import { Button } from './internal/Button.js';
import { Tooltip } from './internal/Tooltip.js';
import { TooltipProvider } from '@radix-ui/react-tooltip';
const Thread = forwardRef(
({
thread,
indentCommentContent = true,
showActions = "hover",
showDeletedComments,
showResolveAction = true,
showReactions = true,
showComposer = "collapsed",
showAttachments = true,
showComposerFormattingControls = true,
onResolvedChange,
onCommentEdit,
onCommentDelete,
onThreadDelete,
onAuthorClick,
onMentionClick,
onAttachmentClick,
onComposerSubmit,
overrides,
className,
...props
}, forwardedRef) => {
const markThreadAsResolved = useMarkRoomThreadAsResolved(thread.roomId);
const markThreadAsUnresolved = useMarkRoomThreadAsUnresolved(thread.roomId);
const $ = useOverrides(overrides);
const firstCommentIndex = useMemo(() => {
return showDeletedComments ? 0 : thread.comments.findIndex((comment) => comment.body);
}, [showDeletedComments, thread.comments]);
const lastCommentIndex = useMemo(() => {
return showDeletedComments ? thread.comments.length - 1 : findLastIndex(thread.comments, (comment) => comment.body);
}, [showDeletedComments, thread.comments]);
const { status: subscriptionStatus, unreadSince } = useThreadSubscription(
thread.id
);
const unreadIndex = 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] = useState();
const newIndicatorIndex = newIndex === void 0 ? unreadIndex : newIndex;
useEffect(() => {
if (unreadIndex) {
setNewIndex(
(persistedUnreadIndex) => Math.min(persistedUnreadIndex ?? Infinity, unreadIndex)
);
}
}, [unreadIndex]);
const stopPropagation = useCallback((event) => {
event.stopPropagation();
}, []);
const handleResolvedChange = useCallback(
(resolved) => {
onResolvedChange?.(resolved);
if (resolved) {
markThreadAsResolved(thread.id);
} else {
markThreadAsUnresolved(thread.id);
}
},
[
markThreadAsResolved,
markThreadAsUnresolved,
onResolvedChange,
thread.id
]
);
const handleCommentDelete = useCallback(
(comment) => {
onCommentDelete?.(comment);
const filteredComments = thread.comments.filter(
(comment2) => comment2.body
);
if (filteredComments.length <= 1) {
onThreadDelete?.(thread);
}
},
[onCommentDelete, onThreadDelete, thread]
);
return /* @__PURE__ */ jsx(TooltipProvider, {
children: /* @__PURE__ */ jsxs("div", {
className: classNames(
"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__ */ jsx("div", {
className: "lb-thread-comments",
children: thread.comments.map((comment, index) => {
const isFirstComment = index === firstCommentIndex;
const isUnread = unreadIndex !== void 0 && index >= unreadIndex;
const children = /* @__PURE__ */ jsx(Comment, {
overrides,
className: "lb-thread-comment",
"data-unread": isUnread ? "" : void 0,
comment,
indentContent: indentCommentContent,
showDeleted: showDeletedComments,
showActions,
showReactions,
showAttachments,
showComposerFormattingControls,
onCommentEdit,
onCommentDelete: handleCommentDelete,
onAuthorClick,
onMentionClick,
onAttachmentClick,
autoMarkReadThreadId: index === lastCommentIndex && isUnread ? thread.id : void 0,
additionalActionsClassName: isFirstComment ? "lb-thread-actions" : void 0,
additionalActions: isFirstComment && showResolveAction ? /* @__PURE__ */ jsx(Tooltip, {
content: thread.resolved ? $.THREAD_UNRESOLVE : $.THREAD_RESOLVE,
children: /* @__PURE__ */ jsx(TogglePrimitive.Root, {
pressed: thread.resolved,
onPressedChange: handleResolvedChange,
asChild: true,
children: /* @__PURE__ */ jsx(Button, {
className: "lb-comment-action",
onClick: stopPropagation,
"aria-label": thread.resolved ? $.THREAD_UNRESOLVE : $.THREAD_RESOLVE,
icon: thread.resolved ? /* @__PURE__ */ jsx(ResolvedIcon, {}) : /* @__PURE__ */ jsx(ResolveIcon, {})
})
})
}) : null
}, comment.id);
return index === newIndicatorIndex && newIndicatorIndex !== firstCommentIndex && newIndicatorIndex <= lastCommentIndex ? /* @__PURE__ */ jsxs(Fragment, {
children: [
/* @__PURE__ */ jsx("div", {
className: "lb-thread-new-indicator",
"aria-label": $.THREAD_NEW_INDICATOR_DESCRIPTION,
children: /* @__PURE__ */ jsxs("span", {
className: "lb-thread-new-indicator-label",
children: [
/* @__PURE__ */ jsx(ArrowDownIcon, {
className: "lb-thread-new-indicator-label-icon"
}),
$.THREAD_NEW_INDICATOR
]
})
}),
children
]
}, comment.id) : children;
})
}),
showComposer && /* @__PURE__ */ jsx(Composer, {
className: "lb-thread-composer",
threadId: thread.id,
defaultCollapsed: showComposer === "collapsed" ? true : void 0,
showAttachments,
showFormattingControls: showComposerFormattingControls,
onComposerSubmit,
overrides: {
COMPOSER_PLACEHOLDER: $.THREAD_COMPOSER_PLACEHOLDER,
COMPOSER_SEND: $.THREAD_COMPOSER_SEND,
...overrides
},
roomId: thread.roomId
})
]
})
});
}
);
export { Thread };
//# sourceMappingURL=Thread.js.map