Repository: dream-num/univer
Pull request #5091: feat(thread-comment-ui): use tailwindcss to rewrite the style of thread-comment-ui
Author: login: weird94
Created: 2025-04-25T09:55:07Z | Merged: 2025-04-25T16:07:20Z

<!--
 Thank you for submitting a Pull Request.
 Please read our Pull Request guidelines:
 https://github.com/dream-num/univer/blob/dev/CONTRIBUTING.md#submitting-pull-requests
-->

<!-- Associate issues with the pull request if there is one. Separate them width commas. -->
<!-- Feel free to delete this if there is no related issue. -->

<!-- A description of the proposed changes. -->

<!-- How to test them. -->

<!-- Uncomment the below lines if there are breaking changes introduced in this PR. -->
<!-- BREAKING CHANGE:
Before:

After: -->

## Pull Request Checklist

- [ ] Related tickets or issues have been linked in the PR description (or missing issue).
- [ ] [Naming convention](https://github.com/dream-num/univer/blob/dev/docs/NAMING_CONVENTION.md) is followed (**do please** check it especially when you created new plugins, commands and resources).
- [ ] Unit tests have been added for the changes (if applicable).
- [ ] Breaking changes have been documented (or no breaking changes introduced in this PR).



Diff:
diff --git a/mockdata/src/sheets/demo/default-workbook-data-demo.ts b/mockdata/src/sheets/demo/default-workbook-data-demo.ts
index 4a06119ba5fc..08978083d2e1 100644
--- a/mockdata/src/sheets/demo/default-workbook-data-demo.ts
+++ b/mockdata/src/sheets/demo/default-workbook-data-demo.ts
@@ -24118,20 +24118,20 @@ export const DEFAULT_WORKBOOK_DATA_DEMO: IWorkbookData = {
         {
             name: 'SHEET_UNIVER_THREAD_COMMENT_PLUGIN',
             data: JSON.stringify({
-                'sheet-0011': [{ text: { textRuns: [], paragraphs: [{ startIndex: 3, paragraphStyle: {} }], sectionBreaks: [{ startIndex: 4 }], dataStream: '123\\n\\r', customRanges: [] }, dT: '2024/05/17 21:16', id: 'jwV0QtHwUbhG3o--iy1qa', ref: 'H9', personId: 'mockId', unitId: 'workbook-01', subUnitId: 'sheet-0011' }],
+                'sheet-0011': [{ text: { textRuns: [], paragraphs: [{ startIndex: 3, paragraphStyle: {} }], sectionBreaks: [{ startIndex: 4 }], dataStream: '123\r\n', customRanges: [] }, dT: '2024/05/17 21:16', id: 'jwV0QtHwUbhG3o--iy1qa', ref: 'H9', personId: 'Owner_qxVnhPbQ', unitId: 'workbook-01', subUnitId: 'sheet-0011' }],
                 'dv-test': [
                     {
                         text: {
                             textRuns: [],
                             paragraphs: [{ startIndex: 3, paragraphStyle: {} }],
                             sectionBreaks: [{ startIndex: 4 }],
-                            dataStream: '1\\n\\r',
+                            dataStream: '1\r\n',
                             customRanges: [],
                         },
                         dT: '2024/05/17 21:16',
                         id: '12',
                         ref: 'C6',
-                        personId: 'mockId',
+                        personId: '123',
                         unitId: 'workbook-01',
                         subUnitId: 'dv-test',
                     },
@@ -24140,13 +24140,13 @@ export const DEFAULT_WORKBOOK_DATA_DEMO: IWorkbookData = {
                             textRuns: [],
                             paragraphs: [{ startIndex: 3, paragraphStyle: {} }],
                             sectionBreaks: [{ startIndex: 4 }],
-                            dataStream: '1\\n\\r',
+                            dataStream: '1\r\n',
                             customRanges: [],
                         },
                         dT: '2024/05/17 21:16',
                         id: '1',
                         ref: 'B6',
-                        personId: 'mockId',
+                        personId: 'Owner_qxVnhPbQ',
                         unitId: 'workbook-01',
                         subUnitId: 'dv-test',
                     },
@@ -24155,13 +24155,13 @@ export const DEFAULT_WORKBOOK_DATA_DEMO: IWorkbookData = {
                             textRuns: [],
                             paragraphs: [{ startIndex: 3, paragraphStyle: {} }],
                             sectionBreaks: [{ startIndex: 4 }],
-                            dataStream: '2\\n\\r',
+                            dataStream: '2\r\n',
                             customRanges: [],
                         },
                         dT: '2024/05/17 21:16',
                         id: '2',
                         ref: 'B7',
-                        personId: 'mockId',
+                        personId: 'Owner_qxVnhPbQ',
                         unitId: 'workbook-01',
                         subUnitId: 'dv-test',
                     },
@@ -24170,13 +24170,13 @@ export const DEFAULT_WORKBOOK_DATA_DEMO: IWorkbookData = {
                             textRuns: [],
                             paragraphs: [{ startIndex: 3, paragraphStyle: {} }],
                             sectionBreaks: [{ startIndex: 4 }],
-                            dataStream: '3\\n\\r',
+                            dataStream: '3\r\n',
                             customRanges: [],
                         },
                         dT: '2024/05/17 21:16',
                         id: '3',
                         ref: 'B8',
-                        personId: 'mockId',
+                        personId: 'Owner_qxVnhPbQ',
                         unitId: 'workbook-01',
                         subUnitId: 'dv-test',
                     },
@@ -24185,13 +24185,13 @@ export const DEFAULT_WORKBOOK_DATA_DEMO: IWorkbookData = {
                             textRuns: [],
                             paragraphs: [{ startIndex: 3, paragraphStyle: {} }],
                             sectionBreaks: [{ startIndex: 4 }],
-                            dataStream: '4\\n\\r',
+                            dataStream: '4\r\n',
                             customRanges: [],
                         },
                         dT: '2024/05/17 21:16',
                         id: '4',
                         ref: 'B9',
-                        personId: 'mockId',
+                        personId: 'Owner_qxVnhPbQ',
                         unitId: 'workbook-01',
                         subUnitId: 'dv-test',
                     },
diff --git a/packages/docs-ui/src/commands/commands/replace-content.command.ts b/packages/docs-ui/src/commands/commands/replace-content.command.ts
index e4ad2ea696e3..f0010cf49553 100644
--- a/packages/docs-ui/src/commands/commands/replace-content.command.ts
+++ b/packages/docs-ui/src/commands/commands/replace-content.command.ts
@@ -38,7 +38,6 @@ export const ReplaceSnapshotCommand: ICommand<IReplaceSnapshotCommandParams> = {
         const univerInstanceService = accessor.get(IUniverInstanceService);
         const commandService = accessor.get(ICommandService);
         const docSelectionManagerService = accessor.get(DocSelectionManagerService);
-
         const docDataModel = univerInstanceService.getUnit<DocumentDataModel>(unitId, UniverInstanceType.UNIVER_DOC);
         const prevSnapshot = docDataModel?.getSelfOrHeaderFooterModel(segmentId).getSnapshot();
 
diff --git a/packages/docs-ui/src/services/editor/editor.ts b/packages/docs-ui/src/services/editor/editor.ts
index 5c71e9163332..9670588ffdb2 100644
--- a/packages/docs-ui/src/services/editor/editor.ts
+++ b/packages/docs-ui/src/services/editor/editor.ts
@@ -313,10 +313,8 @@ export class Editor extends Disposable implements IEditor {
 
     // Set the new document data.
     setDocumentData(data: IDocumentData, textRanges: Nullable<ITextRangeWithStyle[]>) {
-        const { id } = data;
-
         this._commandService.syncExecuteCommand(ReplaceSnapshotCommand.id, {
-            unitId: id,
+            unitId: this.getEditorId(),
             snapshot: data,
             textRanges,
         });
diff --git a/packages/thread-comment-ui/src/locale/en-US.ts b/packages/thread-comment-ui/src/locale/en-US.ts
index 2170d2c007f8..0c70b8888d91 100644
--- a/packages/thread-comment-ui/src/locale/en-US.ts
+++ b/packages/thread-comment-ui/src/locale/en-US.ts
@@ -24,6 +24,7 @@ const enUS: typeof zhCN = {
             filterEmpty: 'No match result',
             reset: 'Reset Filter',
             addComment: 'Add Comment',
+            solved: 'Solved',
         },
         editor: {
             placeholder: 'Reply or add others with @',
diff --git a/packages/thread-comment-ui/src/locale/fa-IR.ts b/packages/thread-comment-ui/src/locale/fa-IR.ts
index 6f3202e3f8ca..17c8ad4d2621 100644
--- a/packages/thread-comment-ui/src/locale/fa-IR.ts
+++ b/packages/thread-comment-ui/src/locale/fa-IR.ts
@@ -24,6 +24,7 @@ const locale: typeof zhCN = {
             filterEmpty: 'نتیجه ای یافت نشد',
             reset: 'بازنشانی فیلتر',
             addComment: 'افزودن نظر',
+            solved: 'حل شده',
         },
         editor: {
             placeholder: 'پاسخ یا اضافه کردن دیگران با @',
diff --git a/packages/thread-comment-ui/src/locale/fr-FR.ts b/packages/thread-comment-ui/src/locale/fr-FR.ts
index 5b5a630ef454..8fac01f0a6fe 100644
--- a/packages/thread-comment-ui/src/locale/fr-FR.ts
+++ b/packages/thread-comment-ui/src/locale/fr-FR.ts
@@ -24,6 +24,7 @@ const enUS: typeof zhCN = {
             filterEmpty: 'Aucun résultat correspondant',
             reset: 'Réinitialiser le filtre',
             addComment: 'Ajouter un commentaire',
+            solved: 'Résolu',
         },
         editor: {
             placeholder: 'Répondre ou ajouter d\'autres avec @',
diff --git a/packages/thread-comment-ui/src/locale/ru-RU.ts b/packages/thread-comment-ui/src/locale/ru-RU.ts
index 5fc87dad898b..4fd114c41153 100644
--- a/packages/thread-comment-ui/src/locale/ru-RU.ts
+++ b/packages/thread-comment-ui/src/locale/ru-RU.ts
@@ -22,6 +22,7 @@ export default {
             filterEmpty: 'No match result',
             reset: 'Reset Filter',
             addComment: 'Add Comment',
+            solved: 'Solved',
         },
         editor: {
             placeholder: 'Reply or add others with @',
diff --git a/packages/thread-comment-ui/src/locale/vi-VN.ts b/packages/thread-comment-ui/src/locale/vi-VN.ts
index 22142b4b4671..7e6537e6336d 100644
--- a/packages/thread-comment-ui/src/locale/vi-VN.ts
+++ b/packages/thread-comment-ui/src/locale/vi-VN.ts
@@ -24,6 +24,7 @@ const enUS: typeof zhCN = {
             filterEmpty: 'Không có kết quả phù hợp',
             reset: 'Đặt lại',
             addComment: 'Thêm bình luận',
+            solved: 'Đã giải quyết',
         },
         editor: {
             placeholder: 'Phản hồi',
diff --git a/packages/thread-comment-ui/src/locale/zh-CN.ts b/packages/thread-comment-ui/src/locale/zh-CN.ts
index a0880350ec87..1072c57c18ab 100644
--- a/packages/thread-comment-ui/src/locale/zh-CN.ts
+++ b/packages/thread-comment-ui/src/locale/zh-CN.ts
@@ -22,6 +22,7 @@ export default {
             filterEmpty: '没有匹配的结果',
             reset: '重置',
             addComment: '添加评论',
+            solved: '已解决',
         },
         editor: {
             placeholder: '回复',
diff --git a/packages/thread-comment-ui/src/locale/zh-TW.ts b/packages/thread-comment-ui/src/locale/zh-TW.ts
index 69e0bf2b9d82..2082ea530a61 100644
--- a/packages/thread-comment-ui/src/locale/zh-TW.ts
+++ b/packages/thread-comment-ui/src/locale/zh-TW.ts
@@ -24,6 +24,7 @@ const enUS: typeof zhCN = {
             filterEmpty: '沒有符合的結果',
             reset: '重置',
             addComment: '新增評論',
+            solved: '已解決',
         },
         editor: {
             placeholder: '回覆',
diff --git a/packages/thread-comment-ui/src/views/thread-comment-editor/index.tsx b/packages/thread-comment-ui/src/views/thread-comment-editor/index.tsx
index 3ea73a3a5e6d..0f928871da0f 100644
--- a/packages/thread-comment-ui/src/views/thread-comment-editor/index.tsx
+++ b/packages/thread-comment-ui/src/views/thread-comment-editor/index.tsx
@@ -14,11 +14,11 @@
  * limitations under the License.
  */
 
-import type { IDocumentBody, IDocumentData } from '@univerjs/core';
+import type { IDocumentBody, IDocumentData, IUser } from '@univerjs/core';
 import type { Editor, IKeyboardEventConfig } from '@univerjs/docs-ui';
 import type { IThreadComment } from '@univerjs/thread-comment';
 import { BuildTextUtils, DOCS_NORMAL_EDITOR_UNIT_ID_KEY, ICommandService, LocaleService, Tools, UniverInstanceType } from '@univerjs/core';
-import { Button } from '@univerjs/design';
+import { Button, clsx } from '@univerjs/design';
 import { BreakLineCommand, IEditorService, RichTextEditor } from '@univerjs/docs-ui';
 import { KeyCode, useDependency } from '@univerjs/ui';
 import React, { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
@@ -82,8 +82,16 @@ export const ThreadCommentEditor = forwardRef<IThreadCommentEditorInstance, IThr
 
     useImperativeHandle(ref, () => ({
         reply(text) {
-            editor.current?.focus();
-            editor.current?.setDocumentData(getSnapshot(text));
+            if (!editor.current) {
+                return;
+            }
+            editorService.focus(editor.current!.getEditorId() ?? '');
+            const documentData = getSnapshot(text);
+            editor.current?.setDocumentData(documentData, [{
+                startOffset: documentData.body!.dataStream.length - 2,
+                endOffset: documentData.body!.dataStream.length - 2,
+                collapsed: true,
+            }]);
         },
     }));
 
@@ -123,7 +131,7 @@ export const ThreadCommentEditor = forwardRef<IThreadCommentEditorInstance, IThr
             />
             {editing
                 ? (
-                    <div className={styles.threadCommentEditorButtons}>
+                    <div className="univer-mt-3 univer-flex univer-flex-row univer-justify-end">
                         <Button
                             style={{ marginRight: 12 }}
                             onClick={() => {
@@ -148,3 +156,15 @@ export const ThreadCommentEditor = forwardRef<IThreadCommentEditorInstance, IThr
         </div>
     );
 });
+
+export const ThreadCommentSuggestion = ({ active, user }: { active: boolean; user: IUser }) => (
+    <div
+        className={clsx(
+            'univer-flex univer-items-center univer-text-sm univer-text-black',
+            { 'univer-bg-gray-50': active }
+        )}
+    >
+        <img draggable={false} className="univer-mr-[6px] univer-h-6 univer-w-6 univer-rounded-full" src={user.avatar} />
+        <span>{user.name}</span>
+    </div>
+);
diff --git a/packages/thread-comment-ui/src/views/thread-comment-editor/util.ts b/packages/thread-comment-ui/src/views/thread-comment-editor/util.ts
index cc104affa411..55ba7ed2010f 100644
--- a/packages/thread-comment-ui/src/views/thread-comment-editor/util.ts
+++ b/packages/thread-comment-ui/src/views/thread-comment-editor/util.ts
@@ -14,8 +14,9 @@
  * limitations under the License.
  */
 
+import type { IDocumentBody } from '@univerjs/core';
 import type { IThreadCommentMention } from '@univerjs/thread-comment';
-import { CustomRangeType, getBodySlice, type IDocumentBody } from '@univerjs/core';
+import { CustomRangeType, getBodySlice } from '@univerjs/core';
 
 export type TextNode = {
     type: 'text';
@@ -86,6 +87,7 @@ export const transformTextNodes2Document = (nodes: TextNode[]): IDocumentBody =>
                     startIndex: start,
                     endIndex: end,
                     properties: {},
+                    wholeEntity: true,
                 });
                 break;
             }
@@ -95,7 +97,7 @@ export const transformTextNodes2Document = (nodes: TextNode[]): IDocumentBody =>
         }
     });
 
-    str += '\n\r';
+    str += '\r\n';
 
     return {
         textRuns: [],
diff --git a/packages/thread-comment-ui/src/views/thread-comment-panel/index.tsx b/packages/thread-comment-ui/src/views/thread-comment-panel/index.tsx
index e4fcb2be82a7..31b745289314 100644
--- a/packages/thread-comment-ui/src/views/thread-comment-panel/index.tsx
+++ b/packages/thread-comment-ui/src/views/thread-comment-panel/index.tsx
@@ -27,7 +27,6 @@ import React, { useEffect, useMemo, useRef, useState } from 'react';
 import { SetActiveCommentOperation } from '../../commands/operations/comment.operations';
 import { ThreadCommentPanelService } from '../../services/thread-comment-panel.service';
 import { ThreadCommentTree } from '../thread-comment-tree';
-import styles from './index.module.less';
 
 export interface IThreadCommentPanelProps {
     unitId: string;
@@ -161,6 +160,7 @@ export const ThreadCommentPanel = (props: IThreadCommentPanelProps) => {
 
     const renderComment = (comment: IThreadComment) => (
         <ThreadCommentTree
+            full
             prefix={prefix}
             getSubUnitName={getSubUnitName}
             key={comment.id}
@@ -196,8 +196,8 @@ export const ThreadCommentPanel = (props: IThreadCommentPanelProps) => {
     );
 
     return (
-        <div className={styles.threadCommentPanel}>
-            <div className={styles.threadCommentPanelForms}>
+        <div className="univer-flex univer-min-h-full univer-flex-col univer-pb-3">
+            <div className="univer-mt-3 univer-flex univer-flex-row univer-justify-between">
                 {type === UniverInstanceType.UNIVER_SHEET
                     ? (
                         <Select
@@ -241,37 +241,44 @@ export const ThreadCommentPanel = (props: IThreadCommentPanelProps) => {
                     ]}
                 />
             </div>
-            {unSolvedComments.map(renderComment)}
-            {solvedComments.length ? <div className={styles.threadCommentPanelSolved}>已解决</div> : null}
-            {solvedComments.map(renderComment)}
-            {renderComments.length
-                ? null
-                : (
-                    <div className={styles.threadCommentPanelEmpty}>
-                        {isFiltering ?
-                            localeService.t('threadCommentUI.panel.filterEmpty')
-                            : localeService.t('threadCommentUI.panel.empty')}
+            {renderComments.length === 0
+                ? (
+                    <div
+                        className={`
+                          univer-flex univer-flex-1 univer-flex-col univer-items-center univer-justify-center
+                          univer-text-[13px] univer-text-gray-600
+                        `}
+                    >
+                        {localeService.t('threadCommentUI.panel.empty')}
                         {isFiltering
                             ? (
-                                <Button
-                                    variant="link"
-                                    onClick={onReset}
-                                >
-                                    {localeService.t('threadCommentUI.panel.reset')}
-                                </Button>
+                                <div className="univer-mt-2 univer-flex univer-flex-row">
+                                    <Button onClick={onReset}>
+                                        {localeService.t('threadCommentUI.panel.reset')}
+                                    </Button>
+                                </div>
                             )
-                            : (
-                                <Button
-                                    id="thread-comment-add"
-                                    className={styles.threadCommentPanelAdd}
-                                    variant="primary"
-                                    onClick={onAdd}
-                                    disabled={disableAdd}
-                                >
-                                    <IncreaseSingle />
-                                    {localeService.t('threadCommentUI.panel.addComment')}
-                                </Button>
-                            )}
+                            : !disableAdd
+                                ? (
+                                    <div className="univer-mt-2 univer-flex univer-flex-row">
+                                        <Button onClick={onAdd}>
+                                            <IncreaseSingle className="univer-mr-[6px]" />
+                                            {localeService.t('threadCommentUI.panel.add')}
+                                        </Button>
+                                    </div>
+                                )
+                                : null}
+                    </div>
+                )
+                : (
+                    <div className="univer-mt-3 univer-flex univer-flex-col univer-gap-3">
+                        {unSolvedComments.map(renderComment)}
+                        {solvedComments.length > 0 && (
+                            <div className="univer-text-xs">
+                                {localeService.t('threadCommentUI.panel.solved')}
+                            </div>
+                        )}
+                        {solvedComments.map(renderComment)}
                     </div>
                 )}
         </div>
diff --git a/packages/thread-comment-ui/src/views/thread-comment-tree/index.tsx b/packages/thread-comment-ui/src/views/thread-comment-tree/index.tsx
index 0be30318e417..f66884246407 100644
--- a/packages/thread-comment-ui/src/views/thread-comment-tree/index.tsx
+++ b/packages/thread-comment-ui/src/views/thread-comment-tree/index.tsx
@@ -16,20 +16,21 @@
 
 import type { IUser, UniverInstanceType } from '@univerjs/core';
 import type { IAddCommentCommandParams, IThreadComment, IUpdateCommentCommandParams } from '@univerjs/thread-comment';
+import type { IUniverUIConfig } from '@univerjs/ui';
 import type { IThreadCommentEditorInstance } from '../thread-comment-editor';
 import { generateRandomId, ICommandService, LocaleService, UserManagerService } from '@univerjs/core';
 import { clsx, Dropdown, Tooltip } from '@univerjs/design';
 import { DeleteSingle, MoreHorizontalSingle, ReplyToCommentSingle, ResolvedSingle, SolveSingle } from '@univerjs/icons';
 import { AddCommentCommand, DeleteCommentCommand, DeleteCommentTreeCommand, getDT, ResolveCommentCommand, ThreadCommentModel, UpdateCommentCommand } from '@univerjs/thread-comment';
-import { useDependency, useObservable } from '@univerjs/ui';
+import { UI_PLUGIN_CONFIG_KEY, useConfigValue, useDependency, useObservable } from '@univerjs/ui';
 import { useEffect, useMemo, useRef, useState } from 'react';
 import { debounceTime } from 'rxjs';
 import { SetActiveCommentOperation } from '../../commands/operations/comment.operations';
 import { ThreadCommentEditor } from '../thread-comment-editor';
 import { transformDocument2TextNodes, transformTextNodes2Document } from '../thread-comment-editor/util';
-import styles from './index.module.less';
 
 export interface IThreadCommentTreeProps {
+    full?: boolean;
     id?: string;
     unitId: string;
     subUnitId: string;
@@ -47,6 +48,7 @@ export interface IThreadCommentTreeProps {
     onAddComment?: (comment: IThreadComment) => boolean;
     onDeleteComment?: (comment: IThreadComment) => boolean;
     onResolve?: (resolved: boolean) => void;
+    style?: React.CSSProperties;
 }
 
 export interface IThreadCommentItemProps {
@@ -77,6 +79,8 @@ const ThreadCommentItem = (props: IThreadCommentItemProps) => {
     const isCommentBySelf = currentUser?.userID === item.personId;
     const isMock = item.id === MOCK_ID;
     const [showReply, setShowReply] = useState(false);
+    const uiConfig = useConfigValue<IUniverUIConfig>(UI_PLUGIN_CONFIG_KEY);
+    const avatarFallback = uiConfig?.avatarFallback;
 
     const handleDeleteItem = () => {
         if (onDeleteComment?.(item) === false) {
@@ -97,65 +101,92 @@ const ThreadCommentItem = (props: IThreadCommentItemProps) => {
     };
 
     return (
-        <div className={styles.threadCommentItem} onMouseLeave={() => setShowReply(false)} onMouseEnter={() => setShowReply(true)}>
-            <img className={styles.threadCommentItemHead} src={user?.avatar} />
-            <div className={styles.threadCommentItemTitle}>
-                <div className={styles.threadCommentUsername}>
-                    {user?.name || ' '}
-                </div>
-                <div>
-                    {(isMock || resolved)
-                        ? null
-                        : (
-                            showReply
+        <div className="univer-relative univer-mb-3 univer-pl-[30px]" onMouseLeave={() => setShowReply(false)} onMouseEnter={() => setShowReply(true)}>
+            <div
+                className={`
+                  univer-absolute univer-left-0 univer-top-0 univer-h-6 univer-w-6 univer-rounded-full univer-bg-cover
+                  univer-bg-center univer-bg-no-repeat
+                `}
+                style={{
+                    backgroundImage: `url(${user?.avatar || avatarFallback})`,
+                }}
+            />
+            {user
+                ? (
+                    <div className="univer-mb-1 univer-flex univer-h-6 univer-items-center univer-justify-between">
+                        <div className="univer-text-sm univer-font-medium univer-leading-5">
+                            {user?.name || ' '}
+                        </div>
+                        <div>
+                            {(isMock || resolved)
+                                ? null
+                                : (
+                                    showReply && user
+                                        ? (
+                                            <div
+                                                className={`
+                                                  univer-ml-1 univer-inline-flex univer-h-6 univer-w-6
+                                                  univer-cursor-pointer univer-items-center univer-justify-center
+                                                  univer-rounded-[3px] univer-text-base
+                                                  hover:univer-bg-gray-50
+                                                `}
+                                                onClick={() => onReply(user)}
+                                            >
+                                                <ReplyToCommentSingle />
+                                            </div>
+                                        )
+                                        : null
+                                )}
+                            {isCommentBySelf && !isMock && !resolved
                                 ? (
-                                    <div className={styles.threadCommentIcon} onClick={() => onReply(user)}>
-                                        <ReplyToCommentSingle />
-                                    </div>
-                                )
-                                : null
-                        )}
-                    {isCommentBySelf && !isMock && !resolved
-                        ? (
-                            <Dropdown
-                                overlay={(
-                                    <div className="univer-rounded-lg univer-p-4 univer-theme">
-                                        <ul
+                                    <Dropdown
+                                        overlay={(
+                                            <div className="univer-rounded-lg univer-p-4 univer-theme">
+                                                <ul
+                                                    className={`
+                                                      univer-m-0 univer-grid univer-list-none univer-gap-2 univer-p-0
+                                                      univer-text-sm
+                                                      [&_a]:univer-cursor-pointer [&_a]:univer-rounded [&_a]:univer-p-1
+                                                    `}
+                                                >
+                                                    <li>
+                                                        <a
+                                                            className="hover:univer-bg-gray-200"
+                                                            onClick={() => onEditingChange?.(true)}
+                                                        >
+                                                            {localeService.t('threadCommentUI.item.edit')}
+                                                        </a>
+                                                    </li>
+                                                    <li>
+                                                        <a
+                                                            className="hover:univer-bg-gray-200"
+                                                            onClick={handleDeleteItem}
+                                                        >
+                                                            {localeService.t('threadCommentUI.item.delete')}
+                                                        </a>
+                                                    </li>
+                                                </ul>
+                                            </div>
+                                        )}
+                                    >
+                                        <div
                                             className={`
-                                              univer-m-0 univer-grid univer-list-none univer-gap-2 univer-p-0
-                                              univer-text-sm
-                                              [&_a]:univer-cursor-pointer [&_a]:univer-rounded [&_a]:univer-p-1
+                                              univer-ml-1 univer-inline-flex univer-h-6 univer-w-6 univer-cursor-pointer
+                                              univer-items-center univer-justify-center univer-rounded-[3px]
+                                              univer-text-base
+                                              hover:univer-bg-gray-50
                                             `}
                                         >
-                                            <li>
-                                                <a
-                                                    className="hover:univer-bg-gray-200"
-                                                    onClick={() => onEditingChange?.(true)}
-                                                >
-                                                    {localeService.t('threadCommentUI.item.edit')}
-                                                </a>
-                                            </li>
-                                            <li>
-                                                <a
-                                                    className="hover:univer-bg-gray-200"
-                                                    onClick={handleDeleteItem}
-                                                >
-                                                    {localeService.t('threadCommentUI.item.delete')}
-                                                </a>
-                                            </li>
-                                        </ul>
-                                    </div>
-                                )}
-                            >
-                                <div className={styles.threadCommentIcon}>
-                                    <MoreHorizontalSingle />
-                                </div>
-                            </Dropdown>
-                        )
-                        : null}
-                </div>
-            </div>
-            <div className={styles.threadCommentItemTime}>{item.dT}</div>
+                                            <MoreHorizontalSingle />
+                                        </div>
+                                    </Dropdown>
+                                )
+                                : null}
+                        </div>
+                    </div>
+                )
+                : null}
+            <div className="univer-mb-1 univer-text-xs univer-leading-[1.5] univer-text-gray-600">{item.dT}</div>
             {editing
                 ? (
                     <ThreadCommentEditor
@@ -184,14 +215,14 @@ const ThreadCommentItem = (props: IThreadCommentItemProps) => {
                     />
                 )
                 : (
-                    <div className={styles.threadCommentItemContent}>
+                    <div className="univer-text-[13px] univer-leading-5 univer-text-black">
                         {transformDocument2TextNodes(item.text).map((paragraph, i) => (
                             <div key={i} className="univer-break-words">
                                 {paragraph.map((item, i) => {
                                     switch (item.type) {
                                         case 'mention':
                                             return (
-                                                <a className={styles.threadCommentItemAt} key={i}>
+                                                <a className="univer-text-blue-600" key={i}>
                                                     {item.content.label}
                                                     {' '}
                                                 </a>
@@ -227,6 +258,8 @@ export const ThreadCommentTree = (props: IThreadCommentTreeProps) => {
         onDeleteComment,
         onResolve,
         type,
+        style,
+        full,
     } = props;
     const threadCommentModel = useDependency(ThreadCommentModel);
     const [isHover, setIsHover] = useState(false);
@@ -308,9 +341,17 @@ export const ThreadCommentTree = (props: IThreadCommentTreeProps) => {
 
     return (
         <div
-            className={clsx(styles.threadComment, {
-                [styles.threadCommentActive]: !resolved && (showHighlight || isHover || prefix === 'cell'),
-            })}
+            className={clsx(
+                `
+                  univer-relative univer-box-border univer-rounded-lg univer-border univer-border-solid
+                  univer-border-gray-200 univer-bg-white univer-p-4
+                `,
+                full ? 'univer-w-full' : 'univer-w-[278px]',
+                {
+                    'univer-shadow': !resolved && (showHighlight || isHover || prefix === 'cell'),
+                }
+            )}
+            style={style}
             onClick={onClick}
             id={`${prefix}-${unitId}-${subUnitId}-${id}`}
             onMouseEnter={() => {
@@ -322,29 +363,68 @@ export const ThreadCommentTree = (props: IThreadCommentTreeProps) => {
                 setIsHover(false);
             }}
         >
-            {!resolved && showHighlight ? <div className={styles.threadCommentHighlight} /> : null}
-            <div className={styles.threadCommentTitle}>
-                <div className={styles.threadCommentTitlePosition}>
-                    <div className={styles.threadCommentTitleHighlight} />
+            {!resolved && showHighlight
+                ? (
+                    <div
+                        className={`
+                          univer-absolute univer-left-0 univer-right-0 univer-top-0 univer-h-[6px]
+                          univer-rounded-t-[6px] univer-bg-yellow-400
+                        `}
+                    />
+                )
+                : null}
+            <div
+                className={`
+                  univer-mb-4 univer-flex univer-flex-row univer-items-center univer-justify-between univer-text-sm
+                  univer-leading-5
+                `}
+            >
+                <div className="univer-flex univer-flex-1 univer-flex-row univer-items-center univer-overflow-hidden">
+                    <div
+                        className={`
+                          univer-mr-2 univer-h-[14px] univer-w-[3px] univer-flex-shrink-0 univer-flex-grow-0
+                          univer-rounded-[1.5px] univer-bg-yellow-400
+                        `}
+                    />
                     <Tooltip showIfEllipsis title={title}>
-                        <div className={styles.threadCommentTitlePositionText}>
+                        <div
+                            className={`
+                              univer-flex-1 univer-overflow-hidden univer-text-ellipsis univer-whitespace-nowrap
+                            `}
+                        >
                             {title}
                         </div>
                     </Tooltip>
                 </div>
                 {comments
                     ? (
-                        <div className={styles.threadCommentIconContainer}>
+                        <div className="univer-flex univer-flex-shrink-0 univer-flex-grow-0 univer-flex-row">
                             <div
                                 onClick={handleResolve}
-                                className={styles.threadCommentIcon}
-                                style={{ color: resolved ? 'rgb(var(--green-500))' : '' }}
+                                className={clsx(
+                                    `
+                                      univer-ml-1 univer-inline-flex univer-h-6 univer-w-6 univer-cursor-pointer
+                                      univer-items-center univer-justify-center univer-rounded-[3px] univer-text-base
+                                      hover:univer-bg-gray-50
+                                    `,
+                                    {
+                                        'univer-text-green-500': resolved,
+                                    }
+                                )}
                             >
                                 {resolved ? <ResolvedSingle /> : <SolveSingle />}
                             </div>
                             {currentUser?.userID === comments.root.personId
                                 ? (
-                                    <div className={styles.threadCommentIcon} onClick={handleDeleteRoot}>
+                                    <div
+                                        className={`
+                                          univer-ml-1 univer-inline-flex univer-h-6 univer-w-6 univer-cursor-pointer
+                                          univer-items-center univer-justify-center univer-rounded-[3px]
+                                          univer-text-base
+                                          hover:univer-bg-gray-50
+                                        `}
+                                        onClick={handleDeleteRoot}
+                                    >
                                         <DeleteSingle />
                                     </div>
                                 )
@@ -353,7 +433,14 @@ export const ThreadCommentTree = (props: IThreadCommentTreeProps) => {
                     )
                     : null}
             </div>
-            <div className={styles.threadCommentContent} ref={scroller}>
+            <div
+                className={`
+                  univer-max-h-[300px] univer-overflow-y-auto univer-overflow-x-hidden
+                  [scrollbar-gutter:auto]
+                  [scrollbar-width:thin]
+                `}
+                ref={scroller}
+            >
                 {renderComments.map(
                     (item) => (
                         <ThreadCommentItem
@@ -378,13 +465,19 @@ export const ThreadCommentTree = (props: IThreadCommentTreeProps) => {
                                     return;
                                 }
                                 requestAnimationFrame(() => {
-                                    editorRef.current?.reply(transformTextNodes2Document([{
-                                        type: 'mention',
-                                        content: {
-                                            id: user.userID,
-                                            label: user.name,
+                                    editorRef.current?.reply(transformTextNodes2Document([
+                                        {
+                                            type: 'mention',
+                                            content: {
+                                                id: user.userID,
+                                                label: `@${user.name}`,
+                                            },
+                                        },
+                                        {
+                                            type: 'text',
+                                            content: ' ',
                                         },
-                                    }]));
+                                    ]));
                                 });
                             }}
                             onAddComment={onAddComment}
diff --git a/packages/ui/src/controllers/config.schema.ts b/packages/ui/src/controllers/config.schema.ts
index 7f2c2f504539..44f2de49a9f6 100644
--- a/packages/ui/src/controllers/config.schema.ts
+++ b/packages/ui/src/controllers/config.schema.ts
@@ -31,6 +31,11 @@ export interface IUniverUIConfig extends IWorkbenchOptions {
     menu?: MenuConfig;
 
     popupRootId?: string;
+
+    /**
+     * The fallback avatar for user.
+     */
+    avatarFallback?: string;
 }
 
 export const defaultPluginConfig: IUniverUIConfig = {};


Reviews:
user: login: jikkai
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-04-25T12:37:28Z
user: login: jikkai
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-25T14:47:02Z

Review comments:
diff_hunk: @@ -123,7 +131,7 @@ export const ThreadCommentEditor = forwardRef<IThreadCommentEditorInstance, IThr
             />
             {editing
                 ? (
-                    <div className={styles.threadCommentEditorButtons}>
+                    <div className="univer-mt-3 univer-flex univer-flex-row univer-justify-end">
                         <Button
                             style={{ marginRight: 12 }}
path: packages/thread-comment-ui/src/views/thread-comment-editor/index.tsx
user: login: jikkai
body: This should also be replaced with a Tailwind CSS class.


created_at: 2025-04-25T12:35:44Z
author_association: MEMBER
diff_hunk: @@ -148,3 +156,15 @@ export const ThreadCommentEditor = forwardRef<IThreadCommentEditorInstance, IThr
         </div>
     );
 });
+
+export const ThreadCommentSuggestion = ({ active, user }: { active: boolean; user: IUser }) => (
+    <div
+        className={clsx(
+            'univer-flex univer-items-center univer-text-sm univer-text-black',
+            { 'univer-bg-gray-50': active }
+        )}
+    >
+        <img className="univer-mr-[6px] univer-h-6 univer-w-6 univer-rounded-full" src={user.avatar} />
path: packages/thread-comment-ui/src/views/thread-comment-editor/index.tsx
user: login: jikkai
body: How about setting the `img` tag to `draggable={false}` here to prevent users from accidentally dragging it?

created_at: 2025-04-25T12:36:46Z
author_association: MEMBER
diff_hunk: @@ -161,6 +160,7 @@ export const ThreadCommentPanel = (props: IThreadCommentPanelProps) => {
 
     const renderComment = (comment: IThreadComment) => (
         <ThreadCommentTree
+            style={{ width: '100%' }}
path: packages/thread-comment-ui/src/views/thread-comment-panel/index.tsx
user: login: jikkai
body: univer-w-full
created_at: 2025-04-25T12:37:11Z
author_association: MEMBER

PR comments:
user: login: github-actions[bot]
created_at: 2025-04-25T10:02:46Z
body: <!-- playwright-report-github-action -- 🎭 Playwright Tests -->

### Playwright test results

![passed](https://icongr.am/octicons/check-circle.svg?size=14&color=3fb950)  **25 passed**

#### Details

![stats](https://icongr.am/octicons/pulse.svg?size=14&color=abb4bf)  25 tests across 12 suites  
![duration](https://icongr.am/octicons/clock.svg?size=14&color=abb4bf)  6 minutes, 13 seconds  
![commit](https://icongr.am/octicons/git-pull-request.svg?size=14&color=abb4bf)  [8170ad0](https://github.com/dream-num/univer/commit/8170ad002ca27fa45d8db3e43a3d903b197fd0b4)  
![info](https://icongr.am/octicons/info.svg?size=14&color=abb4bf)  For more information, [see full report]()
author_association: NONE
user: login: codecov[bot]
created_at: 2025-04-25T10:06:54Z
body: ## [Codecov](https://app.codecov.io/gh/dream-num/univer/pull/5091?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dream-num) Report
Attention: Patch coverage is `0%` with `43 lines` in your changes missing coverage. Please review.
> Project coverage is 33.07%. Comparing base [(`05b07f4`)](https://app.codecov.io/gh/dream-num/univer/commit/05b07f46c03728cc085b7f5171732bbb5266b6f8?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dream-num) to head [(`8170ad0`)](https://app.codecov.io/gh/dream-num/univer/commit/8170ad002ca27fa45d8db3e43a3d903b197fd0b4?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dream-num).
> Report is 2 commits behind head on dev.

| [Files with missing lines](https://app.codecov.io/gh/dream-num/univer/pull/5091?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dream-num) | Patch % | Lines |
|---|---|---|
| [...comment-ui/src/views/thread-comment-tree/index.tsx](https://app.codecov.io/gh/dream-num/univer/pull/5091?src=pr&el=tree&filepath=packages%2Fthread-comment-ui%2Fsrc%2Fviews%2Fthread-comment-tree%2Findex.tsx&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dream-num#diff-cGFja2FnZXMvdGhyZWFkLWNvbW1lbnQtdWkvc3JjL3ZpZXdzL3RocmVhZC1jb21tZW50LXRyZWUvaW5kZXgudHN4) | 0.00% | [26 Missing :warning: ](https://app.codecov.io/gh/dream-num/univer/pull/5091?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dream-num) |
| [...mment-ui/src/views/thread-comment-editor/index.tsx](https://app.codecov.io/gh/dream-num/univer/pull/5091?src=pr&el=tree&filepath=packages%2Fthread-comment-ui%2Fsrc%2Fviews%2Fthread-comment-editor%2Findex.tsx&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dream-num#diff-cGFja2FnZXMvdGhyZWFkLWNvbW1lbnQtdWkvc3JjL3ZpZXdzL3RocmVhZC1jb21tZW50LWVkaXRvci9pbmRleC50c3g=) | 0.00% | [8 Missing :warning: ](https://app.codecov.io/gh/dream-num/univer/pull/5091?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dream-num) |
| [...omment-ui/src/views/thread-comment-panel/index.tsx](https://app.codecov.io/gh/dream-num/univer/pull/5091?src=pr&el=tree&filepath=packages%2Fthread-comment-ui%2Fsrc%2Fviews%2Fthread-comment-panel%2Findex.tsx&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dream-num#diff-cGFja2FnZXMvdGhyZWFkLWNvbW1lbnQtdWkvc3JjL3ZpZXdzL3RocmVhZC1jb21tZW50LXBhbmVsL2luZGV4LnRzeA==) | 0.00% | [8 Missing :warning: ](https://app.codecov.io/gh/dream-num/univer/pull/5091?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dream-num) |
| [...comment-ui/src/views/thread-comment-editor/util.ts](https://app.codecov.io/gh/dream-num/univer/pull/5091?src=pr&el=tree&filepath=packages%2Fthread-comment-ui%2Fsrc%2Fviews%2Fthread-comment-editor%2Futil.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dream-num#diff-cGFja2FnZXMvdGhyZWFkLWNvbW1lbnQtdWkvc3JjL3ZpZXdzL3RocmVhZC1jb21tZW50LWVkaXRvci91dGlsLnRz) | 0.00% | [1 Missing :warning: ](https://app.codecov.io/gh/dream-num/univer/pull/5091?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dream-num) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##              dev    #5091      +/-   ##
==========================================
- Coverage   33.07%   33.07%   -0.01%     
==========================================
  Files        2715     2715              
  Lines      141974   141988      +14     
  Branches    31563    31571       +8     
==========================================
  Hits        46958    46958              
- Misses      95016    95030      +14     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/dream-num/univer/pull/5091?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dream-num).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dream-num).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-25T10:16:30Z
body: ## View Deployment
| 📑 Demo (React@19) | Demo (React@16) | 📚 Storybook |
| --- | --- | --- |
| [🔗 Preview link](https://univer-j0vrqalob-univer.vercel.app/) | [🔗 Preview link](https://univer-j0vrqalob-univer.vercel.app/react16/) | [🔗 Preview link](https://univer-design-9gufep0kr-univer.vercel.app/) |
author_association: NONE