Skip to content

feat: Improve Date panel keyboard navigation and semantics#986

Open
Pareder wants to merge 3 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 3 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

  • 新功能 / 改进
    • 预设项改为按钮交互,并将预设容器调整为纵向 flex 布局(新增间距)。
    • 选择器与日期面板增强无障碍:弹窗语义、展开状态、翻页按钮与表格/单元格的读屏标签与网格语义完善,并支持自定义翻页文案。
    • 单元格可标识“今天/当前月份/当前季度/当前十年/当前年份”等当前状态。
  • Bug 修复
    • 修正并统一“当前状态”无障碍信息的呈现逻辑,提升辅助技术体验。
  • 测试
    • 调整预设相关用例的 DOM 选择器以匹配新按钮结构。

@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 Plus

Run ID: d28a989d-0971-4ad3-a560-c48f3d90b6bf

📥 Commits

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

⛔ 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 (3)
  • src/PickerInput/Selector/Input.tsx
  • src/generate/dateFns.ts
  • tests/panel.spec.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/panel.spec.tsx
  • src/PickerInput/Selector/Input.tsx

Walkthrough

为 Picker 组件补充弹出层、输入框、预设项、日期表格、单元格和面板导航的无障碍语义;预设项改为 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 输出 aria-haspopuparia-expanded,并由 useInputProps 透传 open
预设面板改用 button 元素
src/PickerInput/Popup/PresetPanel.tsx, assets/index.less, tests/*.spec.tsx
预设项由 ul/li 改为 button,样式和测试选择器同步更新。
PanelHeader 导航标签
src/PickerPanel/PanelHeader.tsx
新增可配置导航标签,四个翻页按钮优先使用传入的 aria-label,并移除 tabIndex={-1}
PanelBody 表格 ARIA 结构
src/PickerPanel/PanelBody.tsx
新增 getCellAttributestableLabel,并为表格、行和单元格补充 ARIA 角色及状态属性。
日期面板接入无障碍属性
src/PickerPanel/{DatePanel,MonthPanel,QuarterPanel,YearPanel,DecadePanel}/*, tests/panel.spec.tsx
各面板新增标题、导航标签和当前日期单元格属性,并将其传入 PanelBody;月份标签测试覆盖格式化与回退场景。
语句终止符整理
src/generate/dateFns.ts
为两个箭头函数表达式补充分号。

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

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant Input
  participant Popup
  participant PanelBody
  User->>Input: 打开日期选择器
  Input->>Popup: 传递 open 状态
  Popup->>PanelBody: 渲染 dialog 内的日期 grid
  PanelBody-->>User: 返回表格、单元格和当前日期 ARIA 属性
Loading

Possibly related PRs

Suggested reviewers: zombiej

Poem

小兔跳进面板中,
button 排好一行行;
aria 标签轻声唱,
月年季度都明亮;
点开 dialog 看得清。 🐰

🚥 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

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 (b465cd6) to head (bfde552).

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.

@Pareder

Pareder commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

@afc163 @zombieJ Could you please review?

1 similar comment
@Pareder

Pareder commented Jul 21, 2026

Copy link
Copy Markdown
Contributor Author

@afc163 @zombieJ Could you please 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.

1 participant