Skip to content
Open
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
145 changes: 145 additions & 0 deletions packages/x/components/choice/Item.tsx
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>
)}
Comment on lines +113 to +124

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

推荐徽章的默认文案 'AI Recommended' 和 'Recommended' 是硬编码的。该组件支持 i18n,因此这些字符串应该来自语言包文件。

为了解决这个问题,你可以将 locale 对象从 Choice 主组件通过 ChoiceContext 向下传递。

  1. packages/x/components/choice/context.ts 中,将 locale 添加到 ChoiceContextProps
  2. packages/x/components/choice/index.tsx 中,将 locale 对象传递到 ChoiceContext.Provider 的 value 中。
  3. Item.tsx 文件中,从 context 中消费 locale 并使用相应的键,例如:item.recommendedReason || locale.aiRecommendeditem.recommendedReason || locale.recommended

目前的语言包文件已经包含了 recommended 键,你可能需要为 'AI Recommended' 添加一个新的键。


{/* 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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

禁用原因被重复渲染:卡片内常驻文案 + Tooltip 悬浮文案内容完全相同

Line 127-129 在 disabled && item.disabledReason 时始终内联渲染原因文本;Line 134-140 又用 Tooltip 包裹整个 itemNodetitle 同样是 item.disabledReason。按提交历史描述,Tooltip 是后续为满足验收标准补充的能力,但原有的常驻内联文案没有被替换/移除,导致同一段文案在卡片上永久可见,鼠标悬浮时又在浮层里再次出现,属于重复展示,与“禁用原因提示”应为 hover 提示的设计意图冲突。

🐛 建议修复(二选一,示例保留 Tooltip 方案)
-      {/* Disabled reason (inline) */}
-      {disabled && item.disabledReason && (
-        <div className={`${prefixCls}-item-disabled-reason`}>{item.disabledReason}</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.

Suggested change
{/* 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>
);
}
</div>
);
// Wrap with Tooltip for disabled reason on hover
if (disabled && item.disabledReason) {
return (
<Tooltip title={item.disabledReason} placement="top">
{itemNode}
</Tooltip>
);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/x/components/choice/Item.tsx` around lines 126 - 140, Remove the
always-visible disabled-reason block from the item content rendered by the
component, while preserving the Tooltip wrapper around itemNode when disabled
and item.disabledReason are present. Keep item.disabledReason as the Tooltip
title so the reason is shown only on hover.


return itemNode;
};

export default ChoiceItem;
Loading