Skip to content

fix: refactor clear icon to button element to make it accessible#1224

Open
Pareder wants to merge 1 commit into
react-component:masterfrom
Pareder:fix/clear-btn-accessibility
Open

fix: refactor clear icon to button element to make it accessible#1224
Pareder wants to merge 1 commit into
react-component:masterfrom
Pareder:fix/clear-btn-accessibility

Conversation

@Pareder

@Pareder Pareder commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

This PR improves clear button functionality by:

  • changing custom div to navite button element
  • using aria-label attribute by extending allowClear object type

Summary by CodeRabbit

发行说明

  • 新功能

    • 清除按钮现支持配置自定义标签,提升无障碍体验。
    • 清除按钮改进为标准 button 元素,支持键盘操作激活。
  • 改进

    • 增强清除按钮的无障碍支持,包含 aria-label 属性。
    • 优化清除按钮交互,点击时保持输入框焦点,不触发下拉菜单展开。

@vercel

vercel Bot commented Jun 8, 2026

Copy link
Copy Markdown

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

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

概览

PR 为 Select 组件的清除按钮添加可访问性标签支持,扩展 allowClear 配置以支持 label 字段,将清除按钮重构为原生 button 元素,并调整事件处理策略以改进交互体验。

变更内容

Clear 按钮可访问性标签支持

层 / 文件 概述
类型和配置扩展
src/BaseSelect/index.tsx, src/hooks/useAllowClear.tsx, src/SelectInput/index.tsx
BaseSelectProps.allowClearSelectInputProps 新增 label?: string 字段;AllowClearConfig 接口定义新增 label 属性;useAllowClear 参数类型扩展为支持 label 配置。
useAllowClear hook label 处理
src/hooks/useAllowClear.tsx
hook 返回对象在 mergedAllowClear 为真时填充 label,为假时置为空字符串。
BaseSelect clearLabel 解构和传递
src/BaseSelect/index.tsx
useAllowClear 返回值解构 label 并映射为 clearLabel,传递给 SelectInput 组件。
SelectInput 清除按钮 button 元素重构
src/SelectInput/index.tsx
清除按钮从 Affix 包装改为原生 button 元素,新增 aria-label={clearLabel} 属性;onMouseDown 调用 preventDefault() 并设置 _select_lazy = true 以阻止根级下拉触发;清除回调改为在 onClick 时触发,保留 clearIcon 作为 button 子内容。
测试覆盖
tests/Select.test.tsx, tests/shared/allowClearTest.tsx
更新 clear 按钮焦点测试为使用 click 事件;新增 button 元素和 aria-label 渲染验证;新增 mousedown 默认行为阻止测试;新增 click 不打开下拉的验证;新增键盘激活清除的测试场景。

可能相关的 PR

  • react-component/select#1099: 同样修改了 select "clear" API 周边的 allowClear/useAllowClear 逻辑,主 PR 新增 allowClear.label 支持,而 #1099 则移除并重构了弃用的 API。

建议的审查人

  • afc163
  • zombieJ

🐰 清除按钮焕新颜,标签描述真关键,
原生 button 显身手,键盘无障碍体验,
事件流转从点击,用户交互更完善!

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 标题准确概括了主要变更,即将清除图标重构为原生按钮元素以改进可访问性,这是本次 PR 的核心改动。
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

src/BaseSelect/index.tsx

ESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox.

src/SelectInput/index.tsx

ESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox.

src/hooks/useAllowClear.tsx

ESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox.

  • 2 others

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.

@codecov

codecov Bot commented Jun 8, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.44%. Comparing base (7876c6c) to head (c12b0fe).

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #1224   +/-   ##
=======================================
  Coverage   99.44%   99.44%           
=======================================
  Files          31       31           
  Lines        1266     1267    +1     
  Branches      442      465   +23     
=======================================
+ Hits         1259     1260    +1     
  Misses          7        7           

☔ View full report in Codecov by Harness.
📢 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.

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

Copy link
Copy Markdown
Contributor

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 improves the accessibility and keyboard navigation of the clear button in the Select component by changing its markup from a div to a native button element and adding support for an accessible label. Feedback on these changes suggests adding a default fallback for the label to prevent type contract violations and ensure accessibility. Additionally, minor cleanups are recommended in the tests to remove an unused mock and simplify click event dispatching.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

return {
allowClear: mergedAllowClear,
clearIcon: mergedAllowClear ? allowClearConfig.clearIcon || clearIcon || '×' : null,
label: mergedAllowClear ? allowClearConfig.label : '',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The AllowClearConfig interface defines label as a required string. However, when allowClear is passed as true or as an object without a label property, allowClearConfig.label will be undefined. Returning allowClearConfig.label directly violates the type contract and leaves the clear button without an accessible name for screen readers.\n\nProviding a default fallback like 'clear' resolves the type mismatch and ensures the button remains accessible by default.

Suggested change
label: mergedAllowClear ? allowClearConfig.label : '',
label: mergedAllowClear ? allowClearConfig.label || 'clear' : '',

Comment thread tests/Select.test.tsx
Comment on lines +2511 to 2512
const clickPreventDefault = jest.fn();
const { container } = render(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The clickPreventDefault mock is unused because the component does not call preventDefault() on the click event (it only calls it on the mousedown event). Removing this definition avoids unused variable warnings and simplifies the test setup.

    const { container } = render(

Comment thread tests/Select.test.tsx
Comment on lines +2521 to +2523
const clickEvent = createEvent.click(container.querySelector('.rc-select-clear'));
clickEvent.preventDefault = clickPreventDefault;
fireEvent(container.querySelector('.rc-select-clear'), clickEvent);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Since we don't need to mock preventDefault on the click event, we can simplify the event dispatching by directly calling fireEvent.click.

Suggested change
const clickEvent = createEvent.click(container.querySelector('.rc-select-clear'));
clickEvent.preventDefault = clickPreventDefault;
fireEvent(container.querySelector('.rc-select-clear'), clickEvent);
fireEvent.click(container.querySelector('.rc-select-clear'));

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/hooks/useAllowClear.tsx`:
- Line 41: The clear button label can be undefined when callers pass allowClear
or clearIcon without allowClear.label; update useAllowClear to provide a
non-empty fallback string for the button label (e.g. a default like "Clear" or
an i18n key) instead of passing allowClearConfig.label directly—locate the
mergedAllowClear construction and the place where label: mergedAllowClear ?
allowClearConfig.label : '' is set and replace it so label is always a safe,
non-empty string (refer to mergedAllowClear and allowClearConfig.label in
useAllowClear).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0e63c184-4f12-49e8-b5e1-a7e46891d276

📥 Commits

Reviewing files that changed from the base of the PR and between 7876c6c and c12b0fe.

⛔ Files ignored due to path filters (1)
  • tests/__snapshots__/Select.test.tsx.snap is excluded by !**/*.snap
📒 Files selected for processing (5)
  • src/BaseSelect/index.tsx
  • src/SelectInput/index.tsx
  • src/hooks/useAllowClear.tsx
  • tests/Select.test.tsx
  • tests/shared/allowClearTest.tsx

return {
allowClear: mergedAllowClear,
clearIcon: mergedAllowClear ? allowClearConfig.clearIcon || clearIcon || '×' : null,
label: mergedAllowClear ? allowClearConfig.label : '',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

清除按钮在未传 allowClear.label 时可能缺少可访问名称

这里直接透传 allowClearConfig.label,当调用方只写 allowClear 或只传 clearIcon 时会是 undefined,下游 button 可能变成无可访问名称(尤其是纯图标 clearIcon)。建议提供非空兜底文案(可后续接入 i18n 文案源)。

💡 建议修复
   return useMemo(() => {
     const mergedAllowClear =
       !disabled &&
       allowClearConfig.allowClear !== false &&
       (displayValues.length || mergedSearchValue) &&
       !(mode === 'combobox' && mergedSearchValue === '');

     return {
       allowClear: mergedAllowClear,
       clearIcon: mergedAllowClear ? allowClearConfig.clearIcon || clearIcon || '×' : null,
-      label: mergedAllowClear ? allowClearConfig.label : '',
+      label: mergedAllowClear ? allowClearConfig.label ?? 'Clear' : '',
     };
   }, [allowClearConfig, clearIcon, disabled, displayValues.length, mergedSearchValue, mode]);
📝 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
label: mergedAllowClear ? allowClearConfig.label : '',
label: mergedAllowClear ? allowClearConfig.label ?? 'Clear' : '',
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/useAllowClear.tsx` at line 41, The clear button label can be
undefined when callers pass allowClear or clearIcon without allowClear.label;
update useAllowClear to provide a non-empty fallback string for the button label
(e.g. a default like "Clear" or an i18n key) instead of passing
allowClearConfig.label directly—locate the mergedAllowClear construction and the
place where label: mergedAllowClear ? allowClearConfig.label : '' is set and
replace it so label is always a safe, non-empty string (refer to
mergedAllowClear and allowClearConfig.label in useAllowClear).

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.

1 participant