Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(bubble): add editable support #328

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

tabzzz1
Copy link
Contributor

@tabzzz1 tabzzz1 commented Dec 9, 2024

feat: #296

Basic Demo

x-bubble-editable.mov

Summary by CodeRabbit

  • 功能

    • 允许Bubble开启editable进入编辑态,具体的操作及ui如上demo所示
    • editable属性添加了editingonChangeonEndonCancel以及editorTextareaConfigeditorButtonConfig属性来允许用户对Bubble组件的编辑态进行操作和监听
  • 文档

    • 添加了editable属性的API文档和使用示例

具体使用方法和其他事项见:#305

Summary by CodeRabbit

  • 新功能

    • 添加了可编辑的气泡组件,允许用户直接在界面上修改内容。
    • 引入了新的 Editor 组件,提供文本编辑区域和操作按钮。
  • 文档

    • 更新了气泡组件的文档,新增了关于 editable 属性的说明,包括中英文版本。
    • 在文档中添加了示例,展示了气泡组件的编辑效果。
  • 样式

    • 为编辑器部分添加了新的样式规则,优化了视觉效果。

Copy link
Contributor

coderabbitai bot commented Dec 9, 2024

📝 Walkthrough

Walkthrough

该拉取请求对 Bubble 组件进行了重大修改,新增了可编辑功能。Bubble 组件现在接受一个 editable 属性,用于配置编辑行为,并引入了 useEditableConfig 钩子来管理编辑状态。内部状态管理现在包含 isEditing,并根据 editable 属性的值进行更新。Editor 组件被集成以处理文本编辑,并在 enableEdit 为真且 isEditing 活动时显示。相关文档也进行了更新,以说明新功能的使用。

Changes

文件路径 更改摘要
components/bubble/Bubble.tsx 更新 Bubble 组件,添加 editable 属性,整合 useEditableConfig 钩子,管理编辑状态,新增编辑生命周期处理函数,更新渲染逻辑以显示 Editor 组件。
components/bubble/Editor.tsx 新增 Editor 组件,提供可编辑文本区域,处理输入状态和事件,使用 Ant Design 组件,支持动态按钮渲染。
components/bubble/demo/editable.md editable 属性添加中英文文档,解释其编辑功能的使用。
components/bubble/demo/editable.tsx 新增 App 组件,利用 Ant Design 创建可编辑气泡界面,管理编辑状态和历史记录,提供编辑、删除和导航功能。
components/bubble/index.en-US.md 更新文档,新增 "Editing effect" 示例,定义 EditConfig 接口,描述编辑功能相关属性及其默认值。
components/bubble/index.zh-CN.md 更新文档,新增 "编辑效果" 示例,扩展 API 部分,定义 EditConfig 接口及其属性。
components/bubble/interface.ts 添加 EditorButtonsConfig 类型和 EditConfig 接口,更新 BubbleProps 接口以包含 editable 属性。
components/bubble/style/index.ts 为编辑器部分添加新样式规则,更新现有样式。
components/bubble/hooks/useEditableConfig.ts 新增 useEditableConfig 钩子,管理编辑状态和配置,提供编辑生命周期处理函数。

Possibly related PRs

Suggested reviewers

  • YumoImer

🐰 在气泡中编辑,
文字如花绽放,
点击即开启,
变化随心所欲,
让内容更生动,
兔子欢快跳,
新功能真美妙! 🌼


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Experiment)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

github-actions bot commented Dec 9, 2024

Preview is ready

Copy link

codecov bot commented Dec 9, 2024

Bundle Report

Bundle size has no change ✅

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🧹 Outside diff range and nitpick comments (11)
components/bubble/hooks/useMergedConfig.ts (1)

7-17: 建议优化空值处理逻辑

当前实现在处理 propConfig 为非对象时直接使用 null,这可能导致意外的合并结果。建议添加类型守卫和更严格的空值处理。

建议修改为:

  return React.useMemo<readonly [boolean, Target]>(() => {
    const support = !!propConfig;
+   const config = support && typeof propConfig === 'object' ? propConfig : {};
    return [
      support,
      {
        ...templateConfig,
-       ...(support && typeof propConfig === 'object' ? propConfig : null),
+       ...config,
      },
    ] as const;
  }, [propConfig]);
components/bubble/interface.ts (2)

2-3: 建议统一 antd 的导入路径

建议统一使用 ES 模块的导入路径,避免混用 'lib' 和 'es'。

import { TextAreaProps } from 'antd/es/input';
-import { ButtonProps } from 'antd/lib';
+import { ButtonProps } from 'antd/es/button';

20-27: 建议完善 EditConfig 接口的文档注释

建议为 EditConfig 接口添加 JSDoc 文档注释,说明每个属性的用途和约束条件。同时建议为回调函数添加更严格的类型定义。

+/**
+ * 编辑配置接口
+ */
 export interface EditConfig {
+  /** 是否处于编辑状态 */
   editing?: boolean;
+  /** 内容变化时的回调函数 */
   onChange?: (content: string) => void;
+  /** 取消编辑的回调函数 */
   onCancel?: VoidFunction;
+  /** 完成编辑的回调函数 */
   onEnd?: (content: string) => void;
+  /** 编辑器文本域配置 */
   editorTextAreaConfig?: TextAreaProps;
+  /** 编辑器按钮配置 */
   editorButtonConfig?: EditorButtonConfig[];
 }
components/bubble/index.zh-CN.md (1)

83-90: 建议完善 API 文档

  1. 建议添加版本信息
  2. 建议优化属性描述,使其更清晰易懂
  3. 建议添加示例代码

建议更新表格内容:

 | 属性 | 说明 | 类型 | 默认值 | 版本 |
 | --- | --- | --- | --- | --- |
-| editing | 控制是否是编辑中状态 | boolean | false |  |
+| editing | 控制气泡是否处于编辑状态 | boolean | false | 1.0.0 |
-| onChange | 文本域编辑时触发 | (content?:string) => void | - |  |
+| onChange | 编辑器内容变化时的回调函数 | (content: string) => void | - | 1.0.0 |
components/bubble/Editor.tsx (2)

11-21: 接口定义清晰,但缺少必要的属性验证

建议为可选的回调函数添加类型保护,以避免在未提供回调时的潜在运行时错误。

 interface EditableProps {
   prefixCls: string;
   value: string;
-  onChange?: (value: string) => void;
+  onChange?: ((value: string) => void) | null;
-  onCancel?: () => void;
+  onCancel?: (() => void) | null;
-  onEnd?: (value: string) => void;
+  onEnd?: ((value: string) => void) | null;
   editorClassName?: string;
   editorStyle?: React.CSSProperties;
   editorTextAreaConfig?: EditConfig['editorTextAreaConfig'];
   editorButtonConfig?: EditConfig['editorButtonConfig'];
 }

52-55: 建议优化文本处理逻辑

当前的换行符处理方式可能过于简单,建议考虑更多的边界情况。

 const onTextAreaChange: React.ChangeEventHandler<HTMLTextAreaElement> = ({ target }) => {
-  setCurrent(target.value.replace(/[\n\r]/g, ''));
-  onChange?.(target.value.replace(/[\n\r]/g, ''));
+  const sanitizedValue = target.value.replace(/[\n\r]+/g, ' ').trim();
+  setCurrent(sanitizedValue);
+  onChange?.(sanitizedValue);
 };
components/bubble/demo/editable.tsx (1)

30-30: 建议改进初始状态的注释

当前的中文注释应该使用英文,保持代码注释的一致性。

-  const [editHistory, setEditHistory] = React.useState(['Good morning, how are you?']); // 编辑历史记录
+  const [editHistory, setEditHistory] = React.useState(['Good morning, how are you?']); // Edit history records
components/bubble/index.en-US.md (2)

84-84: 属性描述中的拼写错误

"Wether" 应该修改为 "Whether"。

-| editing | Wether to be editable | boolean | false |  |
+| editing | Whether to be editable | boolean | false |  |

88-89: 建议完善配置项的文档说明

配置项的说明不够详细,建议添加更多使用示例和注意事项。

建议在文档中添加以下内容:

| editorTextAreaConfig | Configure settings related to textarea in the editor. For example: `{ maxLength: 100, showCount: true }` for limiting input length. | TextAreaProps | variant="borderless" autoSize={{ minRows: 2, maxRows: 3 }} |  |
| editorButtonConfig | Configure settings related to buttons in the editor. Example: `[{ type: 'save', text: 'Submit', option: { type: 'primary' } }]` for customizing button appearance. | { type: 'save' \| 'cancel'; text?: string; option?: ButtonProps }[] | [{ type: 'save', text: 'Save', option: { size: 'small', type: 'primary' } }, { type: 'cancel', text: 'Cancel', option: { size: 'small' } }] |  |
components/bubble/style/index.ts (2)

144-152: 建议优化编辑器样式的灵活性

编辑器样式的实现整体不错,但有以下几点建议:

  • minWidth: '30%' 可能过于限制,建议考虑使用更小的值或让它可配置
  • 建议添加 transition 属性来实现平滑的编辑状态切换效果
[`& ${componentCls}-editor`]: {
-  minWidth: '30%',
+  minWidth: '20%',
  maxWidth: '100%',
  border: `1px solid ${token.colorPrimary}`,
  padding: token.paddingSM,
  borderRadius: token.borderRadius,
  boxShadow: token.boxShadow,
+  transition: `all ${token.motionDurationMid}`,
},

95-98: 移除不必要的行尾注释

建议移除行尾的注释,它们没有提供额外的信息价值。

- flex: 'auto', //
+ flex: 'auto',
- alignItems: 'flex-start', //
+ alignItems: 'flex-start',
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 32ddbe7 and fe4db8b.

📒 Files selected for processing (9)
  • components/bubble/Bubble.tsx (4 hunks)
  • components/bubble/Editor.tsx (1 hunks)
  • components/bubble/demo/editable.md (1 hunks)
  • components/bubble/demo/editable.tsx (1 hunks)
  • components/bubble/hooks/useMergedConfig.ts (1 hunks)
  • components/bubble/index.en-US.md (2 hunks)
  • components/bubble/index.zh-CN.md (2 hunks)
  • components/bubble/interface.ts (3 hunks)
  • components/bubble/style/index.ts (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • components/bubble/demo/editable.md

components/bubble/hooks/useMergedConfig.ts Outdated Show resolved Hide resolved
components/bubble/index.zh-CN.md Outdated Show resolved Hide resolved
Comment on lines +43 to +50
React.useEffect(() => {
if (textAreaRef.current?.resizableTextArea) {
const { textArea } = textAreaRef.current.resizableTextArea;
textArea.focus();
const { length } = textArea.value;
textArea.setSelectionRange(length, length);
}
}, []);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

建议优化文本区域的焦点管理

当前的焦点管理逻辑可能在某些情况下不够稳定。建议添加错误处理并使用 requestAnimationFrame 来确保 DOM 已经准备就绪。

 React.useEffect(() => {
-  if (textAreaRef.current?.resizableTextArea) {
-    const { textArea } = textAreaRef.current.resizableTextArea;
-    textArea.focus();
-    const { length } = textArea.value;
-    textArea.setSelectionRange(length, length);
-  }
+  requestAnimationFrame(() => {
+    try {
+      if (textAreaRef.current?.resizableTextArea) {
+        const { textArea } = textAreaRef.current.resizableTextArea;
+        textArea.focus();
+        const { length } = textArea.value;
+        textArea.setSelectionRange(length, length);
+      }
+    } catch (error) {
+      console.error('Failed to focus textarea:', error);
+    }
+  });
 }, []);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
React.useEffect(() => {
if (textAreaRef.current?.resizableTextArea) {
const { textArea } = textAreaRef.current.resizableTextArea;
textArea.focus();
const { length } = textArea.value;
textArea.setSelectionRange(length, length);
}
}, []);
React.useEffect(() => {
requestAnimationFrame(() => {
try {
if (textAreaRef.current?.resizableTextArea) {
const { textArea } = textAreaRef.current.resizableTextArea;
textArea.focus();
const { length } = textArea.value;
textArea.setSelectionRange(length, length);
}
} catch (error) {
console.error('Failed to focus textarea:', error);
}
});
}, []);

components/bubble/Editor.tsx Outdated Show resolved Hide resolved
components/bubble/demo/editable.tsx Outdated Show resolved Hide resolved
components/bubble/demo/editable.tsx Outdated Show resolved Hide resolved
components/bubble/Bubble.tsx Outdated Show resolved Hide resolved
Comment on lines 151 to 152
value={mergedContent as string}
onChange={onEditChange}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

增强类型安全性

当前实现中存在不安全的类型转换:

  • mergedContent as string 可能导致运行时错误
  • 建议添加类型检查
+ const isValidContent = (content: unknown): content is string => {
+   return typeof content === 'string';
+ };

let fullContent: React.ReactNode =
  enableEdit && isEditing ? (
    <Editor
      prefixCls={prefixCls}
-     value={mergedContent as string}
+     value={isValidContent(mergedContent) ? mergedContent : String(mergedContent)}
      onChange={onEditChange}
      // ... rest of the props
    />
  ) : (
    // ... rest of the code
  );

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 147 to 172
let fullContent: React.ReactNode =
enableEdit && isEditing ? (
<Editor
prefixCls={prefixCls}
value={mergedContent as string}
onChange={onEditChange}
onCancel={onEditCancel}
onEnd={onEditEnd}
editorStyle={{
...contextConfig.styles.editor,
...styles.editor,
}}
editorClassName={classnames(
`${prefixCls}-editor`,
contextConfig.classNames.editor,
classNames.editor,
)}
editorTextAreaConfig={editConfig?.editorTextAreaConfig}
editorButtonConfig={editConfig?.editorButtonConfig}
/>
) : (
<div
style={{
...contextConfig.styles.content,
...styles.content,
}}
className={classnames(
`${prefixCls}-content`,
`${prefixCls}-content-${variant}`,
shape && `${prefixCls}-content-${shape}`,
contextConfig.classNames.content,
classNames.content,
)}
>
{contentNode}
</div>
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

优化编辑器集成逻辑

编辑器集成实现有以下改进空间:

  1. 样式合并逻辑可以提取到自定义 hook
  2. 缺少编辑器的加载状态处理
  3. 建议添加编辑器的错误边界处理
+ const useEditorStyles = (prefixCls: string) => {
+   const contextConfig = useXComponentConfig('bubble');
+   return {
+     style: {
+       ...contextConfig.styles.editor,
+       ...styles.editor,
+     },
+     className: classnames(
+       `${prefixCls}-editor`,
+       contextConfig.classNames.editor,
+       classNames.editor,
+     ),
+   };
+ };

let fullContent: React.ReactNode =
  enableEdit && isEditing ? (
+   <ErrorBoundary fallback={<div>编辑器加载失败</div>}>
    <Editor
      prefixCls={prefixCls}
      value={mergedContent as string}
      onChange={onEditChange}
      onCancel={onEditCancel}
      onEnd={onEditEnd}
-     editorStyle={{
-       ...contextConfig.styles.editor,
-       ...styles.editor,
-     }}
-     editorClassName={classnames(
-       `${prefixCls}-editor`,
-       contextConfig.classNames.editor,
-       classNames.editor,
-     )}
+     {...useEditorStyles(prefixCls)}
      editorTextAreaConfig={editConfig?.editorTextAreaConfig}
      editorButtonConfig={editConfig?.editorButtonConfig}
+     loading={loading}
    />
+   </ErrorBoundary>
  ) : (
    // ... rest of the code
  );

Committable suggestion skipped: line range outside the PR's diff.

@tabzzz1 tabzzz1 force-pushed the feature/bubble-editable branch from fe4db8b to f2ff4c6 Compare December 9, 2024 04:41
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (1)
components/bubble/index.zh-CN.md (1)

81-88: 建议完善属性描述

以下建议可以提升文档的可用性:

  1. editorTextAreaConfig 的默认值建议使用代码块格式展示,提高可读性
  2. 建议为 onChangeonEnd 添加参数说明,说明 content 的具体含义
  3. 建议补充说明 editorButtonConfig 的按钮显示顺序规则

建议按如下方式优化默认值的展示:

- editorTextAreaConfig | 配置编辑器中文本域的相关设置 | TextAreaProps | variant="borderless" autoSize={{ minRows: 2, maxRows: 3 }} |  |
+ editorTextAreaConfig | 配置编辑器中文本域的相关设置 | TextAreaProps | ```{ variant: "borderless", autoSize: { minRows: 2, maxRows: 3 } }``` |  |
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between fe4db8b and f2ff4c6.

📒 Files selected for processing (2)
  • components/bubble/index.en-US.md (2 hunks)
  • components/bubble/index.zh-CN.md (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • components/bubble/index.en-US.md
🔇 Additional comments (2)
components/bubble/index.zh-CN.md (2)

27-27: 示例代码位置合理!

新增的编辑功能示例放置在基础功能之后,高级功能之前,符合文档的逻辑组织结构。


70-79: 建议保持接口定义与实际代码一致

文档中的 EditConfig 接口定义需要与 interface.ts 中的实现保持一致。

components/bubble/Editor.tsx Outdated Show resolved Hide resolved
components/bubble/Bubble.tsx Outdated Show resolved Hide resolved
components/bubble/hooks/useMergedConfig.ts Outdated Show resolved Hide resolved
components/bubble/style/index.ts Outdated Show resolved Hide resolved
@@ -142,6 +141,15 @@ const genBubbleStyle: GenerateStyle<BubbleToken> = (token) => {
},
},
},
// =========================== Editor =============================
[`& ${componentCls}-editor`]: {
minWidth: '30%',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个设置 30% 有啥考虑嘛

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

抱歉,这里当时是预设值,想和大家探讨一下初始值设置为多少比较好。个人感觉50%以下比较不错...

components/bubble/interface.ts Outdated Show resolved Hide resolved
components/bubble/interface.ts Show resolved Hide resolved
components/bubble/Editor.tsx Outdated Show resolved Hide resolved
components/bubble/Editor.tsx Show resolved Hide resolved
components/bubble/Editor.tsx Outdated Show resolved Hide resolved
@YumoImer
Copy link
Collaborator

YumoImer commented Dec 9, 2024

@tabzzz1 先评论这些~辛苦看下~有些需要等你改完我再评~另外兔子的评价也可以参考看下,觉得误报关闭就好~

@tabzzz1
Copy link
Contributor Author

tabzzz1 commented Dec 9, 2024

@tabzzz1 先评论这些~辛苦看下~有些需要等你改完我再评~另外兔子的评价也可以参考看下,觉得误报关闭就好~

谢谢!辛苦你啦 @YumoImer

@tabzzz1 tabzzz1 force-pushed the feature/bubble-editable branch from f2ff4c6 to bdc8fe4 Compare December 11, 2024 07:46
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (7)
components/bubble/hooks/useEditableConfig.ts (2)

4-15: 建议优化默认配置的返回值

建议将默认配置对象提取为常量,以提高代码可维护性和避免每次渲染时重新创建对象。

+const DEFAULT_CONFIG = {
+  enableEdit: false,
+  isEditing: false,
+  onEditorChange: () => {},
+  onEditorCancel: () => {},
+  onEditorEnd: () => {},
+  editorTextAreaConfig: undefined,
+  editorButtonsConfig: [],
+} as const;

 function useEditableConfig(editable: BubbleProps['editable']) {
   if (!editable) {
-    return {
-      enableEdit: false,
-      isEditing: false,
-      onEditorChange: () => {},
-      onEditorCancel: () => {},
-      onEditorEnd: () => {},
-      editorTextAreaConfig: undefined,
-      editorButtonsConfig: [],
-    };
+    return DEFAULT_CONFIG;
   }

17-22: 建议添加防抖处理

考虑到 editing 属性的变化可能会频繁触发状态更新,建议添加防抖处理以优化性能。

+import { useDebounceFn } from 'ahooks';

 const editConfig = React.useMemo(() => editable, [editable]);
 const [isEditing, setIsEditing] = React.useState(editable.editing || false);

+const { run: debouncedSetIsEditing } = useDebounceFn(
+  (value: boolean) => setIsEditing(value),
+  { wait: 300 }
+);

 React.useEffect(() => {
-  setIsEditing(editConfig.editing || false);
+  debouncedSetIsEditing(editConfig.editing || false);
 }, [editConfig.editing]);
components/bubble/interface.ts (2)

18-22: 建议扩展按钮类型

当前按钮类型仅支持 'save' 和 'cancel',建议考虑添加更多常用类型,如 'reset' 等。

 type EditorButtonsConfig = {
-  type: 'save' | 'cancel';
+  type: 'save' | 'cancel' | 'reset';
   text?: string;
   option?: ButtonProps;
 };

24-33: 建议添加必要的类型注释

接口的属性缺少详细的类型注释,建议添加 JSDoc 注释以提高代码可维护性。

 export interface EditConfig {
+  /** 是否处于编辑状态 */
   editing?: boolean;
+  /** 内容变化时的回调函数 */
   onChange?: (content: string) => void;
+  /** 取消编辑的回调函数 */
   onCancel?: VoidFunction;
+  /** 完成编辑的回调函数 */
   onEnd?: (content: string) => void;
+  /** 编辑器的自定义样式 */
   styles?: React.CSSProperties;
+  /** 编辑器的自定义类名 */
   classNames?: string;
+  /** 文本域的配置项 */
   textarea?: TextAreaProps;
+  /** 按钮的配置项 */
   buttons?: EditorButtonsConfig[];
 }
components/bubble/index.zh-CN.md (1)

81-88: 建议完善属性说明

API 表格中的属性说明不够详细,建议补充更多使用场景和示例说明。

建议添加以下内容:

  • editing 属性:补充说明如何通过受控模式管理编辑状态
  • onChange 事件:补充说明触发时机和参数说明
  • editorTextAreaConfig:补充常用配置示例
  • editorButtonConfig:补充自定义按钮的使用示例
components/bubble/Bubble.tsx (1)

67-75: 建议优化状态管理的健壮性

建议添加以下优化:

+ const debouncedOnChange = React.useCallback(
+   debounce((value: string) => {
+     try {
+       editConfig?.onChange?.(value);
+     } catch (error) {
+       console.error('Failed to update content:', error);
+     }
+   }, 300),
+   [editConfig]
+ );

const {
  enableEdit,
  isEditing,
- onEditorChange,
+ onEditorChange: rawOnChange,
  onEditorCancel,
  onEditorEnd,
  editorTextAreaConfig,
  editorButtonsConfig,
} = useEditableConfig(editable);

+ const onEditorChange = React.useCallback((value: string) => {
+   try {
+     debouncedOnChange(value);
+   } catch (error) {
+     console.error('Failed to update content:', error);
+   }
+ }, [debouncedOnChange]);
components/bubble/demo/editable.tsx (1)

30-30: 建议优化代码注释和历史记录管理

  1. 建议将中文注释改为英文:
- const [editHistory, setEditHistory] = React.useState(['Good morning, how are you?']); // 编辑历史记录
+ const [editHistory, setEditHistory] = React.useState(['Good morning, how are you?']); // Edit history records
  1. 建议使用 useReducer 来管理复杂的历史记录状态
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between f2ff4c6 and bdc8fe4.

📒 Files selected for processing (9)
  • components/bubble/Bubble.tsx (4 hunks)
  • components/bubble/Editor.tsx (1 hunks)
  • components/bubble/demo/editable.md (1 hunks)
  • components/bubble/demo/editable.tsx (1 hunks)
  • components/bubble/hooks/useEditableConfig.ts (1 hunks)
  • components/bubble/index.en-US.md (2 hunks)
  • components/bubble/index.zh-CN.md (2 hunks)
  • components/bubble/interface.ts (3 hunks)
  • components/bubble/style/index.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • components/bubble/demo/editable.md
  • components/bubble/style/index.ts
  • components/bubble/Editor.tsx
🔇 Additional comments (5)
components/bubble/index.zh-CN.md (1)

70-79: ⚠️ Potential issue

接口定义与实现不一致

文档中的 EditConfig 接口与 interface.ts 中的定义不一致,需要同步更新。

 interface EditConfig {
   editing?: boolean;
   onChange?: (content: string) => void;
   onCancel?: () => void;
   onEnd?: (content: string) => void;
-  editorTextAreaConfig?: TextAreaProps;
-  editorButtonConfig?: { type: 'save' | 'cancel'; text?: string; option?: ButtonProps }[];
+  textarea?: TextAreaProps;
+  buttons?: EditorButtonsConfig[];
+  styles?: React.CSSProperties;
+  classNames?: string;
 }

Likely invalid or redundant comment.

components/bubble/Bubble.tsx (2)

7-8: 代码结构清晰,实现合理!

使用自定义 Hook 封装编辑相关逻辑,符合 React 最佳实践。

Also applies to: 45-45, 66-76


136-156: 🛠️ Refactor suggestion

建议加强类型安全性和错误处理

当前实现存在以下改进空间:

  1. 类型转换不安全:mergedContent as string
  2. 样式合并逻辑可以提取到自定义 hook
  3. 缺少错误边界处理

建议按如下方式重构:

+ const isValidContent = (content: unknown): content is string => {
+   return typeof content === 'string';
+ };

+ const useEditorStyles = (prefixCls: string, contextConfig: any, editable: any) => {
+   return {
+     style: {
+       ...contextConfig.styles.editor,
+       ...editable.styles,
+     },
+     className: classnames(
+       `${prefixCls}-editor`,
+       contextConfig.classNames.editor,
+       editable.classNames,
+     ),
+   };
+ };

let fullContent: React.ReactNode =
  enableEdit && isEditing ? (
+   <ErrorBoundary fallback={<div>编辑器加载失败</div>}>
      <Editor
        prefixCls={prefixCls}
-       value={mergedContent as string}
+       value={isValidContent(mergedContent) ? mergedContent : String(mergedContent)}
        onChange={onEditorChange}
        onCancel={onEditorCancel}
        onEnd={onEditorEnd}
-       style={{...}}
-       className={...}
+       {...useEditorStyles(prefixCls, contextConfig, editable)}
        editorTextAreaConfig={editorTextAreaConfig}
        editorButtonsConfig={editorButtonsConfig}
      />
+   </ErrorBoundary>
  ) : (
    // ...
  );

Likely invalid or redundant comment.

components/bubble/index.en-US.md (1)

26-26: 文档完善,示例清晰!

新增的编辑功能文档结构清晰,接口定义完整,示例代码充分。

Also applies to: 67-87

components/bubble/demo/editable.tsx (1)

36-45: 🛠️ Refactor suggestion

建议增强删除操作的健壮性

当前删除实现存在以下问题:

  1. 注释 Some else logic 不清晰
  2. 缺少删除操作的错误处理

建议按如下方式重构:

 const triggerDelete = () => {
+  try {
     if (editHistory.length === 1 || editing) return null;
-    // Some else logic
     setEditHistory((prev) => {
       const newHistory = [...prev];
       newHistory.splice(currentIndex, 1);
       setCurrentIndex(Math.min(currentIndex, newHistory.length - 1));
       return newHistory;
     });
+  } catch (error) {
+    console.error('Failed to delete history:', error);
+  }
 };

Likely invalid or redundant comment.

Comment on lines +24 to +36
const handlers = {
onEditorChange: (value: string) => {
editConfig.onChange?.(value);
},
onEditorCancel: () => {
editConfig.onCancel?.();
setIsEditing(false);
},
onEditorEnd: (value: string) => {
editConfig.onEnd?.(value);
setIsEditing(false);
},
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

建议添加错误处理

在事件处理函数中建议添加错误处理,以防止回调函数执行失败影响用户体验。

 const handlers = {
   onEditorChange: (value: string) => {
+    try {
       editConfig.onChange?.(value);
+    } catch (error) {
+      console.error('编辑器内容更新失败:', error);
+    }
   },
   onEditorCancel: () => {
+    try {
       editConfig.onCancel?.();
       setIsEditing(false);
+    } catch (error) {
+      console.error('取消编辑失败:', error);
+    }
   },
   onEditorEnd: (value: string) => {
+    try {
       editConfig.onEnd?.(value);
       setIsEditing(false);
+    } catch (error) {
+      console.error('结束编辑失败:', error);
+    }
   },
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handlers = {
onEditorChange: (value: string) => {
editConfig.onChange?.(value);
},
onEditorCancel: () => {
editConfig.onCancel?.();
setIsEditing(false);
},
onEditorEnd: (value: string) => {
editConfig.onEnd?.(value);
setIsEditing(false);
},
};
const handlers = {
onEditorChange: (value: string) => {
try {
editConfig.onChange?.(value);
} catch (error) {
console.error('编辑器内容更新失败:', error);
}
},
onEditorCancel: () => {
try {
editConfig.onCancel?.();
setIsEditing(false);
} catch (error) {
console.error('取消编辑失败:', error);
}
},
onEditorEnd: (value: string) => {
try {
editConfig.onEnd?.(value);
setIsEditing(false);
} catch (error) {
console.error('结束编辑失败:', error);
}
},
};

@tabzzz1
Copy link
Contributor Author

tabzzz1 commented Dec 11, 2024

@YumoImer 如果您有时间的话劳烦您review一下代码~文档部分待代码没问题后我再来补充下😄

@YumoImer
Copy link
Collaborator

@YumoImer 如果您有时间的话劳烦您review一下代码~文档部分待代码没问题后我再来补充下😄

好的,非常感谢,我的朋友。这两天工作有点忙,回复不及时请见谅~

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants