-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
feat(Choice): 新增 Choice 选择交互组件 #1978
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: main
Are you sure you want to change the base?
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 | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,145 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { CheckCircleFilled } from '@ant-design/icons'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { Radio, Tooltip } from 'antd'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { clsx } from 'clsx'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import React from 'react'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { ChoiceContext } from './context'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import type { ChoiceItemType } from './interface'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export interface ChoiceItemProps { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| item: ChoiceItemType; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| index: number; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const ChoiceItem: React.FC<ChoiceItemProps> = ({ item, index }) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const context = React.useContext(ChoiceContext); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| prefixCls, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mode, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| disabled: groupDisabled, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| indicator, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isSelected, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onItemClick, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| classNames, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| styles, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } = context; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const selected = isSelected(item.key); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const disabled = groupDisabled || item.disabled; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const itemCls = clsx(`${prefixCls}-item`, classNames?.item, { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [`${prefixCls}-item-selected`]: selected, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [`${prefixCls}-item-disabled`]: disabled, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [`${prefixCls}-item-recommended`]: item.recommended, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [`${prefixCls}-item-recommended-primary`]: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| item.recommended === 'primary' || item.recommended === true, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [`${prefixCls}-item-recommended-secondary`]: item.recommended === 'secondary', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const renderIndicator = () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (indicator === 'none') return null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const indicatorCls = clsx(`${prefixCls}-indicator`, classNames?.indicator); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (indicator === 'check') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <span className={indicatorCls} style={styles?.indicator}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {selected && <CheckCircleFilled style={{ color: 'inherit' }} />} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </span> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (indicator === 'radio') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <span className={indicatorCls} style={styles?.indicator}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Radio checked={selected} disabled={disabled} /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </span> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (indicator === 'number') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <span className={indicatorCls} style={styles?.indicator}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {selected ? ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <CheckCircleFilled /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) : ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <span className={`${prefixCls}-indicator-number`}>{index + 1}</span> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </span> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const itemNode = ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className={itemCls} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| style={styles?.item} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data-role={mode === 'single' ? 'radio' : 'checkbox'} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data-selected={selected} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data-disabled={disabled} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tabIndex={disabled ? -1 : 0} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onClick={() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (disabled) return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onItemClick(item, index); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onKeyDown={(e) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (disabled) return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (e.key === 'Enter' || e.key === ' ') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| e.preventDefault(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onItemClick(item, index); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {/* Indicator */} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {renderIndicator()} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {/* Content */} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className={clsx(`${prefixCls}-item-content`, classNames?.itemContent)} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| style={styles?.itemContent} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {/* Icon + Label row */} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {item.icon && <div className={`${prefixCls}-item-icon`}>{item.icon}</div>} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {item.label && <div className={`${prefixCls}-item-label`}>{item.label}</div>} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {item.description && <div className={`${prefixCls}-item-desc`}>{item.description}</div>} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {item.extra && <div className={`${prefixCls}-item-extra`}>{item.extra}</div>} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {/* Recommended badge */} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {(item.recommended === true || item.recommended === 'primary') && ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className={`${prefixCls}-item-recommend-badge`}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {item.recommendedReason || 'AI Recommended'} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {item.recommended === 'secondary' && ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className={`${prefixCls}-item-recommend-badge ${prefixCls}-item-recommend-badge-secondary`} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {item.recommendedReason || 'Recommended'} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {/* Disabled reason (inline) */} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {disabled && item.disabledReason && ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className={`${prefixCls}-item-disabled-reason`}>{item.disabledReason}</div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Wrap with Tooltip for disabled reason on hover | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (disabled && item.disabledReason) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Tooltip title={item.disabledReason} placement="top"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {itemNode} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Tooltip> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+126
to
+140
Contributor
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. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 禁用原因被重复渲染:卡片内常驻文案 + Tooltip 悬浮文案内容完全相同 Line 127-129 在 🐛 建议修复(二选一,示例保留 Tooltip 方案)- {/* Disabled reason (inline) */}
- {disabled && item.disabledReason && (
- <div className={`${prefixCls}-item-disabled-reason`}>{item.disabledReason}</div>
- )}
</div>
);📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return itemNode; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export default ChoiceItem; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
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.
推荐徽章的默认文案 'AI Recommended' 和 'Recommended' 是硬编码的。该组件支持 i18n,因此这些字符串应该来自语言包文件。
为了解决这个问题,你可以将
locale对象从Choice主组件通过ChoiceContext向下传递。packages/x/components/choice/context.ts中,将locale添加到ChoiceContextProps。packages/x/components/choice/index.tsx中,将locale对象传递到ChoiceContext.Provider的 value 中。Item.tsx文件中,从 context 中消费locale并使用相应的键,例如:item.recommendedReason || locale.aiRecommended和item.recommendedReason || locale.recommended。目前的语言包文件已经包含了
recommended键,你可能需要为 'AI Recommended' 添加一个新的键。