-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
feat(Actions): 支持自定义每个操作项的 Tooltip #1977
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 | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,6 +1,6 @@ | ||||||||||||||||||||||||||||||||||
| import { CloseCircleOutlined, LoadingOutlined } from '@ant-design/icons'; | ||||||||||||||||||||||||||||||||||
| import pickAttrs from '@rc-component/util/lib/pickAttrs'; | ||||||||||||||||||||||||||||||||||
| import { Tooltip } from 'antd'; | ||||||||||||||||||||||||||||||||||
| import { Tooltip, type TooltipProps } from 'antd'; | ||||||||||||||||||||||||||||||||||
| import { clsx } from 'clsx'; | ||||||||||||||||||||||||||||||||||
| import React from 'react'; | ||||||||||||||||||||||||||||||||||
| import useMobile from '../_util/hooks/use-mobile'; | ||||||||||||||||||||||||||||||||||
|
|
@@ -69,6 +69,11 @@ export interface ActionsItemProps extends Omit<React.HTMLAttributes<HTMLDivEleme | |||||||||||||||||||||||||||||||||
| * @descEN Semantic structure styles | ||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||
| styles?: Partial<Record<SemanticType, React.CSSProperties>>; | ||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||
| * @desc 自定义操作项的 Tooltip,设为 false 时不渲染 Tooltip | ||||||||||||||||||||||||||||||||||
| * @descEN Tooltip for the action item, set to false to disable Tooltip | ||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||
| tooltip?: string | TooltipProps | false; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| const ActionsItem: React.FC<ActionsItemProps> = (props) => { | ||||||||||||||||||||||||||||||||||
|
|
@@ -77,6 +82,7 @@ const ActionsItem: React.FC<ActionsItemProps> = (props) => { | |||||||||||||||||||||||||||||||||
| defaultIcon, | ||||||||||||||||||||||||||||||||||
| runningIcon, | ||||||||||||||||||||||||||||||||||
| label, | ||||||||||||||||||||||||||||||||||
| tooltip, | ||||||||||||||||||||||||||||||||||
| className, | ||||||||||||||||||||||||||||||||||
| classNames = {}, | ||||||||||||||||||||||||||||||||||
| styles = {}, | ||||||||||||||||||||||||||||||||||
|
|
@@ -138,7 +144,14 @@ const ActionsItem: React.FC<ActionsItemProps> = (props) => { | |||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| return isMobile ? innerNode : <Tooltip title={label}>{innerNode}</Tooltip>; | ||||||||||||||||||||||||||||||||||
| // ============================ Tooltip ============================ | ||||||||||||||||||||||||||||||||||
| const tooltipEnabled = tooltip !== false; | ||||||||||||||||||||||||||||||||||
| const tooltipProps: TooltipProps = | ||||||||||||||||||||||||||||||||||
| typeof tooltip === 'string' | ||||||||||||||||||||||||||||||||||
| ? { title: tooltip } | ||||||||||||||||||||||||||||||||||
| : (tooltip as TooltipProps) || { title: label }; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| return isMobile || !tooltipEnabled ? innerNode : <Tooltip {...tooltipProps}>{innerNode}</Tooltip>; | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+147
to
+154
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 | 🟡 Minor | ⚡ Quick win 修复 Tooltip 对象配置时丢失默认 当用户仅通过 🐛 修复建议 // ============================ Tooltip ============================
const tooltipEnabled = tooltip !== false;
const tooltipProps: TooltipProps =
typeof tooltip === 'string'
? { title: tooltip }
- : (tooltip as TooltipProps) || { title: label };
+ : { title: label, ...(tooltip as TooltipProps) };
return isMobile || !tooltipEnabled ? innerNode : <Tooltip {...tooltipProps}>{innerNode}</Tooltip>;📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| export default ActionsItem; | ||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,4 +1,4 @@ | ||||||||||||||||||||||||||||||||||
| import { Tooltip } from 'antd'; | ||||||||||||||||||||||||||||||||||
| import { Tooltip, type TooltipProps } from 'antd'; | ||||||||||||||||||||||||||||||||||
| import { clsx } from 'clsx'; | ||||||||||||||||||||||||||||||||||
| import React from 'react'; | ||||||||||||||||||||||||||||||||||
| import useMobile from '../_util/hooks/use-mobile'; | ||||||||||||||||||||||||||||||||||
|
|
@@ -30,6 +30,17 @@ const Item: React.FC<ActionsItemProps> = (props) => { | |||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| const iconElement = <div className={`${prefixCls}-icon`}>{item?.icon}</div>; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| // ============================ Tooltip ============================ | ||||||||||||||||||||||||||||||||||
| const tooltipConfig = item.tooltip; | ||||||||||||||||||||||||||||||||||
| const tooltipEnabled = tooltipConfig !== false; | ||||||||||||||||||||||||||||||||||
| const tooltipProps: TooltipProps = | ||||||||||||||||||||||||||||||||||
| typeof tooltipConfig === 'string' | ||||||||||||||||||||||||||||||||||
| ? { title: tooltipConfig } | ||||||||||||||||||||||||||||||||||
| : (tooltipConfig as TooltipProps) || { title: item.label }; | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+36
to
+39
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. 当 建议将传入的
Suggested change
Comment on lines
+36
to
+39
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 合并 TooltipProps 以避免丢失默认的 当 建议在传入对象配置时,保留 💡 建议的修复- const tooltipProps: TooltipProps =
- typeof tooltipConfig === 'string'
- ? { title: tooltipConfig }
- : (tooltipConfig as TooltipProps) || { title: item.label };
+ const tooltipProps: TooltipProps =
+ typeof tooltipConfig === 'string'
+ ? { title: tooltipConfig }
+ : { title: item.label, ...(typeof tooltipConfig === 'object' ? tooltipConfig : {}) };📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| const wrappedIcon = | ||||||||||||||||||||||||||||||||||
| isMobile || !tooltipEnabled ? iconElement : <Tooltip {...tooltipProps}>{iconElement}</Tooltip>; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||
| <div | ||||||||||||||||||||||||||||||||||
| className={clsx(`${prefixCls}-item`, classNames.item, { | ||||||||||||||||||||||||||||||||||
|
|
@@ -50,7 +61,7 @@ const Item: React.FC<ActionsItemProps> = (props) => { | |||||||||||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||||||||||||
| key={itemKey} | ||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||
| {isMobile ? iconElement : <Tooltip title={item.label}>{iconElement}</Tooltip>} | ||||||||||||||||||||||||||||||||||
| {wrappedIcon} | ||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,11 +1,54 @@ | ||
| import React from 'react'; | ||
| import { render } from '../../../tests/utils'; | ||
| import { fireEvent, render, waitFakeTimer } from '../../../tests/utils'; | ||
| import ActionsItem from '../ActionsItem'; | ||
|
|
||
| describe('Actions.Item', () => { | ||
| beforeEach(() => { | ||
| jest.useFakeTimers(); | ||
| }); | ||
|
|
||
| afterEach(() => { | ||
| jest.useRealTimers(); | ||
| }); | ||
|
|
||
| it('renders with no status', () => { | ||
| const { getByText } = render(<ActionsItem defaultIcon="default-icon" />); | ||
| expect(getByText('default-icon')).toBeTruthy(); | ||
| render(<ActionsItem defaultIcon="default-icon" status={'xxx' as any} />); | ||
| }); | ||
|
|
||
| it('renders default tooltip with label', async () => { | ||
| const { container } = render( | ||
| <ActionsItem defaultIcon={<span>icon</span>} label="Action Label" />, | ||
| ); | ||
| const trigger = container.querySelector('.ant-actions-button-item')!; | ||
| fireEvent.mouseEnter(trigger); | ||
| await waitFakeTimer(); | ||
| const tooltip = document.querySelector('.ant-tooltip'); | ||
| expect(tooltip).toBeInTheDocument(); | ||
| expect(tooltip?.textContent).toBe('Action Label'); | ||
| }); | ||
|
|
||
| it('renders custom tooltip string', async () => { | ||
| const { container } = render( | ||
| <ActionsItem defaultIcon={<span>icon</span>} label="Action Label" tooltip="Custom Tooltip" />, | ||
| ); | ||
| const trigger = container.querySelector('.ant-actions-button-item')!; | ||
| fireEvent.mouseEnter(trigger); | ||
| await waitFakeTimer(); | ||
| const tooltip = document.querySelector('.ant-tooltip'); | ||
| expect(tooltip).toBeInTheDocument(); | ||
| expect(tooltip?.textContent).toBe('Custom Tooltip'); | ||
| }); | ||
|
|
||
| it('does not render tooltip when tooltip is false', async () => { | ||
| const { container } = render( | ||
| <ActionsItem defaultIcon={<span>icon</span>} label="Action Label" tooltip={false} />, | ||
| ); | ||
| const trigger = container.querySelector('.ant-actions-button-item')!; | ||
| fireEvent.mouseEnter(trigger); | ||
| await waitFakeTimer(); | ||
| const tooltip = document.querySelector('.ant-tooltip'); | ||
| expect(tooltip).not.toBeInTheDocument(); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| ## zh-CN | ||
|
|
||
| 自定义每个操作项的 Tooltip,支持传入字符串、TooltipProps 或 `false` 关闭。 | ||
|
|
||
| ## en-US | ||
|
|
||
| Customize the Tooltip of each action item, supports string, TooltipProps, or `false` to disable. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| import { EditOutlined, RedoOutlined } from '@ant-design/icons'; | ||
| import { Actions } from '@ant-design/x'; | ||
| import React from 'react'; | ||
|
|
||
| const App: React.FC = () => { | ||
| return ( | ||
| <Actions | ||
| items={[ | ||
| { | ||
| key: 'retry', | ||
| icon: <RedoOutlined />, | ||
| label: 'Retry', | ||
| // Custom tooltip text | ||
| tooltip: 'Click to retry', | ||
| }, | ||
| { | ||
| key: 'edit', | ||
| icon: <EditOutlined />, | ||
| label: 'Edit', | ||
| // Custom tooltip props (e.g. placement, color) | ||
| tooltip: { | ||
| title: 'Edit content', | ||
| placement: 'bottom', | ||
| color: 'blue', | ||
| }, | ||
| }, | ||
| { | ||
| key: 'share', | ||
| icon: <EditOutlined />, | ||
| label: 'Share', | ||
| // Disable tooltip | ||
| tooltip: false, | ||
| }, | ||
| ]} | ||
| /> | ||
| ); | ||
| }; | ||
|
|
||
| export default App; |
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.
当
tooltip传入一个TooltipProps对象(例如{ placement: 'bottom' })但未指定title时,当前的实现会导致 Tooltip 渲染时没有标题内容。建议将传入的
TooltipProps与默认的title: label进行合并,这样用户在仅自定义 Tooltip 属性(如位置、颜色等)时,依然能保留默认的label作为标题,避免出现空白 Tooltip 的问题。