@liveblocks/react-ui 3.15.0-components1 → 3.15.0-thread2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_private/index.cjs +7 -1
- package/dist/_private/index.cjs.map +1 -1
- package/dist/_private/index.d.cts +10 -4
- package/dist/_private/index.d.ts +10 -4
- package/dist/_private/index.js +3 -1
- package/dist/_private/index.js.map +1 -1
- package/dist/components/AiChat.cjs +10 -2
- package/dist/components/AiChat.cjs.map +1 -1
- package/dist/components/AiChat.js +10 -2
- package/dist/components/AiChat.js.map +1 -1
- package/dist/components/Comment.cjs +277 -232
- package/dist/components/Comment.cjs.map +1 -1
- package/dist/components/Comment.js +263 -237
- package/dist/components/Comment.js.map +1 -1
- package/dist/components/Composer.cjs +4 -2
- package/dist/components/Composer.cjs.map +1 -1
- package/dist/components/Composer.js +5 -3
- package/dist/components/Composer.js.map +1 -1
- package/dist/components/InboxNotification.cjs +6 -4
- package/dist/components/InboxNotification.cjs.map +1 -1
- package/dist/components/InboxNotification.js +7 -5
- package/dist/components/InboxNotification.js.map +1 -1
- package/dist/components/Thread.cjs +71 -45
- package/dist/components/Thread.cjs.map +1 -1
- package/dist/components/Thread.js +56 -49
- package/dist/components/Thread.js.map +1 -1
- package/dist/components/internal/AiComposer.cjs +2 -1
- package/dist/components/internal/AiComposer.cjs.map +1 -1
- package/dist/components/internal/AiComposer.js +2 -1
- package/dist/components/internal/AiComposer.js.map +1 -1
- package/dist/components/internal/CodeBlock.cjs +2 -1
- package/dist/components/internal/CodeBlock.cjs.map +1 -1
- package/dist/components/internal/CodeBlock.js +2 -1
- package/dist/components/internal/CodeBlock.js.map +1 -1
- package/dist/components/internal/Dropdown.cjs +28 -7
- package/dist/components/internal/Dropdown.cjs.map +1 -1
- package/dist/components/internal/Dropdown.js +7 -7
- package/dist/components/internal/Dropdown.js.map +1 -1
- package/dist/components/internal/EmojiPicker.cjs +27 -6
- package/dist/components/internal/EmojiPicker.cjs.map +1 -1
- package/dist/components/internal/EmojiPicker.js +6 -6
- package/dist/components/internal/EmojiPicker.js.map +1 -1
- package/dist/components/internal/List.cjs +2 -2
- package/dist/components/internal/List.cjs.map +1 -1
- package/dist/components/internal/List.js +2 -2
- package/dist/components/internal/List.js.map +1 -1
- package/dist/components/internal/Tooltip.cjs +28 -7
- package/dist/components/internal/Tooltip.cjs.map +1 -1
- package/dist/components/internal/Tooltip.js +7 -7
- package/dist/components/internal/Tooltip.js.map +1 -1
- package/dist/index.cjs +0 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +197 -213
- package/dist/index.d.ts +197 -213
- package/dist/index.js +0 -6
- package/dist/index.js.map +1 -1
- package/dist/primitives/AiComposer/index.cjs +4 -5
- package/dist/primitives/AiComposer/index.cjs.map +1 -1
- package/dist/primitives/AiComposer/index.js +4 -5
- package/dist/primitives/AiComposer/index.js.map +1 -1
- package/dist/primitives/AiMessage/index.cjs +2 -2
- package/dist/primitives/AiMessage/index.cjs.map +1 -1
- package/dist/primitives/AiMessage/index.js +2 -2
- package/dist/primitives/AiMessage/index.js.map +1 -1
- package/dist/primitives/Collapsible/index.cjs +4 -4
- package/dist/primitives/Collapsible/index.cjs.map +1 -1
- package/dist/primitives/Collapsible/index.js +4 -4
- package/dist/primitives/Collapsible/index.js.map +1 -1
- package/dist/primitives/Comment/index.cjs +4 -4
- package/dist/primitives/Comment/index.cjs.map +1 -1
- package/dist/primitives/Comment/index.js +4 -4
- package/dist/primitives/Comment/index.js.map +1 -1
- package/dist/primitives/Composer/index.cjs +35 -23
- package/dist/primitives/Composer/index.cjs.map +1 -1
- package/dist/primitives/Composer/index.js +16 -23
- package/dist/primitives/Composer/index.js.map +1 -1
- package/dist/primitives/Duration.cjs +2 -2
- package/dist/primitives/Duration.cjs.map +1 -1
- package/dist/primitives/Duration.js +2 -2
- package/dist/primitives/Duration.js.map +1 -1
- package/dist/primitives/FileSize.cjs +2 -2
- package/dist/primitives/FileSize.cjs.map +1 -1
- package/dist/primitives/FileSize.js +2 -2
- package/dist/primitives/FileSize.js.map +1 -1
- package/dist/primitives/Markdown.cjs +2 -2
- package/dist/primitives/Markdown.cjs.map +1 -1
- package/dist/primitives/Markdown.js +2 -2
- package/dist/primitives/Markdown.js.map +1 -1
- package/dist/primitives/Timestamp.cjs +2 -2
- package/dist/primitives/Timestamp.cjs.map +1 -1
- package/dist/primitives/Timestamp.js +2 -2
- package/dist/primitives/Timestamp.js.map +1 -1
- package/dist/utils/Portal.cjs +2 -2
- package/dist/utils/Portal.cjs.map +1 -1
- package/dist/utils/Portal.js +2 -2
- package/dist/utils/Portal.js.map +1 -1
- package/dist/utils/use-stable-component.cjs +32 -0
- package/dist/utils/use-stable-component.cjs.map +1 -0
- package/dist/utils/use-stable-component.js +30 -0
- package/dist/utils/use-stable-component.js.map +1 -0
- package/dist/version.cjs +1 -1
- package/dist/version.cjs.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +10 -6
- package/src/styles/dark/index.css +1 -1
- package/src/styles/index.css +4 -252
- package/styles/dark/attributes.css +1 -1
- package/styles/dark/attributes.css.map +1 -1
- package/styles/dark/media-query.css +1 -1
- package/styles/dark/media-query.css.map +1 -1
- package/styles.css +1 -1
- package/styles.css.map +1 -1
- package/dist/components/AvatarStack.cjs +0 -115
- package/dist/components/AvatarStack.cjs.map +0 -1
- package/dist/components/AvatarStack.js +0 -113
- package/dist/components/AvatarStack.js.map +0 -1
- package/dist/components/CommentPin.cjs +0 -27
- package/dist/components/CommentPin.cjs.map +0 -1
- package/dist/components/CommentPin.js +0 -25
- package/dist/components/CommentPin.js.map +0 -1
- package/dist/components/Cursor.cjs +0 -40
- package/dist/components/Cursor.cjs.map +0 -1
- package/dist/components/Cursor.js +0 -38
- package/dist/components/Cursor.js.map +0 -1
- package/dist/components/Cursors.cjs +0 -252
- package/dist/components/Cursors.cjs.map +0 -1
- package/dist/components/Cursors.js +0 -250
- package/dist/components/Cursors.js.map +0 -1
- package/dist/components/FloatingComposer.cjs +0 -82
- package/dist/components/FloatingComposer.cjs.map +0 -1
- package/dist/components/FloatingComposer.js +0 -80
- package/dist/components/FloatingComposer.js.map +0 -1
- package/dist/components/FloatingThread.cjs +0 -82
- package/dist/components/FloatingThread.cjs.map +0 -1
- package/dist/components/FloatingThread.js +0 -80
- package/dist/components/FloatingThread.js.map +0 -1
- package/dist/utils/animation-loop.cjs +0 -44
- package/dist/utils/animation-loop.cjs.map +0 -1
- package/dist/utils/animation-loop.js +0 -42
- package/dist/utils/animation-loop.js.map +0 -1
- package/dist/utils/use-pre-resolve-user.cjs +0 -18
- package/dist/utils/use-pre-resolve-user.cjs.map +0 -1
- package/dist/utils/use-pre-resolve-user.js +0 -16
- package/dist/utils/use-pre-resolve-user.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Thread.cjs","sources":["../../src/components/Thread.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n type BaseMetadata,\n type CommentData,\n type DCM,\n type DTM,\n Permission,\n type ThreadData,\n} from \"@liveblocks/core\";\nimport { findLastIndex } from \"@liveblocks/core\";\nimport {\n useMarkRoomThreadAsResolved,\n useMarkRoomThreadAsUnresolved,\n useRoomPermissions,\n useRoomThreadSubscription,\n} from \"@liveblocks/react/_private\";\nimport { Toggle as TogglePrimitive } from \"radix-ui\";\nimport type {\n ComponentPropsWithoutRef,\n ForwardedRef,\n PropsWithChildren,\n ReactNode,\n RefAttributes,\n SyntheticEvent,\n} from \"react\";\nimport {\n forwardRef,\n Fragment,\n useCallback,\n useEffect,\n useMemo,\n useState,\n} from \"react\";\n\nimport type { GlobalComponents } from \"../components\";\nimport { ArrowDownIcon } from \"../icons/ArrowDown\";\nimport { BellIcon } from \"../icons/Bell\";\nimport { BellCrossedIcon } from \"../icons/BellCrossed\";\nimport { CheckCircleIcon } from \"../icons/CheckCircle\";\nimport { CheckCircleFillIcon } from \"../icons/CheckCircleFill\";\nimport type {\n CommentOverrides,\n ComposerOverrides,\n GlobalOverrides,\n ThreadOverrides,\n} from \"../overrides\";\nimport { useOverrides } from \"../overrides\";\nimport { cn } from \"../utils/cn\";\nimport type { CommentProps } from \"./Comment\";\nimport { Comment } from \"./Comment\";\nimport type { ComposerProps } from \"./Composer\";\nimport { Composer } from \"./Composer\";\nimport { Button } from \"./internal/Button\";\nimport { Tooltip, TooltipProvider } from \"./internal/Tooltip\";\n\nexport interface ThreadProps<\n TM extends BaseMetadata = DTM,\n CM extends BaseMetadata = DCM,\n> extends ComponentPropsWithoutRef<\"div\"> {\n /**\n * The thread to display.\n */\n thread: ThreadData<TM, CM>;\n\n /**\n * How to show or hide the composer to reply to the thread.\n */\n showComposer?: boolean | \"collapsed\";\n\n /**\n * Whether to show the action to resolve the thread.\n */\n showResolveAction?: boolean;\n\n /**\n * How to show or hide the actions.\n */\n showActions?: CommentProps[\"showActions\"];\n\n /**\n * Whether to show reactions.\n */\n showReactions?: CommentProps[\"showReactions\"];\n\n /**\n * Whether to show the composer's formatting controls.\n */\n showComposerFormattingControls?: ComposerProps[\"showFormattingControls\"];\n\n /**\n * Add (or change) items to display in a comment's dropdown.\n */\n commentDropdownItems?:\n | ReactNode\n | ((props: PropsWithChildren<{ comment: CommentData }>) => ReactNode);\n\n /**\n * The maximum number of comments to show.\n *\n * The first and last comments are always shown and by default if some comments\n * are hidden, only the first comment will be shown before the \"show more\" button\n * and after it will be shown all the newest comments to fit the limit set.\n *\n * It's possible to customize this by setting `maxVisibleComments` to an object:\n *\n * @example\n * // Only show the last comment, and all the older ones to fit the limit.\n * <Thread maxVisibleComments={{ max: 5, show: \"oldest\" }} />\n *\n * @example\n * // Show as many old comments as new ones to fit the limit.\n * <Thread maxVisibleComments={{ max: 5, show: \"both\" }} />\n */\n maxVisibleComments?:\n | number\n | { max: number; show: \"oldest\" | \"both\" | \"newest\" };\n\n /**\n * Whether to blur the composer editor when the composer is submitted.\n */\n blurComposerOnSubmit?: ComposerProps[\"blurOnSubmit\"];\n\n /**\n * Whether to indent the comments' content.\n */\n indentCommentContent?: CommentProps[\"indentContent\"];\n\n /**\n * Whether to show deleted comments.\n */\n showDeletedComments?: CommentProps[\"showDeleted\"];\n\n /**\n * Whether to show attachments.\n */\n showAttachments?: boolean;\n\n /**\n * The event handler called when changing the resolved status.\n */\n onResolvedChange?: (resolved: boolean) => void;\n\n /**\n * The event handler called when a comment is edited.\n */\n onCommentEdit?: CommentProps[\"onCommentEdit\"];\n\n /**\n * The event handler called when a comment is deleted.\n */\n onCommentDelete?: CommentProps[\"onCommentDelete\"];\n\n /**\n * The event handler called when the thread is deleted.\n * A thread is deleted when all its comments are deleted.\n */\n onThreadDelete?: (thread: ThreadData<TM, CM>) => void;\n\n /**\n * The event handler called when clicking on a comment's author.\n */\n onAuthorClick?: CommentProps[\"onAuthorClick\"];\n\n /**\n * The event handler called when clicking on a mention.\n */\n onMentionClick?: CommentProps[\"onMentionClick\"];\n\n /**\n * The event handler called when clicking on a comment's attachment.\n */\n onAttachmentClick?: CommentProps[\"onAttachmentClick\"];\n\n /**\n * The event handler called when the composer is submitted.\n */\n onComposerSubmit?: ComposerProps[\"onComposerSubmit\"];\n\n /**\n * Whether to focus the composer on mount.\n */\n autoFocus?: ComposerProps[\"autoFocus\"];\n\n /**\n * Override the component's strings.\n */\n overrides?: Partial<\n GlobalOverrides & ThreadOverrides & CommentOverrides & ComposerOverrides\n >;\n\n /**\n * Override the component's components.\n */\n components?: Partial<GlobalComponents>;\n}\n\n/**\n * Displays a thread of comments, with a composer to reply\n * to it.\n *\n * @example\n * <>\n * {threads.map((thread) => (\n * <Thread key={thread.id} thread={thread} />\n * ))}\n * </>\n */\nexport const Thread = forwardRef(\n <TM extends BaseMetadata = DTM, CM extends BaseMetadata = DCM>(\n {\n thread,\n indentCommentContent = true,\n showActions = \"hover\",\n showDeletedComments,\n showResolveAction = true,\n showReactions = true,\n showComposer = \"collapsed\",\n showAttachments = true,\n showComposerFormattingControls = true,\n maxVisibleComments,\n commentDropdownItems,\n onResolvedChange,\n onCommentEdit,\n onCommentDelete,\n onThreadDelete,\n onAuthorClick,\n onMentionClick,\n onAttachmentClick,\n onComposerSubmit,\n blurComposerOnSubmit,\n autoFocus,\n overrides,\n components,\n className,\n ...props\n }: ThreadProps<TM, CM>,\n forwardedRef: ForwardedRef<HTMLDivElement>\n ) => {\n const markThreadAsResolved = useMarkRoomThreadAsResolved(thread.roomId);\n const markThreadAsUnresolved = useMarkRoomThreadAsUnresolved(thread.roomId);\n const $ = useOverrides(overrides);\n const [showAllComments, setShowAllComments] = useState(false);\n const firstCommentIndex = useMemo(() => {\n return showDeletedComments\n ? 0\n : thread.comments.findIndex((comment) => comment.body);\n }, [showDeletedComments, thread.comments]);\n const lastCommentIndex = useMemo(() => {\n return showDeletedComments\n ? thread.comments.length - 1\n : findLastIndex(thread.comments, (comment) => Boolean(comment.body));\n }, [showDeletedComments, thread.comments]);\n const commentsCount = useMemo(() => {\n return showDeletedComments\n ? thread.comments.length\n : thread.comments.filter((comment) => comment.body).length;\n }, [showDeletedComments, thread.comments]);\n const hiddenComments = useMemo(() => {\n const maxVisibleCommentsCount =\n typeof maxVisibleComments === \"number\"\n ? maxVisibleComments\n : maxVisibleComments?.max;\n const visibleCommentsShow =\n (typeof maxVisibleComments === \"object\"\n ? maxVisibleComments?.show\n : undefined) ?? \"newest\";\n\n // If we explicitly want to show all comments or there's no limit set,\n // no need to hide any comments.\n if (showAllComments || maxVisibleCommentsCount === undefined) {\n return;\n }\n\n const comments = thread.comments\n .map((comment, index) => ({ comment, index }))\n .filter(({ comment }) => showDeletedComments || comment.body);\n\n // There aren't enough comments so no need to hide any.\n if (comments.length <= Math.max(maxVisibleCommentsCount, 2)) {\n return;\n }\n\n const firstVisibleComment = comments[0]!;\n const lastVisibleComment = comments[comments.length - 1]!;\n\n // Always show the first and last comments even if the limit is set to lower than 2.\n if (maxVisibleCommentsCount <= 2) {\n const firstHiddenCommentIndex =\n comments[1]?.index ?? firstVisibleComment.index;\n const lastHiddenCommentIndex =\n comments[comments.length - 2]?.index ?? lastVisibleComment.index;\n\n return {\n firstIndex: firstHiddenCommentIndex,\n lastIndex: lastHiddenCommentIndex,\n count: comments.slice(1, comments.length - 1).length,\n };\n }\n\n const remainingVisibleCommentsCount = maxVisibleCommentsCount - 2;\n\n // Split the remaining visible comments before, after, or equally.\n const beforeVisibleCommentsCount =\n visibleCommentsShow === \"oldest\"\n ? remainingVisibleCommentsCount\n : visibleCommentsShow === \"newest\"\n ? 0\n : Math.floor(remainingVisibleCommentsCount / 2);\n const afterVisibleCommentsCount =\n visibleCommentsShow === \"oldest\"\n ? 0\n : visibleCommentsShow === \"newest\"\n ? remainingVisibleCommentsCount\n : Math.ceil(remainingVisibleCommentsCount / 2);\n\n // The first comment is always visible so `+ 1` to skip it.\n const firstHiddenComment = comments[1 + beforeVisibleCommentsCount];\n // The last comment is always visible so `- 2` to skip it.\n const lastHiddenComment =\n comments[comments.length - 2 - afterVisibleCommentsCount];\n\n // There aren't any comments to hide besides the first and last ones.\n if (\n !firstHiddenComment ||\n !lastHiddenComment ||\n firstHiddenComment.index > lastHiddenComment.index\n ) {\n return;\n }\n\n return {\n firstIndex: firstHiddenComment.index,\n lastIndex: lastHiddenComment.index,\n count: thread.comments\n .slice(firstHiddenComment.index, lastHiddenComment.index + 1)\n .filter((comment) => showDeletedComments || comment.body).length,\n };\n }, [\n maxVisibleComments,\n showAllComments,\n showDeletedComments,\n thread.comments,\n ]);\n const {\n status: subscriptionStatus,\n unreadSince,\n subscribe,\n unsubscribe,\n } = useRoomThreadSubscription(thread.roomId, thread.id);\n const unreadIndex = useMemo(() => {\n // The user is not subscribed to this thread.\n if (subscriptionStatus !== \"subscribed\") {\n return;\n }\n\n // The user hasn't read the thread yet, so all comments are unread.\n if (unreadSince === null) {\n return firstCommentIndex;\n }\n\n // The user has read the thread, so we find the first unread comment.\n const unreadIndex = thread.comments.findIndex(\n (comment) =>\n (showDeletedComments ? true : comment.body) &&\n comment.createdAt > unreadSince\n );\n\n return unreadIndex >= 0 && unreadIndex < thread.comments.length\n ? unreadIndex\n : undefined;\n }, [\n firstCommentIndex,\n showDeletedComments,\n subscriptionStatus,\n thread.comments,\n unreadSince,\n ]);\n const [newIndex, setNewIndex] = useState<number>();\n const newIndicatorIndex = newIndex === undefined ? unreadIndex : newIndex;\n\n useEffect(() => {\n if (unreadIndex) {\n // Keep the \"new\" indicator at the lowest unread index.\n setNewIndex((persistedUnreadIndex) =>\n Math.min(persistedUnreadIndex ?? Infinity, unreadIndex)\n );\n }\n }, [unreadIndex]);\n\n const permissions = useRoomPermissions(thread.roomId);\n const canComment =\n permissions.size > 0\n ? permissions.has(Permission.CommentsWrite) ||\n permissions.has(Permission.Write)\n : true;\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n const handleResolvedChange = useCallback(\n (resolved: boolean) => {\n onResolvedChange?.(resolved);\n\n if (resolved) {\n markThreadAsResolved(thread.id);\n } else {\n markThreadAsUnresolved(thread.id);\n }\n },\n [\n markThreadAsResolved,\n markThreadAsUnresolved,\n onResolvedChange,\n thread.id,\n ]\n );\n\n const handleCommentDelete = useCallback(\n (comment: CommentData) => {\n onCommentDelete?.(comment);\n\n const filteredComments = thread.comments.filter(\n (comment) => comment.body\n );\n\n if (filteredComments.length <= 1) {\n onThreadDelete?.(thread);\n }\n },\n [onCommentDelete, onThreadDelete, thread]\n );\n\n const handleSubscribeChange = useCallback(() => {\n if (subscriptionStatus === \"subscribed\") {\n unsubscribe();\n } else {\n subscribe();\n }\n }, [subscriptionStatus, subscribe, unsubscribe]);\n\n let currentCommentIndex = 0;\n\n return (\n <TooltipProvider>\n <div\n className={cn(\n \"lb-root lb-thread\",\n showActions === \"hover\" && \"lb-thread:show-actions-hover\",\n className\n )}\n data-resolved={thread.resolved ? \"\" : undefined}\n data-unread={unreadIndex !== undefined ? \"\" : undefined}\n dir={$.dir}\n {...props}\n ref={forwardedRef}\n >\n <div className=\"lb-thread-comments\" role=\"feed\">\n {thread.comments.map((comment, index) => {\n const isNonDeletedComment = showDeletedComments || comment.body;\n const isFirstComment = index === firstCommentIndex;\n const isUnread =\n unreadIndex !== undefined && index >= unreadIndex;\n const isHidden =\n hiddenComments &&\n index >= hiddenComments.firstIndex &&\n index <= hiddenComments.lastIndex;\n const isFirstHiddenComment =\n isHidden && index === hiddenComments.firstIndex;\n\n if (isFirstHiddenComment) {\n return (\n <div\n key={`${comment.id}:show-more`}\n className=\"lb-thread-show-more\"\n >\n <Button\n variant=\"ghost\"\n className=\"lb-thread-show-more-button\"\n onClick={() => setShowAllComments(true)}\n >\n {$.THREAD_SHOW_MORE_COMMENTS(hiddenComments.count)}\n </Button>\n </div>\n );\n }\n\n if (isNonDeletedComment) {\n currentCommentIndex++;\n }\n\n if (isHidden) {\n return null;\n }\n\n const children = (\n <Comment\n key={comment.id}\n tabIndex={0}\n aria-posinset={currentCommentIndex}\n aria-setsize={commentsCount}\n overrides={overrides}\n className=\"lb-thread-comment\"\n data-unread={isUnread ? \"\" : undefined}\n comment={comment}\n indentContent={indentCommentContent}\n showDeleted={showDeletedComments}\n showActions={showActions}\n showReactions={showReactions}\n showAttachments={showAttachments}\n showComposerFormattingControls={\n showComposerFormattingControls\n }\n onCommentEdit={onCommentEdit}\n onCommentDelete={handleCommentDelete}\n onAuthorClick={onAuthorClick}\n onMentionClick={onMentionClick}\n onAttachmentClick={onAttachmentClick}\n components={components}\n autoMarkReadThreadId={\n index === lastCommentIndex && isUnread\n ? thread.id\n : undefined\n }\n actionsClassName={\n isFirstComment ? \"lb-thread-actions\" : undefined\n }\n actions={\n isFirstComment && showResolveAction ? (\n <Tooltip\n content={\n thread.resolved\n ? $.THREAD_UNRESOLVE\n : $.THREAD_RESOLVE\n }\n >\n <TogglePrimitive.Root\n pressed={thread.resolved}\n onPressedChange={handleResolvedChange}\n asChild\n >\n <Button\n className=\"lb-comment-action\"\n onClick={stopPropagation}\n aria-label={\n thread.resolved\n ? $.THREAD_UNRESOLVE\n : $.THREAD_RESOLVE\n }\n icon={\n thread.resolved ? (\n <CheckCircleFillIcon />\n ) : (\n <CheckCircleIcon />\n )\n }\n disabled={!canComment}\n />\n </TogglePrimitive.Root>\n </Tooltip>\n ) : null\n }\n dropdownItems={({ children }) => {\n const threadDropdownItems = isFirstComment ? (\n <>\n <Comment.DropdownItem\n onSelect={handleSubscribeChange}\n icon={\n subscriptionStatus === \"subscribed\" ? (\n <BellCrossedIcon />\n ) : (\n <BellIcon />\n )\n }\n >\n {subscriptionStatus === \"subscribed\"\n ? $.THREAD_UNSUBSCRIBE\n : $.THREAD_SUBSCRIBE}\n </Comment.DropdownItem>\n </>\n ) : null;\n\n if (typeof commentDropdownItems === \"function\") {\n return commentDropdownItems({\n children: (\n <>\n {threadDropdownItems}\n {children}\n </>\n ),\n comment,\n });\n }\n\n return threadDropdownItems ||\n commentDropdownItems ||\n children ? (\n <>\n {threadDropdownItems}\n {children}\n {commentDropdownItems}\n </>\n ) : null;\n }}\n />\n );\n\n return index === newIndicatorIndex &&\n newIndicatorIndex !== firstCommentIndex &&\n newIndicatorIndex <= lastCommentIndex ? (\n <Fragment key={comment.id}>\n <div\n className=\"lb-thread-new-indicator\"\n aria-label={$.THREAD_NEW_INDICATOR_DESCRIPTION}\n >\n <span className=\"lb-thread-new-indicator-label\">\n <ArrowDownIcon className=\"lb-thread-new-indicator-label-icon\" />\n {$.THREAD_NEW_INDICATOR}\n </span>\n </div>\n {children}\n </Fragment>\n ) : (\n children\n );\n })}\n </div>\n {showComposer && (\n <Composer\n className=\"lb-thread-composer\"\n threadId={thread.id}\n defaultCollapsed={showComposer === \"collapsed\" ? true : undefined}\n showAttachments={showAttachments}\n showFormattingControls={showComposerFormattingControls}\n onComposerSubmit={onComposerSubmit}\n blurOnSubmit={blurComposerOnSubmit}\n overrides={{\n COMPOSER_PLACEHOLDER: $.THREAD_COMPOSER_PLACEHOLDER,\n COMPOSER_SEND: $.THREAD_COMPOSER_SEND,\n ...overrides,\n }}\n roomId={thread.roomId}\n autoFocus={autoFocus}\n />\n )}\n </div>\n </TooltipProvider>\n );\n }\n) as <TM extends BaseMetadata = DTM, CM extends BaseMetadata = DCM>(\n props: ThreadProps<TM, CM> & RefAttributes<HTMLDivElement>\n) => JSX.Element;\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;AAgNO;AAAe;AAElB;AACE;AACuB;AACT;AACd;AACoB;AACJ;AACD;AACG;AACe;AACjC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACG;AAIL;AACA;AACA;AACA;AACA;AACE;AAEuD;AAEzD;AACE;AAEqE;AAEvE;AACE;AAEsD;AAExD;AACE;AAIA;AAOA;AACE;AAAA;AAGF;AAKA;AACE;AAAA;AAGF;AACA;AAGA;AACE;AAEA;AAGA;AAAO;AACO;AACD;AACmC;AAChD;AAGF;AAGA;AAMA;AAQA;AAEA;AAIA;AAKE;AAAA;AAGF;AAAO;AAC0B;AACF;AAG+B;AAC9D;AACC;AACD;AACA;AACA;AACO;AAET;AAAM;AACI;AACR;AACA;AACA;AAEF;AAEE;AACE;AAAA;AAIF;AACE;AAAO;AAIT;AAAoC;AAGZ;AAGxB;AAEI;AACH;AACD;AACA;AACA;AACO;AACP;AAEF;AACA;AAEA;AACE;AAEE;AAAA;AACwD;AACxD;AACF;AAGF;AACA;AAMA;AACE;AAAsB;AAGxB;AAA6B;AAEzB;AAEA;AACE;AAA8B;AAE9B;AAAgC;AAClC;AACF;AACA;AACE;AACA;AACA;AACO;AACT;AAGF;AAA4B;AAExB;AAEA;AAAyC;AAClB;AAGvB;AACE;AAAuB;AACzB;AACF;AACwC;AAG1C;AACE;AACE;AAAY;AAEZ;AAAU;AACZ;AAGF;AAEA;AAEI;AAAC;AAAA;AACY;AACT;AAC2B;AAC3B;AACF;AACsC;AACQ;AACvC;AACH;AACC;AAEL;AAEI;AACA;AACA;AAEA;AAIA;AAGA;AACE;AACE;AAAC;AAAA;AAEW;AAEV;AAAC;AAAA;AACS;AACE;AAC4B;AAEW;AAAA;AACnD;AAAA;AATkB;AAUpB;AAIJ;AACE;AAAA;AAGF;AACE;AAAO;AAGT;AACE;AAAC;AAAA;AAEW;AACK;AACD;AACd;AACU;AACmB;AAC7B;AACe;AACF;AACb;AACA;AACA;AACA;AAGA;AACiB;AACjB;AACA;AACA;AACA;AAIM;AAGmC;AAIrC;AAAC;AAAA;AAIS;AAGR;AAAiB;AAAhB;AACiB;AACC;AACV;AAEP;AAAC;AAAA;AACW;AACD;AAID;AAMa;AAGV;AAAA;AACb;AAAA;AACF;AAAA;AAEA;AAGJ;AAEI;AAAS;AAAR;AACW;AAKI;AAMR;AAAA;AAKZ;AACE;AAA4B;AAGrB;AAAA;AACA;AACH;AAEF;AACD;AAGH;AAIK;AAAA;AACA;AACA;AAED;AACN;AAAA;AA1Ga;AA8GjB;AAII;AAAA;AAAC;AAAA;AACW;AACI;AAGZ;AAA8D;AAC3D;AACL;AAAA;AACF;AACC;AAGH;AAGN;AAEE;AAAC;AAAA;AACW;AACO;AACuC;AACxD;AACwB;AACxB;AACc;AACH;AACe;AACP;AACd;AACL;AACe;AACf;AAAA;AACF;AAAA;AAAA;AAGN;AAGN;;"}
|
|
1
|
+
{"version":3,"file":"Thread.cjs","sources":["../../src/components/Thread.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n type BaseMetadata,\n type CommentData,\n type DCM,\n type DTM,\n findLastIndex,\n Permission,\n type ThreadData,\n} from \"@liveblocks/core\";\nimport {\n useMarkRoomThreadAsRead,\n useMarkRoomThreadAsResolved,\n useMarkRoomThreadAsUnresolved,\n useRoomPermissions,\n useRoomThreadSubscription,\n} from \"@liveblocks/react/_private\";\nimport * as TogglePrimitive from \"@radix-ui/react-toggle\";\nimport {\n type ComponentPropsWithoutRef,\n type ComponentType,\n type ForwardedRef,\n forwardRef,\n Fragment,\n type PropsWithChildren,\n type ReactNode,\n type RefAttributes,\n type SyntheticEvent,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\n\nimport type { GlobalComponents } from \"../components\";\nimport { ArrowDownIcon } from \"../icons/ArrowDown\";\nimport { BellIcon } from \"../icons/Bell\";\nimport { BellCrossedIcon } from \"../icons/BellCrossed\";\nimport { CheckCircleIcon } from \"../icons/CheckCircle\";\nimport { CheckCircleFillIcon } from \"../icons/CheckCircleFill\";\nimport type {\n CommentOverrides,\n ComposerOverrides,\n GlobalOverrides,\n ThreadOverrides,\n} from \"../overrides\";\nimport { useOverrides } from \"../overrides\";\nimport { cn } from \"../utils/cn\";\nimport { useStableComponent } from \"../utils/use-stable-component\";\nimport { useIntersectionCallback } from \"../utils/use-visible\";\nimport { useWindowFocus } from \"../utils/use-window-focus\";\nimport { Comment as DefaultComment, type CommentProps } from \"./Comment\";\nimport { Composer, type ComposerProps } from \"./Composer\";\nimport { Button } from \"./internal/Button\";\nimport { Tooltip, TooltipProvider } from \"./internal/Tooltip\";\n\n// An invisible marker used to mark a thread as read only when it becomes visible.\nfunction MarkThreadAsReadMarker({ thread }: { thread: ThreadData }) {\n const { id: threadId, roomId } = thread;\n const ref = useRef<HTMLDivElement>(null);\n const markThreadAsRead = useMarkRoomThreadAsRead(roomId);\n const isWindowFocused = useWindowFocus();\n\n useIntersectionCallback(\n ref,\n (isIntersecting) => {\n if (isIntersecting) {\n markThreadAsRead(threadId);\n }\n },\n {\n // The underlying IntersectionObserver is only enabled when the window is focused\n enabled: isWindowFocused,\n }\n );\n\n return (\n <div\n ref={ref}\n style={{ height: 0 }}\n aria-hidden\n data-mark-as-read-marker=\"\"\n />\n );\n}\n\nexport interface ThreadComponents<\n // Future components might reference thread metadata so we declare it\n // already to respect the `TM` → `CM` order used everywhere else.\n _TM extends BaseMetadata = DTM,\n CM extends BaseMetadata = DCM,\n> {\n /**\n * The component used to display comments.\n */\n Comment: ComponentType<CommentProps<CM>>;\n}\n\nexport interface ThreadProps<\n TM extends BaseMetadata = DTM,\n CM extends BaseMetadata = DCM,\n> extends ComponentPropsWithoutRef<\"div\"> {\n /**\n * The thread to display.\n */\n thread: ThreadData<TM, CM>;\n\n /**\n * How to show or hide the composer to reply to the thread.\n */\n showComposer?: boolean | \"collapsed\";\n\n /**\n * Whether to show the action to resolve the thread.\n */\n showResolveAction?: boolean;\n\n /**\n * How to show or hide the actions.\n */\n showActions?: CommentProps[\"showActions\"];\n\n /**\n * Whether to show reactions.\n */\n showReactions?: CommentProps[\"showReactions\"];\n\n /**\n * Whether to show the composer's formatting controls.\n */\n showComposerFormattingControls?: ComposerProps[\"showFormattingControls\"];\n\n /**\n * Add (or change) items to display in a comment's dropdown.\n */\n commentDropdownItems?:\n | ReactNode\n | ((props: PropsWithChildren<{ comment: CommentData<CM> }>) => ReactNode);\n\n /**\n * The maximum number of comments to show.\n *\n * The first and last comments are always shown and by default if some comments\n * are hidden, only the first comment will be shown before the \"show more\" button\n * and after it will be shown all the newest comments to fit the limit set.\n *\n * It's possible to customize this by setting `maxVisibleComments` to an object:\n *\n * @example\n * // Only show the last comment, and all the older ones to fit the limit.\n * <Thread maxVisibleComments={{ max: 5, show: \"oldest\" }} />\n *\n * @example\n * // Show as many old comments as new ones to fit the limit.\n * <Thread maxVisibleComments={{ max: 5, show: \"both\" }} />\n */\n maxVisibleComments?:\n | number\n | { max: number; show: \"oldest\" | \"both\" | \"newest\" };\n\n /**\n * Whether to blur the composer editor when the composer is submitted.\n */\n blurComposerOnSubmit?: ComposerProps[\"blurOnSubmit\"];\n\n /**\n * Whether to indent the comments' content.\n */\n indentCommentContent?: CommentProps[\"indentContent\"];\n\n /**\n * Additional content to display below the thread's comments.\n */\n additionalContent?: ReactNode;\n\n /**\n * Whether to show deleted comments.\n */\n showDeletedComments?: CommentProps[\"showDeleted\"];\n\n /**\n * Whether to show attachments.\n */\n showAttachments?: boolean;\n\n /**\n * The event handler called when changing the resolved status.\n */\n onResolvedChange?: (resolved: boolean) => void;\n\n /**\n * The event handler called when a comment is edited.\n */\n onCommentEdit?: CommentProps[\"onCommentEdit\"];\n\n /**\n * The event handler called when a comment is deleted.\n */\n onCommentDelete?: CommentProps[\"onCommentDelete\"];\n\n /**\n * The event handler called when the thread is deleted.\n * A thread is deleted when all its comments are deleted.\n */\n onThreadDelete?: (thread: ThreadData<TM, CM>) => void;\n\n /**\n * The event handler called when clicking on a comment's author.\n */\n onAuthorClick?: CommentProps[\"onAuthorClick\"];\n\n /**\n * The event handler called when clicking on a mention.\n */\n onMentionClick?: CommentProps[\"onMentionClick\"];\n\n /**\n * The event handler called when clicking on a comment's attachment.\n */\n onAttachmentClick?: CommentProps[\"onAttachmentClick\"];\n\n /**\n * The event handler called when the composer is submitted.\n */\n onComposerSubmit?: ComposerProps[\"onComposerSubmit\"];\n\n /**\n * Override the component's strings.\n */\n overrides?: Partial<\n GlobalOverrides & ThreadOverrides & CommentOverrides & ComposerOverrides\n >;\n\n /**\n * Override the component's components.\n */\n components?: Partial<GlobalComponents & ThreadComponents>;\n}\n\n/**\n * Displays a thread of comments, with a composer to reply\n * to it.\n *\n * @example\n * <>\n * {threads.map((thread) => (\n * <Thread key={thread.id} thread={thread} />\n * ))}\n * </>\n */\nexport const Thread = forwardRef(\n <TM extends BaseMetadata = DTM, CM extends BaseMetadata = DCM>(\n {\n thread,\n indentCommentContent = true,\n showActions = \"hover\",\n showDeletedComments,\n showResolveAction = true,\n showReactions = true,\n showComposer = \"collapsed\",\n showAttachments = true,\n showComposerFormattingControls = true,\n maxVisibleComments,\n commentDropdownItems,\n additionalContent,\n onResolvedChange,\n onCommentEdit,\n onCommentDelete,\n onThreadDelete,\n onAuthorClick,\n onMentionClick,\n onAttachmentClick,\n onComposerSubmit,\n blurComposerOnSubmit,\n overrides,\n components,\n className,\n ...props\n }: ThreadProps<TM, CM>,\n forwardedRef: ForwardedRef<HTMLDivElement>\n ) => {\n const Comment = useStableComponent(components?.Comment, DefaultComment);\n const markThreadAsResolved = useMarkRoomThreadAsResolved(thread.roomId);\n const markThreadAsUnresolved = useMarkRoomThreadAsUnresolved(thread.roomId);\n const $ = useOverrides(overrides);\n const [showAllComments, setShowAllComments] = useState(false);\n const firstCommentIndex = useMemo(() => {\n return showDeletedComments\n ? 0\n : thread.comments.findIndex((comment) => comment.body);\n }, [showDeletedComments, thread.comments]);\n const lastCommentIndex = useMemo(() => {\n return showDeletedComments\n ? thread.comments.length - 1\n : findLastIndex(thread.comments, (comment) => Boolean(comment.body));\n }, [showDeletedComments, thread.comments]);\n const hiddenComments = useMemo(() => {\n const maxVisibleCommentsCount =\n typeof maxVisibleComments === \"number\"\n ? maxVisibleComments\n : maxVisibleComments?.max;\n const visibleCommentsShow =\n (typeof maxVisibleComments === \"object\"\n ? maxVisibleComments?.show\n : undefined) ?? \"newest\";\n\n // If we explicitly want to show all comments or there's no limit set,\n // no need to hide any comments.\n if (showAllComments || maxVisibleCommentsCount === undefined) {\n return;\n }\n\n const comments = thread.comments\n .map((comment, index) => ({ comment, index }))\n .filter(({ comment }) => showDeletedComments || comment.body);\n\n // There aren't enough comments so no need to hide any.\n if (comments.length <= Math.max(maxVisibleCommentsCount, 2)) {\n return;\n }\n\n const firstVisibleComment = comments[0]!;\n const lastVisibleComment = comments[comments.length - 1]!;\n\n // Always show the first and last comments even if the limit is set to lower than 2.\n if (maxVisibleCommentsCount <= 2) {\n const firstHiddenCommentIndex =\n comments[1]?.index ?? firstVisibleComment.index;\n const lastHiddenCommentIndex =\n comments[comments.length - 2]?.index ?? lastVisibleComment.index;\n\n return {\n firstIndex: firstHiddenCommentIndex,\n lastIndex: lastHiddenCommentIndex,\n count: comments.slice(1, comments.length - 1).length,\n };\n }\n\n const remainingVisibleCommentsCount = maxVisibleCommentsCount - 2;\n\n // Split the remaining visible comments before, after, or equally.\n const beforeVisibleCommentsCount =\n visibleCommentsShow === \"oldest\"\n ? remainingVisibleCommentsCount\n : visibleCommentsShow === \"newest\"\n ? 0\n : Math.floor(remainingVisibleCommentsCount / 2);\n const afterVisibleCommentsCount =\n visibleCommentsShow === \"oldest\"\n ? 0\n : visibleCommentsShow === \"newest\"\n ? remainingVisibleCommentsCount\n : Math.ceil(remainingVisibleCommentsCount / 2);\n\n // The first comment is always visible so `+ 1` to skip it.\n const firstHiddenComment = comments[1 + beforeVisibleCommentsCount];\n // The last comment is always visible so `- 2` to skip it.\n const lastHiddenComment =\n comments[comments.length - 2 - afterVisibleCommentsCount];\n\n // There aren't any comments to hide besides the first and last ones.\n if (\n !firstHiddenComment ||\n !lastHiddenComment ||\n firstHiddenComment.index > lastHiddenComment.index\n ) {\n return;\n }\n\n return {\n firstIndex: firstHiddenComment.index,\n lastIndex: lastHiddenComment.index,\n count: thread.comments\n .slice(firstHiddenComment.index, lastHiddenComment.index + 1)\n .filter((comment) => showDeletedComments || comment.body).length,\n };\n }, [\n maxVisibleComments,\n showAllComments,\n showDeletedComments,\n thread.comments,\n ]);\n const {\n status: subscriptionStatus,\n unreadSince,\n subscribe,\n unsubscribe,\n } = useRoomThreadSubscription(thread.roomId, thread.id);\n const unreadIndex = useMemo(() => {\n // The user is not subscribed to this thread.\n if (subscriptionStatus !== \"subscribed\") {\n return;\n }\n\n // The user hasn't read the thread yet, so all comments are unread.\n if (unreadSince === null) {\n return firstCommentIndex;\n }\n\n // The user has read the thread, so we find the first unread comment.\n const unreadIndex = thread.comments.findIndex(\n (comment) =>\n (showDeletedComments ? true : comment.body) &&\n comment.createdAt > unreadSince\n );\n\n return unreadIndex >= 0 && unreadIndex < thread.comments.length\n ? unreadIndex\n : undefined;\n }, [\n firstCommentIndex,\n showDeletedComments,\n subscriptionStatus,\n thread.comments,\n unreadSince,\n ]);\n const [newIndex, setNewIndex] = useState<number>();\n const newIndicatorIndex = newIndex === undefined ? unreadIndex : newIndex;\n\n useEffect(() => {\n if (unreadIndex) {\n // Keep the \"new\" indicator at the lowest unread index.\n setNewIndex((persistedUnreadIndex) =>\n Math.min(persistedUnreadIndex ?? Infinity, unreadIndex)\n );\n }\n }, [unreadIndex]);\n\n const permissions = useRoomPermissions(thread.roomId);\n const canComment =\n permissions.size > 0\n ? permissions.has(Permission.CommentsWrite) ||\n permissions.has(Permission.Write)\n : true;\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n const handleResolvedChange = useCallback(\n (resolved: boolean) => {\n onResolvedChange?.(resolved);\n\n if (resolved) {\n markThreadAsResolved(thread.id);\n } else {\n markThreadAsUnresolved(thread.id);\n }\n },\n [\n markThreadAsResolved,\n markThreadAsUnresolved,\n onResolvedChange,\n thread.id,\n ]\n );\n\n const handleCommentDelete = useCallback(\n (comment: CommentData) => {\n onCommentDelete?.(comment);\n\n const filteredComments = thread.comments.filter(\n (comment) => comment.body\n );\n\n if (filteredComments.length <= 1) {\n onThreadDelete?.(thread);\n }\n },\n [onCommentDelete, onThreadDelete, thread]\n );\n\n const handleSubscribeChange = useCallback(() => {\n if (subscriptionStatus === \"subscribed\") {\n unsubscribe();\n } else {\n subscribe();\n }\n }, [subscriptionStatus, subscribe, unsubscribe]);\n\n return (\n <TooltipProvider>\n <div\n className={cn(\n \"lb-root lb-thread\",\n showActions === \"hover\" && \"lb-thread:show-actions-hover\",\n className\n )}\n data-resolved={thread.resolved ? \"\" : undefined}\n data-unread={unreadIndex !== undefined ? \"\" : undefined}\n dir={$.dir}\n {...props}\n ref={forwardedRef}\n >\n <div className=\"lb-thread-comments\">\n {thread.comments.map((comment, index) => {\n const isFirstComment = index === firstCommentIndex;\n const isUnread =\n unreadIndex !== undefined && index >= unreadIndex;\n const isHidden =\n hiddenComments &&\n index >= hiddenComments.firstIndex &&\n index <= hiddenComments.lastIndex;\n const isHiddenBecauseDeleted =\n !showDeletedComments && !comment.body;\n const isFirstHiddenComment =\n isHidden && index === hiddenComments.firstIndex;\n\n if (isFirstHiddenComment) {\n return (\n <div\n key={`${comment.id}-show-more`}\n className=\"lb-thread-show-more\"\n >\n <Button\n variant=\"ghost\"\n className=\"lb-thread-show-more-button\"\n onClick={() => setShowAllComments(true)}\n >\n {$.THREAD_SHOW_MORE_COMMENTS(hiddenComments.count)}\n </Button>\n </div>\n );\n }\n\n if (isHidden || isHiddenBecauseDeleted) {\n return null;\n }\n\n const children = (\n <Comment\n key={comment.id}\n overrides={overrides}\n className=\"lb-thread-comment\"\n data-unread={isUnread ? \"\" : undefined}\n comment={comment}\n indentContent={indentCommentContent}\n showDeleted={showDeletedComments}\n showActions={showActions}\n showReactions={showReactions}\n showAttachments={showAttachments}\n showComposerFormattingControls={\n showComposerFormattingControls\n }\n onCommentEdit={onCommentEdit}\n onCommentDelete={handleCommentDelete}\n onAuthorClick={onAuthorClick}\n onMentionClick={onMentionClick}\n onAttachmentClick={onAttachmentClick}\n components={components}\n actionsClassName={\n isFirstComment ? \"lb-thread-actions\" : undefined\n }\n actions={\n isFirstComment && showResolveAction ? (\n <Tooltip\n content={\n thread.resolved\n ? $.THREAD_UNRESOLVE\n : $.THREAD_RESOLVE\n }\n >\n <TogglePrimitive.Root\n pressed={thread.resolved}\n onPressedChange={handleResolvedChange}\n asChild\n >\n <Button\n className=\"lb-comment-action\"\n onClick={stopPropagation}\n aria-label={\n thread.resolved\n ? $.THREAD_UNRESOLVE\n : $.THREAD_RESOLVE\n }\n icon={\n thread.resolved ? (\n <CheckCircleFillIcon />\n ) : (\n <CheckCircleIcon />\n )\n }\n disabled={!canComment}\n />\n </TogglePrimitive.Root>\n </Tooltip>\n ) : null\n }\n internalDropdownItems={\n isFirstComment ? (\n <DefaultComment.DropdownItem\n onSelect={handleSubscribeChange}\n icon={\n subscriptionStatus === \"subscribed\" ? (\n <BellCrossedIcon />\n ) : (\n <BellIcon />\n )\n }\n >\n {subscriptionStatus === \"subscribed\"\n ? $.THREAD_UNSUBSCRIBE\n : $.THREAD_SUBSCRIBE}\n </DefaultComment.DropdownItem>\n ) : undefined\n }\n dropdownItems={\n commentDropdownItems as CommentProps[\"dropdownItems\"]\n }\n />\n );\n\n return index === newIndicatorIndex &&\n newIndicatorIndex !== firstCommentIndex &&\n newIndicatorIndex <= lastCommentIndex ? (\n <Fragment key={comment.id}>\n <div\n className=\"lb-thread-new-indicator\"\n aria-label={$.THREAD_NEW_INDICATOR_DESCRIPTION}\n >\n <span className=\"lb-thread-new-indicator-label\">\n <ArrowDownIcon className=\"lb-thread-new-indicator-label-icon\" />\n {$.THREAD_NEW_INDICATOR}\n </span>\n </div>\n {children}\n </Fragment>\n ) : (\n children\n );\n })}\n </div>\n {unreadIndex !== undefined && (\n <MarkThreadAsReadMarker thread={thread} />\n )}\n {additionalContent}\n {showComposer && (\n <Composer\n className=\"lb-thread-composer\"\n threadId={thread.id}\n defaultCollapsed={showComposer === \"collapsed\" ? true : undefined}\n showAttachments={showAttachments}\n showFormattingControls={showComposerFormattingControls}\n onComposerSubmit={onComposerSubmit}\n blurOnSubmit={blurComposerOnSubmit}\n overrides={{\n COMPOSER_PLACEHOLDER: $.THREAD_COMPOSER_PLACEHOLDER,\n COMPOSER_SEND: $.THREAD_COMPOSER_SEND,\n ...overrides,\n }}\n roomId={thread.roomId}\n />\n )}\n </div>\n </TooltipProvider>\n );\n }\n) as <TM extends BaseMetadata = DTM, CM extends BaseMetadata = DCM>(\n props: ThreadProps<TM, CM> & RefAttributes<HTMLDivElement>\n) => JSX.Element;\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2DA;AACE;AACA;AACA;AACA;AAEA;AAAA;AACE;AAEE;AACE;AAAyB;AAC3B;AACF;AACA;AAAA;AAEW;AACX;AAGF;AACE;AAAC;AAAA;AACC;AACmB;AACR;AACc;AAAA;AAG/B;AAsKO;AAAe;AAElB;AACE;AACuB;AACT;AACd;AACoB;AACJ;AACD;AACG;AACe;AACjC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACG;AAIL;AACA;AACA;AACA;AACA;AACA;AACE;AAEuD;AAEzD;AACE;AAEqE;AAEvE;AACE;AAIA;AAOA;AACE;AAAA;AAGF;AAKA;AACE;AAAA;AAGF;AACA;AAGA;AACE;AAEA;AAGA;AAAO;AACO;AACD;AACmC;AAChD;AAGF;AAGA;AAMA;AAQA;AAEA;AAIA;AAKE;AAAA;AAGF;AAAO;AAC0B;AACF;AAG+B;AAC9D;AACC;AACD;AACA;AACA;AACO;AAET;AAAM;AACI;AACR;AACA;AACA;AAEF;AAEE;AACE;AAAA;AAIF;AACE;AAAO;AAIT;AAAoC;AAGZ;AAGxB;AAEI;AACH;AACD;AACA;AACA;AACO;AACP;AAEF;AACA;AAEA;AACE;AAEE;AAAA;AACwD;AACxD;AACF;AAGF;AACA;AAMA;AACE;AAAsB;AAGxB;AAA6B;AAEzB;AAEA;AACE;AAA8B;AAE9B;AAAgC;AAClC;AACF;AACA;AACE;AACA;AACA;AACO;AACT;AAGF;AAA4B;AAExB;AAEA;AAAyC;AAClB;AAGvB;AACE;AAAuB;AACzB;AACF;AACwC;AAG1C;AACE;AACE;AAAY;AAEZ;AAAU;AACZ;AAGF;AAEI;AAAC;AAAA;AACY;AACT;AAC2B;AAC3B;AACF;AACsC;AACQ;AACvC;AACH;AACC;AAEL;AAEI;AACA;AAEA;AAIA;AAEA;AAGA;AACE;AACE;AAAC;AAAA;AAEW;AAEV;AAAC;AAAA;AACS;AACE;AAC4B;AAEW;AAAA;AACnD;AAAA;AATkB;AAUpB;AAIJ;AACE;AAAO;AAGT;AACE;AAAC;AAAA;AAEC;AACU;AACmB;AAC7B;AACe;AACF;AACb;AACA;AACA;AACA;AAGA;AACiB;AACjB;AACA;AACA;AACA;AAEyC;AAIrC;AAAC;AAAA;AAIS;AAGR;AAAiB;AAAhB;AACiB;AACC;AACV;AAEP;AAAC;AAAA;AACW;AACD;AAID;AAMa;AAGV;AAAA;AACb;AAAA;AACF;AAAA;AAEA;AAIF;AAAgB;AAAf;AACW;AAKI;AAMR;AAAA;AAEN;AAGJ;AAAA;AA5EW;AAiFjB;AAII;AAAA;AAAC;AAAA;AACW;AACI;AAGZ;AAA8D;AAC3D;AACL;AAAA;AACF;AACC;AAGH;AAGN;AAE0C;AAEzC;AAEC;AAAC;AAAA;AACW;AACO;AACuC;AACxD;AACwB;AACxB;AACc;AACH;AACe;AACP;AACd;AACL;AACe;AAAA;AACjB;AAAA;AAAA;AAGN;AAGN;;"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx, jsxs
|
|
2
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
3
3
|
import { findLastIndex, Permission } from '@liveblocks/core';
|
|
4
|
-
import { useMarkRoomThreadAsResolved, useMarkRoomThreadAsUnresolved, useRoomThreadSubscription, useRoomPermissions } from '@liveblocks/react/_private';
|
|
5
|
-
import
|
|
6
|
-
import { forwardRef, useState, useMemo, useEffect, useCallback, Fragment
|
|
4
|
+
import { useMarkRoomThreadAsRead, useMarkRoomThreadAsResolved, useMarkRoomThreadAsUnresolved, useRoomThreadSubscription, useRoomPermissions } from '@liveblocks/react/_private';
|
|
5
|
+
import * as TogglePrimitive from '@radix-ui/react-toggle';
|
|
6
|
+
import { useRef, forwardRef, useState, useMemo, useEffect, useCallback, Fragment } from 'react';
|
|
7
7
|
import { ArrowDownIcon } from '../icons/ArrowDown.js';
|
|
8
8
|
import { BellIcon } from '../icons/Bell.js';
|
|
9
9
|
import { BellCrossedIcon } from '../icons/BellCrossed.js';
|
|
@@ -11,12 +11,43 @@ import { CheckCircleIcon } from '../icons/CheckCircle.js';
|
|
|
11
11
|
import { CheckCircleFillIcon } from '../icons/CheckCircleFill.js';
|
|
12
12
|
import { useOverrides } from '../overrides.js';
|
|
13
13
|
import { cn } from '../utils/cn.js';
|
|
14
|
+
import { useStableComponent } from '../utils/use-stable-component.js';
|
|
15
|
+
import { useIntersectionCallback } from '../utils/use-visible.js';
|
|
16
|
+
import { useWindowFocus } from '../utils/use-window-focus.js';
|
|
14
17
|
import { Comment } from './Comment.js';
|
|
15
18
|
import { Composer } from './Composer.js';
|
|
16
19
|
import { Button } from './internal/Button.js';
|
|
17
|
-
import {
|
|
20
|
+
import { Tooltip } from './internal/Tooltip.js';
|
|
21
|
+
import { TooltipProvider } from '@radix-ui/react-tooltip';
|
|
18
22
|
|
|
19
23
|
|
|
24
|
+
function MarkThreadAsReadMarker({ thread }) {
|
|
25
|
+
const { id: threadId, roomId } = thread;
|
|
26
|
+
const ref = useRef(null);
|
|
27
|
+
const markThreadAsRead = useMarkRoomThreadAsRead(roomId);
|
|
28
|
+
const isWindowFocused = useWindowFocus();
|
|
29
|
+
useIntersectionCallback(
|
|
30
|
+
ref,
|
|
31
|
+
(isIntersecting) => {
|
|
32
|
+
if (isIntersecting) {
|
|
33
|
+
markThreadAsRead(threadId);
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
// The underlying IntersectionObserver is only enabled when the window is focused
|
|
38
|
+
enabled: isWindowFocused
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
return /* @__PURE__ */ jsx(
|
|
42
|
+
"div",
|
|
43
|
+
{
|
|
44
|
+
ref,
|
|
45
|
+
style: { height: 0 },
|
|
46
|
+
"aria-hidden": true,
|
|
47
|
+
"data-mark-as-read-marker": ""
|
|
48
|
+
}
|
|
49
|
+
);
|
|
50
|
+
}
|
|
20
51
|
const Thread = forwardRef(
|
|
21
52
|
({
|
|
22
53
|
thread,
|
|
@@ -30,6 +61,7 @@ const Thread = forwardRef(
|
|
|
30
61
|
showComposerFormattingControls = true,
|
|
31
62
|
maxVisibleComments,
|
|
32
63
|
commentDropdownItems,
|
|
64
|
+
additionalContent,
|
|
33
65
|
onResolvedChange,
|
|
34
66
|
onCommentEdit,
|
|
35
67
|
onCommentDelete,
|
|
@@ -39,12 +71,12 @@ const Thread = forwardRef(
|
|
|
39
71
|
onAttachmentClick,
|
|
40
72
|
onComposerSubmit,
|
|
41
73
|
blurComposerOnSubmit,
|
|
42
|
-
autoFocus,
|
|
43
74
|
overrides,
|
|
44
75
|
components,
|
|
45
76
|
className,
|
|
46
77
|
...props
|
|
47
78
|
}, forwardedRef) => {
|
|
79
|
+
const Comment$1 = useStableComponent(components?.Comment, Comment);
|
|
48
80
|
const markThreadAsResolved = useMarkRoomThreadAsResolved(thread.roomId);
|
|
49
81
|
const markThreadAsUnresolved = useMarkRoomThreadAsUnresolved(thread.roomId);
|
|
50
82
|
const $ = useOverrides(overrides);
|
|
@@ -55,9 +87,6 @@ const Thread = forwardRef(
|
|
|
55
87
|
const lastCommentIndex = useMemo(() => {
|
|
56
88
|
return showDeletedComments ? thread.comments.length - 1 : findLastIndex(thread.comments, (comment) => Boolean(comment.body));
|
|
57
89
|
}, [showDeletedComments, thread.comments]);
|
|
58
|
-
const commentsCount = useMemo(() => {
|
|
59
|
-
return showDeletedComments ? thread.comments.length : thread.comments.filter((comment) => comment.body).length;
|
|
60
|
-
}, [showDeletedComments, thread.comments]);
|
|
61
90
|
const hiddenComments = useMemo(() => {
|
|
62
91
|
const maxVisibleCommentsCount = typeof maxVisibleComments === "number" ? maxVisibleComments : maxVisibleComments?.max;
|
|
63
92
|
const visibleCommentsShow = (typeof maxVisibleComments === "object" ? maxVisibleComments?.show : void 0) ?? "newest";
|
|
@@ -171,7 +200,6 @@ const Thread = forwardRef(
|
|
|
171
200
|
subscribe();
|
|
172
201
|
}
|
|
173
202
|
}, [subscriptionStatus, subscribe, unsubscribe]);
|
|
174
|
-
let currentCommentIndex = 0;
|
|
175
203
|
return /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs(
|
|
176
204
|
"div",
|
|
177
205
|
{
|
|
@@ -186,11 +214,11 @@ const Thread = forwardRef(
|
|
|
186
214
|
...props,
|
|
187
215
|
ref: forwardedRef,
|
|
188
216
|
children: [
|
|
189
|
-
/* @__PURE__ */ jsx("div", { className: "lb-thread-comments",
|
|
190
|
-
const isNonDeletedComment = showDeletedComments || comment.body;
|
|
217
|
+
/* @__PURE__ */ jsx("div", { className: "lb-thread-comments", children: thread.comments.map((comment, index) => {
|
|
191
218
|
const isFirstComment = index === firstCommentIndex;
|
|
192
219
|
const isUnread = unreadIndex !== void 0 && index >= unreadIndex;
|
|
193
220
|
const isHidden = hiddenComments && index >= hiddenComments.firstIndex && index <= hiddenComments.lastIndex;
|
|
221
|
+
const isHiddenBecauseDeleted = !showDeletedComments && !comment.body;
|
|
194
222
|
const isFirstHiddenComment = isHidden && index === hiddenComments.firstIndex;
|
|
195
223
|
if (isFirstHiddenComment) {
|
|
196
224
|
return /* @__PURE__ */ jsx(
|
|
@@ -207,21 +235,15 @@ const Thread = forwardRef(
|
|
|
207
235
|
}
|
|
208
236
|
)
|
|
209
237
|
},
|
|
210
|
-
`${comment.id}
|
|
238
|
+
`${comment.id}-show-more`
|
|
211
239
|
);
|
|
212
240
|
}
|
|
213
|
-
if (
|
|
214
|
-
currentCommentIndex++;
|
|
215
|
-
}
|
|
216
|
-
if (isHidden) {
|
|
241
|
+
if (isHidden || isHiddenBecauseDeleted) {
|
|
217
242
|
return null;
|
|
218
243
|
}
|
|
219
244
|
const children = /* @__PURE__ */ jsx(
|
|
220
|
-
Comment,
|
|
245
|
+
Comment$1,
|
|
221
246
|
{
|
|
222
|
-
tabIndex: 0,
|
|
223
|
-
"aria-posinset": currentCommentIndex,
|
|
224
|
-
"aria-setsize": commentsCount,
|
|
225
247
|
overrides,
|
|
226
248
|
className: "lb-thread-comment",
|
|
227
249
|
"data-unread": isUnread ? "" : void 0,
|
|
@@ -238,14 +260,13 @@ const Thread = forwardRef(
|
|
|
238
260
|
onMentionClick,
|
|
239
261
|
onAttachmentClick,
|
|
240
262
|
components,
|
|
241
|
-
autoMarkReadThreadId: index === lastCommentIndex && isUnread ? thread.id : void 0,
|
|
242
263
|
actionsClassName: isFirstComment ? "lb-thread-actions" : void 0,
|
|
243
264
|
actions: isFirstComment && showResolveAction ? /* @__PURE__ */ jsx(
|
|
244
265
|
Tooltip,
|
|
245
266
|
{
|
|
246
267
|
content: thread.resolved ? $.THREAD_UNRESOLVE : $.THREAD_RESOLVE,
|
|
247
268
|
children: /* @__PURE__ */ jsx(
|
|
248
|
-
|
|
269
|
+
TogglePrimitive.Root,
|
|
249
270
|
{
|
|
250
271
|
pressed: thread.resolved,
|
|
251
272
|
onPressedChange: handleResolvedChange,
|
|
@@ -264,34 +285,19 @@ const Thread = forwardRef(
|
|
|
264
285
|
)
|
|
265
286
|
}
|
|
266
287
|
) : null,
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
children: subscriptionStatus === "subscribed" ? $.THREAD_UNSUBSCRIBE : $.THREAD_SUBSCRIBE
|
|
274
|
-
}
|
|
275
|
-
) }) : null;
|
|
276
|
-
if (typeof commentDropdownItems === "function") {
|
|
277
|
-
return commentDropdownItems({
|
|
278
|
-
children: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
279
|
-
threadDropdownItems,
|
|
280
|
-
children2
|
|
281
|
-
] }),
|
|
282
|
-
comment
|
|
283
|
-
});
|
|
288
|
+
internalDropdownItems: isFirstComment ? /* @__PURE__ */ jsx(
|
|
289
|
+
Comment.DropdownItem,
|
|
290
|
+
{
|
|
291
|
+
onSelect: handleSubscribeChange,
|
|
292
|
+
icon: subscriptionStatus === "subscribed" ? /* @__PURE__ */ jsx(BellCrossedIcon, {}) : /* @__PURE__ */ jsx(BellIcon, {}),
|
|
293
|
+
children: subscriptionStatus === "subscribed" ? $.THREAD_UNSUBSCRIBE : $.THREAD_SUBSCRIBE
|
|
284
294
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
children2,
|
|
288
|
-
commentDropdownItems
|
|
289
|
-
] }) : null;
|
|
290
|
-
}
|
|
295
|
+
) : void 0,
|
|
296
|
+
dropdownItems: commentDropdownItems
|
|
291
297
|
},
|
|
292
298
|
comment.id
|
|
293
299
|
);
|
|
294
|
-
return index === newIndicatorIndex && newIndicatorIndex !== firstCommentIndex && newIndicatorIndex <= lastCommentIndex ? /* @__PURE__ */ jsxs(Fragment
|
|
300
|
+
return index === newIndicatorIndex && newIndicatorIndex !== firstCommentIndex && newIndicatorIndex <= lastCommentIndex ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
295
301
|
/* @__PURE__ */ jsx(
|
|
296
302
|
"div",
|
|
297
303
|
{
|
|
@@ -306,6 +312,8 @@ const Thread = forwardRef(
|
|
|
306
312
|
children
|
|
307
313
|
] }, comment.id) : children;
|
|
308
314
|
}) }),
|
|
315
|
+
unreadIndex !== void 0 && /* @__PURE__ */ jsx(MarkThreadAsReadMarker, { thread }),
|
|
316
|
+
additionalContent,
|
|
309
317
|
showComposer && /* @__PURE__ */ jsx(
|
|
310
318
|
Composer,
|
|
311
319
|
{
|
|
@@ -321,8 +329,7 @@ const Thread = forwardRef(
|
|
|
321
329
|
COMPOSER_SEND: $.THREAD_COMPOSER_SEND,
|
|
322
330
|
...overrides
|
|
323
331
|
},
|
|
324
|
-
roomId: thread.roomId
|
|
325
|
-
autoFocus
|
|
332
|
+
roomId: thread.roomId
|
|
326
333
|
}
|
|
327
334
|
)
|
|
328
335
|
]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Thread.js","sources":["../../src/components/Thread.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n type BaseMetadata,\n type CommentData,\n type DCM,\n type DTM,\n Permission,\n type ThreadData,\n} from \"@liveblocks/core\";\nimport { findLastIndex } from \"@liveblocks/core\";\nimport {\n useMarkRoomThreadAsResolved,\n useMarkRoomThreadAsUnresolved,\n useRoomPermissions,\n useRoomThreadSubscription,\n} from \"@liveblocks/react/_private\";\nimport { Toggle as TogglePrimitive } from \"radix-ui\";\nimport type {\n ComponentPropsWithoutRef,\n ForwardedRef,\n PropsWithChildren,\n ReactNode,\n RefAttributes,\n SyntheticEvent,\n} from \"react\";\nimport {\n forwardRef,\n Fragment,\n useCallback,\n useEffect,\n useMemo,\n useState,\n} from \"react\";\n\nimport type { GlobalComponents } from \"../components\";\nimport { ArrowDownIcon } from \"../icons/ArrowDown\";\nimport { BellIcon } from \"../icons/Bell\";\nimport { BellCrossedIcon } from \"../icons/BellCrossed\";\nimport { CheckCircleIcon } from \"../icons/CheckCircle\";\nimport { CheckCircleFillIcon } from \"../icons/CheckCircleFill\";\nimport type {\n CommentOverrides,\n ComposerOverrides,\n GlobalOverrides,\n ThreadOverrides,\n} from \"../overrides\";\nimport { useOverrides } from \"../overrides\";\nimport { cn } from \"../utils/cn\";\nimport type { CommentProps } from \"./Comment\";\nimport { Comment } from \"./Comment\";\nimport type { ComposerProps } from \"./Composer\";\nimport { Composer } from \"./Composer\";\nimport { Button } from \"./internal/Button\";\nimport { Tooltip, TooltipProvider } from \"./internal/Tooltip\";\n\nexport interface ThreadProps<\n TM extends BaseMetadata = DTM,\n CM extends BaseMetadata = DCM,\n> extends ComponentPropsWithoutRef<\"div\"> {\n /**\n * The thread to display.\n */\n thread: ThreadData<TM, CM>;\n\n /**\n * How to show or hide the composer to reply to the thread.\n */\n showComposer?: boolean | \"collapsed\";\n\n /**\n * Whether to show the action to resolve the thread.\n */\n showResolveAction?: boolean;\n\n /**\n * How to show or hide the actions.\n */\n showActions?: CommentProps[\"showActions\"];\n\n /**\n * Whether to show reactions.\n */\n showReactions?: CommentProps[\"showReactions\"];\n\n /**\n * Whether to show the composer's formatting controls.\n */\n showComposerFormattingControls?: ComposerProps[\"showFormattingControls\"];\n\n /**\n * Add (or change) items to display in a comment's dropdown.\n */\n commentDropdownItems?:\n | ReactNode\n | ((props: PropsWithChildren<{ comment: CommentData }>) => ReactNode);\n\n /**\n * The maximum number of comments to show.\n *\n * The first and last comments are always shown and by default if some comments\n * are hidden, only the first comment will be shown before the \"show more\" button\n * and after it will be shown all the newest comments to fit the limit set.\n *\n * It's possible to customize this by setting `maxVisibleComments` to an object:\n *\n * @example\n * // Only show the last comment, and all the older ones to fit the limit.\n * <Thread maxVisibleComments={{ max: 5, show: \"oldest\" }} />\n *\n * @example\n * // Show as many old comments as new ones to fit the limit.\n * <Thread maxVisibleComments={{ max: 5, show: \"both\" }} />\n */\n maxVisibleComments?:\n | number\n | { max: number; show: \"oldest\" | \"both\" | \"newest\" };\n\n /**\n * Whether to blur the composer editor when the composer is submitted.\n */\n blurComposerOnSubmit?: ComposerProps[\"blurOnSubmit\"];\n\n /**\n * Whether to indent the comments' content.\n */\n indentCommentContent?: CommentProps[\"indentContent\"];\n\n /**\n * Whether to show deleted comments.\n */\n showDeletedComments?: CommentProps[\"showDeleted\"];\n\n /**\n * Whether to show attachments.\n */\n showAttachments?: boolean;\n\n /**\n * The event handler called when changing the resolved status.\n */\n onResolvedChange?: (resolved: boolean) => void;\n\n /**\n * The event handler called when a comment is edited.\n */\n onCommentEdit?: CommentProps[\"onCommentEdit\"];\n\n /**\n * The event handler called when a comment is deleted.\n */\n onCommentDelete?: CommentProps[\"onCommentDelete\"];\n\n /**\n * The event handler called when the thread is deleted.\n * A thread is deleted when all its comments are deleted.\n */\n onThreadDelete?: (thread: ThreadData<TM, CM>) => void;\n\n /**\n * The event handler called when clicking on a comment's author.\n */\n onAuthorClick?: CommentProps[\"onAuthorClick\"];\n\n /**\n * The event handler called when clicking on a mention.\n */\n onMentionClick?: CommentProps[\"onMentionClick\"];\n\n /**\n * The event handler called when clicking on a comment's attachment.\n */\n onAttachmentClick?: CommentProps[\"onAttachmentClick\"];\n\n /**\n * The event handler called when the composer is submitted.\n */\n onComposerSubmit?: ComposerProps[\"onComposerSubmit\"];\n\n /**\n * Whether to focus the composer on mount.\n */\n autoFocus?: ComposerProps[\"autoFocus\"];\n\n /**\n * Override the component's strings.\n */\n overrides?: Partial<\n GlobalOverrides & ThreadOverrides & CommentOverrides & ComposerOverrides\n >;\n\n /**\n * Override the component's components.\n */\n components?: Partial<GlobalComponents>;\n}\n\n/**\n * Displays a thread of comments, with a composer to reply\n * to it.\n *\n * @example\n * <>\n * {threads.map((thread) => (\n * <Thread key={thread.id} thread={thread} />\n * ))}\n * </>\n */\nexport const Thread = forwardRef(\n <TM extends BaseMetadata = DTM, CM extends BaseMetadata = DCM>(\n {\n thread,\n indentCommentContent = true,\n showActions = \"hover\",\n showDeletedComments,\n showResolveAction = true,\n showReactions = true,\n showComposer = \"collapsed\",\n showAttachments = true,\n showComposerFormattingControls = true,\n maxVisibleComments,\n commentDropdownItems,\n onResolvedChange,\n onCommentEdit,\n onCommentDelete,\n onThreadDelete,\n onAuthorClick,\n onMentionClick,\n onAttachmentClick,\n onComposerSubmit,\n blurComposerOnSubmit,\n autoFocus,\n overrides,\n components,\n className,\n ...props\n }: ThreadProps<TM, CM>,\n forwardedRef: ForwardedRef<HTMLDivElement>\n ) => {\n const markThreadAsResolved = useMarkRoomThreadAsResolved(thread.roomId);\n const markThreadAsUnresolved = useMarkRoomThreadAsUnresolved(thread.roomId);\n const $ = useOverrides(overrides);\n const [showAllComments, setShowAllComments] = useState(false);\n const firstCommentIndex = useMemo(() => {\n return showDeletedComments\n ? 0\n : thread.comments.findIndex((comment) => comment.body);\n }, [showDeletedComments, thread.comments]);\n const lastCommentIndex = useMemo(() => {\n return showDeletedComments\n ? thread.comments.length - 1\n : findLastIndex(thread.comments, (comment) => Boolean(comment.body));\n }, [showDeletedComments, thread.comments]);\n const commentsCount = useMemo(() => {\n return showDeletedComments\n ? thread.comments.length\n : thread.comments.filter((comment) => comment.body).length;\n }, [showDeletedComments, thread.comments]);\n const hiddenComments = useMemo(() => {\n const maxVisibleCommentsCount =\n typeof maxVisibleComments === \"number\"\n ? maxVisibleComments\n : maxVisibleComments?.max;\n const visibleCommentsShow =\n (typeof maxVisibleComments === \"object\"\n ? maxVisibleComments?.show\n : undefined) ?? \"newest\";\n\n // If we explicitly want to show all comments or there's no limit set,\n // no need to hide any comments.\n if (showAllComments || maxVisibleCommentsCount === undefined) {\n return;\n }\n\n const comments = thread.comments\n .map((comment, index) => ({ comment, index }))\n .filter(({ comment }) => showDeletedComments || comment.body);\n\n // There aren't enough comments so no need to hide any.\n if (comments.length <= Math.max(maxVisibleCommentsCount, 2)) {\n return;\n }\n\n const firstVisibleComment = comments[0]!;\n const lastVisibleComment = comments[comments.length - 1]!;\n\n // Always show the first and last comments even if the limit is set to lower than 2.\n if (maxVisibleCommentsCount <= 2) {\n const firstHiddenCommentIndex =\n comments[1]?.index ?? firstVisibleComment.index;\n const lastHiddenCommentIndex =\n comments[comments.length - 2]?.index ?? lastVisibleComment.index;\n\n return {\n firstIndex: firstHiddenCommentIndex,\n lastIndex: lastHiddenCommentIndex,\n count: comments.slice(1, comments.length - 1).length,\n };\n }\n\n const remainingVisibleCommentsCount = maxVisibleCommentsCount - 2;\n\n // Split the remaining visible comments before, after, or equally.\n const beforeVisibleCommentsCount =\n visibleCommentsShow === \"oldest\"\n ? remainingVisibleCommentsCount\n : visibleCommentsShow === \"newest\"\n ? 0\n : Math.floor(remainingVisibleCommentsCount / 2);\n const afterVisibleCommentsCount =\n visibleCommentsShow === \"oldest\"\n ? 0\n : visibleCommentsShow === \"newest\"\n ? remainingVisibleCommentsCount\n : Math.ceil(remainingVisibleCommentsCount / 2);\n\n // The first comment is always visible so `+ 1` to skip it.\n const firstHiddenComment = comments[1 + beforeVisibleCommentsCount];\n // The last comment is always visible so `- 2` to skip it.\n const lastHiddenComment =\n comments[comments.length - 2 - afterVisibleCommentsCount];\n\n // There aren't any comments to hide besides the first and last ones.\n if (\n !firstHiddenComment ||\n !lastHiddenComment ||\n firstHiddenComment.index > lastHiddenComment.index\n ) {\n return;\n }\n\n return {\n firstIndex: firstHiddenComment.index,\n lastIndex: lastHiddenComment.index,\n count: thread.comments\n .slice(firstHiddenComment.index, lastHiddenComment.index + 1)\n .filter((comment) => showDeletedComments || comment.body).length,\n };\n }, [\n maxVisibleComments,\n showAllComments,\n showDeletedComments,\n thread.comments,\n ]);\n const {\n status: subscriptionStatus,\n unreadSince,\n subscribe,\n unsubscribe,\n } = useRoomThreadSubscription(thread.roomId, thread.id);\n const unreadIndex = useMemo(() => {\n // The user is not subscribed to this thread.\n if (subscriptionStatus !== \"subscribed\") {\n return;\n }\n\n // The user hasn't read the thread yet, so all comments are unread.\n if (unreadSince === null) {\n return firstCommentIndex;\n }\n\n // The user has read the thread, so we find the first unread comment.\n const unreadIndex = thread.comments.findIndex(\n (comment) =>\n (showDeletedComments ? true : comment.body) &&\n comment.createdAt > unreadSince\n );\n\n return unreadIndex >= 0 && unreadIndex < thread.comments.length\n ? unreadIndex\n : undefined;\n }, [\n firstCommentIndex,\n showDeletedComments,\n subscriptionStatus,\n thread.comments,\n unreadSince,\n ]);\n const [newIndex, setNewIndex] = useState<number>();\n const newIndicatorIndex = newIndex === undefined ? unreadIndex : newIndex;\n\n useEffect(() => {\n if (unreadIndex) {\n // Keep the \"new\" indicator at the lowest unread index.\n setNewIndex((persistedUnreadIndex) =>\n Math.min(persistedUnreadIndex ?? Infinity, unreadIndex)\n );\n }\n }, [unreadIndex]);\n\n const permissions = useRoomPermissions(thread.roomId);\n const canComment =\n permissions.size > 0\n ? permissions.has(Permission.CommentsWrite) ||\n permissions.has(Permission.Write)\n : true;\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n const handleResolvedChange = useCallback(\n (resolved: boolean) => {\n onResolvedChange?.(resolved);\n\n if (resolved) {\n markThreadAsResolved(thread.id);\n } else {\n markThreadAsUnresolved(thread.id);\n }\n },\n [\n markThreadAsResolved,\n markThreadAsUnresolved,\n onResolvedChange,\n thread.id,\n ]\n );\n\n const handleCommentDelete = useCallback(\n (comment: CommentData) => {\n onCommentDelete?.(comment);\n\n const filteredComments = thread.comments.filter(\n (comment) => comment.body\n );\n\n if (filteredComments.length <= 1) {\n onThreadDelete?.(thread);\n }\n },\n [onCommentDelete, onThreadDelete, thread]\n );\n\n const handleSubscribeChange = useCallback(() => {\n if (subscriptionStatus === \"subscribed\") {\n unsubscribe();\n } else {\n subscribe();\n }\n }, [subscriptionStatus, subscribe, unsubscribe]);\n\n let currentCommentIndex = 0;\n\n return (\n <TooltipProvider>\n <div\n className={cn(\n \"lb-root lb-thread\",\n showActions === \"hover\" && \"lb-thread:show-actions-hover\",\n className\n )}\n data-resolved={thread.resolved ? \"\" : undefined}\n data-unread={unreadIndex !== undefined ? \"\" : undefined}\n dir={$.dir}\n {...props}\n ref={forwardedRef}\n >\n <div className=\"lb-thread-comments\" role=\"feed\">\n {thread.comments.map((comment, index) => {\n const isNonDeletedComment = showDeletedComments || comment.body;\n const isFirstComment = index === firstCommentIndex;\n const isUnread =\n unreadIndex !== undefined && index >= unreadIndex;\n const isHidden =\n hiddenComments &&\n index >= hiddenComments.firstIndex &&\n index <= hiddenComments.lastIndex;\n const isFirstHiddenComment =\n isHidden && index === hiddenComments.firstIndex;\n\n if (isFirstHiddenComment) {\n return (\n <div\n key={`${comment.id}:show-more`}\n className=\"lb-thread-show-more\"\n >\n <Button\n variant=\"ghost\"\n className=\"lb-thread-show-more-button\"\n onClick={() => setShowAllComments(true)}\n >\n {$.THREAD_SHOW_MORE_COMMENTS(hiddenComments.count)}\n </Button>\n </div>\n );\n }\n\n if (isNonDeletedComment) {\n currentCommentIndex++;\n }\n\n if (isHidden) {\n return null;\n }\n\n const children = (\n <Comment\n key={comment.id}\n tabIndex={0}\n aria-posinset={currentCommentIndex}\n aria-setsize={commentsCount}\n overrides={overrides}\n className=\"lb-thread-comment\"\n data-unread={isUnread ? \"\" : undefined}\n comment={comment}\n indentContent={indentCommentContent}\n showDeleted={showDeletedComments}\n showActions={showActions}\n showReactions={showReactions}\n showAttachments={showAttachments}\n showComposerFormattingControls={\n showComposerFormattingControls\n }\n onCommentEdit={onCommentEdit}\n onCommentDelete={handleCommentDelete}\n onAuthorClick={onAuthorClick}\n onMentionClick={onMentionClick}\n onAttachmentClick={onAttachmentClick}\n components={components}\n autoMarkReadThreadId={\n index === lastCommentIndex && isUnread\n ? thread.id\n : undefined\n }\n actionsClassName={\n isFirstComment ? \"lb-thread-actions\" : undefined\n }\n actions={\n isFirstComment && showResolveAction ? (\n <Tooltip\n content={\n thread.resolved\n ? $.THREAD_UNRESOLVE\n : $.THREAD_RESOLVE\n }\n >\n <TogglePrimitive.Root\n pressed={thread.resolved}\n onPressedChange={handleResolvedChange}\n asChild\n >\n <Button\n className=\"lb-comment-action\"\n onClick={stopPropagation}\n aria-label={\n thread.resolved\n ? $.THREAD_UNRESOLVE\n : $.THREAD_RESOLVE\n }\n icon={\n thread.resolved ? (\n <CheckCircleFillIcon />\n ) : (\n <CheckCircleIcon />\n )\n }\n disabled={!canComment}\n />\n </TogglePrimitive.Root>\n </Tooltip>\n ) : null\n }\n dropdownItems={({ children }) => {\n const threadDropdownItems = isFirstComment ? (\n <>\n <Comment.DropdownItem\n onSelect={handleSubscribeChange}\n icon={\n subscriptionStatus === \"subscribed\" ? (\n <BellCrossedIcon />\n ) : (\n <BellIcon />\n )\n }\n >\n {subscriptionStatus === \"subscribed\"\n ? $.THREAD_UNSUBSCRIBE\n : $.THREAD_SUBSCRIBE}\n </Comment.DropdownItem>\n </>\n ) : null;\n\n if (typeof commentDropdownItems === \"function\") {\n return commentDropdownItems({\n children: (\n <>\n {threadDropdownItems}\n {children}\n </>\n ),\n comment,\n });\n }\n\n return threadDropdownItems ||\n commentDropdownItems ||\n children ? (\n <>\n {threadDropdownItems}\n {children}\n {commentDropdownItems}\n </>\n ) : null;\n }}\n />\n );\n\n return index === newIndicatorIndex &&\n newIndicatorIndex !== firstCommentIndex &&\n newIndicatorIndex <= lastCommentIndex ? (\n <Fragment key={comment.id}>\n <div\n className=\"lb-thread-new-indicator\"\n aria-label={$.THREAD_NEW_INDICATOR_DESCRIPTION}\n >\n <span className=\"lb-thread-new-indicator-label\">\n <ArrowDownIcon className=\"lb-thread-new-indicator-label-icon\" />\n {$.THREAD_NEW_INDICATOR}\n </span>\n </div>\n {children}\n </Fragment>\n ) : (\n children\n );\n })}\n </div>\n {showComposer && (\n <Composer\n className=\"lb-thread-composer\"\n threadId={thread.id}\n defaultCollapsed={showComposer === \"collapsed\" ? true : undefined}\n showAttachments={showAttachments}\n showFormattingControls={showComposerFormattingControls}\n onComposerSubmit={onComposerSubmit}\n blurOnSubmit={blurComposerOnSubmit}\n overrides={{\n COMPOSER_PLACEHOLDER: $.THREAD_COMPOSER_PLACEHOLDER,\n COMPOSER_SEND: $.THREAD_COMPOSER_SEND,\n ...overrides,\n }}\n roomId={thread.roomId}\n autoFocus={autoFocus}\n />\n )}\n </div>\n </TooltipProvider>\n );\n }\n) as <TM extends BaseMetadata = DTM, CM extends BaseMetadata = DCM>(\n props: ThreadProps<TM, CM> & RefAttributes<HTMLDivElement>\n) => JSX.Element;\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;AAgNO;AAAe;AAElB;AACE;AACuB;AACT;AACd;AACoB;AACJ;AACD;AACG;AACe;AACjC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACG;AAIL;AACA;AACA;AACA;AACA;AACE;AAEuD;AAEzD;AACE;AAEqE;AAEvE;AACE;AAEsD;AAExD;AACE;AAIA;AAOA;AACE;AAAA;AAGF;AAKA;AACE;AAAA;AAGF;AACA;AAGA;AACE;AAEA;AAGA;AAAO;AACO;AACD;AACmC;AAChD;AAGF;AAGA;AAMA;AAQA;AAEA;AAIA;AAKE;AAAA;AAGF;AAAO;AAC0B;AACF;AAG+B;AAC9D;AACC;AACD;AACA;AACA;AACO;AAET;AAAM;AACI;AACR;AACA;AACA;AAEF;AAEE;AACE;AAAA;AAIF;AACE;AAAO;AAIT;AAAoC;AAGZ;AAGxB;AAEI;AACH;AACD;AACA;AACA;AACO;AACP;AAEF;AACA;AAEA;AACE;AAEE;AAAA;AACwD;AACxD;AACF;AAGF;AACA;AAMA;AACE;AAAsB;AAGxB;AAA6B;AAEzB;AAEA;AACE;AAA8B;AAE9B;AAAgC;AAClC;AACF;AACA;AACE;AACA;AACA;AACO;AACT;AAGF;AAA4B;AAExB;AAEA;AAAyC;AAClB;AAGvB;AACE;AAAuB;AACzB;AACF;AACwC;AAG1C;AACE;AACE;AAAY;AAEZ;AAAU;AACZ;AAGF;AAEA;AAEI;AAAC;AAAA;AACY;AACT;AAC2B;AAC3B;AACF;AACsC;AACQ;AACvC;AACH;AACC;AAEL;AAEI;AACA;AACA;AAEA;AAIA;AAGA;AACE;AACE;AAAC;AAAA;AAEW;AAEV;AAAC;AAAA;AACS;AACE;AAC4B;AAEW;AAAA;AACnD;AAAA;AATkB;AAUpB;AAIJ;AACE;AAAA;AAGF;AACE;AAAO;AAGT;AACE;AAAC;AAAA;AAEW;AACK;AACD;AACd;AACU;AACmB;AAC7B;AACe;AACF;AACb;AACA;AACA;AACA;AAGA;AACiB;AACjB;AACA;AACA;AACA;AAIM;AAGmC;AAIrC;AAAC;AAAA;AAIS;AAGR;AAAiB;AAAhB;AACiB;AACC;AACV;AAEP;AAAC;AAAA;AACW;AACD;AAID;AAMa;AAGV;AAAA;AACb;AAAA;AACF;AAAA;AAEA;AAGJ;AAEI;AAAS;AAAR;AACW;AAKI;AAMR;AAAA;AAKZ;AACE;AAA4B;AAGrB;AAAA;AACA;AACH;AAEF;AACD;AAGH;AAIK;AAAA;AACA;AACA;AAED;AACN;AAAA;AA1Ga;AA8GjB;AAII;AAAA;AAAC;AAAA;AACW;AACI;AAGZ;AAA8D;AAC3D;AACL;AAAA;AACF;AACC;AAGH;AAGN;AAEE;AAAC;AAAA;AACW;AACO;AACuC;AACxD;AACwB;AACxB;AACc;AACH;AACe;AACP;AACd;AACL;AACe;AACf;AAAA;AACF;AAAA;AAAA;AAGN;AAGN;;"}
|
|
1
|
+
{"version":3,"file":"Thread.js","sources":["../../src/components/Thread.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n type BaseMetadata,\n type CommentData,\n type DCM,\n type DTM,\n findLastIndex,\n Permission,\n type ThreadData,\n} from \"@liveblocks/core\";\nimport {\n useMarkRoomThreadAsRead,\n useMarkRoomThreadAsResolved,\n useMarkRoomThreadAsUnresolved,\n useRoomPermissions,\n useRoomThreadSubscription,\n} from \"@liveblocks/react/_private\";\nimport * as TogglePrimitive from \"@radix-ui/react-toggle\";\nimport {\n type ComponentPropsWithoutRef,\n type ComponentType,\n type ForwardedRef,\n forwardRef,\n Fragment,\n type PropsWithChildren,\n type ReactNode,\n type RefAttributes,\n type SyntheticEvent,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\n\nimport type { GlobalComponents } from \"../components\";\nimport { ArrowDownIcon } from \"../icons/ArrowDown\";\nimport { BellIcon } from \"../icons/Bell\";\nimport { BellCrossedIcon } from \"../icons/BellCrossed\";\nimport { CheckCircleIcon } from \"../icons/CheckCircle\";\nimport { CheckCircleFillIcon } from \"../icons/CheckCircleFill\";\nimport type {\n CommentOverrides,\n ComposerOverrides,\n GlobalOverrides,\n ThreadOverrides,\n} from \"../overrides\";\nimport { useOverrides } from \"../overrides\";\nimport { cn } from \"../utils/cn\";\nimport { useStableComponent } from \"../utils/use-stable-component\";\nimport { useIntersectionCallback } from \"../utils/use-visible\";\nimport { useWindowFocus } from \"../utils/use-window-focus\";\nimport { Comment as DefaultComment, type CommentProps } from \"./Comment\";\nimport { Composer, type ComposerProps } from \"./Composer\";\nimport { Button } from \"./internal/Button\";\nimport { Tooltip, TooltipProvider } from \"./internal/Tooltip\";\n\n// An invisible marker used to mark a thread as read only when it becomes visible.\nfunction MarkThreadAsReadMarker({ thread }: { thread: ThreadData }) {\n const { id: threadId, roomId } = thread;\n const ref = useRef<HTMLDivElement>(null);\n const markThreadAsRead = useMarkRoomThreadAsRead(roomId);\n const isWindowFocused = useWindowFocus();\n\n useIntersectionCallback(\n ref,\n (isIntersecting) => {\n if (isIntersecting) {\n markThreadAsRead(threadId);\n }\n },\n {\n // The underlying IntersectionObserver is only enabled when the window is focused\n enabled: isWindowFocused,\n }\n );\n\n return (\n <div\n ref={ref}\n style={{ height: 0 }}\n aria-hidden\n data-mark-as-read-marker=\"\"\n />\n );\n}\n\nexport interface ThreadComponents<\n // Future components might reference thread metadata so we declare it\n // already to respect the `TM` → `CM` order used everywhere else.\n _TM extends BaseMetadata = DTM,\n CM extends BaseMetadata = DCM,\n> {\n /**\n * The component used to display comments.\n */\n Comment: ComponentType<CommentProps<CM>>;\n}\n\nexport interface ThreadProps<\n TM extends BaseMetadata = DTM,\n CM extends BaseMetadata = DCM,\n> extends ComponentPropsWithoutRef<\"div\"> {\n /**\n * The thread to display.\n */\n thread: ThreadData<TM, CM>;\n\n /**\n * How to show or hide the composer to reply to the thread.\n */\n showComposer?: boolean | \"collapsed\";\n\n /**\n * Whether to show the action to resolve the thread.\n */\n showResolveAction?: boolean;\n\n /**\n * How to show or hide the actions.\n */\n showActions?: CommentProps[\"showActions\"];\n\n /**\n * Whether to show reactions.\n */\n showReactions?: CommentProps[\"showReactions\"];\n\n /**\n * Whether to show the composer's formatting controls.\n */\n showComposerFormattingControls?: ComposerProps[\"showFormattingControls\"];\n\n /**\n * Add (or change) items to display in a comment's dropdown.\n */\n commentDropdownItems?:\n | ReactNode\n | ((props: PropsWithChildren<{ comment: CommentData<CM> }>) => ReactNode);\n\n /**\n * The maximum number of comments to show.\n *\n * The first and last comments are always shown and by default if some comments\n * are hidden, only the first comment will be shown before the \"show more\" button\n * and after it will be shown all the newest comments to fit the limit set.\n *\n * It's possible to customize this by setting `maxVisibleComments` to an object:\n *\n * @example\n * // Only show the last comment, and all the older ones to fit the limit.\n * <Thread maxVisibleComments={{ max: 5, show: \"oldest\" }} />\n *\n * @example\n * // Show as many old comments as new ones to fit the limit.\n * <Thread maxVisibleComments={{ max: 5, show: \"both\" }} />\n */\n maxVisibleComments?:\n | number\n | { max: number; show: \"oldest\" | \"both\" | \"newest\" };\n\n /**\n * Whether to blur the composer editor when the composer is submitted.\n */\n blurComposerOnSubmit?: ComposerProps[\"blurOnSubmit\"];\n\n /**\n * Whether to indent the comments' content.\n */\n indentCommentContent?: CommentProps[\"indentContent\"];\n\n /**\n * Additional content to display below the thread's comments.\n */\n additionalContent?: ReactNode;\n\n /**\n * Whether to show deleted comments.\n */\n showDeletedComments?: CommentProps[\"showDeleted\"];\n\n /**\n * Whether to show attachments.\n */\n showAttachments?: boolean;\n\n /**\n * The event handler called when changing the resolved status.\n */\n onResolvedChange?: (resolved: boolean) => void;\n\n /**\n * The event handler called when a comment is edited.\n */\n onCommentEdit?: CommentProps[\"onCommentEdit\"];\n\n /**\n * The event handler called when a comment is deleted.\n */\n onCommentDelete?: CommentProps[\"onCommentDelete\"];\n\n /**\n * The event handler called when the thread is deleted.\n * A thread is deleted when all its comments are deleted.\n */\n onThreadDelete?: (thread: ThreadData<TM, CM>) => void;\n\n /**\n * The event handler called when clicking on a comment's author.\n */\n onAuthorClick?: CommentProps[\"onAuthorClick\"];\n\n /**\n * The event handler called when clicking on a mention.\n */\n onMentionClick?: CommentProps[\"onMentionClick\"];\n\n /**\n * The event handler called when clicking on a comment's attachment.\n */\n onAttachmentClick?: CommentProps[\"onAttachmentClick\"];\n\n /**\n * The event handler called when the composer is submitted.\n */\n onComposerSubmit?: ComposerProps[\"onComposerSubmit\"];\n\n /**\n * Override the component's strings.\n */\n overrides?: Partial<\n GlobalOverrides & ThreadOverrides & CommentOverrides & ComposerOverrides\n >;\n\n /**\n * Override the component's components.\n */\n components?: Partial<GlobalComponents & ThreadComponents>;\n}\n\n/**\n * Displays a thread of comments, with a composer to reply\n * to it.\n *\n * @example\n * <>\n * {threads.map((thread) => (\n * <Thread key={thread.id} thread={thread} />\n * ))}\n * </>\n */\nexport const Thread = forwardRef(\n <TM extends BaseMetadata = DTM, CM extends BaseMetadata = DCM>(\n {\n thread,\n indentCommentContent = true,\n showActions = \"hover\",\n showDeletedComments,\n showResolveAction = true,\n showReactions = true,\n showComposer = \"collapsed\",\n showAttachments = true,\n showComposerFormattingControls = true,\n maxVisibleComments,\n commentDropdownItems,\n additionalContent,\n onResolvedChange,\n onCommentEdit,\n onCommentDelete,\n onThreadDelete,\n onAuthorClick,\n onMentionClick,\n onAttachmentClick,\n onComposerSubmit,\n blurComposerOnSubmit,\n overrides,\n components,\n className,\n ...props\n }: ThreadProps<TM, CM>,\n forwardedRef: ForwardedRef<HTMLDivElement>\n ) => {\n const Comment = useStableComponent(components?.Comment, DefaultComment);\n const markThreadAsResolved = useMarkRoomThreadAsResolved(thread.roomId);\n const markThreadAsUnresolved = useMarkRoomThreadAsUnresolved(thread.roomId);\n const $ = useOverrides(overrides);\n const [showAllComments, setShowAllComments] = useState(false);\n const firstCommentIndex = useMemo(() => {\n return showDeletedComments\n ? 0\n : thread.comments.findIndex((comment) => comment.body);\n }, [showDeletedComments, thread.comments]);\n const lastCommentIndex = useMemo(() => {\n return showDeletedComments\n ? thread.comments.length - 1\n : findLastIndex(thread.comments, (comment) => Boolean(comment.body));\n }, [showDeletedComments, thread.comments]);\n const hiddenComments = useMemo(() => {\n const maxVisibleCommentsCount =\n typeof maxVisibleComments === \"number\"\n ? maxVisibleComments\n : maxVisibleComments?.max;\n const visibleCommentsShow =\n (typeof maxVisibleComments === \"object\"\n ? maxVisibleComments?.show\n : undefined) ?? \"newest\";\n\n // If we explicitly want to show all comments or there's no limit set,\n // no need to hide any comments.\n if (showAllComments || maxVisibleCommentsCount === undefined) {\n return;\n }\n\n const comments = thread.comments\n .map((comment, index) => ({ comment, index }))\n .filter(({ comment }) => showDeletedComments || comment.body);\n\n // There aren't enough comments so no need to hide any.\n if (comments.length <= Math.max(maxVisibleCommentsCount, 2)) {\n return;\n }\n\n const firstVisibleComment = comments[0]!;\n const lastVisibleComment = comments[comments.length - 1]!;\n\n // Always show the first and last comments even if the limit is set to lower than 2.\n if (maxVisibleCommentsCount <= 2) {\n const firstHiddenCommentIndex =\n comments[1]?.index ?? firstVisibleComment.index;\n const lastHiddenCommentIndex =\n comments[comments.length - 2]?.index ?? lastVisibleComment.index;\n\n return {\n firstIndex: firstHiddenCommentIndex,\n lastIndex: lastHiddenCommentIndex,\n count: comments.slice(1, comments.length - 1).length,\n };\n }\n\n const remainingVisibleCommentsCount = maxVisibleCommentsCount - 2;\n\n // Split the remaining visible comments before, after, or equally.\n const beforeVisibleCommentsCount =\n visibleCommentsShow === \"oldest\"\n ? remainingVisibleCommentsCount\n : visibleCommentsShow === \"newest\"\n ? 0\n : Math.floor(remainingVisibleCommentsCount / 2);\n const afterVisibleCommentsCount =\n visibleCommentsShow === \"oldest\"\n ? 0\n : visibleCommentsShow === \"newest\"\n ? remainingVisibleCommentsCount\n : Math.ceil(remainingVisibleCommentsCount / 2);\n\n // The first comment is always visible so `+ 1` to skip it.\n const firstHiddenComment = comments[1 + beforeVisibleCommentsCount];\n // The last comment is always visible so `- 2` to skip it.\n const lastHiddenComment =\n comments[comments.length - 2 - afterVisibleCommentsCount];\n\n // There aren't any comments to hide besides the first and last ones.\n if (\n !firstHiddenComment ||\n !lastHiddenComment ||\n firstHiddenComment.index > lastHiddenComment.index\n ) {\n return;\n }\n\n return {\n firstIndex: firstHiddenComment.index,\n lastIndex: lastHiddenComment.index,\n count: thread.comments\n .slice(firstHiddenComment.index, lastHiddenComment.index + 1)\n .filter((comment) => showDeletedComments || comment.body).length,\n };\n }, [\n maxVisibleComments,\n showAllComments,\n showDeletedComments,\n thread.comments,\n ]);\n const {\n status: subscriptionStatus,\n unreadSince,\n subscribe,\n unsubscribe,\n } = useRoomThreadSubscription(thread.roomId, thread.id);\n const unreadIndex = useMemo(() => {\n // The user is not subscribed to this thread.\n if (subscriptionStatus !== \"subscribed\") {\n return;\n }\n\n // The user hasn't read the thread yet, so all comments are unread.\n if (unreadSince === null) {\n return firstCommentIndex;\n }\n\n // The user has read the thread, so we find the first unread comment.\n const unreadIndex = thread.comments.findIndex(\n (comment) =>\n (showDeletedComments ? true : comment.body) &&\n comment.createdAt > unreadSince\n );\n\n return unreadIndex >= 0 && unreadIndex < thread.comments.length\n ? unreadIndex\n : undefined;\n }, [\n firstCommentIndex,\n showDeletedComments,\n subscriptionStatus,\n thread.comments,\n unreadSince,\n ]);\n const [newIndex, setNewIndex] = useState<number>();\n const newIndicatorIndex = newIndex === undefined ? unreadIndex : newIndex;\n\n useEffect(() => {\n if (unreadIndex) {\n // Keep the \"new\" indicator at the lowest unread index.\n setNewIndex((persistedUnreadIndex) =>\n Math.min(persistedUnreadIndex ?? Infinity, unreadIndex)\n );\n }\n }, [unreadIndex]);\n\n const permissions = useRoomPermissions(thread.roomId);\n const canComment =\n permissions.size > 0\n ? permissions.has(Permission.CommentsWrite) ||\n permissions.has(Permission.Write)\n : true;\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n const handleResolvedChange = useCallback(\n (resolved: boolean) => {\n onResolvedChange?.(resolved);\n\n if (resolved) {\n markThreadAsResolved(thread.id);\n } else {\n markThreadAsUnresolved(thread.id);\n }\n },\n [\n markThreadAsResolved,\n markThreadAsUnresolved,\n onResolvedChange,\n thread.id,\n ]\n );\n\n const handleCommentDelete = useCallback(\n (comment: CommentData) => {\n onCommentDelete?.(comment);\n\n const filteredComments = thread.comments.filter(\n (comment) => comment.body\n );\n\n if (filteredComments.length <= 1) {\n onThreadDelete?.(thread);\n }\n },\n [onCommentDelete, onThreadDelete, thread]\n );\n\n const handleSubscribeChange = useCallback(() => {\n if (subscriptionStatus === \"subscribed\") {\n unsubscribe();\n } else {\n subscribe();\n }\n }, [subscriptionStatus, subscribe, unsubscribe]);\n\n return (\n <TooltipProvider>\n <div\n className={cn(\n \"lb-root lb-thread\",\n showActions === \"hover\" && \"lb-thread:show-actions-hover\",\n className\n )}\n data-resolved={thread.resolved ? \"\" : undefined}\n data-unread={unreadIndex !== undefined ? \"\" : undefined}\n dir={$.dir}\n {...props}\n ref={forwardedRef}\n >\n <div className=\"lb-thread-comments\">\n {thread.comments.map((comment, index) => {\n const isFirstComment = index === firstCommentIndex;\n const isUnread =\n unreadIndex !== undefined && index >= unreadIndex;\n const isHidden =\n hiddenComments &&\n index >= hiddenComments.firstIndex &&\n index <= hiddenComments.lastIndex;\n const isHiddenBecauseDeleted =\n !showDeletedComments && !comment.body;\n const isFirstHiddenComment =\n isHidden && index === hiddenComments.firstIndex;\n\n if (isFirstHiddenComment) {\n return (\n <div\n key={`${comment.id}-show-more`}\n className=\"lb-thread-show-more\"\n >\n <Button\n variant=\"ghost\"\n className=\"lb-thread-show-more-button\"\n onClick={() => setShowAllComments(true)}\n >\n {$.THREAD_SHOW_MORE_COMMENTS(hiddenComments.count)}\n </Button>\n </div>\n );\n }\n\n if (isHidden || isHiddenBecauseDeleted) {\n return null;\n }\n\n const children = (\n <Comment\n key={comment.id}\n overrides={overrides}\n className=\"lb-thread-comment\"\n data-unread={isUnread ? \"\" : undefined}\n comment={comment}\n indentContent={indentCommentContent}\n showDeleted={showDeletedComments}\n showActions={showActions}\n showReactions={showReactions}\n showAttachments={showAttachments}\n showComposerFormattingControls={\n showComposerFormattingControls\n }\n onCommentEdit={onCommentEdit}\n onCommentDelete={handleCommentDelete}\n onAuthorClick={onAuthorClick}\n onMentionClick={onMentionClick}\n onAttachmentClick={onAttachmentClick}\n components={components}\n actionsClassName={\n isFirstComment ? \"lb-thread-actions\" : undefined\n }\n actions={\n isFirstComment && showResolveAction ? (\n <Tooltip\n content={\n thread.resolved\n ? $.THREAD_UNRESOLVE\n : $.THREAD_RESOLVE\n }\n >\n <TogglePrimitive.Root\n pressed={thread.resolved}\n onPressedChange={handleResolvedChange}\n asChild\n >\n <Button\n className=\"lb-comment-action\"\n onClick={stopPropagation}\n aria-label={\n thread.resolved\n ? $.THREAD_UNRESOLVE\n : $.THREAD_RESOLVE\n }\n icon={\n thread.resolved ? (\n <CheckCircleFillIcon />\n ) : (\n <CheckCircleIcon />\n )\n }\n disabled={!canComment}\n />\n </TogglePrimitive.Root>\n </Tooltip>\n ) : null\n }\n internalDropdownItems={\n isFirstComment ? (\n <DefaultComment.DropdownItem\n onSelect={handleSubscribeChange}\n icon={\n subscriptionStatus === \"subscribed\" ? (\n <BellCrossedIcon />\n ) : (\n <BellIcon />\n )\n }\n >\n {subscriptionStatus === \"subscribed\"\n ? $.THREAD_UNSUBSCRIBE\n : $.THREAD_SUBSCRIBE}\n </DefaultComment.DropdownItem>\n ) : undefined\n }\n dropdownItems={\n commentDropdownItems as CommentProps[\"dropdownItems\"]\n }\n />\n );\n\n return index === newIndicatorIndex &&\n newIndicatorIndex !== firstCommentIndex &&\n newIndicatorIndex <= lastCommentIndex ? (\n <Fragment key={comment.id}>\n <div\n className=\"lb-thread-new-indicator\"\n aria-label={$.THREAD_NEW_INDICATOR_DESCRIPTION}\n >\n <span className=\"lb-thread-new-indicator-label\">\n <ArrowDownIcon className=\"lb-thread-new-indicator-label-icon\" />\n {$.THREAD_NEW_INDICATOR}\n </span>\n </div>\n {children}\n </Fragment>\n ) : (\n children\n );\n })}\n </div>\n {unreadIndex !== undefined && (\n <MarkThreadAsReadMarker thread={thread} />\n )}\n {additionalContent}\n {showComposer && (\n <Composer\n className=\"lb-thread-composer\"\n threadId={thread.id}\n defaultCollapsed={showComposer === \"collapsed\" ? true : undefined}\n showAttachments={showAttachments}\n showFormattingControls={showComposerFormattingControls}\n onComposerSubmit={onComposerSubmit}\n blurOnSubmit={blurComposerOnSubmit}\n overrides={{\n COMPOSER_PLACEHOLDER: $.THREAD_COMPOSER_PLACEHOLDER,\n COMPOSER_SEND: $.THREAD_COMPOSER_SEND,\n ...overrides,\n }}\n roomId={thread.roomId}\n />\n )}\n </div>\n </TooltipProvider>\n );\n }\n) as <TM extends BaseMetadata = DTM, CM extends BaseMetadata = DCM>(\n props: ThreadProps<TM, CM> & RefAttributes<HTMLDivElement>\n) => JSX.Element;\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA2DA;AACE;AACA;AACA;AACA;AAEA;AAAA;AACE;AAEE;AACE;AAAyB;AAC3B;AACF;AACA;AAAA;AAEW;AACX;AAGF;AACE;AAAC;AAAA;AACC;AACmB;AACR;AACc;AAAA;AAG/B;AAsKO;AAAe;AAElB;AACE;AACuB;AACT;AACd;AACoB;AACJ;AACD;AACG;AACe;AACjC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACG;AAIL;AACA;AACA;AACA;AACA;AACA;AACE;AAEuD;AAEzD;AACE;AAEqE;AAEvE;AACE;AAIA;AAOA;AACE;AAAA;AAGF;AAKA;AACE;AAAA;AAGF;AACA;AAGA;AACE;AAEA;AAGA;AAAO;AACO;AACD;AACmC;AAChD;AAGF;AAGA;AAMA;AAQA;AAEA;AAIA;AAKE;AAAA;AAGF;AAAO;AAC0B;AACF;AAG+B;AAC9D;AACC;AACD;AACA;AACA;AACO;AAET;AAAM;AACI;AACR;AACA;AACA;AAEF;AAEE;AACE;AAAA;AAIF;AACE;AAAO;AAIT;AAAoC;AAGZ;AAGxB;AAEI;AACH;AACD;AACA;AACA;AACO;AACP;AAEF;AACA;AAEA;AACE;AAEE;AAAA;AACwD;AACxD;AACF;AAGF;AACA;AAMA;AACE;AAAsB;AAGxB;AAA6B;AAEzB;AAEA;AACE;AAA8B;AAE9B;AAAgC;AAClC;AACF;AACA;AACE;AACA;AACA;AACO;AACT;AAGF;AAA4B;AAExB;AAEA;AAAyC;AAClB;AAGvB;AACE;AAAuB;AACzB;AACF;AACwC;AAG1C;AACE;AACE;AAAY;AAEZ;AAAU;AACZ;AAGF;AAEI;AAAC;AAAA;AACY;AACT;AAC2B;AAC3B;AACF;AACsC;AACQ;AACvC;AACH;AACC;AAEL;AAEI;AACA;AAEA;AAIA;AAEA;AAGA;AACE;AACE;AAAC;AAAA;AAEW;AAEV;AAAC;AAAA;AACS;AACE;AAC4B;AAEW;AAAA;AACnD;AAAA;AATkB;AAUpB;AAIJ;AACE;AAAO;AAGT;AACE;AAAC;AAAA;AAEC;AACU;AACmB;AAC7B;AACe;AACF;AACb;AACA;AACA;AACA;AAGA;AACiB;AACjB;AACA;AACA;AACA;AAEyC;AAIrC;AAAC;AAAA;AAIS;AAGR;AAAiB;AAAhB;AACiB;AACC;AACV;AAEP;AAAC;AAAA;AACW;AACD;AAID;AAMa;AAGV;AAAA;AACb;AAAA;AACF;AAAA;AAEA;AAIF;AAAgB;AAAf;AACW;AAKI;AAMR;AAAA;AAEN;AAGJ;AAAA;AA5EW;AAiFjB;AAII;AAAA;AAAC;AAAA;AACW;AACI;AAGZ;AAA8D;AAC3D;AACL;AAAA;AACF;AACC;AAGH;AAGN;AAE0C;AAEzC;AAEC;AAAC;AAAA;AACW;AACO;AACuC;AACxD;AACwB;AACxB;AACc;AACH;AACe;AACP;AACd;AACL;AACe;AAAA;AACjB;AAAA;AAAA;AAGN;AAGN;;"}
|
|
@@ -11,6 +11,7 @@ var contexts = require('../../primitives/AiComposer/contexts.cjs');
|
|
|
11
11
|
var cn = require('../../utils/cn.cjs');
|
|
12
12
|
var Button = require('./Button.cjs');
|
|
13
13
|
var Tooltip = require('./Tooltip.cjs');
|
|
14
|
+
var TooltipPrimitive = require('@radix-ui/react-tooltip');
|
|
14
15
|
|
|
15
16
|
function AiComposerAction({
|
|
16
17
|
overrides: overrides$1
|
|
@@ -77,7 +78,7 @@ const AiComposer = react.forwardRef(
|
|
|
77
78
|
},
|
|
78
79
|
[onComposerSubmit, sendAiMessage, onComposerSubmitted]
|
|
79
80
|
);
|
|
80
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
81
|
+
return /* @__PURE__ */ jsxRuntime.jsx(TooltipPrimitive.TooltipProvider, { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
81
82
|
index.AiComposerForm,
|
|
82
83
|
{
|
|
83
84
|
className: cn.cn(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AiComposer.cjs","sources":["../../../src/components/internal/AiComposer.tsx"],"sourcesContent":["import {\n type AiChatMessage,\n type CopilotId,\n type MessageId,\n} from \"@liveblocks/core\";\nimport { useSendAiMessage } from \"@liveblocks/react\";\nimport {\n type ComponentProps,\n type FormEvent,\n forwardRef,\n type SyntheticEvent,\n useCallback,\n} from \"react\";\n\nimport { SendIcon } from \"../../icons/Send\";\nimport { StopIcon } from \"../../icons/Stop\";\nimport {\n type AiComposerOverrides,\n type GlobalOverrides,\n useOverrides,\n} from \"../../overrides\";\nimport * as AiComposerPrimitive from \"../../primitives/AiComposer\";\nimport { useAiComposer } from \"../../primitives/AiComposer/contexts\";\nimport type {\n AiComposerEditorProps,\n AiComposerFormProps,\n AiComposerSubmitMessage,\n} from \"../../primitives/AiComposer/types\";\nimport { cn } from \"../../utils/cn\";\nimport { Button } from \"./Button\";\nimport { ShortcutTooltip, TooltipProvider } from \"./Tooltip\";\n\n/* -------------------------------------------------------------------------------------------------\n * AiComposer\n * -----------------------------------------------------------------------------------------------*/\nexport interface AiComposerProps\n extends Omit<ComponentProps<\"form\">, \"defaultValue\"> {\n /**\n * The composer's initial value.\n */\n defaultValue?: string;\n\n /**\n * The event handler called when the composer is submitted.\n */\n onComposerSubmit?: (\n message: AiComposerSubmitMessage,\n event: FormEvent<HTMLFormElement>\n ) => void;\n\n /**\n * The event handler called after the composer is submitted.\n *\n * @internal This API will change, and is not considered stable. DO NOT RELY on it.\n */\n onComposerSubmitted?: (message: AiChatMessage) => void;\n\n /**\n * Whether the composer is disabled.\n */\n disabled?: AiComposerFormProps[\"disabled\"];\n\n /**\n * Whether to focus the composer on mount.\n */\n autoFocus?: AiComposerEditorProps[\"autoFocus\"];\n\n /**\n * Override the component's strings.\n */\n overrides?: Partial<GlobalOverrides & AiComposerOverrides>;\n\n /**\n * The ID of the chat the composer belongs to.\n */\n chatId: string;\n\n /**\n * The ID of the copilot to use to send the message.\n */\n copilotId?: CopilotId;\n\n /**\n * The time, in milliseconds, before an AI response will timeout.\n */\n responseTimeout?: number;\n\n /**\n * @internal\n */\n branchId?: MessageId;\n\n /**\n * @internal\n */\n stream?: boolean;\n}\n\nfunction AiComposerAction({\n overrides,\n}: {\n overrides?: AiComposerProps[\"overrides\"];\n}) {\n const { canAbort } = useAiComposer();\n const $ = useOverrides(overrides);\n\n const preventDefault = useCallback((event: SyntheticEvent) => {\n event.preventDefault();\n }, []);\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n return canAbort ? (\n <ShortcutTooltip content={$.AI_COMPOSER_ABORT}>\n <AiComposerPrimitive.Abort asChild>\n <Button\n onPointerDown={preventDefault}\n onClick={stopPropagation}\n className=\"lb-ai-composer-action\"\n variant=\"secondary\"\n aria-label={$.AI_COMPOSER_ABORT}\n icon={<StopIcon />}\n />\n </AiComposerPrimitive.Abort>\n </ShortcutTooltip>\n ) : (\n <ShortcutTooltip content={$.AI_COMPOSER_SEND} shortcut=\"Enter\">\n <AiComposerPrimitive.Submit asChild>\n <Button\n onPointerDown={preventDefault}\n onClick={stopPropagation}\n className=\"lb-ai-composer-action\"\n variant=\"primary\"\n aria-label={$.AI_COMPOSER_SEND}\n icon={<SendIcon />}\n />\n </AiComposerPrimitive.Submit>\n </ShortcutTooltip>\n );\n}\n\nexport const AiComposer = forwardRef<HTMLFormElement, AiComposerProps>(\n (\n {\n defaultValue,\n onComposerSubmit,\n disabled,\n autoFocus,\n overrides,\n className,\n chatId,\n branchId,\n copilotId,\n responseTimeout,\n stream = true,\n onComposerSubmitted,\n ...props\n },\n forwardedRef\n ) => {\n const $ = useOverrides(overrides);\n const sendAiMessage = useSendAiMessage(chatId, {\n stream,\n copilotId,\n timeout: responseTimeout,\n });\n\n const handleComposerSubmit = useCallback(\n (message: AiComposerSubmitMessage, event: FormEvent<HTMLFormElement>) => {\n onComposerSubmit?.(message, event);\n\n if (event.isDefaultPrevented()) return;\n\n const newMessage = sendAiMessage(message.text);\n\n onComposerSubmitted?.(newMessage);\n },\n [onComposerSubmit, sendAiMessage, onComposerSubmitted]\n );\n\n return (\n <TooltipProvider>\n <AiComposerPrimitive.Form\n className={cn(\n \"lb-root lb-ai-composer lb-ai-composer-form\",\n className\n )}\n dir={$.dir}\n {...props}\n disabled={disabled}\n ref={forwardedRef}\n onComposerSubmit={handleComposerSubmit}\n chatId={chatId}\n branchId={branchId}\n >\n <div className=\"lb-ai-composer-editor-container\">\n <AiComposerPrimitive.Editor\n autoFocus={autoFocus}\n className=\"lb-ai-composer-editor\"\n placeholder={$.AI_COMPOSER_PLACEHOLDER}\n defaultValue={defaultValue}\n />\n\n <div className=\"lb-ai-composer-footer\">\n <div className=\"lb-ai-composer-editor-actions\">\n {/* No actions for now but it makes sense to keep the DOM structure */}\n </div>\n\n <div className=\"lb-ai-composer-actions\">\n <AiComposerAction overrides={overrides} />\n </div>\n </div>\n </div>\n </AiComposerPrimitive.Form>\n </TooltipProvider>\n );\n }\n);\n"],"names":["overrides","useAiComposer","useOverrides","useCallback","jsx","ShortcutTooltip","AiComposerPrimitive.Abort","Button","StopIcon","AiComposerPrimitive.Submit","SendIcon","forwardRef","useSendAiMessage","TooltipProvider","AiComposerPrimitive.Form","cn","jsxs","AiComposerPrimitive.Editor"],"mappings":"
|
|
1
|
+
{"version":3,"file":"AiComposer.cjs","sources":["../../../src/components/internal/AiComposer.tsx"],"sourcesContent":["import {\n type AiChatMessage,\n type CopilotId,\n type MessageId,\n} from \"@liveblocks/core\";\nimport { useSendAiMessage } from \"@liveblocks/react\";\nimport {\n type ComponentProps,\n type FormEvent,\n forwardRef,\n type SyntheticEvent,\n useCallback,\n} from \"react\";\n\nimport { SendIcon } from \"../../icons/Send\";\nimport { StopIcon } from \"../../icons/Stop\";\nimport {\n type AiComposerOverrides,\n type GlobalOverrides,\n useOverrides,\n} from \"../../overrides\";\nimport * as AiComposerPrimitive from \"../../primitives/AiComposer\";\nimport { useAiComposer } from \"../../primitives/AiComposer/contexts\";\nimport type {\n AiComposerEditorProps,\n AiComposerFormProps,\n AiComposerSubmitMessage,\n} from \"../../primitives/AiComposer/types\";\nimport { cn } from \"../../utils/cn\";\nimport { Button } from \"./Button\";\nimport { ShortcutTooltip, TooltipProvider } from \"./Tooltip\";\n\n/* -------------------------------------------------------------------------------------------------\n * AiComposer\n * -----------------------------------------------------------------------------------------------*/\nexport interface AiComposerProps\n extends Omit<ComponentProps<\"form\">, \"defaultValue\"> {\n /**\n * The composer's initial value.\n */\n defaultValue?: string;\n\n /**\n * The event handler called when the composer is submitted.\n */\n onComposerSubmit?: (\n message: AiComposerSubmitMessage,\n event: FormEvent<HTMLFormElement>\n ) => void;\n\n /**\n * The event handler called after the composer is submitted.\n *\n * @internal This API will change, and is not considered stable. DO NOT RELY on it.\n */\n onComposerSubmitted?: (message: AiChatMessage) => void;\n\n /**\n * Whether the composer is disabled.\n */\n disabled?: AiComposerFormProps[\"disabled\"];\n\n /**\n * Whether to focus the composer on mount.\n */\n autoFocus?: AiComposerEditorProps[\"autoFocus\"];\n\n /**\n * Override the component's strings.\n */\n overrides?: Partial<GlobalOverrides & AiComposerOverrides>;\n\n /**\n * The ID of the chat the composer belongs to.\n */\n chatId: string;\n\n /**\n * The ID of the copilot to use to send the message.\n */\n copilotId?: CopilotId;\n\n /**\n * The time, in milliseconds, before an AI response will timeout.\n */\n responseTimeout?: number;\n\n /**\n * @internal\n */\n branchId?: MessageId;\n\n /**\n * @internal\n */\n stream?: boolean;\n}\n\nfunction AiComposerAction({\n overrides,\n}: {\n overrides?: AiComposerProps[\"overrides\"];\n}) {\n const { canAbort } = useAiComposer();\n const $ = useOverrides(overrides);\n\n const preventDefault = useCallback((event: SyntheticEvent) => {\n event.preventDefault();\n }, []);\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n return canAbort ? (\n <ShortcutTooltip content={$.AI_COMPOSER_ABORT}>\n <AiComposerPrimitive.Abort asChild>\n <Button\n onPointerDown={preventDefault}\n onClick={stopPropagation}\n className=\"lb-ai-composer-action\"\n variant=\"secondary\"\n aria-label={$.AI_COMPOSER_ABORT}\n icon={<StopIcon />}\n />\n </AiComposerPrimitive.Abort>\n </ShortcutTooltip>\n ) : (\n <ShortcutTooltip content={$.AI_COMPOSER_SEND} shortcut=\"Enter\">\n <AiComposerPrimitive.Submit asChild>\n <Button\n onPointerDown={preventDefault}\n onClick={stopPropagation}\n className=\"lb-ai-composer-action\"\n variant=\"primary\"\n aria-label={$.AI_COMPOSER_SEND}\n icon={<SendIcon />}\n />\n </AiComposerPrimitive.Submit>\n </ShortcutTooltip>\n );\n}\n\nexport const AiComposer = forwardRef<HTMLFormElement, AiComposerProps>(\n (\n {\n defaultValue,\n onComposerSubmit,\n disabled,\n autoFocus,\n overrides,\n className,\n chatId,\n branchId,\n copilotId,\n responseTimeout,\n stream = true,\n onComposerSubmitted,\n ...props\n },\n forwardedRef\n ) => {\n const $ = useOverrides(overrides);\n const sendAiMessage = useSendAiMessage(chatId, {\n stream,\n copilotId,\n timeout: responseTimeout,\n });\n\n const handleComposerSubmit = useCallback(\n (message: AiComposerSubmitMessage, event: FormEvent<HTMLFormElement>) => {\n onComposerSubmit?.(message, event);\n\n if (event.isDefaultPrevented()) return;\n\n const newMessage = sendAiMessage(message.text);\n\n onComposerSubmitted?.(newMessage);\n },\n [onComposerSubmit, sendAiMessage, onComposerSubmitted]\n );\n\n return (\n <TooltipProvider>\n <AiComposerPrimitive.Form\n className={cn(\n \"lb-root lb-ai-composer lb-ai-composer-form\",\n className\n )}\n dir={$.dir}\n {...props}\n disabled={disabled}\n ref={forwardedRef}\n onComposerSubmit={handleComposerSubmit}\n chatId={chatId}\n branchId={branchId}\n >\n <div className=\"lb-ai-composer-editor-container\">\n <AiComposerPrimitive.Editor\n autoFocus={autoFocus}\n className=\"lb-ai-composer-editor\"\n placeholder={$.AI_COMPOSER_PLACEHOLDER}\n defaultValue={defaultValue}\n />\n\n <div className=\"lb-ai-composer-footer\">\n <div className=\"lb-ai-composer-editor-actions\">\n {/* No actions for now but it makes sense to keep the DOM structure */}\n </div>\n\n <div className=\"lb-ai-composer-actions\">\n <AiComposerAction overrides={overrides} />\n </div>\n </div>\n </div>\n </AiComposerPrimitive.Form>\n </TooltipProvider>\n );\n }\n);\n"],"names":["overrides","useAiComposer","useOverrides","useCallback","jsx","ShortcutTooltip","AiComposerPrimitive.Abort","Button","StopIcon","AiComposerPrimitive.Submit","SendIcon","forwardRef","useSendAiMessage","TooltipProvider","AiComposerPrimitive.Form","cn","jsxs","AiComposerPrimitive.Editor"],"mappings":"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"}
|
|
@@ -8,7 +8,8 @@ import { AiComposerAbort, AiComposerSubmit, AiComposerForm, Editor as AiComposer
|
|
|
8
8
|
import { useAiComposer } from '../../primitives/AiComposer/contexts.js';
|
|
9
9
|
import { cn } from '../../utils/cn.js';
|
|
10
10
|
import { Button } from './Button.js';
|
|
11
|
-
import { ShortcutTooltip
|
|
11
|
+
import { ShortcutTooltip } from './Tooltip.js';
|
|
12
|
+
import { TooltipProvider } from '@radix-ui/react-tooltip';
|
|
12
13
|
|
|
13
14
|
function AiComposerAction({
|
|
14
15
|
overrides
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AiComposer.js","sources":["../../../src/components/internal/AiComposer.tsx"],"sourcesContent":["import {\n type AiChatMessage,\n type CopilotId,\n type MessageId,\n} from \"@liveblocks/core\";\nimport { useSendAiMessage } from \"@liveblocks/react\";\nimport {\n type ComponentProps,\n type FormEvent,\n forwardRef,\n type SyntheticEvent,\n useCallback,\n} from \"react\";\n\nimport { SendIcon } from \"../../icons/Send\";\nimport { StopIcon } from \"../../icons/Stop\";\nimport {\n type AiComposerOverrides,\n type GlobalOverrides,\n useOverrides,\n} from \"../../overrides\";\nimport * as AiComposerPrimitive from \"../../primitives/AiComposer\";\nimport { useAiComposer } from \"../../primitives/AiComposer/contexts\";\nimport type {\n AiComposerEditorProps,\n AiComposerFormProps,\n AiComposerSubmitMessage,\n} from \"../../primitives/AiComposer/types\";\nimport { cn } from \"../../utils/cn\";\nimport { Button } from \"./Button\";\nimport { ShortcutTooltip, TooltipProvider } from \"./Tooltip\";\n\n/* -------------------------------------------------------------------------------------------------\n * AiComposer\n * -----------------------------------------------------------------------------------------------*/\nexport interface AiComposerProps\n extends Omit<ComponentProps<\"form\">, \"defaultValue\"> {\n /**\n * The composer's initial value.\n */\n defaultValue?: string;\n\n /**\n * The event handler called when the composer is submitted.\n */\n onComposerSubmit?: (\n message: AiComposerSubmitMessage,\n event: FormEvent<HTMLFormElement>\n ) => void;\n\n /**\n * The event handler called after the composer is submitted.\n *\n * @internal This API will change, and is not considered stable. DO NOT RELY on it.\n */\n onComposerSubmitted?: (message: AiChatMessage) => void;\n\n /**\n * Whether the composer is disabled.\n */\n disabled?: AiComposerFormProps[\"disabled\"];\n\n /**\n * Whether to focus the composer on mount.\n */\n autoFocus?: AiComposerEditorProps[\"autoFocus\"];\n\n /**\n * Override the component's strings.\n */\n overrides?: Partial<GlobalOverrides & AiComposerOverrides>;\n\n /**\n * The ID of the chat the composer belongs to.\n */\n chatId: string;\n\n /**\n * The ID of the copilot to use to send the message.\n */\n copilotId?: CopilotId;\n\n /**\n * The time, in milliseconds, before an AI response will timeout.\n */\n responseTimeout?: number;\n\n /**\n * @internal\n */\n branchId?: MessageId;\n\n /**\n * @internal\n */\n stream?: boolean;\n}\n\nfunction AiComposerAction({\n overrides,\n}: {\n overrides?: AiComposerProps[\"overrides\"];\n}) {\n const { canAbort } = useAiComposer();\n const $ = useOverrides(overrides);\n\n const preventDefault = useCallback((event: SyntheticEvent) => {\n event.preventDefault();\n }, []);\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n return canAbort ? (\n <ShortcutTooltip content={$.AI_COMPOSER_ABORT}>\n <AiComposerPrimitive.Abort asChild>\n <Button\n onPointerDown={preventDefault}\n onClick={stopPropagation}\n className=\"lb-ai-composer-action\"\n variant=\"secondary\"\n aria-label={$.AI_COMPOSER_ABORT}\n icon={<StopIcon />}\n />\n </AiComposerPrimitive.Abort>\n </ShortcutTooltip>\n ) : (\n <ShortcutTooltip content={$.AI_COMPOSER_SEND} shortcut=\"Enter\">\n <AiComposerPrimitive.Submit asChild>\n <Button\n onPointerDown={preventDefault}\n onClick={stopPropagation}\n className=\"lb-ai-composer-action\"\n variant=\"primary\"\n aria-label={$.AI_COMPOSER_SEND}\n icon={<SendIcon />}\n />\n </AiComposerPrimitive.Submit>\n </ShortcutTooltip>\n );\n}\n\nexport const AiComposer = forwardRef<HTMLFormElement, AiComposerProps>(\n (\n {\n defaultValue,\n onComposerSubmit,\n disabled,\n autoFocus,\n overrides,\n className,\n chatId,\n branchId,\n copilotId,\n responseTimeout,\n stream = true,\n onComposerSubmitted,\n ...props\n },\n forwardedRef\n ) => {\n const $ = useOverrides(overrides);\n const sendAiMessage = useSendAiMessage(chatId, {\n stream,\n copilotId,\n timeout: responseTimeout,\n });\n\n const handleComposerSubmit = useCallback(\n (message: AiComposerSubmitMessage, event: FormEvent<HTMLFormElement>) => {\n onComposerSubmit?.(message, event);\n\n if (event.isDefaultPrevented()) return;\n\n const newMessage = sendAiMessage(message.text);\n\n onComposerSubmitted?.(newMessage);\n },\n [onComposerSubmit, sendAiMessage, onComposerSubmitted]\n );\n\n return (\n <TooltipProvider>\n <AiComposerPrimitive.Form\n className={cn(\n \"lb-root lb-ai-composer lb-ai-composer-form\",\n className\n )}\n dir={$.dir}\n {...props}\n disabled={disabled}\n ref={forwardedRef}\n onComposerSubmit={handleComposerSubmit}\n chatId={chatId}\n branchId={branchId}\n >\n <div className=\"lb-ai-composer-editor-container\">\n <AiComposerPrimitive.Editor\n autoFocus={autoFocus}\n className=\"lb-ai-composer-editor\"\n placeholder={$.AI_COMPOSER_PLACEHOLDER}\n defaultValue={defaultValue}\n />\n\n <div className=\"lb-ai-composer-footer\">\n <div className=\"lb-ai-composer-editor-actions\">\n {/* No actions for now but it makes sense to keep the DOM structure */}\n </div>\n\n <div className=\"lb-ai-composer-actions\">\n <AiComposerAction overrides={overrides} />\n </div>\n </div>\n </div>\n </AiComposerPrimitive.Form>\n </TooltipProvider>\n );\n }\n);\n"],"names":["AiComposerPrimitive.Abort","AiComposerPrimitive.Submit","AiComposerPrimitive.Form","AiComposerPrimitive.Editor"],"mappings":"
|
|
1
|
+
{"version":3,"file":"AiComposer.js","sources":["../../../src/components/internal/AiComposer.tsx"],"sourcesContent":["import {\n type AiChatMessage,\n type CopilotId,\n type MessageId,\n} from \"@liveblocks/core\";\nimport { useSendAiMessage } from \"@liveblocks/react\";\nimport {\n type ComponentProps,\n type FormEvent,\n forwardRef,\n type SyntheticEvent,\n useCallback,\n} from \"react\";\n\nimport { SendIcon } from \"../../icons/Send\";\nimport { StopIcon } from \"../../icons/Stop\";\nimport {\n type AiComposerOverrides,\n type GlobalOverrides,\n useOverrides,\n} from \"../../overrides\";\nimport * as AiComposerPrimitive from \"../../primitives/AiComposer\";\nimport { useAiComposer } from \"../../primitives/AiComposer/contexts\";\nimport type {\n AiComposerEditorProps,\n AiComposerFormProps,\n AiComposerSubmitMessage,\n} from \"../../primitives/AiComposer/types\";\nimport { cn } from \"../../utils/cn\";\nimport { Button } from \"./Button\";\nimport { ShortcutTooltip, TooltipProvider } from \"./Tooltip\";\n\n/* -------------------------------------------------------------------------------------------------\n * AiComposer\n * -----------------------------------------------------------------------------------------------*/\nexport interface AiComposerProps\n extends Omit<ComponentProps<\"form\">, \"defaultValue\"> {\n /**\n * The composer's initial value.\n */\n defaultValue?: string;\n\n /**\n * The event handler called when the composer is submitted.\n */\n onComposerSubmit?: (\n message: AiComposerSubmitMessage,\n event: FormEvent<HTMLFormElement>\n ) => void;\n\n /**\n * The event handler called after the composer is submitted.\n *\n * @internal This API will change, and is not considered stable. DO NOT RELY on it.\n */\n onComposerSubmitted?: (message: AiChatMessage) => void;\n\n /**\n * Whether the composer is disabled.\n */\n disabled?: AiComposerFormProps[\"disabled\"];\n\n /**\n * Whether to focus the composer on mount.\n */\n autoFocus?: AiComposerEditorProps[\"autoFocus\"];\n\n /**\n * Override the component's strings.\n */\n overrides?: Partial<GlobalOverrides & AiComposerOverrides>;\n\n /**\n * The ID of the chat the composer belongs to.\n */\n chatId: string;\n\n /**\n * The ID of the copilot to use to send the message.\n */\n copilotId?: CopilotId;\n\n /**\n * The time, in milliseconds, before an AI response will timeout.\n */\n responseTimeout?: number;\n\n /**\n * @internal\n */\n branchId?: MessageId;\n\n /**\n * @internal\n */\n stream?: boolean;\n}\n\nfunction AiComposerAction({\n overrides,\n}: {\n overrides?: AiComposerProps[\"overrides\"];\n}) {\n const { canAbort } = useAiComposer();\n const $ = useOverrides(overrides);\n\n const preventDefault = useCallback((event: SyntheticEvent) => {\n event.preventDefault();\n }, []);\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n return canAbort ? (\n <ShortcutTooltip content={$.AI_COMPOSER_ABORT}>\n <AiComposerPrimitive.Abort asChild>\n <Button\n onPointerDown={preventDefault}\n onClick={stopPropagation}\n className=\"lb-ai-composer-action\"\n variant=\"secondary\"\n aria-label={$.AI_COMPOSER_ABORT}\n icon={<StopIcon />}\n />\n </AiComposerPrimitive.Abort>\n </ShortcutTooltip>\n ) : (\n <ShortcutTooltip content={$.AI_COMPOSER_SEND} shortcut=\"Enter\">\n <AiComposerPrimitive.Submit asChild>\n <Button\n onPointerDown={preventDefault}\n onClick={stopPropagation}\n className=\"lb-ai-composer-action\"\n variant=\"primary\"\n aria-label={$.AI_COMPOSER_SEND}\n icon={<SendIcon />}\n />\n </AiComposerPrimitive.Submit>\n </ShortcutTooltip>\n );\n}\n\nexport const AiComposer = forwardRef<HTMLFormElement, AiComposerProps>(\n (\n {\n defaultValue,\n onComposerSubmit,\n disabled,\n autoFocus,\n overrides,\n className,\n chatId,\n branchId,\n copilotId,\n responseTimeout,\n stream = true,\n onComposerSubmitted,\n ...props\n },\n forwardedRef\n ) => {\n const $ = useOverrides(overrides);\n const sendAiMessage = useSendAiMessage(chatId, {\n stream,\n copilotId,\n timeout: responseTimeout,\n });\n\n const handleComposerSubmit = useCallback(\n (message: AiComposerSubmitMessage, event: FormEvent<HTMLFormElement>) => {\n onComposerSubmit?.(message, event);\n\n if (event.isDefaultPrevented()) return;\n\n const newMessage = sendAiMessage(message.text);\n\n onComposerSubmitted?.(newMessage);\n },\n [onComposerSubmit, sendAiMessage, onComposerSubmitted]\n );\n\n return (\n <TooltipProvider>\n <AiComposerPrimitive.Form\n className={cn(\n \"lb-root lb-ai-composer lb-ai-composer-form\",\n className\n )}\n dir={$.dir}\n {...props}\n disabled={disabled}\n ref={forwardedRef}\n onComposerSubmit={handleComposerSubmit}\n chatId={chatId}\n branchId={branchId}\n >\n <div className=\"lb-ai-composer-editor-container\">\n <AiComposerPrimitive.Editor\n autoFocus={autoFocus}\n className=\"lb-ai-composer-editor\"\n placeholder={$.AI_COMPOSER_PLACEHOLDER}\n defaultValue={defaultValue}\n />\n\n <div className=\"lb-ai-composer-footer\">\n <div className=\"lb-ai-composer-editor-actions\">\n {/* No actions for now but it makes sense to keep the DOM structure */}\n </div>\n\n <div className=\"lb-ai-composer-actions\">\n <AiComposerAction overrides={overrides} />\n </div>\n </div>\n </div>\n </AiComposerPrimitive.Form>\n </TooltipProvider>\n );\n }\n);\n"],"names":["AiComposerPrimitive.Abort","AiComposerPrimitive.Submit","AiComposerPrimitive.Form","AiComposerPrimitive.Editor"],"mappings":";;;;;;;;;;;;;AAkGA,SAAS,gBAAiB,CAAA;AAAA,EACxB,SAAA;AACF,CAEG,EAAA;AACD,EAAM,MAAA,EAAE,QAAS,EAAA,GAAI,aAAc,EAAA,CAAA;AACnC,EAAM,MAAA,CAAA,GAAI,aAAa,SAAS,CAAA,CAAA;AAEhC,EAAM,MAAA,cAAA,GAAiB,WAAY,CAAA,CAAC,KAA0B,KAAA;AAC5D,IAAA,KAAA,CAAM,cAAe,EAAA,CAAA;AAAA,GACvB,EAAG,EAAE,CAAA,CAAA;AAEL,EAAM,MAAA,eAAA,GAAkB,WAAY,CAAA,CAAC,KAA0B,KAAA;AAC7D,IAAA,KAAA,CAAM,eAAgB,EAAA,CAAA;AAAA,GACxB,EAAG,EAAE,CAAA,CAAA;AAEL,EAAO,OAAA,QAAA,mBACJ,GAAA,CAAA,eAAA,EAAA,EAAgB,OAAS,EAAA,CAAA,CAAE,iBAC1B,EAAA,QAAA,kBAAA,GAAA,CAACA,eAAoB,EAApB,EAA0B,OAAA,EAAO,IAChC,EAAA,QAAA,kBAAA,GAAA;AAAA,IAAC,MAAA;AAAA,IAAA;AAAA,MACC,aAAe,EAAA,cAAA;AAAA,MACf,OAAS,EAAA,eAAA;AAAA,MACT,SAAU,EAAA,uBAAA;AAAA,MACV,OAAQ,EAAA,WAAA;AAAA,MACR,cAAY,CAAE,CAAA,iBAAA;AAAA,MACd,IAAA,sBAAO,QAAS,EAAA,EAAA,CAAA;AAAA,KAAA;AAAA,GAEpB,EAAA,CAAA,EACF,CAEA,mBAAA,GAAA,CAAC,mBAAgB,OAAS,EAAA,CAAA,CAAE,gBAAkB,EAAA,QAAA,EAAS,SACrD,QAAC,kBAAA,GAAA,CAAAC,gBAAA,EAAA,EAA2B,SAAO,IACjC,EAAA,QAAA,kBAAA,GAAA;AAAA,IAAC,MAAA;AAAA,IAAA;AAAA,MACC,aAAe,EAAA,cAAA;AAAA,MACf,OAAS,EAAA,eAAA;AAAA,MACT,SAAU,EAAA,uBAAA;AAAA,MACV,OAAQ,EAAA,SAAA;AAAA,MACR,cAAY,CAAE,CAAA,gBAAA;AAAA,MACd,IAAA,sBAAO,QAAS,EAAA,EAAA,CAAA;AAAA,KAAA;AAAA,KAEpB,CACF,EAAA,CAAA,CAAA;AAEJ,CAAA;AAEO,MAAM,UAAa,GAAA,UAAA;AAAA,EACxB,CACE;AAAA,IACE,YAAA;AAAA,IACA,gBAAA;AAAA,IACA,QAAA;AAAA,IACA,SAAA;AAAA,IACA,SAAA;AAAA,IACA,SAAA;AAAA,IACA,MAAA;AAAA,IACA,QAAA;AAAA,IACA,SAAA;AAAA,IACA,eAAA;AAAA,IACA,MAAS,GAAA,IAAA;AAAA,IACT,mBAAA;AAAA,IACA,GAAG,KAAA;AAAA,KAEL,YACG,KAAA;AACH,IAAM,MAAA,CAAA,GAAI,aAAa,SAAS,CAAA,CAAA;AAChC,IAAM,MAAA,aAAA,GAAgB,iBAAiB,MAAQ,EAAA;AAAA,MAC7C,MAAA;AAAA,MACA,SAAA;AAAA,MACA,OAAS,EAAA,eAAA;AAAA,KACV,CAAA,CAAA;AAED,IAAA,MAAM,oBAAuB,GAAA,WAAA;AAAA,MAC3B,CAAC,SAAkC,KAAsC,KAAA;AACvE,QAAA,gBAAA,GAAmB,SAAS,KAAK,CAAA,CAAA;AAEjC,QAAA,IAAI,MAAM,kBAAmB,EAAA;AAAG,UAAA,OAAA;AAEhC,QAAM,MAAA,UAAA,GAAa,aAAc,CAAA,OAAA,CAAQ,IAAI,CAAA,CAAA;AAE7C,QAAA,mBAAA,GAAsB,UAAU,CAAA,CAAA;AAAA,OAClC;AAAA,MACA,CAAC,gBAAkB,EAAA,aAAA,EAAe,mBAAmB,CAAA;AAAA,KACvD,CAAA;AAEA,IAAA,2BACG,eACC,EAAA,EAAA,QAAA,kBAAA,GAAA;AAAA,MAACC,cAAoB;AAAA,MAApB;AAAA,QACC,SAAW,EAAA,EAAA;AAAA,UACT,4CAAA;AAAA,UACA,SAAA;AAAA,SACF;AAAA,QACA,KAAK,CAAE,CAAA,GAAA;AAAA,QACN,GAAG,KAAA;AAAA,QACJ,QAAA;AAAA,QACA,GAAK,EAAA,YAAA;AAAA,QACL,gBAAkB,EAAA,oBAAA;AAAA,QAClB,MAAA;AAAA,QACA,QAAA;AAAA,QAEA,QAAA,kBAAA,IAAA,CAAC,KAAI,EAAA,EAAA,SAAA,EAAU,iCACb,EAAA,QAAA,EAAA;AAAA,0BAAA,GAAA;AAAA,YAACC,gBAAoB;AAAA,YAApB;AAAA,cACC,SAAA;AAAA,cACA,SAAU,EAAA,uBAAA;AAAA,cACV,aAAa,CAAE,CAAA,uBAAA;AAAA,cACf,YAAA;AAAA,aAAA;AAAA,WACF;AAAA,0BAEA,IAAA,CAAC,KAAI,EAAA,EAAA,SAAA,EAAU,uBACb,EAAA,QAAA,EAAA;AAAA,4BAAC,GAAA,CAAA,KAAA,EAAA,EAAI,WAAU,+BAEf,EAAA,CAAA;AAAA,gCAEC,KAAI,EAAA,EAAA,SAAA,EAAU,0BACb,QAAC,kBAAA,GAAA,CAAA,gBAAA,EAAA,EAAiB,WAAsB,CAC1C,EAAA,CAAA;AAAA,WACF,EAAA,CAAA;AAAA,SACF,EAAA,CAAA;AAAA,OAAA;AAAA,KAEJ,EAAA,CAAA,CAAA;AAAA,GAEJ;AACF;;;;"}
|
|
@@ -6,6 +6,7 @@ require('../../icons/index.cjs');
|
|
|
6
6
|
var overrides = require('../../overrides.cjs');
|
|
7
7
|
var Button = require('./Button.cjs');
|
|
8
8
|
var Tooltip = require('./Tooltip.cjs');
|
|
9
|
+
var TooltipPrimitive = require('@radix-ui/react-tooltip');
|
|
9
10
|
var Check = require('../../icons/Check.cjs');
|
|
10
11
|
var Copy = require('../../icons/Copy.cjs');
|
|
11
12
|
|
|
@@ -34,7 +35,7 @@ function CodeBlock({ title, code, overrides: overrides$1 }) {
|
|
|
34
35
|
console.error(error);
|
|
35
36
|
}
|
|
36
37
|
}, [code]);
|
|
37
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
38
|
+
return /* @__PURE__ */ jsxRuntime.jsx(TooltipPrimitive.TooltipProvider, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lb-root lb-code-block", children: [
|
|
38
39
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lb-code-block-header", children: [
|
|
39
40
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "lb-code-block-title", children: title }),
|
|
40
41
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "lb-code-block-header-actions", children: /* @__PURE__ */ jsxRuntime.jsx(Tooltip.Tooltip, { content: isCopied ? null : $.COPY_TO_CLIPBOARD, children: /* @__PURE__ */ jsxRuntime.jsx(
|