Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 44 additions & 7 deletions src/packages/calendarcard/calendarcard.taro.tsx
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'
Expand All @@ -24,6 +24,7 @@ const defaultProps = {
...ComponentDefaults,
type: 'single',
firstDayOfWeek: 0,
weekdays: [],
}

const prefixCls = 'nut-calendarcard'
Expand All @@ -47,6 +48,7 @@ export const CalendarCard = React.forwardRef<
renderDay,
renderDayTop,
renderDayBottom,
weekdays,
onDayClick,
onPageChange,
onChange,
Expand Down Expand Up @@ -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}号`
}
Comment on lines +250 to +268

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

未区分 prev/next 月份的日期,可能导致同一数字在网格中被读三次。

days 数组同时包含上月、本月、下月的日期。当前实现只读 day.date,例如月初网格里会出现 30号31号 等上月日期,与下月开头的日期混在一起,盲人用户会听到 "30号"、"30号"——无法分辨这是哪个月的 30 号。建议对 day.type !== 'current' 的单元格附加月份信息(或将其设为 ariaHidden / 不参与 tab 序),与 handleDayClick 中已经过滤非当前月点击的逻辑保持一致。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/packages/calendarcard/calendarcard.taro.tsx` around lines 250 - 262,
getAriaLabel currently only reads day.date which causes identical labels for
same numeric days from prev/next months; update getAriaLabel (used with days
array and predicates isActive/isDisable) to distinguish non-current-month cells
by appending month info (e.g. "上月30号"/"下月2号" or full month name) when day.type
!== 'current', or alternatively mark those cells as aria-hidden and remove them
from tab order (tabIndex=-1) to match handleDayClick's filtering of non-current
clicks; ensure isActive/isDisable logic still applies and labels remain
localized and consistent with existing strings.

⚠️ Potential issue | 🟠 Major

aria 文案硬编码为中文,破坏国际化能力。

该组件其它文案(如 monthTitleweekdays)均通过 locale 提供本地化,但 getAriaLabel 直接拼接了 "已选定"、"今日"、"号"、"按钮变暗" 等中文字符串。非中文 locale 下屏幕阅读器仍会朗读中文,对盲人用户造成严重的可访问性退化。建议将这些文案抽到 locale.calendaritem 中(例如 selectedtodaydaydisabled 等键),由调用方按 locale 翻译。

另外,注释中提到 "已选定 1号 按钮"、"3号 按钮 变暗" 等期望朗读结果都包含 "按钮",但实际返回值中并没有 "按钮"。由于 ariaRole="button" 已在父级设置,多数读屏会自动朗读 role,因此 label 中不需重复,但建议同步更新注释以避免歧义。

🌐 建议改造方向(示意)
-  // 日期无障碍朗读
-  const getAriaLabel = (day: CalendarCardDay) => {
-    const today = isToday(day)
-    // 日期选定时,朗读="已选定 1号 按钮"
-    if (isActive(day)) {
-      return today ? `已选定今日${day.date}号` : `已选定${day.date}号`
-    }
-    // 若不可选中,朗读=“3号 按钮 变暗”
-    if (isDisable(day)) {
-      return today ? `${day.date}号今日按钮变暗` : `${day.date}号按钮变暗`
-    }
-    // 未选定时,朗读=“2号 按钮”
-    return today ? `${day.date}号今日` : `${day.date}号`
-  }
+  // 日期无障碍朗读(文案由 locale 提供,避免硬编码语言)
+  const getAriaLabel = (day: CalendarCardDay) => {
+    const a11y = locale.calendaritem.a11y // 需在 locale 中补充
+    const today = isToday(day)
+    const base = today ? a11y.today(day.date) : a11y.day(day.date)
+    if (isActive(day)) return a11y.selected(base)
+    if (isDisable(day)) return a11y.disabled(base)
+    return base
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/packages/calendarcard/calendarcard.taro.tsx` around lines 250 - 262,
getAriaLabel currently hardcodes Chinese strings and breaks i18n; change it to
use locale entries (e.g., locale.calendaritem.selected, .today, .day, .disabled)
instead of literal "已选定"/"今日"/"号"/"按钮变暗", and build the label by composing these
localized tokens together when computing getAriaLabel(day) (still using
isActive, isDisable, isToday to pick which tokens to combine); also update the
surrounding comment to remove the misleading "按钮" text (ariaRole="button" is set
on the parent so the role need not be repeated in the label).


const getClasses = (day: CalendarCardDay) => {
/**
* active: single、multiple 激活日期
Expand Down Expand Up @@ -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 (
Expand Down Expand Up @@ -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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

与维护者意图相悖:内层 ariaHidden 会丢失有效信息,建议移除。

维护者在前次评审中已明确表示 "这个会有有效信息,被忽略~ 不需要处理 ariahidden"。当前代码仍在 ${prefixCls}-day-inner 上设置了 ariaHidden 并以 @ts-ignore 屏蔽类型检查。这会让 renderDay 自定义内容(角标、农历、节日等业务关键信息)对屏幕阅读器不可见,且 @ts-ignore 还会掩盖该属性在 Taro View 类型上是否存在的真实问题。

♻️ 建议移除
-              {/* `@ts-ignore` */}
-              <View className={`${prefixCls}-day-inner`} ariaHidden>
+              <View className={`${prefixCls}-day-inner`}>
                 {renderDay ? renderDay(day) : day.date}
               </View>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/packages/calendarcard/calendarcard.taro.tsx` around lines 480 - 481,
Remove the unnecessary ariaHidden prop and the accompanying `@ts-ignore` on the
inner day container so screen readers can access custom rendered content; edit
the <View className={`${prefixCls}-day-inner`} ariaHidden> (inside renderDay /
calendar day cell) to simply be <View className={`${prefixCls}-day-inner`}> and
drop the `@ts-ignore`; if you intended to hide non-essential decorative elements,
explicitly apply aria-hidden only to those specific sub-elements rather than the
entire `${prefixCls}-day-inner` container.

{renderDay ? renderDay(day) : day.date}
</View>
<View className={`${prefixCls}-day-bottom`}>
Expand Down
6 changes: 6 additions & 0 deletions src/packages/calendarcard/demo.taro.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import Demo9 from './demos/taro/demo9'
import Demo10 from './demos/taro/demo10'
import Demo11 from './demos/taro/demo11'
import Demo12 from './demos/taro/demo12'
import Demo13 from './demos/taro/demo13'

const CalendarDemo = () => {
const [translated] = useTranslate({
Expand All @@ -34,6 +35,7 @@ const CalendarDemo = () => {
confirm: '确定',
ref: '使用 Ref 上的方法',
title: '搭配 Ref 使用自定义头',
customWeekdays: '自定义周几',
},
'zh-TW': {
single: '選擇單個日期',
Expand All @@ -50,6 +52,7 @@ const CalendarDemo = () => {
confirm: '確定',
ref: '使用 Ref 上的方法',
title: '搭配 Ref 使用自定義头',
customWeekdays: '自定義周几',
},
'en-US': {
single: 'Select a single date',
Expand All @@ -66,6 +69,7 @@ const CalendarDemo = () => {
confirm: 'Confirm',
ref: 'Use ref',
title: 'Custom title',
customWeekdays: 'Custom weekdays',
},
})

Expand Down Expand Up @@ -97,6 +101,8 @@ const CalendarDemo = () => {
<Demo11 />
<View className="h2">{translated.title}</View>
<Demo12 />
<View className="h2">{translated.customWeekdays}</View>
<Demo13 />
Comment thread
coderabbitai[bot] marked this conversation as resolved.
</ScrollView>
</>
)
Expand Down
24 changes: 24 additions & 0 deletions src/packages/calendarcard/demos/taro/demo13.tsx
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 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 tsx

Repository: 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 -20

Repository: jdf2e/nutui-react

Length of output: 136


🏁 Script executed:

# Read the demo13.tsx file
cat -n src/packages/calendarcard/demos/taro/demo13.tsx

Repository: 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.ts

Repository: 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 -100

Repository: jdf2e/nutui-react

Length of output: 3275


🏁 Script executed:

# Final verification - check the type definition of CalendarCardMonth
rg "type CalendarCardMonth" src/ -A 5

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

Repository: jdf2e/nutui-react

Length of output: 524


变量命名误导和类型缺失

  • dateStr 实际存放的是 getMonth() 返回的数字(0-11),命名容易让人误以为是字符串。建议重命名为 monthcurrentMonth
  • onPageChange 形参 val: any 可改为已导出的 CalendarCardMonth 类型,避免 any 并提升对 val.month 字段的类型保护。
♻️ 建议修改
-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)
   }

注:CalendarCard 内部的 month 取值范围为 1-12(参见 calendarcard.taro.tsx 第 70 行 date.getMonth() + 1),而 Date.prototype.getMonth() 返回 0-11,因此初始化时也应 +1 以保持一致,否则首次渲染前显示的值会比 onPageChange 回调后的值少 1。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/packages/calendarcard/demos/taro/demo13.tsx` around lines 8 - 12,
变量命名和类型不一致:把 dateStr 重命名为 month 或 currentMonth(并把 setDate 重命名为
setMonth)以反映它实际存储的是月份数字,且在初始化时用 date.getMonth() + 1 保持与 CalendarCard 的 1-12
约定一致;将 onPageChange 的参数从 any 改为已导出的类型 CalendarCardMonth(确保导入该类型)并在回调中使用
setMonth(val.month) 来更新月份,从而移除 any 并保证类型安全。

const CalendarCardRef = useRef<CalendarCardRef>(null)

return (
<CalendarCard
ref={CalendarCardRef}
defaultValue={date}
onPageChange={onPageChange}
weekdays={['周日', '周一', '周二', '周三', '周四', '周五', '周六']}
firstDayOfWeek={1}
/>
)
}
export default Demo13
1 change: 1 addition & 0 deletions src/types/spec/calendarcard/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export interface BaseCalendarCard extends BaseProps {
firstDayOfWeek: number // 0-6
startDate: Date
endDate: Date
weekdays?: string[]
disableDay: (day: CalendarCardDay) => boolean
renderDay: (day: CalendarCardDay) => ReactNode
renderDayTop: (day: CalendarCardDay) => ReactNode
Expand Down
Loading