-
Notifications
You must be signed in to change notification settings - Fork 288
feat: 添加组件无障碍功能支持 #3385
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
base: feat_v3.x_cpp
Are you sure you want to change the base?
feat: 添加组件无障碍功能支持 #3385
Conversation
- button - Checkbox - Countdown - Empty - Image - InputNumber - Price - Progress - NavBar - NoticeBar - Radio - Switch - Tabbar - Tabs - Toast
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
无障碍访问功能增强评审Walkthrough本次 PR 在多个 NutUI React 组件中系统地添加了 ARIA 无障碍访问支持,包括语义化角色(role)、标签(label)、禁用状态(disabled)等属性。这些改进涵盖按钮、对话框、图片、输入框、选项卡等核心组件的 Web 版本和 Taro 版本,并新增相应的演示文件。 Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes 需要特别关注的领域
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
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.
Actionable comments posted: 30
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
src/packages/noticebar/noticebar.taro.tsx (1)
466-474: 建议为 leftIcon 添加 aria-hidden 属性注意到 Web 版本(src/packages/noticebar/noticebar.tsx Lines 460-462)为 leftIcon 添加了
aria-hidden属性,但 Taro 版本缺少此属性。为了保持一致性并提供更好的无障碍体验,建议也为 Taro 版本的装饰性 leftIcon 添加aria-hidden。建议修改:
const renderLeftIcon = useCallback(() => { return ( <> {leftIcon ? ( - <View className="nut-noticebar-box-left-icon">{leftIcon}</View> + <View className="nut-noticebar-box-left-icon" aria-hidden> + {leftIcon} + </View> ) : null} </> ) }, [leftIcon])src/packages/swiper/swiper.taro.tsx (1)
51-155:ariaLabel未被透传导致改动无效在解构 props 后并没有把
ariaLabel传给任一容器或TaroSwiper本体,最终渲染里不会出现该属性,使用方设置ariaLabel也不会生效。请补上透传,例如直接给TaroSwiper增加ariaLabel={ariaLabel}(或其它合适的节点),以兑现新增的对外 API。<TaroSwiper current={innerValue} circular={loop} autoplay={autoplay || autoPlay} vertical={direction === 'vertical' || vertical} indicatorDots={false} onChange={(e) => { handleOnChange(e) props.onChange?.(e) }} style={{ width: !width ? '100%' : width, height: !height ? pxTransform(150) : height, }} + ariaLabel={ariaLabel} {...rest} >src/packages/inputnumber/inputnumber.tsx (1)
203-240: 需补足键盘可操作性为减号/加号容器加了
role="button"与tabIndex={0}后,它们会进入键盘焦点,但未绑定onKeyDown(或改用<button>元素),键盘用户按回车/空格不会触发增减逻辑。请补上键盘事件(或改用原生 button),保障无障碍交互。<div className={`${classPrefix}-minus`} onClick={handleReduce} role="button" tabIndex={0} aria-label={translated.mins} aria-disabled={Number(shadowValue) <= Number(min) || disabled} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault() + e.currentTarget.click() + } + }} > ... <div className={`${classPrefix}-add`} role="button" tabIndex={0} aria-label={translated.plus} aria-disabled={Number(shadowValue) >= Number(max) || disabled} onClick={handlePlus} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault() + e.currentTarget.click() + } + }} >
🧹 Nitpick comments (21)
src/packages/tabpane/tabpane.tsx (1)
35-37: ARIA 实现良好,建议补充aria-labelledby属性。添加
role="tabpanel"和动态tabIndex是正确的无障碍实现。tabIndex逻辑确保激活的面板可聚焦(0),非激活的面板不可聚焦(-1)。建议补充
aria-labelledby属性,指向对应的 tab 元素的 ID,以便屏幕阅读器能够建立 tab 和 tabpanel 之间的关联关系。参考 ARIA 最佳实践:
- <div className={classes} role="tabpanel" tabIndex={active ? 0 : -1}> + <div + className={classes} + role="tabpanel" + tabIndex={active ? 0 : -1} + aria-labelledby={/* 对应 tab 的 ID */} + > {!disabled && children} </div>src/packages/tabpane/tabpane.taro.tsx (1)
36-38: ARIA 实现良好,建议补充ariaLabelledby属性。使用
ariaRole="tabpanel"和动态tabIndex是正确的 Taro 无障碍实现。tabIndex逻辑确保激活的面板可聚焦(0),非激活的面板不可聚焦(-1)。建议补充
ariaLabelledby属性(注意 Taro 使用驼峰命名),指向对应的 tab 元素的 ID,以便屏幕阅读器能够建立 tab 和 tabpanel 之间的关联关系。参考 ARIA 最佳实践:
- <View className={classes} ariaRole="tabpanel" tabIndex={active ? 0 : -1}> + <View + className={classes} + ariaRole="tabpanel" + tabIndex={active ? 0 : -1} + ariaLabelledby={/* 对应 tab 的 ID */} + > {!disabled && children} </View>src/packages/progress/progress.taro.tsx (1)
225-225: 考虑国际化支持。默认的
ariaLabel值使用硬编码的中文文本进度${percent}%,这可能不适合国际化项目。建议:
- 如果项目有国际化方案,使用国际化函数包装默认文本
- 或者在文档中明确说明开发者应该为国际化场景提供自定义
ariaLabel示例(假设项目使用 i18n):
- ariaLabel={ariaLabel || `进度${percent}%`} + ariaLabel={ariaLabel || i18n.t('progress.ariaLabel', { percent })}或者如果没有国际化方案,至少在组件文档中说明此问题。
src/packages/popup/popup.tsx (1)
309-309: 建议补充aria-modal和可访问名称属性当前仅添加了
role="dialog",但根据 ARIA 最佳实践,对话框还应该包含:
aria-modal="true":当overlay为 true 时,表明这是一个模态对话框aria-labelledby或aria-label:为对话框提供可访问的名称,可以关联到title属性建议应用以下改进:
<div ref={nodeRef} style={popStyles} className={popClassName} onClick={onClick} onTouchStart={handleTouchStart} onTouchMove={handleTouchMove} onTouchEnd={handleTouchEnd} onTouchCancel={handleTouchEnd} role="dialog" + aria-modal={overlay ? 'true' : undefined} + aria-label={typeof title === 'string' ? title : undefined} >src/packages/popup/popup.taro.tsx (1)
349-349: 建议补充ariaModal和可访问名称属性与
popup.tsx类似,Taro 版本也应该补充完整的对话框 ARIA 属性:
ariaModal={overlay}:当overlay为 true 时标记为模态对话框ariaLabel:为对话框提供可访问的名称建议应用以下改进:
onTouchEnd={handleTouchEnd} onTouchCancel={handleTouchEnd} ariaRole="dialog" + ariaModal={overlay} + ariaLabel={typeof title === 'string' ? title : undefined} >src/packages/radio/radio.taro.tsx (1)
140-147: 建议添加tabIndex以支持键盘访问当前实现已正确添加了
ariaRole、ariaChecked和ariaDisabled属性,但缺少键盘可访问性支持。参考 Web 版本的 Checkbox 实现(checkbox.tsx第 213 行),应该添加tabIndex属性。建议应用以下改进:
<View className={cls} style={style} onClick={handleClick} ariaRole="radio" ariaChecked={checkedStatement} ariaDisabled={disabled} + tabIndex={disabled ? -1 : 0} >src/types/spec/noticebar/base.ts (1)
26-26: 建议将 rightIconAriaLabel 设为可选属性将无障碍属性设为必需可能会破坏现有代码。根据 PR 中其他文件的模式(如 src/types/spec/tabbar/base.ts 中的 ariaLabel 等属性),无障碍属性通常应该是可选的,以支持渐进式采用。
应用此 diff 使属性可选:
- rightIconAriaLabel: string + rightIconAriaLabel?: stringsrc/packages/navbar/demos/h5/demo4.tsx (1)
52-62: 移除冗余和不必要的 aria-label
- 第55行:
aria-label="页面标题"与可见文本内容重复,可以移除- 第59行:
aria-label="副标题"用于非交互的展示性元素,通常不需要额外的 aria-label建议修改:
<div style={{ ...styles.flexCenter, flexDirection: 'column' }}> <span style={styles.title} onClick={(e) => Toast.show('标题')} - aria-label="页面标题" > 页面标题 </span> - <span style={styles.description} aria-label="副标题"> + <span style={styles.description}> 副标题 </span> </div>src/packages/tabs/demos/h5/demo25.tsx (1)
36-36: 简化 JSX 表达式
{item.icon || null}中的|| null是多余的,JSX 会自动处理 falsy 值。建议改为:
-{item.icon || null} +{item.icon}src/sites/components/header.tsx (1)
32-32: 考虑将 aria-label 国际化添加
role="button"和aria-label="back"是很好的无障碍实践。不过,aria-label的值 "back" 是硬编码的英文字符串。为了更好的用户体验,建议使用国际化的标签值,就像组件名称的处理方式一样。src/packages/navbar/navbar.tsx (1)
69-69: 返回按钮的 aria-label 应该国际化为返回按钮添加
aria-label="back"是好的做法,但标签值是硬编码的英文字符串。建议使用国际化的文本,以保持与组件其他部分的一致性。src/packages/switch/demos/h5/demo9.tsx (1)
7-7: 改善 ariaLabel 的描述性以提高可访问性当前的 ariaLabel 值 "弹幕" 对于屏幕阅读器用户来说不够描述性。ARIA 标签应该清楚地传达控件的目的和功能。建议使用更具描述性的标签,例如 "开启弹幕" 或 "弹幕开关",以便用户理解这是一个用于控制弹幕功能的开关。
应用以下差异来改进 ariaLabel:
- <Switch defaultChecked ariaLabel="弹幕" /> + <Switch defaultChecked ariaLabel="弹幕开关" />src/packages/tabbar/demos/taro/demo10.tsx (1)
10-10: 建议优化 ariaRoledescription 的使用。
ariaRoledescription属性应该描述元素的角色类型,而不是操作行为。当前设置为"切换tab操作"描述的是用户操作,而非角色本身。根据 ARIA 规范,
aria-roledescription应该提供对角色的人类可读描述。建议:
- 改为
"导航标签"或"标签页",更准确地描述角色- 或者移除此属性,使用元素默认的角色描述
参考修改示例:
<Tabbar.Item title="首页" ariaLabel="首页" - ariaRoledescription="切换tab操作" + ariaRoledescription="导航标签" icon={<Home />} />Also applies to: 16-16, 22-22, 28-28, 34-34
src/packages/countdown/demos/h5/demo10.tsx (1)
13-35: 建议为不同展示类型的倒计时提供更具描述性的 ARIA 标签。目前三个 CountDown 组件使用相同的
ariaLabel。虽然它们显示相同的倒计时,但使用了不同的展示类型(primary、默认、text)。为了更好的无障碍体验,建议在标签中区分展示类型。可以考虑以下改进:
<Cell> <CountDown endTime={stateRef.current.endTime} type="primary" onEnd={onEnd} - ariaLabel="双十一活动倒计时" + ariaLabel="双十一活动倒计时 主要样式" /> </Cell> <Cell> <CountDown endTime={stateRef.current.endTime} onEnd={onEnd} - ariaLabel="双十一活动倒计时" + ariaLabel="双十一活动倒计时 默认样式" /> </Cell> <Cell> <CountDown endTime={stateRef.current.endTime} type="text" onEnd={onEnd} - ariaLabel="双十一活动倒计时" + ariaLabel="双十一活动倒计时 文本样式" /> </Cell>src/packages/countdown/demos/taro/demo10.tsx (1)
13-35: 建议为不同展示类型的倒计时提供更具描述性的 ARIA 标签。目前三个 CountDown 组件使用相同的
ariaLabel。虽然它们显示相同的倒计时,但使用了不同的展示类型(primary、默认、text)。为了更好的无障碍体验,建议在标签中区分展示类型。可以考虑以下改进:
<Cell> <CountDown endTime={stateRef.current.endTime} type="primary" onEnd={onEnd} - ariaLabel="双十一活动倒计时" + ariaLabel="双十一活动倒计时 主要样式" /> </Cell> <Cell> <CountDown endTime={stateRef.current.endTime} onEnd={onEnd} - ariaLabel="双十一活动倒计时" + ariaLabel="双十一活动倒计时 默认样式" /> </Cell> <Cell> <CountDown endTime={stateRef.current.endTime} type="text" onEnd={onEnd} - ariaLabel="双十一活动倒计时" + ariaLabel="双十一活动倒计时 文本样式" /> </Cell>src/packages/noticebar/noticebar.tsx (1)
484-484: 建议国际化 aria-label 文本当前使用硬编码的
'close'字符串作为 aria-label。为了更好的国际化支持,建议通过 i18n 系统提供可翻译的文本标签,或允许开发者通过 props 自定义关闭按钮的 aria-label。示例改进:
-aria-label={`${closeable ? 'close' : rightIconAriaLabel}`} +aria-label={closeable ? (closeAriaLabel || 'close') : rightIconAriaLabel}同时在 props 中添加
closeAriaLabel属性。Also applies to: 546-546
src/packages/noticebar/noticebar.taro.tsx (1)
494-494: 建议国际化 aria-label 文本与 Web 版本相同,这里也使用了硬编码的
'close'字符串。为了更好的国际化支持,建议通过 i18n 系统提供可翻译的文本标签。参考 Web 版本的改进建议,在 props 中添加
closeAriaLabel属性:-ariaLabel={`${closeable ? 'close' : rightIconAriaLabel}`} +ariaLabel={closeable ? (closeAriaLabel || 'close') : rightIconAriaLabel}src/packages/dialog/content.taro.tsx (1)
31-66: 让ariaModal支持显式传值目前解构出
ariaModal却固定写死为visible,导致外部无法按需覆写。建议改成优先使用传入值(例如ariaModal ?? visible),保持灵活性并避免新增 API 成为摆设。- ariaModal={visible} + ariaModal={ariaModal ?? visible}src/packages/countdown/countdown.taro.tsx (1)
27-27: 建议添加国际化支持ariaLabel 的默认值使用了硬编码的中文字符串,但与 inputnumber 组件不同,此处没有使用
useTranslate提供国际化支持。建议保持组件库的一致性,为 ariaLabel 添加多语言支持。src/packages/inputnumber/inputnumber.taro.tsx (1)
22-22: 建议为 ariaLabel 添加国际化虽然组件中已经使用了
useTranslate来翻译按钮标签,但 ariaLabel 的默认值仍然是硬编码的中文。建议将其也纳入国际化配置中,以提供一致的多语言支持。src/packages/dialog/dialog.taro.tsx (1)
312-312: 建议为 ariaRoledescription 添加国际化ariaRoledescription 使用了硬编码的中文 "背景蒙层",缺少国际化支持。为了提供完整的无障碍体验,建议使用 locale 或 useTranslate 提供多语言支持。
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (90)
src/packages/button/button.taro.tsx(1 hunks)src/packages/checkbox/checkbox.taro.tsx(1 hunks)src/packages/checkbox/checkbox.tsx(1 hunks)src/packages/countdown/countdown.taro.tsx(6 hunks)src/packages/countdown/countdown.tsx(6 hunks)src/packages/countdown/demo.taro.tsx(5 hunks)src/packages/countdown/demo.tsx(5 hunks)src/packages/countdown/demos/h5/demo10.tsx(1 hunks)src/packages/countdown/demos/taro/demo10.tsx(1 hunks)src/packages/dialog/content.taro.tsx(2 hunks)src/packages/dialog/content.tsx(2 hunks)src/packages/dialog/dialog.taro.tsx(5 hunks)src/packages/dialog/dialog.tsx(2 hunks)src/packages/image/demo.taro.tsx(4 hunks)src/packages/image/demo.tsx(4 hunks)src/packages/image/demos/h5/demo9.tsx(1 hunks)src/packages/image/demos/taro/demo9.tsx(1 hunks)src/packages/image/image.taro.tsx(2 hunks)src/packages/image/image.tsx(2 hunks)src/packages/inputnumber/demo.taro.tsx(5 hunks)src/packages/inputnumber/demo.tsx(5 hunks)src/packages/inputnumber/demos/h5/demo11.tsx(1 hunks)src/packages/inputnumber/demos/taro/demo11.tsx(1 hunks)src/packages/inputnumber/inputnumber.taro.tsx(6 hunks)src/packages/inputnumber/inputnumber.tsx(6 hunks)src/packages/navbar/demo.taro.tsx(2 hunks)src/packages/navbar/demo.tsx(2 hunks)src/packages/navbar/demos/h5/demo4.tsx(1 hunks)src/packages/navbar/demos/taro/demo4.tsx(1 hunks)src/packages/navbar/navbar.taro.tsx(3 hunks)src/packages/navbar/navbar.tsx(3 hunks)src/packages/noticebar/demo.taro.tsx(4 hunks)src/packages/noticebar/demo.tsx(4 hunks)src/packages/noticebar/demos/h5/demo12.tsx(1 hunks)src/packages/noticebar/demos/taro/demo12.tsx(1 hunks)src/packages/noticebar/noticebar.taro.tsx(3 hunks)src/packages/noticebar/noticebar.tsx(6 hunks)src/packages/popup/popup.taro.tsx(1 hunks)src/packages/popup/popup.tsx(1 hunks)src/packages/price/price.taro.tsx(1 hunks)src/packages/price/price.tsx(2 hunks)src/packages/progress/progress.taro.tsx(1 hunks)src/packages/progress/progress.tsx(1 hunks)src/packages/radio/radio.taro.tsx(1 hunks)src/packages/radio/radio.tsx(1 hunks)src/packages/steps/steps.taro.tsx(1 hunks)src/packages/steps/steps.tsx(1 hunks)src/packages/swiper/demo.taro.tsx(4 hunks)src/packages/swiper/demos/taro/demo10.tsx(1 hunks)src/packages/swiper/effects/default.tsx(3 hunks)src/packages/swiper/effects/focus.tsx(1 hunks)src/packages/swiper/swiper.taro.tsx(3 hunks)src/packages/swiper/swiper.tsx(2 hunks)src/packages/switch/demo.taro.tsx(5 hunks)src/packages/switch/demo.tsx(5 hunks)src/packages/switch/demos/h5/demo9.tsx(1 hunks)src/packages/switch/demos/taro/demo9.tsx(1 hunks)src/packages/switch/switch.taro.tsx(2 hunks)src/packages/switch/switch.tsx(2 hunks)src/packages/tabbar/demo.taro.tsx(5 hunks)src/packages/tabbar/demo.tsx(5 hunks)src/packages/tabbar/demos/h5/demo10.tsx(1 hunks)src/packages/tabbar/demos/taro/demo10.tsx(1 hunks)src/packages/tabbar/tabbar.taro.tsx(3 hunks)src/packages/tabbar/tabbar.tsx(3 hunks)src/packages/tabbaritem/tabbaritem.taro.tsx(1 hunks)src/packages/tabbaritem/tabbaritem.tsx(2 hunks)src/packages/tabpane/tabpane.taro.tsx(1 hunks)src/packages/tabpane/tabpane.tsx(1 hunks)src/packages/tabs/demo.taro.tsx(4 hunks)src/packages/tabs/demo.tsx(4 hunks)src/packages/tabs/demos/h5/demo24.tsx(1 hunks)src/packages/tabs/demos/h5/demo25.tsx(1 hunks)src/packages/tabs/demos/taro/demo24.tsx(1 hunks)src/packages/tabs/demos/taro/demo25.tsx(1 hunks)src/packages/tabs/tabs.taro.tsx(2 hunks)src/packages/tabs/tabs.tsx(3 hunks)src/packages/textarea/textarea.taro.tsx(1 hunks)src/packages/toast/Notification.tsx(1 hunks)src/packages/toast/toast.taro.tsx(1 hunks)src/sites/components/header.tsx(1 hunks)src/types/spec/countdown/base.ts(1 hunks)src/types/spec/dialog/base.ts(1 hunks)src/types/spec/dialog/taro.ts(1 hunks)src/types/spec/image/base.ts(1 hunks)src/types/spec/inputnumber/base.ts(1 hunks)src/types/spec/noticebar/base.ts(1 hunks)src/types/spec/swiper/taro.ts(1 hunks)src/types/spec/switch/base.ts(1 hunks)src/types/spec/tabbar/base.ts(2 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: oasis-cloud
Repo: jdf2e/nutui-react PR: 2700
File: src/packages/animatingnumbers/animatingnumbers.harmony.css:25-32
Timestamp: 2024-11-06T05:56:06.800Z
Learning: 在优化 NutUI React 动画性能时,添加 `will-change` 属性可能会对布局产生影响,需要谨慎使用。
📚 Learning: 2025-05-02T01:45:09.576Z
Learnt from: irisSong
Repo: jdf2e/nutui-react PR: 3209
File: src/packages/searchbar/searchbar.taro.tsx:111-124
Timestamp: 2025-05-02T01:45:09.576Z
Learning: 在 React/Taro 组件中使用 setTimeout 或 setInterval 时,应当在组件卸载时通过 useEffect 的清理函数清除定时器,以防止内存泄漏。可以使用 useState 存储定时器 ID,并在 useEffect 的返回函数中清除。
Applied to files:
src/packages/countdown/demos/h5/demo10.tsxsrc/packages/countdown/countdown.tsxsrc/packages/navbar/demos/taro/demo4.tsxsrc/packages/countdown/countdown.taro.tsxsrc/packages/countdown/demos/taro/demo10.tsx
📚 Learning: 2024-11-06T05:56:06.800Z
Learnt from: oasis-cloud
Repo: jdf2e/nutui-react PR: 2700
File: src/packages/animatingnumbers/animatingnumbers.harmony.css:25-32
Timestamp: 2024-11-06T05:56:06.800Z
Learning: 在优化 NutUI React 动画性能时,添加 `will-change` 属性可能会对布局产生影响,需要谨慎使用。
Applied to files:
src/packages/noticebar/demos/h5/demo12.tsx
🧬 Code graph analysis (25)
src/packages/countdown/demos/h5/demo10.tsx (2)
src/packages/countdown/countdown.taro.tsx (1)
CountDown(359-361)src/packages/countdown/countdown.tsx (1)
CountDown(312-314)
src/packages/tabs/demos/taro/demo24.tsx (2)
src/packages/tabs/tabs.taro.tsx (1)
Tabs(27-319)src/packages/tabs/tabs.tsx (1)
Tabs(24-234)
src/packages/inputnumber/demos/h5/demo11.tsx (2)
src/packages/inputnumber/inputnumber.taro.tsx (1)
InputNumber(26-256)src/packages/inputnumber/inputnumber.tsx (1)
InputNumber(25-253)
src/types/spec/swiper/taro.ts (1)
src/types/spec/swiper/base.ts (1)
BaseSwiperItem(33-35)
src/packages/image/demos/h5/demo9.tsx (2)
src/packages/image/image.taro.tsx (1)
Image(25-141)src/packages/image/image.tsx (1)
Image(27-204)
src/packages/switch/demos/h5/demo9.tsx (2)
src/packages/switch/switch.taro.tsx (1)
Switch(19-135)src/packages/switch/switch.tsx (1)
Switch(18-136)
src/packages/tabbar/demos/h5/demo10.tsx (2)
src/packages/tabbar/tabbar.taro.tsx (1)
Tabbar(23-101)src/packages/tabbar/tabbar.tsx (1)
Tabbar(22-99)
src/packages/noticebar/demos/h5/demo12.tsx (2)
src/packages/noticebar/noticebar.tsx (1)
NoticeBar(35-554)src/packages/button/button.taro.tsx (1)
Button(64-185)
src/packages/tabbar/demos/taro/demo10.tsx (2)
src/packages/tabbar/tabbar.taro.tsx (1)
Tabbar(23-101)src/packages/tabbar/tabbar.tsx (1)
Tabbar(22-99)
src/packages/navbar/demo.tsx (1)
scripts/create-properties.js (1)
h2(58-58)
src/packages/image/demo.tsx (1)
scripts/create-properties.js (1)
h2(58-58)
src/packages/swiper/demos/taro/demo10.tsx (3)
src/types/spec/swiper/taro.ts (1)
TaroSwiperProps(8-24)src/packages/swiper/swiper.taro.tsx (1)
Swiper(32-181)src/packages/image/image.taro.tsx (1)
Image(25-141)
src/packages/switch/demos/taro/demo9.tsx (1)
src/packages/switch/switch.taro.tsx (1)
Switch(19-135)
src/packages/image/demos/taro/demo9.tsx (1)
src/packages/image/image.taro.tsx (1)
Image(25-141)
src/packages/tabs/demos/h5/demo24.tsx (2)
src/packages/tabs/tabs.taro.tsx (1)
Tabs(27-319)src/packages/tabs/tabs.tsx (1)
Tabs(24-234)
src/packages/inputnumber/demos/taro/demo11.tsx (1)
src/packages/inputnumber/inputnumber.taro.tsx (1)
InputNumber(26-256)
src/packages/navbar/demos/taro/demo4.tsx (4)
src/utils/taro/px-transform.ts (1)
pxTransform(5-9)src/packages/navbar/navbar.taro.tsx (1)
NavBar(19-138)src/utils/taro/platform.ts (1)
harmony(3-7)src/packages/navbar/demos/taro/demo1.tsx (1)
NavBar(6-72)
src/packages/tabs/demos/h5/demo25.tsx (2)
src/packages/tabs/tabs.taro.tsx (1)
Tabs(27-319)src/packages/tabs/tabs.tsx (1)
Tabs(24-234)
src/packages/tabbar/demo.tsx (1)
scripts/create-properties.js (1)
h2(58-58)
src/packages/navbar/demos/h5/demo4.tsx (4)
src/packages/navbar/navbar.taro.tsx (1)
NavBar(19-138)src/packages/navbar/navbar.tsx (1)
NavBar(18-137)src/packages/toast/toast.taro.tsx (1)
Toast(39-197)src/packages/navbar/demos/h5/demo2.tsx (1)
NavBar(5-71)
src/packages/swiper/effects/focus.tsx (1)
src/hooks/use-ref-state.ts (1)
getRefValue(3-5)
src/packages/countdown/demos/taro/demo10.tsx (2)
src/packages/countdown/countdown.taro.tsx (1)
CountDown(359-361)src/packages/countdown/countdown.tsx (1)
CountDown(312-314)
src/packages/countdown/demo.tsx (1)
scripts/create-properties.js (1)
h2(58-58)
src/packages/noticebar/demos/taro/demo12.tsx (2)
src/packages/noticebar/noticebar.taro.tsx (1)
NoticeBar(37-578)src/packages/button/button.taro.tsx (1)
Button(64-185)
src/packages/tabs/demos/taro/demo25.tsx (2)
src/packages/tabs/tabs.taro.tsx (1)
Tabs(27-319)src/packages/tabs/tabs.tsx (1)
Tabs(24-234)
🪛 ast-grep (0.39.9)
src/packages/countdown/countdown.tsx
[warning] 302-302: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
🪛 ESLint
src/packages/steps/steps.taro.tsx
[error] 42-43: Delete ⏎·······
(prettier/prettier)
src/packages/steps/steps.tsx
[error] 42-43: Delete ⏎·······
(prettier/prettier)
🔇 Additional comments (66)
src/packages/toast/Notification.tsx (1)
137-137: LGTM!正确使用 role="alert" 提升通知组件的无障碍性为 Notification 内容容器添加
role="alert"是符合 ARIA 最佳实践的,可以确保屏幕阅读器即时播报重要的通知信息。该角色隐式设置了aria-live="assertive"和aria-atomic="true",无需额外声明。src/packages/toast/toast.taro.tsx (1)
181-181: Taro View 组件的ariaRole属性验证已完成代码实现正确。在代码库中,
ariaRole是 Taro View 组件的标准 ARIA 属性命名约定(对应 HTML 的role属性)。为 Toast 容器添加ariaRole="alert"符合无障碍规范,使屏幕阅读器能够即时播报提示信息。该做法与代码库中其他组件(tabs、switch、radio、button 等)的实践一致。src/packages/textarea/textarea.taro.tsx (1)
144-144: 修正传递给 Taro Textarea 组件的不受支持的属性根据代码验证,
showCount属性在第 144 行被传递给 Taro Textarea 组件,但在src/types/spec/textarea/taro.ts中的类型定义明确将其从TextareaProps中排除:export type TaroTextAreaProps = Omit<BaseTextArea, 'style'> & Omit<TextareaProps, 'showCount' | 'onFocus' | 'onBlur'>这意味着 Taro 的原生 Textarea 组件不支持
showCount属性。该属性不应传递给 Taro 组件。第 146-154 行中已实现的自定义计数显示是实际的字符计数器实现。建议:删除第 144 行的
showCount={showCount}属性,因为它会被 Taro Textarea 组件忽略或导致类型错误。Likely an incorrect or invalid review comment.
src/packages/price/price.taro.tsx (1)
173-179: 无障碍功能增强实现得很好!为组件添加
ariaLabel属性能够让屏幕阅读器准确读取价格信息。实现细节值得肯定:
- 正确使用
replaceSpecialChar将 HTML 实体(如¥)转换为实际字符- aria-label 的构建逻辑准确反映了视觉渲染内容(符号位置、千分位、小数部分)
- 在 harmony 和非 harmony 分支中保持了一致的实现
Also applies to: 187-193
src/packages/price/price.tsx (2)
56-64: 新增的工具函数实现简洁清晰!
replaceSpecialChar函数使用链式调用的方式转换 HTML 实体,比 Taro 版本中的多次赋值方式更加简洁易读。该函数确保了 aria-label 中的符号(如¥)能被屏幕阅读器正确识别。
127-133: 无障碍支持实现准确且完整!为根元素添加
aria-label属性,让屏幕阅读器能够获取格式化后的价格信息。实现要点:
- 正确处理符号位置(前置/后置)
- 包含千分位格式化、小数点和小数部分
- 与视觉渲染逻辑保持一致
- 与 Taro 版本实现保持统一
这是一个优秀的无障碍功能增强!
src/packages/tabbar/demos/h5/demo10.tsx (1)
1-40: LGTM!这个演示很好地展示了 Tabbar 组件的无障碍功能:
- 为 Tabbar 容器添加了
ariaLabel="底部导航"- 每个 TabbarItem 都正确设置了
ariaLabel和ariaRoledescription- 代码结构清晰,易于理解
src/types/spec/switch/base.ts (1)
14-14: LGTM!为
BaseSwitch接口添加ariaLabel属性是正确的,与其他组件的无障碍类型定义保持一致。src/packages/checkbox/checkbox.tsx (1)
212-214: LGTM!复选框的无障碍实现非常完善:
- 正确使用了
role="checkbox"tabIndex根据禁用状态正确设置(禁用时为 -1,启用时为 0)aria-checked正确处理了三种状态:选中、未选中和不确定('mixed')这是一个标准的无障碍复选框实现。
src/packages/swiper/demos/taro/demo10.tsx (1)
1-40: LGTM!这个演示很好地展示了 Swiper 组件的无障碍功能:
- 为每个
Swiper.Item正确设置了ariaLabel,使用数组中的对应标签onChange处理函数使用了正确的类型注解- 代码结构清晰,易于理解和维护
src/packages/navbar/navbar.taro.tsx (1)
110-110: navigation 角色添加正确为导航栏容器添加
ariaRole="navigation"符合无障碍最佳实践,有助于屏幕阅读器用户理解页面结构。src/packages/radio/radio.tsx (1)
133-142: 单选按钮无障碍属性添加正确为单选按钮添加的无障碍属性符合 ARIA 最佳实践:
role="radio"明确语义tabIndex={disabled ? -1 : 0}正确处理禁用状态下的键盘导航aria-checked和aria-disabled准确反映组件状态src/packages/tabs/tabs.tsx (2)
161-161: tablist 角色添加正确为 Tabs 容器添加
role="tablist"符合 ARIA 规范,有助于屏幕阅读器正确识别选项卡组件结构。
185-195: 装饰性元素正确标记为隐藏为 line 和 smile 指示器添加
aria-hidden属性是正确的做法,这些纯装饰性元素不应该暴露给辅助技术。src/types/spec/tabbar/base.ts (1)
13-13: Tabbar 类型定义符合最佳实践为
BaseTabbar和BaseTabbarItem添加的可选无障碍属性设计合理:
- 属性均为可选,支持渐进式采用
- 类型定义清晰,便于 TypeScript 类型检查
Also applies to: 26-28
src/packages/tabs/demos/taro/demo24.tsx (1)
1-27: 示例代码实现正确该 Taro 示例正确演示了受控 Tabs 组件的用法,包括禁用状态的处理。代码清晰易懂,适合作为文档示例。
src/packages/tabs/demos/h5/demo24.tsx (1)
1-27: H5 示例代码实现正确该 H5 示例正确演示了受控 Tabs 组件的用法。代码清晰易懂,适合作为文档示例。
注意:此 H5 示例与 Taro 版本(src/packages/tabs/demos/taro/demo24.tsx)略有不同,Taro 版本包含一个禁用的选项卡,而 H5 版本没有。如果这是有意为之以展示不同场景,则无需调整。
src/packages/navbar/demos/h5/demo4.tsx (1)
34-36: LGTM! 交互元素的 ARIA 标签使用得当为图标按钮添加
aria-label="Share"有助于屏幕阅读器用户理解控件功能。Also applies to: 43-45
src/packages/countdown/demo.tsx (1)
12-12: LGTM! Demo10 集成正确新的 ARIA 演示组件已正确导入和渲染,与现有演示结构保持一致。
Also applies to: 75-76
src/packages/button/button.taro.tsx (1)
157-165: LGTM! ARIA role 属性添加正确由于使用
View组件而非原生Button元素,添加ariaRole="button"对于辅助技术正确识别该组件至关重要。src/types/spec/inputnumber/base.ts (1)
23-23: LGTM! 类型定义符合无障碍功能需求
ariaLabel属性的添加与 PR 目标一致,为 InputNumber 组件提供无障碍支持。src/types/spec/dialog/base.ts (1)
47-47: LGTM! Dialog role 属性添加合理
role属性的添加为 Dialog 组件提供了 ARIA 角色支持。根据相关代码上下文,该属性会被传递为ariaRole到 Content 组件,符合无障碍功能架构设计。src/packages/navbar/demo.taro.tsx (1)
10-46: 代码变更看起来不错!Demo4 组件的导入和渲染符合现有的代码模式,翻译键也已正确添加到所有语言环境中。ARIA 基础用法示例的集成很清晰。
src/packages/dialog/content.tsx (1)
30-63: ARIA 属性添加正确!将
role和aria-modal属性添加到外层容器是正确的做法。aria-modal={visible}准确地反映了对话框的模态状态,这符合 ARIA 最佳实践。src/packages/dialog/dialog.tsx (1)
250-259: 对话框的 ARIA 实现符合标准!在 Content 组件上设置
role="dialog"以及在外层包装器上添加aria-modal={visible}都是正确的做法,完全符合 W3C ARIA 对话框模式的规范。src/packages/swiper/effects/focus.tsx (1)
82-83: 轮播组件的无障碍实现很出色!添加
tabIndex={0}使幻灯片可以通过键盘导航,而aria-hidden={!(index === getRefValue(current))}确保只有当前显示的幻灯片对屏幕阅读器可见。这完全符合 ARIA 轮播组件的最佳实践。src/packages/navbar/navbar.tsx (1)
109-109: 导航栏的语义化角色正确!为导航栏包装器添加
role="navigation"是完全正确的做法,这有助于屏幕阅读器用户理解这是一个导航区域。src/packages/inputnumber/demo.taro.tsx (1)
17-96: Demo 集成清晰规范!Demo11 的导入和渲染遵循了现有的模式,翻译键也已添加到所有语言环境中。ARIA 支持示例的集成很规范。
src/packages/countdown/demo.taro.tsx (1)
15-84: ARIA 演示集成完整!Demo10 的导入和使用与其他演示文件保持一致,翻译键已正确添加。倒计时组件的 ARIA 基础用法演示集成得很好。
src/types/spec/countdown/base.ts (1)
30-30: 代码无误!为倒计时组件添加可选的
ariaLabel属性是合理的,这将提高组件的可访问性支持。src/packages/navbar/demo.tsx (1)
7-7: 代码实现正确!新增的 Demo4 和 ARIA 相关翻译集成良好,遵循了现有的代码模式。翻译在三个语言环境中保持一致。
Also applies to: 15-15, 21-21, 27-27, 39-40
src/types/spec/dialog/taro.ts (1)
6-7: 代码实现正确!添加
ariaModal和ariaRole属性符合 Taro 框架的 ARIA 属性命名约定(使用驼峰式而非短横线分隔)。根据 AI 摘要,这些属性的默认值在组件实现中提供,因此非可选的类型定义是合理的。src/packages/inputnumber/demo.tsx (1)
13-13: 代码实现正确!Demo11 的集成和翻译添加遵循了现有模式。虽然英文翻译 "support ARIA" 使用了小写,但这与文件中其他一些标题的风格保持一致(如 "support formatter"),因此是可接受的。
Also applies to: 31-31, 47-47, 63-63, 90-91
src/packages/tabs/demo.tsx (1)
26-27: 代码实现正确!新增的 Demo24 和 Demo25 集成良好,遵循现有代码结构。翻译在两个语言环境中均已添加。注意英文翻译中 "support ARIA" 使用小写是为了与现有的 title8 "Custom Tab Bar" 样式保持一致。
Also applies to: 54-55, 80-81, 134-137
src/types/spec/image/base.ts (1)
26-26: 代码实现正确!为 Image 组件添加可选的
ariaLabel属性是合理的,这与 PR 中其他组件的无障碍功能增强保持一致。src/packages/image/image.tsx (1)
45-45: LGTM! ARIA 标签实现正确。为 Image 组件添加
ariaLabel属性并正确应用到 img 元素的aria-label上,可以为辅助技术提供更详细的图片描述信息。实现符合 ARIA 最佳实践。Also applies to: 198-198
src/packages/tabs/tabs.taro.tsx (2)
248-248: ARIA 角色设置正确。为标签页列表容器添加了
role="tablist"和ariaRole="tablist",正确标识了标签列表的语义角色。在 Taro 环境中同时设置这两个属性可以确保跨平台兼容性。
289-294: 标签项的 ARIA 属性实现良好。每个标签项正确设置了
ariaRole="tab"、ariaSelected和ariaDisabled属性,能够准确反映标签的激活状态和禁用状态,有助于屏幕阅读器正确理解标签页的交互状态。src/packages/noticebar/demo.taro.tsx (1)
19-19: LGTM! Demo12 集成正确。成功为 NoticeBar 组件添加了 ARIA 支持的示例,翻译准确,集成方式与其他演示保持一致。
Also applies to: 88-89
src/packages/inputnumber/demos/h5/demo11.tsx (1)
12-12: LGTM! ARIA 标签使用得当。为 InputNumber 组件添加了清晰的
ariaLabel属性,准确描述了输入框的用途("商品数量输入框" 和 "金额输入框"),有助于屏幕阅读器用户理解输入控件的功能。Also applies to: 24-24
src/packages/countdown/demos/h5/demo10.tsx (1)
5-7: 演示代码使用 useRef 存储静态倒计时结束时间,符合预期。对于演示目的,使用
useRef存储固定的结束时间是合理的,避免了不必要的重渲染。src/packages/swiper/demo.taro.tsx (1)
17-17: ARIA 演示集成正确。新增的 Demo10 组件和对应的翻译键已正确集成到演示页面中,代码结构清晰。
Also applies to: 31-31, 43-43, 84-85
src/packages/tabbar/demo.taro.tsx (1)
15-15: ARIA 演示集成正确。新增的 Demo10 组件和多语言翻译键已正确添加,支持简体中文、繁体中文和英文。
Also applies to: 29-29, 41-41, 53-53, 78-79
src/packages/countdown/demos/taro/demo10.tsx (1)
5-7: 演示代码使用 useRef 存储静态倒计时结束时间,符合预期。对于演示目的,使用
useRef存储固定的结束时间是合理的,避免了不必要的重渲染。src/packages/tabbar/tabbar.tsx (1)
19-19: 无障碍属性实现正确。为 Tabbar 组件添加的 ARIA 属性符合 Web 无障碍标准:
aria-label允许自定义标签role="tablist"正确标识标签栏的语义角色Also applies to: 37-37, 74-76
src/packages/image/image.taro.tsx (1)
38-38: ariaLabel 属性正确传递给底层图片组件。在 Taro 环境中正确地将无障碍标签传递给原生图片组件。
Also applies to: 133-133
src/packages/tabbar/demo.tsx (1)
12-12: ARIA 演示集成正确。新增的 Demo10 组件和多语言翻译键已正确添加到 H5 演示页面。
Also applies to: 26-26, 38-38, 50-50, 73-74
src/packages/tabs/demo.taro.tsx (1)
31-32: LGTM!新增的 ARIA 演示代码结构清晰,国际化翻译完整。
Also applies to: 60-61, 87-88, 149-152
src/packages/image/demos/taro/demo9.tsx (1)
1-13: LGTM!演示代码简洁明了,正确展示了 Image 组件的无障碍属性使用。
src/packages/inputnumber/demos/taro/demo11.tsx (1)
1-30: LGTM!演示代码展示了 InputNumber 组件的无障碍属性使用,ariaLabel 描述准确且语义清晰。同时正确演示了 ConfigProvider 的主题定制功能。
src/types/spec/swiper/taro.ts (1)
22-23: LGTM!ARIA 相关的类型定义正确,属性命名符合规范。
Also applies to: 29-29
src/packages/switch/switch.taro.tsx (1)
85-95: ARIA 属性实现正确!Switch 组件的无障碍属性实现符合 WAI-ARIA 规范:
ariaRole="switch"正确标识了开关角色ariaChecked和ariaDisabled准确反映了组件状态tabIndex根据禁用状态正确设置,确保键盘导航体验src/packages/noticebar/noticebar.tsx (1)
460-462: 装饰性图标的无障碍处理正确!为 leftIcon 添加
aria-hidden属性是正确的做法,这确保了装饰性图标不会被屏幕阅读器朗读,避免信息冗余。Also applies to: 499-501
src/packages/tabbar/tabbar.taro.tsx (1)
76-78: Tabbar ARIA 实现符合规范!实现符合 WAI-ARIA 的标签栏模式:
- 根容器的
ariaLabel用于标识整个标签栏- 包装器的
ariaRole="tablist"正确标识了标签列表的语义角色与 TabbarItem 中的
role="tab"和aria-selected配合使用,构成完整的无障碍标签栏。src/packages/noticebar/demos/h5/demo12.tsx (1)
1-44: Demo 示例清晰展示了无障碍功能!该演示文件很好地展示了 NoticeBar 组件的新增无障碍特性:
- 基础用法
- 带自定义图标和
rightIconAriaLabel的用法- 与其他组件(Button)的集成
示例代码结构清晰,有助于开发者理解如何使用这些新的 ARIA 属性。
src/packages/tabbaritem/tabbaritem.tsx (1)
122-126: TabbarItem 无障碍实现优秀!标签项的 ARIA 属性实现遵循了 WAI-ARIA 最佳实践:
role="tab"明确标识标签角色tabIndex实现了漫游式焦点管理(roving tabindex),仅活动标签可聚焦aria-selected准确反映选中状态aria-label和aria-roledescription提供了灵活的自定义标签能力与父组件 Tabbar 的
role="tablist"配合,构成完整的标签页无障碍结构。src/packages/noticebar/demos/taro/demo12.tsx (1)
1-63: Taro Demo 全面展示了组件能力!该 Taro 演示文件全面展示了 NoticeBar 在小程序环境中的无障碍功能:
- 水平和垂直两种滚动模式
rightIconAriaLabel在不同场景下的使用- 与事件处理器(
onItemClick)的配合- 自定义图标的 ARIA 标签应用
演示覆盖面广,有助于 Taro 开发者理解组件的完整功能。
src/packages/countdown/countdown.tsx (1)
66-69: ARIA alert 模式实现合理!倒计时结束时使用
role="alert"向屏幕阅读器发送通知是正确的无障碍实践。通过 state 管理 alert 内容并在 3 秒后清除,避免了持续性的干扰。定时器清理也已在
componentWillUnmount中妥善处理(Lines 286-288),防止内存泄漏。Also applies to: 107-112
src/packages/countdown/countdown.taro.tsx (3)
69-72: ARIA 状态管理实现正确引入的 role、alertContent 状态和 alertTimerRef 用于管理 ARIA 提示,实现思路清晰。
273-275: 定时器清理实现正确在组件卸载时正确清理了 alert 定时器,避免了内存泄漏问题。根据学习记录,这是 React/Taro 组件中使用定时器的最佳实践。
Based on learnings
346-352: ARIA 无障碍实现符合标准通过 ariaLabel 和隐藏的 alert 区域实现屏幕阅读器提示,这是标准的无障碍访问模式。隐藏元素使用
display: 'none'是合适的做法。src/packages/inputnumber/inputnumber.taro.tsx (2)
197-209: ARIA 属性正确绑定到减少按钮ariaRole、ariaLabel 和 ariaDisabled 属性正确反映了按钮的状态,增强了无障碍访问性。禁用状态的判断逻辑与图标样式保持一致。
236-242: ARIA 属性正确绑定到增加按钮与减少按钮类似,增加按钮的无障碍属性实现正确,禁用状态判断符合预期。
src/packages/dialog/dialog.taro.tsx (3)
48-48: 角色属性默认值合理role 属性的默认值 'dialog' 符合 ARIA 规范,正确标识了对话框的语义角色。
288-288: 角色属性正确传递到 Content 组件role 属性通过 ariaRole 正确传递给 Content 组件,保持了 ARIA 语义的连贯性。
301-301: ariaModal 绑定正确ariaModal 属性绑定到 visible 状态是正确的实现,符合 ARIA 模态对话框的规范要求。
| {...rest} | ||
| dangerouslySetInnerHTML={{ | ||
| __html: `${renderTime}`, | ||
| __html: `${renderTime}<span style="display:none" role=${role}>${alertContent}</span>`, | ||
| }} |
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.
存在 XSS 安全风险,建议重构
使用 dangerouslySetInnerHTML 注入包含用户提供的 ariaLabel 的内容存在跨站脚本攻击(XSS)风险。虽然 ariaLabel 有默认值,但如果开发者传入了包含恶意脚本的字符串,这些脚本会被执行。
建议采用更安全的实现方式:
方案一:使用独立的 React 元素(推荐)
-<div
- className={`${classPrefix} ${className}`}
- style={{ ...style }}
- aria-label={ariaLabel}
- {...rest}
- dangerouslySetInnerHTML={{
- __html: `${renderTime}<span style="display:none" role=${role}>${alertContent}</span>`,
- }}
-/>
+<div
+ className={`${classPrefix} ${className}`}
+ style={{ ...style }}
+ aria-label={ariaLabel}
+ {...rest}
+>
+ <span dangerouslySetInnerHTML={{ __html: renderTime }} />
+ <span style={{ display: 'none' }} role={role}>
+ {alertContent}
+ </span>
+</div>方案二:如果必须使用 HTML,进行内容转义
使用 DOMPurify 或类似库对 ariaLabel 进行清理:
import DOMPurify from 'dompurify'
const sanitizedLabel = DOMPurify.sanitize(ariaLabel)基于静态分析工具的提示。
🧰 Tools
🪛 ast-grep (0.39.9)
[warning] 302-302: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
🤖 Prompt for AI Agents
In src/packages/countdown/countdown.tsx around lines 302 to 305, the code uses
dangerouslySetInnerHTML to inject renderTime plus an ariaLabel-derived string,
which creates an XSS risk; replace this with safe React elements: render the
rendered time as normal children and add a separate span element (visually
hidden if needed) with the appropriate role and the ariaLabel/alertContent as
plain text child so React escapes it automatically; if you absolutely must
inject HTML instead, sanitize ariaLabel first with a library like DOMPurify and
use the sanitized result in dangerouslySetInnerHTML.
| return list.map((item) => ( | ||
| <div | ||
| onClick={() => setTabvalue(item.paneKey)} | ||
| className={`nut-tabs-titles-item ${tabvalue === item.paneKey ? 'nut-tabs-titles-item-active' : ''}`} | ||
| key={item.paneKey} | ||
| role="tab" | ||
| tabIndex={0} | ||
| aria-selected={tabvalue === item.paneKey} | ||
| > | ||
| {item.icon || null} | ||
| <span className="nut-tabs-titles-item-text">{item.title}</span> | ||
| <span className="nut-tabs-titles-item-line" /> | ||
| </div> | ||
| )) | ||
| }} |
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.
添加 role="tablist" 容器以完善 ARIA 结构
自定义标题渲染器应该包含一个带有 role="tablist" 的外层容器,以符合 ARIA 规范。参考主组件实现(tabs.tsx 第142行)。
建议修改:
title={() => {
- return list.map((item) => (
+ return (
+ <div role="tablist">
+ {list.map((item) => (
+ <div
+ onClick={() => setTabvalue(item.paneKey)}
+ className={`nut-tabs-titles-item ${tabvalue === item.paneKey ? 'nut-tabs-titles-item-active' : ''}`}
+ key={item.paneKey}
+ role="tab"
+ tabIndex={0}
+ aria-selected={tabvalue === item.paneKey}
+ >
+ {item.icon}
+ <span className="nut-tabs-titles-item-text">{item.title}</span>
+ <span className="nut-tabs-titles-item-line" />
+ </div>
+ ))}
+ </div>
+ )
- <div
- onClick={() => setTabvalue(item.paneKey)}
- className={`nut-tabs-titles-item ${tabvalue === item.paneKey ? 'nut-tabs-titles-item-active' : ''}`}
- key={item.paneKey}
- role="tab"
- tabIndex={0}
- aria-selected={tabvalue === item.paneKey}
- >
- {item.icon || null}
- <span className="nut-tabs-titles-item-text">{item.title}</span>
- <span className="nut-tabs-titles-item-line" />
- </div>
- ))
}}📝 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.
| return list.map((item) => ( | |
| <div | |
| onClick={() => setTabvalue(item.paneKey)} | |
| className={`nut-tabs-titles-item ${tabvalue === item.paneKey ? 'nut-tabs-titles-item-active' : ''}`} | |
| key={item.paneKey} | |
| role="tab" | |
| tabIndex={0} | |
| aria-selected={tabvalue === item.paneKey} | |
| > | |
| {item.icon || null} | |
| <span className="nut-tabs-titles-item-text">{item.title}</span> | |
| <span className="nut-tabs-titles-item-line" /> | |
| </div> | |
| )) | |
| }} | |
| return ( | |
| <div role="tablist"> | |
| {list.map((item) => ( | |
| <div | |
| onClick={() => setTabvalue(item.paneKey)} | |
| className={`nut-tabs-titles-item ${tabvalue === item.paneKey ? 'nut-tabs-titles-item-active' : ''}`} | |
| key={item.paneKey} | |
| role="tab" | |
| tabIndex={0} | |
| aria-selected={tabvalue === item.paneKey} | |
| > | |
| {item.icon} | |
| <span className="nut-tabs-titles-item-text">{item.title}</span> | |
| <span className="nut-tabs-titles-item-line" /> | |
| </div> | |
| ))} | |
| </div> | |
| ) |
🤖 Prompt for AI Agents
In src/packages/tabs/demos/h5/demo25.tsx around lines 27 to 41, the custom title
renderer returns tab elements without the required ARIA container; wrap the
mapped items in an outer element that has role="tablist" (and optionally
aria-orientation if relevant) so the tabs form a proper ARIA tablist; implement
by returning a single container element with role="tablist" that contains the
existing map output (keep existing per-tab role="tab" attributes unchanged).
| return list.map((item) => ( | ||
| <View | ||
| onClick={() => setTabvalue(item.paneKey)} | ||
| className={`nut-tabs-titles-item ${tabvalue === item.paneKey ? 'nut-tabs-titles-item-active' : ''}`} | ||
| key={item.paneKey} | ||
| ariaRole="tab" | ||
| tabIndex={0} | ||
| ariaSelected={tabvalue === item.paneKey} | ||
| > | ||
| {item.icon} | ||
| <Text className="nut-tabs-titles-item-text">{item.title}</Text> | ||
| <Text className="nut-tabs-titles-item-line" /> | ||
| </View> | ||
| )) |
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.
验证自定义标题渲染器的完整ARIA结构
当使用自定义 title 渲染器时,确保在外层包装一个带有 role="tablist" 的容器。参考主组件实现(tabs.taro.tsx 第265行),标准的标签列表应该包裹在 <View className="nut-tabs-list" role="tablist" ariaRole="tablist"> 中。
建议修改:
title={() => {
- return list.map((item) => (
+ return (
+ <View role="tablist" ariaRole="tablist">
+ {list.map((item) => (
+ <View
+ onClick={() => setTabvalue(item.paneKey)}
+ className={`nut-tabs-titles-item ${tabvalue === item.paneKey ? 'nut-tabs-titles-item-active' : ''}`}
+ key={item.paneKey}
+ ariaRole="tab"
+ tabIndex={0}
+ ariaSelected={tabvalue === item.paneKey}
+ >
+ {item.icon}
+ <Text className="nut-tabs-titles-item-text">{item.title}</Text>
+ <Text className="nut-tabs-titles-item-line" />
+ </View>
+ ))}
+ </View>
+ )
- <View
- onClick={() => setTabvalue(item.paneKey)}
- className={`nut-tabs-titles-item ${tabvalue === item.paneKey ? 'nut-tabs-titles-item-active' : ''}`}
- key={item.paneKey}
- ariaRole="tab"
- tabIndex={0}
- ariaSelected={tabvalue === item.paneKey}
- >
- {item.icon}
- <Text className="nut-tabs-titles-item-text">{item.title}</Text>
- <Text className="nut-tabs-titles-item-line" />
- </View>
- ))
}}📝 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.
| return list.map((item) => ( | |
| <View | |
| onClick={() => setTabvalue(item.paneKey)} | |
| className={`nut-tabs-titles-item ${tabvalue === item.paneKey ? 'nut-tabs-titles-item-active' : ''}`} | |
| key={item.paneKey} | |
| ariaRole="tab" | |
| tabIndex={0} | |
| ariaSelected={tabvalue === item.paneKey} | |
| > | |
| {item.icon} | |
| <Text className="nut-tabs-titles-item-text">{item.title}</Text> | |
| <Text className="nut-tabs-titles-item-line" /> | |
| </View> | |
| )) | |
| return ( | |
| <View role="tablist" ariaRole="tablist"> | |
| {list.map((item) => ( | |
| <View | |
| onClick={() => setTabvalue(item.paneKey)} | |
| className={`nut-tabs-titles-item ${tabvalue === item.paneKey ? 'nut-tabs-titles-item-active' : ''}`} | |
| key={item.paneKey} | |
| ariaRole="tab" | |
| tabIndex={0} | |
| ariaSelected={tabvalue === item.paneKey} | |
| > | |
| {item.icon} | |
| <Text className="nut-tabs-titles-item-text">{item.title}</Text> | |
| <Text className="nut-tabs-titles-item-line" /> | |
| </View> | |
| ))} | |
| </View> | |
| ) |
🤖 Prompt for AI Agents
In src/packages/tabs/demos/taro/demo25.tsx around lines 28 to 41, the custom
title renderer returns individual tab items but does not ensure they are wrapped
by a container with role="tablist"; update the demo so the rendered tab items
are enclosed in a parent View with className="nut-tabs-list" and
role/ariaRole="tablist" (matching the main component at tabs.taro.tsx line
~265), keeping each child View as role="tab" and preserving existing
ariaSelected/tabIndex attributes.
- Dialog
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.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/packages/button/button.taro.tsx (1)
158-167: 添加键盘事件处理和焦点管理以满足可访问性要求组件使用
View替代语义化button元素后,虽然添加了ariaRole="button",但这仅改变屏幕阅读器的公告,不会自动添加键盘行为。当前实现缺少键盘交互支持:
- 缺少
tabIndex属性,使元素无法通过键盘获得焦点- 缺少
onKeyDown事件处理,无法通过 Enter 和 Space 键触发点击修复方案 - 在
src/packages/button/button.taro.tsx第 158-167 行的View组件中添加:
tabIndex={0}- 使元素可获得焦点onKeyDown处理器 - 监听 Enter 和 Space 键,调用handleClick,并对 Space 键调用preventDefault()防止页面滚动参考实现:对于 Enter 或 Space 键,验证
e.key === 'Enter'或e.key === ' ',调用handleClick()前对 Space 键调用preventDefault()。
🧹 Nitpick comments (7)
src/packages/switch/demos/taro/demo9.tsx (1)
4-10: 建议使用更具描述性的无障碍标签Demo 正确地展示了
ariaLabel属性的使用。不过,当前的标签 "开关按钮" 仅描述了组件类型,而无障碍标签的最佳实践是描述开关所控制的具体功能。建议使用更具体的功能描述,例如 "启用通知" 或 "深色模式切换",这样能更好地向使用辅助技术的用户传达开关的用途。示例修改:
- <Switch defaultChecked ariaLabel="开关按钮" /> + <Switch defaultChecked ariaLabel="启用通知" />或使用其他具体的功能描述。
src/types/spec/dialog/h5.ts (1)
5-6: 建议将 ARIA 属性设为可选。这两个属性目前被定义为必需属性,但在实现中通过
defaultProps提供了默认值。建议将它们改为可选属性以提供更好的开发体验,并与实际使用方式保持一致。应用此修改:
export interface WebContentProps extends BaseContentProps { onClick: (event: MouseEvent) => void - ariaRole: string - ariaLabel: string + ariaRole?: string + ariaLabel?: string }src/packages/overlay/overlay.tsx (1)
83-84: ARIA 属性逻辑正确,建议增强键盘无障碍支持。条件化的
aria-label和aria-hidden实现合理:
- 当蒙层可交互(
closeOnOverlayClick=true)时,暴露给辅助技术- 当蒙层不可交互时,对辅助技术隐藏
建议考虑以下增强:
- 为可交互的蒙层添加
role="button"属性,明确其交互性质- 添加键盘支持:当
closeOnOverlayClick=true时,设置tabIndex={0}并处理onKeyDown事件(Enter/Space 键)以支持键盘关闭示例实现:
+ const handleKeyDown = (e: React.KeyboardEvent) => { + if (closeOnOverlayClick && (e.key === 'Enter' || e.key === ' ')) { + e.preventDefault() + onClick && onClick(e as any) + } + } + <div ref={nodeRef} className={classes} style={styles} {...rest} onClick={handleClick} + onKeyDown={handleKeyDown} + role={closeOnOverlayClick ? 'button' : undefined} + tabIndex={closeOnOverlayClick ? 0 : undefined} aria-label={closeOnOverlayClick ? ariaLabel : ''} aria-hidden={!closeOnOverlayClick} >请验证此模式是否与 PR 中其他组件的无障碍实现保持一致。
src/packages/tabs/demos/h5/demo25.tsx (1)
36-36: 可选优化:简化图标渲染逻辑
item.icon || null中的|| null是多余的,因为如果item.icon为假值,React 会自动忽略它。可以简化为:
- {item.icon || null} + {item.icon}src/packages/button/button.taro.tsx (2)
86-86: 可选:内联 role 常量
role常量仅使用一次,可以直接在第 165 行内联为ariaRole="button"以简化代码。- const role = 'button' const getStyle = useMemo(() => {并在第 165 行:
- ariaRole={role} + ariaRole="button"
165-166: 良好的无障碍支持,建议补充 ARIA 属性接口添加
ariaRole和ariaDisabled属性很好地提升了组件的可访问性。不过,建议在
ButtonProps接口中暴露额外的 ARIA 相关属性(如ariaLabel、ariaLabelledBy、ariaDescribedBy等),以便使用者能够为特定场景提供自定义的无障碍标签。例如,仅包含图标的按钮需要ariaLabel来为屏幕阅读器提供文本描述。可在
ButtonProps接口中添加:export interface ButtonProps extends BasicComponent { color: string shape: ButtonShape type: ButtonType size: ButtonSize fill: ButtonFill block: boolean loading: boolean disabled: boolean icon: React.ReactNode rightIcon: React.ReactNode nativeType: 'submit' | 'reset' // | 'button' onClick: (e: MouseEvent<HTMLButtonElement>) => void ariaLabel?: string ariaLabelledBy?: string ariaDescribedBy?: string }然后在组件中透传这些属性到 View:
(props, ref) => { const { color, shape, fill, loading, disabled, type, size, block, icon, rightIcon, children, className, style, // formType, nativeType, onClick, + ariaLabel, + ariaLabelledBy, + ariaDescribedBy, ...rest } = { ...defaultProps, ...props }<View {...rest} ref={ref} // formType={formType || nativeType} className={buttonClassNames} style={{ ...getStyle, ...style }} onClick={(e) => handleClick(e as any)} ariaRole={role} ariaDisabled={disabled} + ariaLabel={ariaLabel} + ariaLabelledBy={ariaLabelledBy} + ariaDescribedBy={ariaDescribedBy} >src/packages/countdown/countdown.taro.tsx (1)
113-117: 重复触发结束前需先清掉旧定时器当前在
setAlertContent之后直接覆盖alertTimerRef.current,没有先clearTimeout。如果倒计时在 3 秒内被重新拉起并再次结束,第一次留下的 handle 仍会按原定时清空,导致第二次的提示被过早清除,甚至和新的提示互相抢占。建议在设置新定时器前先判空并调用clearTimeout,再写入最新 handle,确保提示生存周期稳定。- setAlertContent(`${ariaLabel}倒计时结束`) - alertTimerRef.current = setTimeout(() => { + setAlertContent(`${ariaLabel}倒计时结束`) + if (alertTimerRef.current) { + clearTimeout(alertTimerRef.current) + } + alertTimerRef.current = setTimeout(() => { setRole('') setAlertContent('') }, 3000)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (28)
src/packages/button/button.taro.tsx(2 hunks)src/packages/checkbox/checkbox.taro.tsx(1 hunks)src/packages/countdown/countdown.taro.tsx(6 hunks)src/packages/countdown/demo.taro.tsx(5 hunks)src/packages/countdown/demo.tsx(5 hunks)src/packages/dialog/content.taro.tsx(2 hunks)src/packages/dialog/content.tsx(2 hunks)src/packages/dialog/dialog.taro.tsx(5 hunks)src/packages/dialog/dialog.tsx(4 hunks)src/packages/image/demo.taro.tsx(4 hunks)src/packages/image/demo.tsx(4 hunks)src/packages/image/demos/h5/demo9.tsx(1 hunks)src/packages/inputnumber/inputnumber.taro.tsx(6 hunks)src/packages/overlay/overlay.taro.tsx(3 hunks)src/packages/overlay/overlay.tsx(3 hunks)src/packages/progress/progress.taro.tsx(1 hunks)src/packages/progress/progress.tsx(1 hunks)src/packages/switch/demo.taro.tsx(5 hunks)src/packages/switch/demo.tsx(5 hunks)src/packages/switch/demos/h5/demo9.tsx(1 hunks)src/packages/switch/demos/taro/demo9.tsx(1 hunks)src/packages/tabbaritem/tabbaritem.taro.tsx(2 hunks)src/packages/tabs/demos/h5/demo25.tsx(1 hunks)src/packages/tabs/tabs.tsx(3 hunks)src/types/spec/dialog/base.ts(1 hunks)src/types/spec/dialog/h5.ts(1 hunks)src/types/spec/dialog/taro.ts(1 hunks)src/types/spec/overlay/base.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (15)
- src/packages/image/demo.tsx
- src/packages/image/demos/h5/demo9.tsx
- src/packages/switch/demo.tsx
- src/packages/image/demo.taro.tsx
- src/packages/inputnumber/inputnumber.taro.tsx
- src/packages/switch/demos/h5/demo9.tsx
- src/packages/progress/progress.tsx
- src/packages/progress/progress.taro.tsx
- src/packages/switch/demo.taro.tsx
- src/types/spec/dialog/taro.ts
- src/types/spec/dialog/base.ts
- src/packages/dialog/content.taro.tsx
- src/packages/countdown/demo.tsx
- src/packages/checkbox/checkbox.taro.tsx
- src/packages/dialog/content.tsx
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2024-11-06T02:16:07.069Z
Learnt from: oasis-cloud
Repo: jdf2e/nutui-react PR: 2540
File: src/packages/trendarrow/trendarrow.taro.tsx:29-30
Timestamp: 2024-11-06T02:16:07.069Z
Learning: 在`TrendArrow`组件(`src/packages/trendarrow/trendarrow.taro.tsx`)中,CSS变量在鸿蒙系统(Harmony OS)中可能未被完全支持,导致兼容性问题。因此,使用硬编码的十六进制颜色值可以确保在鸿蒙系统上的正确显示。
Applied to files:
src/packages/switch/demos/taro/demo9.tsx
📚 Learning: 2025-05-02T01:45:09.576Z
Learnt from: irisSong
Repo: jdf2e/nutui-react PR: 3209
File: src/packages/searchbar/searchbar.taro.tsx:111-124
Timestamp: 2025-05-02T01:45:09.576Z
Learning: 在 React/Taro 组件中使用 setTimeout 或 setInterval 时,应当在组件卸载时通过 useEffect 的清理函数清除定时器,以防止内存泄漏。可以使用 useState 存储定时器 ID,并在 useEffect 的返回函数中清除。
Applied to files:
src/packages/countdown/countdown.taro.tsx
📚 Learning: 2025-02-25T02:21:32.906Z
Learnt from: xiaoyatong
Repo: jdf2e/nutui-react PR: 2990
File: src/packages/pickerview/__test__/pickerview.spec.tsx:0-0
Timestamp: 2025-02-25T02:21:32.906Z
Learning: In React component tests, avoid using setTimeout with fixed delays. Instead, use act() to wrap async state updates and waitFor() for assertions, which makes tests more stable and reliable.
Applied to files:
src/packages/countdown/countdown.taro.tsx
🧬 Code graph analysis (4)
src/packages/tabs/demos/h5/demo25.tsx (2)
src/packages/tabs/tabs.tsx (1)
Tabs(24-238)src/packages/tabs/tabs.taro.tsx (1)
Tabs(27-319)
src/packages/switch/demos/taro/demo9.tsx (1)
src/packages/switch/switch.taro.tsx (1)
Switch(19-135)
src/packages/dialog/dialog.tsx (1)
src/utils/merge-props.ts (1)
mergeProps(4-16)
src/packages/dialog/dialog.taro.tsx (2)
src/hooks/taro/use-custom-event.ts (1)
useParams(32-52)src/utils/merge-props.ts (1)
mergeProps(4-16)
🔇 Additional comments (14)
src/packages/switch/demos/taro/demo9.tsx (1)
1-2: 导入语句正确React 和组件的导入都是正确且必要的。
src/packages/tabbaritem/tabbaritem.taro.tsx (2)
39-40: 正确提取了无障碍属性现在已从 props 中正确解构
ariaLabel和ariaRoledescription,解决了之前评审中指出的缺失问题。
134-138: 无障碍属性实现正确所有之前评审中指出的问题均已修复:
- ✅ 第 136 行:
tabIndex使用了正确的驼峰式命名(之前是小写的tabindex)- ✅ 第 137-138 行:
ariaLabel和ariaRoledescription现在已正确传递给 View 组件- ✅
tabIndex={active ? 0 : -1}实现了正确的 roving tabindex 模式,符合 ARIA 无障碍规范中 tab 组件的键盘导航最佳实践- ✅
ariaRole="tab"和ariaSelected={active}的组合使用正确src/packages/dialog/dialog.taro.tsx (2)
48-48: LGTM!为 Dialog 添加默认的
ariaLabel有助于辅助技术识别组件。
319-319: 无需修改 - Overlay 组件已支持 ariaLabel 属性验证结果确认,Overlay 组件的类型定义
BaseOverlay中已明确包含ariaLabel?: string属性(位置:src/types/spec/overlay/base.ts第 13 行)。TaroOverlayProps继承了该属性,因此 dialog.taro.tsx 第 319 行传递的ariaLabel="背景蒙层"完全符合 Overlay 组件的 Props 定义,无需修改。src/packages/dialog/dialog.tsx (2)
46-46: LGTM!为 Dialog 添加默认的
ariaLabel有助于辅助技术识别组件。
259-260: LGTM!正确地将 ARIA 属性传递给 Content 组件,使对话框容器具有适当的无障碍语义。
src/packages/tabs/tabs.tsx (2)
161-161: LGTM!正确添加了
role="tablist"到外层容器,符合 ARIA 选项卡组件的标准模式。
185-194: LGTM!正确地为装饰性指示器元素添加了
aria-hidden属性,使屏幕阅读器可以忽略这些纯视觉元素。src/types/spec/overlay/base.ts (1)
13-13: LGTM!类型定义清晰。为 BaseOverlay 接口添加可选的
ariaLabel属性,支持无障碍功能,类型定义合理。src/packages/overlay/overlay.tsx (1)
24-24: 默认值和属性传递正确。
ariaLabel的默认值和属性解构处理得当,为蒙层组件提供了合理的无障碍标签。Also applies to: 41-41
src/packages/overlay/overlay.taro.tsx (2)
18-18: 实现与 Web 版本保持一致。Taro 版本的
ariaLabel默认值和属性处理与 Web 版本一致,保证了跨平台的无障碍体验一致性。Also applies to: 36-36
68-69: 无障碍属性实现正确,已验证 Taro 平台支持。代码在第 68-69 行使用的
ariaLabel和ariaHidden属性已符合 Taro 平台规范。Taro View 组件支持 ARIA 属性,包括 role、aria-label、aria-hidden 等标准 aria-* 属性。验证显示,项目中已在多个组件(navbar、tabs、tabpane、tabbar 等)成功使用ariaRole、ariaLabel等属性,确认实现方式与现有代码一致。无需调整,代码已遵循 Taro 无障碍最佳实践。
src/packages/tabs/demos/h5/demo25.tsx (1)
27-40: ARIA 结构验证确认无误,当前代码实现正确验证结果表明:
- 父组件
tabs.tsx第 161 行确实设置了role="tablist"(非第 142 行,但结构相同)- 自定义标题渲染器返回的元素被渲染在该容器内部
- 每个标签项都具有正确的
role="tab"、tabIndex={-1}和aria-selected属性- 当前结构符合 ARIA 规范,不应添加嵌套的
role="tablist"代码实现已满足无障碍访问要求,无需修改。
| <View role={role} style={{ display: 'none' }}> | ||
| {alertContent} | ||
| </View> |
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.
移除 display:none 确保 alert 可被读屏器触达
这里给 role="alert" 的节点加了 display: 'none',读屏器会直接把它从可访问树里剔除,导致倒计时结束的提示永远不会被播报,整个无障碍增强形同虚设。建议改成“视觉隐藏但仍留在可访问树”的写法。可以先套一个不可见样式(例如绝对定位到屏幕外或使用 clip),避免影响布局同时保留对辅助技术的可见性。
- <View role={role} style={{ display: 'none' }}>
- {alertContent}
- </View>
+ <View
+ role={role}
+ style={{
+ position: 'absolute',
+ width: 1,
+ height: 1,
+ padding: 0,
+ margin: -1,
+ overflow: 'hidden',
+ clip: 'rect(0 0 0 0)',
+ whiteSpace: 'nowrap',
+ border: 0,
+ }}
+ >
+ {alertContent}
+ </View>📝 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.
| <View role={role} style={{ display: 'none' }}> | |
| {alertContent} | |
| </View> | |
| <View | |
| role={role} | |
| style={{ | |
| position: 'absolute', | |
| width: 1, | |
| height: 1, | |
| padding: 0, | |
| margin: -1, | |
| overflow: 'hidden', | |
| clip: 'rect(0 0 0 0)', | |
| whiteSpace: 'nowrap', | |
| border: 0, | |
| }} | |
| > | |
| {alertContent} | |
| </View> |
🤖 Prompt for AI Agents
In src/packages/countdown/countdown.taro.tsx around lines 350–352, the element
with role="alert" is using display:'none', which removes it from the
accessibility tree; remove that display:none and instead apply a "visually
hidden" approach (e.g., absolute positioning off-screen or clip-based style that
preserves accessibility) by giving the View a visually-hidden className or style
object so it is visually hidden but remains reachable by screen readers, keeping
role="alert" and the alert content intact.
| <View | ||
| className={closeClasses} | ||
| onClick={handleClose} | ||
| ariaRole="button" | ||
| ariaLabel="close" | ||
| tabindex={0} | ||
| > |
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.
关闭按钮缺少键盘事件支持。
关闭按钮虽然设置了 tabindex={0} 使其可获得键盘焦点,但仅有 onClick 处理器。为了完整的键盘无障碍支持,需要添加键盘事件处理器来响应 Enter 和 Space 键。
建议添加键盘事件处理:
+ const handleKeyDown = (e: any) => {
+ if (e.key === 'Enter' || e.key === ' ') {
+ e.preventDefault()
+ handleClose()
+ }
+ }
+
return (
<View
className={closeClasses}
onClick={handleClose}
+ onKeyDown={handleKeyDown}
ariaRole="button"
ariaLabel="close"
tabindex={0}
>
{React.isValidElement(closeIcon) ? closeIcon : systomIcon}
</View>
)Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/packages/dialog/dialog.taro.tsx around lines 253 to 259, the close button
has tabindex and onClick but lacks keyboard event handling; add an onKeyDown
handler that listens for Enter (key === 'Enter') and Space (key === ' ' or key
=== 'Spacebar' for older browsers) and calls handleClose when either is pressed,
calling event.preventDefault() for Space to avoid scrolling; attach this handler
to the same element (keeping ariaRole="button" and tabindex) so keyboard users
can activate the close action.
| <div | ||
| className={closeClasses} | ||
| onClick={handleClose} | ||
| role="button" | ||
| tabIndex={0} | ||
| aria-label="close" | ||
| > |
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.
关闭按钮缺少键盘事件支持。
关闭按钮虽然设置了 tabIndex={0} 使其可获得键盘焦点,但仅有 onClick 处理器。为了完整的键盘无障碍支持,需要添加键盘事件处理器来响应 Enter 和 Space 键。
建议添加键盘事件处理:
+ const handleKeyDown = (e: React.KeyboardEvent) => {
+ if (e.key === 'Enter' || e.key === ' ') {
+ e.preventDefault()
+ handleClose()
+ }
+ }
+
return (
<div
className={closeClasses}
onClick={handleClose}
+ onKeyDown={handleKeyDown}
role="button"
tabIndex={0}
aria-label="close"
>
{React.isValidElement(closeIcon) ? closeIcon : systomIcon}
</div>
)Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/packages/dialog/dialog.tsx around lines 222 to 228, the close control has
tabIndex and role but only an onClick handler; add a keyboard handler (e.g.,
onKeyDown) that listens for Enter and Space keys and invokes handleClose so
keyboard users can activate the button, call event.preventDefault() for Space to
avoid page scroll, and keep role="button" and tabIndex={0} intact.
| className={`nut-tabs-titles-item ${tabvalue === item.paneKey ? 'nut-tabs-titles-item-active' : ''}`} | ||
| key={item.paneKey} | ||
| role="tab" | ||
| tabIndex={-1} |
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.
严重问题:所有标签页的 tabIndex 均为 -1,导致无法通过键盘访问
当前所有标签页的 tabIndex 都设置为 -1,这意味着没有任何标签页可以通过键盘获得焦点,严重影响键盘导航的可访问性。参考主组件实现(tabs.tsx 第179-184行),激活的标签页应该设置 tabIndex={0},其他标签页设置为 -tabIndex={-1}。
应用以下修改:
role="tab"
- tabIndex={-1}
+ tabIndex={tabvalue === item.paneKey ? 0 : -1}
aria-selected={tabvalue === item.paneKey}📝 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.
| tabIndex={-1} | |
| title={() => { | |
| return list.map((item) => ( | |
| <div | |
| onClick={() => setTabvalue(item.paneKey)} | |
| className={`nut-tabs-titles-item ${tabvalue === item.paneKey ? 'nut-tabs-titles-item-active' : ''}`} | |
| key={item.paneKey} | |
| role="tab" | |
| tabIndex={tabvalue === item.paneKey ? 0 : -1} | |
| aria-selected={tabvalue === item.paneKey} | |
| > | |
| {item.icon} | |
| <span className="nut-tabs-titles-item-text">{item.title}</span> | |
| <span className="nut-tabs-titles-item-line" /> | |
| </div> | |
| )) | |
| }} |
🤖 Prompt for AI Agents
In src/packages/tabs/demos/h5/demo25.tsx around line 33, all tab elements are
currently rendered with tabIndex={-1} which prevents keyboard focus; update the
tabIndex logic to match the main tabs implementation (tabs.tsx lines ~179-184)
so the active/selected tab gets tabIndex={0} and non-active tabs get
tabIndex={-1}; use the component's active/selected state or prop to
conditionally set tabIndex (e.g., tabIndex={isActive ? 0 : -1}) so keyboard
users can focus and navigate tabs.
| role="tab" | ||
| aria-selected={ | ||
| !item.disabled && String(item.value) === String(value) | ||
| } | ||
| tabIndex={ | ||
| !item.disabled && String(item.value) === String(value) | ||
| ? 0 | ||
| : -1 | ||
| } | ||
| aria-disabled={item.disabled} |
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.
ARIA 属性应放置在可交互元素上
当前将 role="tab"、aria-selected、tabIndex 和 aria-disabled 放在了内层的文本 div 上(line 196),但实际的点击处理器 onClick 在外层 div 上(line 167)。这会导致以下问题:
- 屏幕阅读器将焦点定位到内层文本元素,但该元素本身不可交互
- 键盘用户通过 Tab 键聚焦到文本元素后,无法直接通过 Enter/Space 激活选项卡(因为 onClick 在父元素上)
- 违反了 ARIA 最佳实践,即交互角色应放在可交互元素上
根据 ARIA Authoring Practices Guide,role="tab" 和相关属性应该放在响应点击事件的元素上。
应用此 diff 将 ARIA 属性移至外层可交互元素:
<div
key={item.value}
ref={(ref: HTMLDivElement) => titleItemsRef.current.push(ref)}
onClick={() => tabChange(item)}
className={classNames(`${classPrefix}-titles-item`, {
[`nut-tabs-titles-item-active`]:
!item.disabled && String(item.value) === String(value),
[`nut-tabs-titles-item-disabled`]: item.disabled,
[`nut-tabs-titles-item-${align}`]: align,
})}
+ role="tab"
+ aria-selected={
+ !item.disabled && String(item.value) === String(value)
+ }
+ tabIndex={
+ !item.disabled && String(item.value) === String(value)
+ ? 0
+ : -1
+ }
+ aria-disabled={item.disabled}
>
{activeType === 'line' && (
<div
className={classNames(
`${classPrefix}-titles-item-line`,
`${classPrefix}-titles-item-line-${direction}`
)}
style={{ background: activeColor }}
aria-hidden
/>
)}
{activeType === 'smile' && (
<div
className={`${classPrefix}-titles-item-smile`}
aria-hidden
>
<JoySmile color={activeColor} width={40} height={20} />
</div>
)}
<div
className={classNames(
{
[`${classPrefix}-ellipsis`]: direction === 'vertical',
},
`${classPrefix}-titles-item-text`
)}
style={{ color: activeColor }}
- role="tab"
- aria-selected={
- !item.disabled && String(item.value) === String(value)
- }
- tabIndex={
- !item.disabled && String(item.value) === String(value)
- ? 0
- : -1
- }
- aria-disabled={item.disabled}
>
{item.title}
</div>
</div>📝 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.
| role="tab" | |
| aria-selected={ | |
| !item.disabled && String(item.value) === String(value) | |
| } | |
| tabIndex={ | |
| !item.disabled && String(item.value) === String(value) | |
| ? 0 | |
| : -1 | |
| } | |
| aria-disabled={item.disabled} | |
| <div | |
| key={item.value} | |
| ref={(ref: HTMLDivElement) => titleItemsRef.current.push(ref)} | |
| onClick={() => tabChange(item)} | |
| className={classNames(`${classPrefix}-titles-item`, { | |
| [`nut-tabs-titles-item-active`]: | |
| !item.disabled && String(item.value) === String(value), | |
| [`nut-tabs-titles-item-disabled`]: item.disabled, | |
| [`nut-tabs-titles-item-${align}`]: align, | |
| })} | |
| role="tab" | |
| aria-selected={ | |
| !item.disabled && String(item.value) === String(value) | |
| } | |
| tabIndex={ | |
| !item.disabled && String(item.value) === String(value) | |
| ? 0 | |
| : -1 | |
| } | |
| aria-disabled={item.disabled} | |
| > | |
| {activeType === 'line' && ( | |
| <div | |
| className={classNames( | |
| `${classPrefix}-titles-item-line`, | |
| `${classPrefix}-titles-item-line-${direction}` | |
| )} | |
| style={{ background: activeColor }} | |
| aria-hidden | |
| /> | |
| )} | |
| {activeType === 'smile' && ( | |
| <div | |
| className={`${classPrefix}-titles-item-smile`} | |
| aria-hidden | |
| > | |
| <JoySmile color={activeColor} width={40} height={20} /> | |
| </div> | |
| )} | |
| <div | |
| className={classNames( | |
| { | |
| [`${classPrefix}-ellipsis`]: direction === 'vertical', | |
| }, | |
| `${classPrefix}-titles-item-text` | |
| )} | |
| style={{ color: activeColor }} | |
| > | |
| {item.title} | |
| </div> | |
| </div> |
🤖 Prompt for AI Agents
In src/packages/tabs/tabs.tsx around lines 167 to 213, the ARIA attributes
(role="tab", aria-selected, tabIndex, aria-disabled) are currently on the inner
text div (around line 196) while the click handler lives on the outer div
(around line 167); move those ARIA attributes to the outer interactive div so
the element that receives clicks and keyboard events is the same element exposed
to assistive tech. Concretely: remove role, aria-selected, tabIndex and
aria-disabled from the inner div; add role="tab", aria-selected={...},
aria-disabled={item.disabled} and tabIndex={item.disabled ? -1 :
(String(item.value) === String(value) ? 0 : -1)} to the outer div; ensure the
outer div also handles keyboard activation (onKeyDown handling Enter/Space to
call the same handler) and preserves disabled behavior and existing click
handler.
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.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (12)
src/locales/base.ts(1 hunks)src/locales/en-US.ts(1 hunks)src/locales/id-ID.ts(1 hunks)src/locales/tr-TR.ts(1 hunks)src/locales/zh-CN.ts(1 hunks)src/locales/zh-TW.ts(1 hunks)src/packages/dialog/dialog.taro.tsx(5 hunks)src/packages/dialog/dialog.tsx(5 hunks)src/packages/navbar/navbar.taro.tsx(3 hunks)src/packages/navbar/navbar.tsx(3 hunks)src/packages/popup/popup.taro.tsx(5 hunks)src/packages/popup/popup.tsx(5 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
- src/packages/navbar/navbar.taro.tsx
- src/packages/navbar/navbar.tsx
- src/packages/popup/popup.tsx
- src/packages/dialog/dialog.tsx
- src/packages/dialog/dialog.taro.tsx
- src/packages/popup/popup.taro.tsx
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: oasis-cloud
Repo: jdf2e/nutui-react PR: 2700
File: src/packages/animatingnumbers/animatingnumbers.harmony.css:25-32
Timestamp: 2024-11-06T05:56:06.800Z
Learning: 在优化 NutUI React 动画性能时,添加 `will-change` 属性可能会对布局产生影响,需要谨慎使用。
🔇 Additional comments (5)
src/locales/id-ID.ts (1)
13-13: LGTM!翻译准确。印尼语中 "Tutup" 是 "关闭" 的正确翻译,与无障碍功能需求一致。
src/locales/zh-TW.ts (1)
13-13: LGTM!繁体中文翻译准确。"關閉" 是关闭的正确繁体中文翻译。
src/locales/tr-TR.ts (1)
13-13: LGTM!土耳其语翻译准确。"Kapat" 是土耳其语中 "关闭" 的正确翻译。
src/locales/zh-CN.ts (1)
13-13: LGTM!简体中文翻译准确。"关闭" 是关闭的正确简体中文翻译,符合无障碍标签需求。
src/locales/base.ts (1)
11-11: LGTM!类型定义合理。将
close定义为可选属性是合理的,这为向后兼容性和渐进式采用提供了灵活性。所有语言包都已提供此键的翻译,确保了无障碍功能的完整支持。
| style={{ ...getStyle, ...style }} | ||
| onClick={(e) => handleClick(e as any)} | ||
| ariaRole={role} | ||
| ariaDisabled={disabled} |
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.
加一个 button.tsx 下的多语言
| style={style} | ||
| onClick={handleClick} | ||
| role={ariaRole} | ||
| aria-label={ariaLabel} |
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.
这个是否可以生效~看上去不符合react的语法~~
| ariaRole="button" | ||
| ariaLabel={locale.close} | ||
| tabindex={0} | ||
| > |
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.
icon库升级到 @nutui/icons-react-taro 3.0.2-cpp.3.beta.3
| closeOnOverlayClick={closeOnOverlayClick} | ||
| lockScroll={lockScroll} | ||
| onClick={onHandleClickOverlay} | ||
| ariaLabel="背景蒙层" |
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.
这里可以不要,写在overlay 组件里就行了
src/packages/dialog/dialog.taro.tsx
Outdated
| footerDirection={footerDirection} | ||
| visible={visible} | ||
| ariaRole={role} | ||
| ariaLabel={ariaLabel} |
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.
确认是不是会干扰到里边的内容读取
| [`${classPrefix}-left-back-children-rtl`]: left && rtl, | ||
| })} | ||
| onClick={(e) => onBackClick(e)} | ||
| ariaLabel="back" |
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.
暴露一个属性、用户可以自定义
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.
类似close
| onClick={handleClickIcon} | ||
| ariaLabel={`${closeable ? 'close' : rightIconAriaLabel}`} | ||
| > | ||
| {rightIcon || <Close size={12} />} |
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.
可以写在496 行的close上~
| onClick: () => {}, | ||
| afterShow: () => {}, | ||
| afterClose: () => {}, | ||
| ariaLabel: '蒙层', |
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.
类似close
| ) | ||
| const [popupHeight, setPopupHeight] = useState('') | ||
| const [topBottom, setTopBottom] = useState('') | ||
| const role = 'dialog' |
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.
popup
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.
优先修改 popup
| onChange, | ||
| loading: propLoading, | ||
| onLoadingChange, | ||
| ariaLabel, |
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.
默认可以加一下 切换按钮+关闭
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.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
src/packages/dialog/dialog.taro.tsx(3 hunks)src/packages/overlay/overlay.tsx(1 hunks)src/packages/popup/popup.taro.tsx(2 hunks)src/packages/popup/popup.tsx(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- src/packages/popup/popup.taro.tsx
- src/packages/overlay/overlay.tsx
- src/packages/dialog/dialog.taro.tsx
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: oasis-cloud
Repo: jdf2e/nutui-react PR: 2700
File: src/packages/animatingnumbers/animatingnumbers.harmony.css:25-32
Timestamp: 2024-11-06T05:56:06.800Z
Learning: 在优化 NutUI React 动画性能时,添加 `will-change` 属性可能会对布局产生影响,需要谨慎使用。
🪛 GitHub Actions: CI
src/packages/popup/popup.tsx
[error] 223-223: ReferenceError: translated is not defined in renderTitle. Ensure translation data is available or handle undefined gracefully.
🪛 GitHub Check: build
src/packages/popup/popup.tsx
[failure] 223-223:
Cannot find name 'translated'. Did you mean 'CSSTranslate'?
src/packages/popup/popup.tsx
Outdated
| <div | ||
| className={`${classPrefix}-title-wrapper`} | ||
| aria-label={translated.title} | ||
| > |
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.
🧩 Analysis chain
修复未定义的变量引用导致的运行时错误。
代码中引用了未定义的 translated 变量,这会导致运行时错误。根据文件中现有的模式(如第 192 行使用 locale.close),应该使用 locale 对象而不是 translated。
应用以下修复:
- <div
- className={`${classPrefix}-title-wrapper`}
- aria-label={translated.title}
- >
+ <div
+ className={`${classPrefix}-title-wrapper`}
+ aria-label={locale.popup?.title || title}
+ >或者,如果需要传入自定义的 ariaLabel 属性,建议添加一个新的 prop:
+ ariaLabel,
} = { ...defaultProps, ...props }然后使用:
<div
className={`${classPrefix}-title-wrapper`}
- aria-label={translated.title}
+ aria-label={ariaLabel || locale.popup?.title || title}
>请运行以下脚本验证 locale 对象的结构,以确定正确的属性名称:
🏁 Script executed:
#!/bin/bash
# 查找 locale/configprovider 相关的类型定义和默认值
rg -n -A 10 -B 2 "popup.*title|title.*popup" --type=ts --type=tsx -g '!node_modules' -g '!dist'
# 查找 useConfig 的类型定义
ast-grep --pattern 'export const useConfig = () => {
$$$
}'
# 查找 locale 默认配置
fd -e ts -e tsx "locale|lang" --exec rg -l "popup" {}Length of output: 526
Now I'll search the actual codebase for the relevant configuration files and variable definitions:
🏁 Script executed:
#!/bin/bash
# Find getDefaultConfig implementation
echo "=== Searching for getDefaultConfig implementation ==="
rg -n "getDefaultConfig" --type=ts -g '!node_modules' -g '!dist' -A 20
# Find references to "translated" variable
echo -e "\n=== Searching for 'translated' variable ==="
rg -n "translated" --type=ts -g '!node_modules' -g '!dist' -C 3
# Find popup component props and their types
echo -e "\n=== Searching for popup component interface/type ==="
rg -n "interface.*Popup|type.*Popup" --type=ts -g '!node_modules' -g '!dist' -A 10Length of output: 306997
修复:popup.tsx 中引用未定义的 translated(运行时错误)
src/packages/popup/popup.tsx 第 221–224 行使用了未定义的 translated(aria-label={translated.title}),会在渲染时抛出 ReferenceError。将该引用替换为使用 locale 或新增 ariaLabel prop,并以组件的 title 回退。建议最小修复:
- aria-label={translated.title}
+ aria-label={ariaLabel || locale.popup?.title || title}若新增 prop,请在 props 解构处添加 ariaLabel。
📝 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.
| <div | |
| className={`${classPrefix}-title-wrapper`} | |
| aria-label={translated.title} | |
| > | |
| <div | |
| className={`${classPrefix}-title-wrapper`} | |
| aria-label={ariaLabel || locale.popup?.title || title} | |
| > |
🧰 Tools
🪛 GitHub Actions: CI
[error] 223-223: ReferenceError: translated is not defined in renderTitle. Ensure translation data is available or handle undefined gracefully.
🪛 GitHub Check: build
[failure] 223-223:
Cannot find name 'translated'. Did you mean 'CSSTranslate'?
🤖 Prompt for AI Agents
In src/packages/popup/popup.tsx around lines 221-224, the code references an
undefined variable translated (aria-label={translated.title}) causing a runtime
ReferenceError; replace that reference with a safe value such as a new ariaLabel
prop or the locale value with fallback to the component title. Update the
component props destructuring to include ariaLabel (if adding prop), then set
aria-label to ariaLabel ?? locale?.title ?? title (or similar order: locale
first then title) so it never references an undefined symbol; ensure TypeScript
types/prop defaults are updated accordingly.
🤔 这个变动的性质是?
🔗 相关 Issue
💡 需求背景和解决方案
☑️ 请求合并前的自查清单
Summary by CodeRabbit
新功能