Skip to content

Conversation

@aojunhao123
Copy link
Contributor

@aojunhao123 aojunhao123 commented Nov 24, 2025

Summary by CodeRabbit

  • 新功能

    • 调整 Tooltip 行为与属性:children 类型更明确,aria-describedby 仅在提示存在且可见时生效,渲染行为与打开/关闭更一致。
  • 测试

    • 更新并新增单元测试,覆盖默认可见、受控隐藏、强制渲染及 aria-describedby 行为。
  • Chores

    • 更新依赖并添加测试相关开发依赖及对应 TypeScript 类型支持。
  • 文档

    • README 中包名与示例引用已更新为新的组件包名。

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel
Copy link

vercel bot commented Nov 24, 2025

@aojunhao123 is attempting to deploy a commit to the React Component Team on Vercel.

A member of the Team first needs to authorize it.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @aojunhao123, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request integrates @testing-library/jest-dom into the project's development setup. This integration provides a set of custom Jest matchers that make it easier and more intuitive to write assertions for DOM elements in tests, ultimately improving the readability and maintainability of test suites. The changes involve updating both the project's dependencies and its TypeScript configuration to support this new library.

Highlights

  • Dependency Addition: The @testing-library/jest-dom package has been added as a development dependency, enabling enhanced DOM testing capabilities with custom Jest matchers.
  • TypeScript Configuration Update: The tsconfig.json file has been updated to include @testing-library/jest-dom in the types array, ensuring proper type recognition for the new testing utilities.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@coderabbitai
Copy link

coderabbitai bot commented Nov 24, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

新增测试库类型依赖;Tooltip 将 children 明确为显式 prop,调整渲染为接收 { open } 的 render-prop 并按 overlay 与可见性条件注入/移除 aria-describedby;更新相关可访问性与可见性测试;README 中将包名与示例替换为 @rc-component/tooltip(仅文案变更)。

Changes

Cohort / File(s) 变更总结
配置:包与类型
package.json, tsconfig.json
package.json 中新增 devDependency @testing-library/jest-dom@^6.9.1;在 tsconfig.jsoncompilerOptions.types 中添加 @testing-library/jest-dom
组件:Tooltip 可见性与 aria 处理
src/Tooltip.tsx
TooltipProps 中显式添加 children: React.ReactElement;将子渲染逻辑改为接收 { open } 的 render-prop 并作为 TriggergetChildren 透传;仅在 overlay 存在且弹窗已挂载/可见时设置 aria-describedby,并通过克隆子节点直接注入 ariaProps;透传并尊重 forceRender 与挂载逻辑。
测试:可见性与 aria 场景
tests/index.test.tsx
调整/新增对 aria-describedby 的断言:使用 defaultVisible 场景断言立即存在;新增受控切换至隐藏时移除 aria 的测试;更新与挂载/销毁、forceRender 相关的断言与定时处理。
文档:包名与示例替换(仅文案)
README.md
将 README 中的包名、导入路径、示例与徽章从 rc-tooltip 更新为 @rc-component/tooltip,仅文案与链接更新,无实现改动。

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Trigger
    participant Tooltip
    participant Popup

    User->>Trigger: hover / focus / click
    Trigger->>Tooltip: onOpenChange(visible)
    Tooltip->>Tooltip: 更新 mergedVisible / innerVisible
    Tooltip->>Tooltip: 决定 popupMounted(基于 visible/forceRender/destroyOnHidden)
    alt popupMounted 或 visible
        Tooltip->>Popup: 挂载并渲染 overlay
        Popup-->>Tooltip: overlay 已存在
        Tooltip->>Trigger: 克隆子节点并注入 aria-describedby(仅在 overlay 存在且可见)
    else overlay 不存在或不可见
        Tooltip->>Trigger: 确保移除 aria-describedby
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • 重点审查项:
    • src/Tooltip.tsx 中 popup 挂载生命周期与 destroyOnHidden / forceRender 的交互;
    • aria-describedby 条件赋值及子节点克隆注入实现;
    • 测试中对定时器、重渲染与受控/非受控可见性场景的断言准确性。

Possibly related PRs

Poem

🐰 我在代码地里轻轻跳,
aria 只在真时到,挂载先来报到,
测试把关门儿敲,断言清晰不糊脑,
文档换名也记好,合并后我啃胡萝卜。 🥕

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 标题"feat: better a11y"准确概括了拉取请求的主要变更,该变更涉及改进工具提示组件的可访问性功能。
Linked Issues check ✅ Passed 代码变更完整地解决了链接问题#49891中的核心可访问性需求:通过aria-describedby属性将工具提示内容与触发元素关联。
Out of Scope Changes check ✅ Passed 所有代码变更均与改进工具提示可访问性的目标相关,包括依赖更新、TypeScript配置、组件实现和测试更新,以及文档更新。
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request correctly adds @testing-library/jest-dom as a dev dependency. However, the accompanying change to tsconfig.json will likely break your build by excluding necessary type definitions for Jest and Node.js. I've left a critical comment with a suggested fix on that file.

Additionally, please remember to import @testing-library/jest-dom in your test setup file (tests/setup.js) to make the custom matchers available at runtime. Without this step, your tests will fail.

"declaration": true,
"skipLibCheck": true,
"esModuleInterop": true,
"types": ["@testing-library/jest-dom"],
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

By adding the types property, you override TypeScript's default type inclusion. This means that other ambient type definitions, like for Jest (jest) and Node.js (node), will no longer be automatically included, which will likely break your build. You need to explicitly list all required ambient types.1

Suggested change
"types": ["@testing-library/jest-dom"],
"types": ["jest", "node", "@testing-library/jest-dom"],

Rules References

Footnotes

  1. When using the compilerOptions.types property in tsconfig.json, TypeScript will only include the type definitions for the packages explicitly listed in the array. This overrides the default behavior of automatically including all packages found in node_modules/@types. Therefore, you must list all necessary type definitions, such as jest and node, to avoid compilation errors.

@codecov
Copy link

codecov bot commented Nov 24, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (993189f) to head (ac227c5).
⚠️ Report is 3 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff            @@
##            master      #509   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files            3         3           
  Lines           37        36    -1     
  Branches        15        14    -1     
=========================================
- Hits            37        36    -1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@socket-security
Copy link

socket-security bot commented Nov 24, 2025

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​testing-library/​jest-dom@​6.9.110010010091100

View full report

@socket-security
Copy link

socket-security bot commented Nov 24, 2025

All alerts resolved. Learn more about Socket for GitHub.

This PR previously contained dependency changes with security issues that have been resolved, removed, or ignored.

View full report

Copy link

@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: 2

🧹 Nitpick comments (1)
src/Tooltip.tsx (1)

63-63: 建议在数组元素之间添加空格

数组字面量中逗号后缺少空格。建议改为 ['hover', 'focus'] 以保持代码风格一致性。

应用此 diff:

-  trigger = ['hover','focus'],
+  trigger = ['hover', 'focus'],
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dece1b0 and b7388d4.

📒 Files selected for processing (2)
  • src/Tooltip.tsx (5 hunks)
  • tests/index.test.tsx (2 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: thinkasany
Repo: react-component/tooltip PR: 484
File: src/Popup.tsx:20-25
Timestamp: 2024-11-28T13:00:19.052Z
Learning: `overlayInnerClassName` 属性在 `Popup.tsx` 中是供内部使用的,已经通过 `classNames.inner` 在现有的测试用例中得到验证,无需单独添加测试。
📚 Learning: 2024-11-28T13:00:19.052Z
Learnt from: thinkasany
Repo: react-component/tooltip PR: 484
File: src/Popup.tsx:20-25
Timestamp: 2024-11-28T13:00:19.052Z
Learning: `overlayInnerClassName` 属性在 `Popup.tsx` 中是供内部使用的,已经通过 `classNames.inner` 在现有的测试用例中得到验证,无需单独添加测试。

Applied to files:

  • tests/index.test.tsx
  • src/Tooltip.tsx
🔇 Additional comments (6)
src/Tooltip.tsx (3)

97-107: LGTM! popupMounted 初始状态逻辑合理

初始状态正确处理了 forceRender、受控模式和 defaultVisible 三种场景,确保弹窗在应该挂载时被挂载。


167-167: LGTM! aria-describedby 应用逻辑正确

只在 overlay 存在且弹窗已挂载时设置 aria-describedby,符合无障碍最佳实践。确保修复上述 popupMounted 状态管理的问题,以保证此逻辑正确工作。


194-194: LGTM! Trigger 属性配置正确

onOpenChange 使用 handleVisibleChange 包装器确保 popupMounted 状态同步,forceRender 的显式传递保证了渲染行为的一致性。

Also applies to: 198-198

tests/index.test.tsx (3)

505-523: LGTM! 测试正确验证挂载后的 aria-describedby 行为

测试现在正确等待弹窗挂载后再检查 aria-describedby 属性,并验证了在默认 destroyOnHidden={false} 时属性会持久保留。这与新的 popupMounted 状态管理逻辑一致。


535-543: LGTM! 新增测试覆盖了立即挂载场景

新增的两个测试用例正确验证了 defaultVisibleforceRender 场景下 aria-describedby 的立即应用,确保初始挂载时的无障碍属性正确设置。

Also applies to: 545-553


555-572: 测试覆盖了销毁场景,但缺少受控模式测试

此测试正确验证了 destroyOnHidden={true}aria-describedby 的移除行为。但是,测试套件缺少对受控模式下 destroyOnHidden={false} 的测试用例,这正是 src/Tooltip.tsx 中 effect 逻辑存在问题的场景。

建议添加以下测试用例以覆盖受控模式:

it('should keep aria-describedby in controlled mode when destroyOnHidden is false', async () => {
  const App = () => {
    const [visible, setVisible] = React.useState(false);
    return (
      <>
        <button onClick={() => setVisible(!visible)}>Toggle</button>
        <Tooltip visible={visible} destroyOnHidden={false} overlay="tooltip content">
          <button>Target</button>
        </Tooltip>
      </>
    );
  };
  
  const { container } = render(<App />);
  const target = container.querySelectorAll('button')[1];
  const toggle = container.querySelectorAll('button')[0];
  
  expect(target).not.toHaveAttribute('aria-describedby');
  
  fireEvent.click(toggle);
  await waitFakeTimers();
  expect(target).toHaveAttribute('aria-describedby');
  
  fireEvent.click(toggle);
  await waitFakeTimers();
  // Should keep aria-describedby because destroyOnHidden is false
  expect(target).toHaveAttribute('aria-describedby');
});

@aojunhao123 aojunhao123 changed the title chore: add @testing-library/jest-dom as a dev dependency and update tsconfig feat: better a11y Nov 25, 2025
Copy link

@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

🧹 Nitpick comments (1)
src/Tooltip.tsx (1)

97-107: popupMounted 状态机整体合理,但需评估动态切换 destroyOnHidden 的边界场景

这一段通过 popupMounted + updatePopupMounted + handleVisibleChange + useEffect 将「是否挂载」与「是否可见」解耦,并统一考虑了 forceRender、受控/非受控模式以及 destroyOnHidden,可以修复之前在受控模式下 destroyOnHidden={false}aria-describedby 被过早移除的问题,设计是合理的。

不过有一个相对边缘的场景可以评估一下是否需要覆盖:

  • 非受控模式下(不传 visible),Tooltip 已经被展示过一次并关闭,此时 popupMounted 会保持为 truedestroyOnHidden={false} 时符合预期)。
  • 如果这时父组件把 destroyOnHiddenfalse 改为 trueTrigger 很可能会在关闭状态下直接销毁 DOM,但当前逻辑中:
    • isControlledfalseuseEffect 分支不会再调用 updatePopupMounted
    • popupMounted 仍然保持为 true,从而 aria-describedby 依旧指向已经被移除的节点。

如果你们希望支持运行时切换 destroyOnHidden,建议进一步梳理这一场景下 popupMounted 与实际 DOM 的同步策略(例如基于 Trigger 的可见性回调或额外信号),否则可以在文档中说明该属性主要用于初始配置,不建议在运行时频繁切换。

整体而言,这一块逻辑已经明显优于旧实现,上述只是边界行为上的一个建议。

Also applies to: 109-124, 126-140, 194-199

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2a4f330 and 625204d.

📒 Files selected for processing (1)
  • src/Tooltip.tsx (6 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: thinkasany
Repo: react-component/tooltip PR: 484
File: src/Popup.tsx:20-25
Timestamp: 2024-11-28T13:00:19.052Z
Learning: `overlayInnerClassName` 属性在 `Popup.tsx` 中是供内部使用的,已经通过 `classNames.inner` 在现有的测试用例中得到验证,无需单独添加测试。
📚 Learning: 2024-11-28T13:00:19.052Z
Learnt from: thinkasany
Repo: react-component/tooltip PR: 484
File: src/Popup.tsx:20-25
Timestamp: 2024-11-28T13:00:19.052Z
Learning: `overlayInnerClassName` 属性在 `Popup.tsx` 中是供内部使用的,已经通过 `classNames.inner` 在现有的测试用例中得到验证,无需单独添加测试。

Applied to files:

  • src/Tooltip.tsx
🔇 Additional comments (3)
src/Tooltip.tsx (3)

7-7: React hooks 引入合理

useImperativeHandleuseRefuseEffectuseCallback 都在组件中有实际使用,没有多余或遗漏,保持当前写法即可。


63-83: 默认触发改为 ['hover', 'focus'] 有助于键盘无障碍

默认 trigger 从单一 hover 扩展为 ['hover', 'focus'],让仅通过键盘 focus 的用户也能触发 Tooltip,属于向后兼容且更友好的改动;forceRender 的解构与后续透传逻辑也一致,没有发现问题。


162-170: 仅在 Tooltip DOM 已挂载时设置 aria-describedby

'aria-describedby': overlay && popupMounted ? mergedId : null 确保只有在有 overlay 且实际已经挂载(popupMounted 为真)时才给子元素添加 aria-describedby,可以避免 ARIA 指向不存在的节点,是对无障碍行为的正确修复。

Copy link

@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

🧹 Nitpick comments (1)
README.md (1)

98-114: 可选:增强辅助功能文档以反映自动 aria-describedby 处理。

当前文档展示的是手动为 input 元素添加 aria-describedby 的模式。根据本 PR 所做的改进,Tooltip 组件现已在 overlay 挂载时自动应用 aria-describedby。考虑更新文档以说明:

  • 在何时自动应用 aria-describedby(当 overlay 已挂载时)
  • 开发者仍然可以手动控制(例如通过 id prop)

这样可以帮助用户更好地理解新的行为。

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 625204d and e081d1b.

📒 Files selected for processing (2)
  • README.md (4 hunks)
  • src/Tooltip.tsx (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/Tooltip.tsx
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: thinkasany
Repo: react-component/tooltip PR: 484
File: src/Popup.tsx:20-25
Timestamp: 2024-11-28T13:00:19.052Z
Learning: `overlayInnerClassName` 属性在 `Popup.tsx` 中是供内部使用的,已经通过 `classNames.inner` 在现有的测试用例中得到验证,无需单独添加测试。
🪛 markdownlint-cli2 (0.18.1)
README.md

14-14: Link and image reference definitions should be needed
Unused link or image reference definition: "travis-image"

(MD053, link-image-reference-definitions)


15-15: Link and image reference definitions should be needed
Unused link or image reference definition: "travis-url"

(MD053, link-image-reference-definitions)

🔇 Additional comments (1)
README.md (1)

1-1: ✓ 包名更新一致且完整。

从 rc-tooltip 到 @rc-component/tooltip 的包名替换已在整个文档中系统地应用,包括标题、徽章链接、导入语句和资源路径,确保文档的一致性和准确性。

Also applies to: 12-23, 39-39, 44-44, 50-50

Copy link

@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

🧹 Nitpick comments (1)
src/Tooltip.tsx (1)

82-82: 移除 forceRender 属性传递以优化性能。

根据维护者 zombieJ 的反馈:"不需要这个,直接根据是否 visible 加就行了。节省性能。"

forceRender 属性的传递是不必要的,应该基于可见性状态来渲染,这样可以节省性能开销。

应用此 diff 移除 forceRender 相关代码:

     styles,
-    forceRender,
     ...restProps
       popupMotion={motion}
       defaultPopupVisible={defaultVisible}
-      forceRender={forceRender}
       autoDestroy={destroyOnHidden}

基于过往学习,这符合维护者对性能优化的要求。

Also applies to: 165-165

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3c7e909 and 095b9cb.

📒 Files selected for processing (2)
  • src/Tooltip.tsx (5 hunks)
  • tests/index.test.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/index.test.tsx
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: thinkasany
Repo: react-component/tooltip PR: 484
File: src/Popup.tsx:20-25
Timestamp: 2024-11-28T13:00:19.052Z
Learning: `overlayInnerClassName` 属性在 `Popup.tsx` 中是供内部使用的,已经通过 `classNames.inner` 在现有的测试用例中得到验证,无需单独添加测试。
📚 Learning: 2024-11-28T13:00:19.052Z
Learnt from: thinkasany
Repo: react-component/tooltip PR: 484
File: src/Popup.tsx:20-25
Timestamp: 2024-11-28T13:00:19.052Z
Learning: `overlayInnerClassName` 属性在 `Popup.tsx` 中是供内部使用的,已经通过 `classNames.inner` 在现有的测试用例中得到验证,无需单独添加测试。

Applied to files:

  • src/Tooltip.tsx
🔇 Additional comments (4)
src/Tooltip.tsx (4)

7-7: LGTM!

引入 useState 用于管理内部可见性状态,这是支持受控/非受控模式所必需的。


93-107: 受控/非受控模式实现正确。

可见性状态管理逻辑实现符合标准模式:

  • 通过 'visible' in props 判断受控模式
  • innerVisible 管理非受控状态
  • mergedVisible 作为统一的真实值来源
  • handleVisibleChange 正确处理状态更新和回调传播

134-134: 无障碍属性实现符合维护者反馈。

aria-describedby 仅在 overlay 存在且 mergedVisible 为 true 时添加,这确保:

  1. 只有在有内容需要描述时才添加该属性
  2. 引用的 ID 在 DOM 中确实存在(避免引用已销毁的元素)

这与 zombieJ 的反馈一致:"如果没有可见 Tooltip 就不要添加 aria-describedby 属性",并正确解决了链接问题中提到的无障碍问题。


161-161: LGTM!

onOpenChange 连接到 handleVisibleChange 确保了 Trigger 的可见性变化能正确同步到组件的内部状态,并触发用户提供的回调。

src/Tooltip.tsx Outdated
extraProps.popupVisible = props.visible;
}

const [innerVisible, setInnerVisible] = useState(() => defaultVisible || false);
Copy link
Member

Choose a reason for hiding this comment

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

还是有点浪费,多了一个 state update 就多了一个 re-render。我去给 trigger 的 renderProps 加一个 info 好了

src/Tooltip.tsx Outdated
extraProps.popupVisible = props.visible;
}

const handleVisibleChange = (nextVisible: boolean) => {
Copy link
Member

Choose a reason for hiding this comment

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

清理一下

src/Tooltip.tsx Outdated
{...extraProps}
>
{getChildren()}
{({ open }) => getChildren(open)}
Copy link
Member

Choose a reason for hiding this comment

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

这里不用额外的函数,因为本来就支持 renderProps 了

src/Tooltip.tsx Outdated
afterOpenChange={afterVisibleChange}
popupMotion={motion}
defaultPopupVisible={defaultVisible}
forceRender={forceRender}
Copy link
Member

Choose a reason for hiding this comment

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

这个你已经不消费了,还原给解构填入吧

@zombieJ zombieJ merged commit 320e2b2 into react-component:master Nov 28, 2025
6 of 8 checks passed
Copy link

@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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e1082bf and ac227c5.

📒 Files selected for processing (1)
  • src/Tooltip.tsx (3 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: thinkasany
Repo: react-component/tooltip PR: 484
File: src/Popup.tsx:20-25
Timestamp: 2024-11-28T13:00:19.052Z
Learning: `overlayInnerClassName` 属性在 `Popup.tsx` 中是供内部使用的,已经通过 `classNames.inner` 在现有的测试用例中得到验证,无需单独添加测试。
📚 Learning: 2024-11-28T13:00:19.052Z
Learnt from: thinkasany
Repo: react-component/tooltip PR: 484
File: src/Popup.tsx:20-25
Timestamp: 2024-11-28T13:00:19.052Z
Learning: `overlayInnerClassName` 属性在 `Popup.tsx` 中是供内部使用的,已经通过 `classNames.inner` 在现有的测试用例中得到验证,无需单独添加测试。

Applied to files:

  • src/Tooltip.tsx
🔇 Additional comments (3)
src/Tooltip.tsx (3)

25-25: LGTM - 显式 children 类型提升了 API 清晰度。

childrenTriggerProps 的 Pick 中移除并作为显式必需 prop,配合 Line 117 的 React.Children.only() 强制单一子元素,使类型约束更加明确。


119-119: 条件逻辑正确实现了按可见性控制 aria-describedby。

仅在 overlay 存在且 open 为 true 时添加 aria-describedby,符合 zombieJ 提出的性能优化建议和无障碍最佳实践。不可见时使用 undefined 可避免渲染空属性。


159-159: Render prop pattern is correctly implemented and fully supported by @rc-component/trigger v3.7.1.

The code correctly passes getChildren as a render prop function to Trigger. The @rc-component/trigger v3.x library (including v3.7.1) supports passing the { open } parameter to children render functions, so getChildren will receive the current open state when invoked by Trigger. The pattern is compatible and follows the render prop convention properly.

Comment on lines +116 to 122
const getChildren: TriggerProps['children'] = ({ open }) => {
const child = React.Children.only(children);
const originalProps = child?.props || {};
const childProps = {
...originalProps,
'aria-describedby': overlay ? mergedId : null,
const ariaProps: React.AriaAttributes = {
'aria-describedby': overlay && open ? mergedId : undefined,
};
return React.cloneElement<any>(children, childProps) as any;
return React.cloneElement(child, ariaProps);
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

修复 aria-describedby 属性覆盖问题。

React.cloneElement(child, ariaProps) 会直接覆盖子元素已有的 aria-describedby 属性,破坏子元素自身的无障碍关联关系。根据 WAI-ARIA 规范,应使用空格分隔的 ID 列表合并多个描述源。

应用此 diff 以正确合并 aria-describedby:

  const getChildren: TriggerProps['children'] = ({ open }) => {
    const child = React.Children.only(children);
+   const existingDescribedBy = child.props['aria-describedby'];
+   const tooltipDescribedBy = overlay && open ? mergedId : undefined;
+   const mergedDescribedBy = 
+     tooltipDescribedBy && existingDescribedBy
+       ? `${existingDescribedBy} ${tooltipDescribedBy}`
+       : tooltipDescribedBy || existingDescribedBy;
    const ariaProps: React.AriaAttributes = {
-     'aria-describedby': overlay && open ? mergedId : undefined,
+     'aria-describedby': mergedDescribedBy,
    };
    return React.cloneElement(child, ariaProps);
  };
📝 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 getChildren: TriggerProps['children'] = ({ open }) => {
const child = React.Children.only(children);
const originalProps = child?.props || {};
const childProps = {
...originalProps,
'aria-describedby': overlay ? mergedId : null,
const ariaProps: React.AriaAttributes = {
'aria-describedby': overlay && open ? mergedId : undefined,
};
return React.cloneElement<any>(children, childProps) as any;
return React.cloneElement(child, ariaProps);
};
const getChildren: TriggerProps['children'] = ({ open }) => {
const child = React.Children.only(children);
const existingDescribedBy = child.props['aria-describedby'];
const tooltipDescribedBy = overlay && open ? mergedId : undefined;
const mergedDescribedBy =
tooltipDescribedBy && existingDescribedBy
? `${existingDescribedBy} ${tooltipDescribedBy}`
: tooltipDescribedBy || existingDescribedBy;
const ariaProps: React.AriaAttributes = {
'aria-describedby': mergedDescribedBy,
};
return React.cloneElement(child, ariaProps);
};
🤖 Prompt for AI Agents
In src/Tooltip.tsx around lines 116 to 122, React.cloneElement(child, ariaProps)
currently overwrites any existing child aria-describedby; instead read the
child's existing aria-describedby (child.props &&
child.props['aria-describedby']), and if overlay && open and mergedId exist,
merge them by space-separating the existing value and mergedId (deduplicate if
present), then pass the combined string (or undefined if empty) into
cloneElement so existing aria-describedby values are preserved and multiple IDs
follow WAI-ARIA space-separated list rules.

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.

Tooltip component accessibility is lacking

2 participants