Skip to content

feat: Improve Date panel keyboard navigation and semantics#986

Open
Pareder wants to merge 2 commits into
react-component:masterfrom
Pareder:feat/improve-date-panel-keyboard-navigation
Open

feat: Improve Date panel keyboard navigation and semantics#986
Pareder wants to merge 2 commits into
react-component:masterfrom
Pareder:feat/improve-date-panel-keyboard-navigation

Conversation

@Pareder

@Pareder Pareder commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR is a small chunk of a big one #972 focusing on providing correct semantics and making elements keyboard accessible for Date panels.

  • PresetPanel renders native button elements (together with type="button")
  • Popup has role="dialog"
  • Selector Input renders correct ARIA attributes
  • DatePanel/DecadePanel/MonthPanel/QuarterPanel/YearPanel improvements:
    • added correct ARIA attributes
    • removed tabIndex={-1} from header buttons

Summary by CodeRabbit

  • 新功能 / 改进

    • 预设项改为按钮交互,并调整预设容器的纵向弹性布局。
    • 日期/月份/季度/年份/十年面板增强无障碍:增加表格网格语义与单元格当前状态标识,完善读屏标签与表格标题。
    • 面板翻页按钮支持自定义读屏文案;选择器输入新增展开状态可感知信息(aria-expanded)。
  • Bug 修复

    • 更准确地标识“今天/当前月份/当前季度/当前十年/当前年份”的单元格状态,提升辅助技术体验。

@vercel

vercel Bot commented Jun 30, 2026

Copy link
Copy Markdown

@Pareder is attempting to deploy a commit to the afc163's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 98f8790b-8031-4d5a-aa5f-3d04f6290550

📥 Commits

Reviewing files that changed from the base of the PR and between ed09967 and 8608e15.

📒 Files selected for processing (1)
  • tests/panel.spec.tsx

Walkthrough

为 Picker 组件补充无障碍语义与属性:弹出层、输入框、预设项、表格结构、日期单元格和面板导航按钮都增加了对应的 ARIA 配置;同时预设项改为 button 渲染,日期面板统一接入标签与单元格属性扩展。

Changes

无障碍增强与预设按钮重构

Layer / File(s) Summary
弹出层与输入框 ARIA 属性
src/PickerInput/Popup/index.tsx, src/PickerInput/Selector/Input.tsx, src/PickerInput/Selector/hooks/useInputProps.ts
Popup 容器新增 role="dialog"Input 新增 open prop 并输出 aria-haspopup="dialog"aria-expandeduseInputProps 透传 open 字段。
预设面板改用 button 元素
src/PickerInput/Popup/PresetPanel.tsx, assets/index.less, tests/new-range.spec.tsx, tests/picker.spec.tsx, tests/range.spec.tsx
PresetPanelul/li 结构替换为直接渲染 button 元素,flex 容器样式同步更新,测试选择器从 li 改为 button
PanelHeader 可配置 aria-label 与移除 tabIndex
src/PickerPanel/PanelHeader.tsx
新增 PanelHeaderLabels 类型与 labels 字段,四个翻页按钮的 aria-label 改为优先使用 labels 覆盖值,并移除 tabIndex={-1}
PanelBody 表格 ARIA 结构与单元格属性注入
src/PickerPanel/PanelBody.tsx
PanelBodyProps 新增 getCellAttributestableLabeltable 增加 role="grid"/aria-labelthead 增加 aria-hiddentr 增加 role="row"td 增加 role="gridcell"/aria-label/aria-selected/aria-disabledgetCellAttributes 展开。
各日期面板接入 getCellAttributes/tableLabel/labels
src/PickerPanel/DatePanel/index.tsx, src/PickerPanel/MonthPanel/index.tsx, src/PickerPanel/QuarterPanel/index.tsx, src/PickerPanel/YearPanel/index.tsx, src/PickerPanel/DecadePanel/index.tsx, tests/panel.spec.tsx
五个面板均新增 getCellAttributesyearLabel/decadeLabel 计算,并将 tableLabellabels 传入 PanelBody/PanelHeader;测试补充月份标签格式与回退场景。

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

  • react-component/picker#924: 同样涉及 PanelBody<td> 渲染与属性注入调整,和本 PR 在单元格层面的变更直接相关。

Suggested reviewers: zombieJ

Poem

我是小兔蹦蹦跳,
button 替了旧列表,
aria 标记轻轻亮,
面板读起来更顺畅,
月年十年都清楚,
轻轻一点就安康。 🐰

🚥 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 标题准确概括了本次以日期选择器面板无障碍与语义改进为主的变更。
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.
✨ 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.

tests/panel.spec.tsx

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


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.

@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 introduces accessibility improvements across the picker components by transitioning presets from list items to buttons, adding appropriate ARIA roles and attributes (such as role="dialog", aria-expanded, and role="gridcell"), and removing unnecessary tabIndex attributes. The review feedback highlights several key accessibility improvements: removing aria-hidden="true" from the table header to prevent screen reader regressions, adding onFocus and onBlur handlers to preset buttons for keyboard users, applying role="combobox" to inputs using aria-expanded, and ensuring the week picker row correctly sets aria-selected.

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.

Comment on lines 205 to 209
{headerCells && (
<thead>
<thead aria-hidden="true">
<tr>{headerCells}</tr>
</thead>
)}

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.

high

Applying aria-hidden="true" to the <thead> element removes the column headers (e.g., "Mo", "Tu", etc.) from the accessibility tree. This prevents screen readers from announcing the column headers when users navigate through the grid cells, which is a major accessibility regression. Please remove aria-hidden="true" to keep the column headers accessible.

Suggested change
{headerCells && (
<thead>
<thead aria-hidden="true">
<tr>{headerCells}</tr>
</thead>
)}
{headerCells && (
<thead>
<tr>{headerCells}</tr>
</thead>
)}

Comment on lines +27 to +38
<button
key={index}
type="button"
onClick={() => {
onClick(executeValue(value));
}}
onMouseEnter={() => {
onHover(executeValue(value));
}}
onMouseLeave={() => {
onHover(null);
}}

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 this PR focuses on improving keyboard navigation and accessibility, we should ensure that preset buttons also trigger the hover preview state when they receive focus via keyboard tab navigation. Adding onFocus and onBlur handlers will match the mouse hover behavior (onMouseEnter/onMouseLeave) for keyboard users.

        <button
          key={index}
          type="button"
          onClick={() => {
            onClick(executeValue(value));
          }}
          onMouseEnter={() => {
            onHover(executeValue(value));
          }}
          onMouseLeave={() => {
            onHover(null);
          }}
          onFocus={() => {
            onHover(executeValue(value));
          }}
          onBlur={() => {
            onHover(null);
          }}
        >

Comment on lines 407 to 412
<Component
ref={inputRef}
aria-invalid={invalid}
aria-haspopup="dialog"
aria-expanded={open}
autoComplete="off"

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

According to ARIA specifications, the aria-expanded attribute is not supported on a standard textbox input element unless it has role="combobox". Adding role="combobox" ensures that screen readers correctly interpret and announce the expanded/collapsed state of the date picker popup.

Suggested change
<Component
ref={inputRef}
aria-invalid={invalid}
aria-haspopup="dialog"
aria-expanded={open}
autoComplete="off"
<Component
ref={inputRef}
role="combobox"
aria-invalid={invalid}
aria-haspopup="dialog"
aria-expanded={open}
autoComplete="off"

Comment on lines 189 to 193
rows.push(
<tr key={row} className={rowClassName?.(rowStartDate!)}>
<tr key={row} role="row" className={rowClassName?.(rowStartDate!)}>
{rowNode}
</tr>,
);

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

For the week picker (type === 'week'), the entire row represents the selected week rather than individual cells. To make this accessible to screen readers, we should apply aria-selected directly to the <tr> element when the row's week matches the selected value.

    const isRowSelected = type === 'week' && matchValues(rowStartDate!);

    rows.push(
      <tr
        key={row}
        role="row"
        aria-selected={isRowSelected}
        className={rowClassName?.(rowStartDate!)}
      >
        {rowNode}
      </tr>,
    );

@codecov

codecov Bot commented Jun 30, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.93%. Comparing base (6f6bbb3) to head (8608e15).

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #986      +/-   ##
==========================================
+ Coverage   98.81%   98.93%   +0.12%     
==========================================
  Files          66       66              
  Lines        2698     2732      +34     
  Branches      749      736      -13     
==========================================
+ Hits         2666     2703      +37     
+ Misses         29       27       -2     
+ Partials        3        2       -1     

☔ 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.

@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

🧹 Nitpick comments (3)
src/PickerPanel/MonthPanel/index.tsx (1)

60-65: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

aria-current="date" 对非日期单元格语义不够准确(同样适用于 Quarter/Year/Decade 面板)。

aria-currentdate 取值用于标识“当前日期”。月份(以及季度、年、十年)单元格代表的不是具体日期,按 ARIA 规范在没有匹配的具体取值时更合适使用通用值 true。屏幕阅读器对未识别取值通常按 true 处理,因此影响轻微,可按需调整。

♻️ 可选调整
-      return { 'aria-current': 'date' };
+      return { 'aria-current': true };
🤖 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/PickerPanel/MonthPanel/index.tsx` around lines 60 - 65, The cell
accessibility state in MonthPanel is using aria-current="date" for a non-day
unit, which should be generalized. Update getCellAttributes in MonthPanel, and
apply the same pattern in the Quarter/Year/Decade panel helpers, so the
currently selected month-like cell uses aria-current="true" instead of "date".
Keep the logic tied to the existing generateConfig/isSameMonth-style checks and
the getCellAttributes method names so the change is consistent across panels.
src/PickerInput/Popup/index.tsx (1)

222-222: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

建议补充 dialog 的可访问名称。

role="dialog" 已添加,但缺少 aria-label/aria-labelledby,部分屏幕阅读器进入对话框时无法播报名称;如适用也可考虑 aria-modal。属于无障碍完整性的可选增强,可按需采纳。

🤖 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/PickerInput/Popup/index.tsx` at line 222, The Popup dialog currently has
role="dialog" but no accessible name, so update the dialog element in the Popup
component to include an appropriate aria-label or aria-labelledby tied to an
existing visible title, and add aria-modal if the popup is truly modal. Use the
dialog markup in Popup/index.tsx and the surrounding title/content elements to
choose the most suitable label source.
src/PickerInput/Selector/Input.tsx (1)

410-411: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

aria-expanded 建议显式取布尔值。

aria-haspopup="dialog" 恒定存在,而 open 可能为 undefineduseInputProps 中会在清理阶段删除 undefined 字段),此时 aria-expanded 不会被渲染,辅助技术无法获知折叠/展开状态。建议强制为布尔值,保证属性始终存在。

♿ 建议修改
         aria-haspopup="dialog"
-        aria-expanded={open}
+        aria-expanded={!!open}
🤖 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/PickerInput/Selector/Input.tsx` around lines 410 - 411, `Input` 里的
`aria-expanded` 直接绑定了 `open`,当 `open` 为 `undefined` 时属性会被省略,导致辅助技术无法稳定获取展开状态。请在
`Selector/Input.tsx` 的输入渲染逻辑中,针对 `aria-expanded` 强制转换为布尔值,确保该属性始终存在,并保留与
`aria-haspopup="dialog"` 和 `useInputProps` 清理行为一致的可访问性语义。
🤖 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 `@assets/index.less`:
- Around line 175-180: The preset button container in the &-presets styles only
sets layout, so the new native button element still shows browser default
border, padding, background, and font styles. Update the button styling in the
presets section of assets/index.less to reset the button appearance (for example
in the same ruleset that styles the preset items) so the visual matches the old
li-based UI consistently across browsers.

---

Nitpick comments:
In `@src/PickerInput/Popup/index.tsx`:
- Line 222: The Popup dialog currently has role="dialog" but no accessible name,
so update the dialog element in the Popup component to include an appropriate
aria-label or aria-labelledby tied to an existing visible title, and add
aria-modal if the popup is truly modal. Use the dialog markup in Popup/index.tsx
and the surrounding title/content elements to choose the most suitable label
source.

In `@src/PickerInput/Selector/Input.tsx`:
- Around line 410-411: `Input` 里的 `aria-expanded` 直接绑定了 `open`,当 `open` 为
`undefined` 时属性会被省略,导致辅助技术无法稳定获取展开状态。请在 `Selector/Input.tsx` 的输入渲染逻辑中,针对
`aria-expanded` 强制转换为布尔值,确保该属性始终存在,并保留与 `aria-haspopup="dialog"` 和
`useInputProps` 清理行为一致的可访问性语义。

In `@src/PickerPanel/MonthPanel/index.tsx`:
- Around line 60-65: The cell accessibility state in MonthPanel is using
aria-current="date" for a non-day unit, which should be generalized. Update
getCellAttributes in MonthPanel, and apply the same pattern in the
Quarter/Year/Decade panel helpers, so the currently selected month-like cell
uses aria-current="true" instead of "date". Keep the logic tied to the existing
generateConfig/isSameMonth-style checks and the getCellAttributes method names
so the change is consistent across panels.
🪄 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: f700c8f0-e182-4801-91be-9cf73b993afc

📥 Commits

Reviewing files that changed from the base of the PR and between 6f6bbb3 and ed09967.

⛔ Files ignored due to path filters (3)
  • tests/__snapshots__/panel.spec.tsx.snap is excluded by !**/*.snap
  • tests/__snapshots__/picker.spec.tsx.snap is excluded by !**/*.snap
  • tests/__snapshots__/range.spec.tsx.snap is excluded by !**/*.snap
📒 Files selected for processing (15)
  • assets/index.less
  • src/PickerInput/Popup/PresetPanel.tsx
  • src/PickerInput/Popup/index.tsx
  • src/PickerInput/Selector/Input.tsx
  • src/PickerInput/Selector/hooks/useInputProps.ts
  • src/PickerPanel/DatePanel/index.tsx
  • src/PickerPanel/DecadePanel/index.tsx
  • src/PickerPanel/MonthPanel/index.tsx
  • src/PickerPanel/PanelBody.tsx
  • src/PickerPanel/PanelHeader.tsx
  • src/PickerPanel/QuarterPanel/index.tsx
  • src/PickerPanel/YearPanel/index.tsx
  • tests/new-range.spec.tsx
  • tests/picker.spec.tsx
  • tests/range.spec.tsx

Comment thread assets/index.less
Comment on lines 175 to 180
&-presets {
display: flex;
flex-direction: column;
gap: 4px;
background: #ccccff;

ul {
margin: 0;
padding: 0;
list-style: none;
}
}

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

补上预设按钮的样式重置。

这里把 li 换成原生 button 后,只改了容器布局,没有重置按钮自身样式。浏览器默认的边框、内边距、背景和字体会直接暴露出来,预设面板会出现明显的视觉回归,而且不同浏览器表现也不一致。

建议修改
   &-presets {
     display: flex;
     flex-direction: column;
     gap: 4px;
     background: `#ccccff`;
+
+    > button {
+      padding: 0;
+      font: inherit;
+      color: inherit;
+      text-align: start;
+      background: transparent;
+      border: 0;
+      cursor: pointer;
+    }
   }
📝 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
&-presets {
display: flex;
flex-direction: column;
gap: 4px;
background: #ccccff;
ul {
margin: 0;
padding: 0;
list-style: none;
}
}
&-presets {
display: flex;
flex-direction: column;
gap: 4px;
background: `#ccccff`;
> button {
padding: 0;
font: inherit;
color: inherit;
text-align: start;
background: transparent;
border: 0;
cursor: pointer;
}
}
🤖 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 `@assets/index.less` around lines 175 - 180, The preset button container in the
&-presets styles only sets layout, so the new native button element still shows
browser default border, padding, background, and font styles. Update the button
styling in the presets section of assets/index.less to reset the button
appearance (for example in the same ruleset that styles the preset items) so the
visual matches the old li-based UI consistently across browsers.

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