-
Notifications
You must be signed in to change notification settings - Fork 298
feat: 日历组件无障碍 #3437
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: 日历组件无障碍 #3437
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| import React, { useCallback, useEffect, useState, useRef } from 'react' | ||
| import React, { useCallback, useEffect, useState, useRef, useMemo } from 'react' | ||
| import classNames from 'classnames' | ||
| import { View } from '@tarojs/components' | ||
| import { ArrowLeft, ArrowRight, DoubleLeft, DoubleRight } from './icon.taro' | ||
|
|
@@ -24,6 +24,7 @@ const defaultProps = { | |
| ...ComponentDefaults, | ||
| type: 'single', | ||
| firstDayOfWeek: 0, | ||
| weekdays: [], | ||
| } | ||
|
|
||
| const prefixCls = 'nut-calendarcard' | ||
|
|
@@ -47,6 +48,7 @@ export const CalendarCard = React.forwardRef< | |
| renderDay, | ||
| renderDayTop, | ||
| renderDayBottom, | ||
| weekdays, | ||
| onDayClick, | ||
| onPageChange, | ||
| onChange, | ||
|
|
@@ -235,6 +237,36 @@ export const CalendarCard = React.forwardRef< | |
| return d === 0 || d === 6 | ||
| } | ||
|
|
||
| const isToday = (day: CalendarCardDay) => { | ||
| const today = new Date() | ||
| return ( | ||
| day.year === today.getFullYear() && | ||
| day.month === today.getMonth() + 1 && | ||
| day.date === today.getDate() | ||
| ) | ||
| } | ||
|
|
||
| // 日期无障碍朗读 | ||
| const getAriaLabel = (day: CalendarCardDay) => { | ||
| const today = isToday(day) | ||
| // 日期选定时,朗读="已选定 1号 按钮" | ||
| if (isActive(day)) { | ||
| return today | ||
| ? `已选定今日${day.month}月${day.date}号` | ||
| : `已选定${day.month}月${day.date}号` | ||
| } | ||
| // 若不可选中,朗读=“3号 按钮 变暗” | ||
| if (isDisable(day)) { | ||
| return today | ||
| ? `${day.month}月${day.date}号今日按钮变暗` | ||
| : `${day.month}月${day.date}号按钮变暗` | ||
| } | ||
| // 未选定时,朗读=“2号 按钮” | ||
| return today | ||
| ? `${day.month}月${day.date}号今日` | ||
| : `${day.month}月${day.date}号` | ||
| } | ||
|
|
||
| const getClasses = (day: CalendarCardDay) => { | ||
| /** | ||
| * active: single、multiple 激活日期 | ||
|
|
@@ -397,18 +429,20 @@ export const CalendarCard = React.forwardRef< | |
| ) | ||
| } | ||
|
|
||
| const [weekHeader] = useState(() => { | ||
| const weekdays = locale.calendaritem.weekdays.map((day, index) => { | ||
| const weekHeader = useMemo(() => { | ||
| const weekdaysList = | ||
| weekdays.length > 0 ? weekdays : locale.calendaritem.weekdays | ||
| const weekdaysData = weekdaysList.map((day, index) => { | ||
| return { | ||
| name: day, | ||
| key: index, | ||
| } | ||
| }) | ||
| return [ | ||
| ...weekdays.slice(firstDayOfWeek, 7), | ||
| ...weekdays.slice(0, firstDayOfWeek), | ||
| ...weekdaysData.slice(firstDayOfWeek, 7), | ||
| ...weekdaysData.slice(0, firstDayOfWeek), | ||
| ] | ||
| }) | ||
| }, [weekdays, firstDayOfWeek, locale.calendaritem.weekdays]) | ||
|
|
||
| const renderContent = () => { | ||
| return ( | ||
|
|
@@ -437,11 +471,14 @@ export const CalendarCard = React.forwardRef< | |
| )} | ||
| key={`${day.year}-${day.month}-${day.date}`} | ||
| onClick={() => handleDayClick(day)} | ||
| ariaLabel={getAriaLabel(day)} | ||
| ariaRole="button" | ||
| > | ||
| <View className={`${prefixCls}-day-top`}> | ||
| {renderDayTop ? renderDayTop(day) : ''} | ||
| </View> | ||
| <View className={`${prefixCls}-day-inner`}> | ||
| {/* @ts-ignore */} | ||
| <View className={`${prefixCls}-day-inner`} ariaHidden> | ||
|
Comment on lines
+480
to
+481
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 与维护者意图相悖:内层 维护者在前次评审中已明确表示 "这个会有有效信息,被忽略~ 不需要处理 ariahidden"。当前代码仍在 ♻️ 建议移除- {/* `@ts-ignore` */}
- <View className={`${prefixCls}-day-inner`} ariaHidden>
+ <View className={`${prefixCls}-day-inner`}>
{renderDay ? renderDay(day) : day.date}
</View>🤖 Prompt for AI Agents |
||
| {renderDay ? renderDay(day) : day.date} | ||
| </View> | ||
| <View className={`${prefixCls}-day-bottom`}> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| import React, { useRef, useState } from 'react' | ||
| import { CalendarCard, CalendarCardRef } from '@nutui/nutui-react-taro' | ||
|
|
||
| const date = new Date('2025-01-01') | ||
|
|
||
| const Demo13 = () => { | ||
| const [dateStr, setDate] = useState(date.getMonth()) | ||
| const onPageChange = (val: any) => { | ||
| console.log('onPageChange', val) | ||
| setDate(val.month) | ||
| } | ||
|
Comment on lines
+7
to
+11
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # First, let's find and examine the demo file
fd -e tsx "demo13" src/packages/calendarcard/demos/Repository: jdf2e/nutui-react Length of output: 109 🏁 Script executed: # Search for CalendarCardMonth type in the codebase
rg "CalendarCardMonth" src/packages/calendarcard/ --type ts --type tsxRepository: jdf2e/nutui-react Length of output: 89 🏁 Script executed: # Check the CalendarCard component implementation, particularly around the month range
fd -e tsx "calendarcard.taro" src/packages/calendarcard/Repository: jdf2e/nutui-react Length of output: 109 🏁 Script executed: # Look for the index/export files to see what's exported
fd -e ts "index" src/packages/calendarcard/ | head -20Repository: jdf2e/nutui-react Length of output: 136 🏁 Script executed: # Read the demo13.tsx file
cat -n src/packages/calendarcard/demos/taro/demo13.tsxRepository: jdf2e/nutui-react Length of output: 1092 🏁 Script executed: # Check the CalendarCardMonth type - search more broadly
rg "CalendarCardMonth" src/packages/calendarcard/Repository: jdf2e/nutui-react Length of output: 693 🏁 Script executed: # Read the exports from the main index file
cat -n src/packages/calendarcard/index.taro.tsRepository: jdf2e/nutui-react Length of output: 367 🏁 Script executed: # Read the taro component implementation to understand month handling
cat -n src/packages/calendarcard/calendarcard.taro.tsx | head -100Repository: jdf2e/nutui-react Length of output: 3275 🏁 Script executed: # Final verification - check the type definition of CalendarCardMonth
rg "type CalendarCardMonth" src/ -A 5Repository: jdf2e/nutui-react Length of output: 43 🏁 Script executed: # Verify the props type for onPageChange callback
rg "onPageChange" src/packages/calendarcard/calendarcard.taro.tsx -B 2 -A 2Repository: jdf2e/nutui-react Length of output: 524 变量命名误导和类型缺失
♻️ 建议修改-import { CalendarCard, CalendarCardRef } from '@nutui/nutui-react-taro'
+import {
+ CalendarCard,
+ CalendarCardRef,
+ CalendarCardMonth,
+} from '@nutui/nutui-react-taro'
...
- const [dateStr, setDate] = useState(date.getMonth())
- const onPageChange = (val: any) => {
+ const [month, setMonth] = useState(date.getMonth() + 1)
+ const onPageChange = (val: CalendarCardMonth) => {
console.log('onPageChange', val)
- setDate(val.month)
+ setMonth(val.month)
}注: 🤖 Prompt for AI Agents |
||
| const CalendarCardRef = useRef<CalendarCardRef>(null) | ||
|
|
||
| return ( | ||
| <CalendarCard | ||
| ref={CalendarCardRef} | ||
| defaultValue={date} | ||
| onPageChange={onPageChange} | ||
| weekdays={['周日', '周一', '周二', '周三', '周四', '周五', '周六']} | ||
| firstDayOfWeek={1} | ||
| /> | ||
| ) | ||
| } | ||
| export default Demo13 | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
未区分 prev/next 月份的日期,可能导致同一数字在网格中被读三次。
days数组同时包含上月、本月、下月的日期。当前实现只读day.date,例如月初网格里会出现30号、31号等上月日期,与下月开头的日期混在一起,盲人用户会听到 "30号"、"30号"——无法分辨这是哪个月的 30 号。建议对day.type !== 'current'的单元格附加月份信息(或将其设为ariaHidden/ 不参与 tab 序),与handleDayClick中已经过滤非当前月点击的逻辑保持一致。🤖 Prompt for AI Agents
aria 文案硬编码为中文,破坏国际化能力。
该组件其它文案(如
monthTitle、weekdays)均通过locale提供本地化,但getAriaLabel直接拼接了 "已选定"、"今日"、"号"、"按钮变暗" 等中文字符串。非中文 locale 下屏幕阅读器仍会朗读中文,对盲人用户造成严重的可访问性退化。建议将这些文案抽到locale.calendaritem中(例如selected、today、day、disabled等键),由调用方按 locale 翻译。另外,注释中提到 "已选定 1号 按钮"、"3号 按钮 变暗" 等期望朗读结果都包含 "按钮",但实际返回值中并没有 "按钮"。由于
ariaRole="button"已在父级设置,多数读屏会自动朗读 role,因此 label 中不需重复,但建议同步更新注释以避免歧义。🌐 建议改造方向(示意)
🤖 Prompt for AI Agents