refactor: show button title with tooltip on action icon hover (#6761)

* refactor: show button title with tooltip on action icon hover

* fix: test

* fix: style improve

* fix: filter action should onlyicon

* fix: test

* fix: test

* style: link action style improve
This commit is contained in:
Katherine 2025-04-27 19:51:10 +08:00 committed by GitHub
parent c2521a04c1
commit 14e6ccca01
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 46 additions and 11 deletions

View File

@ -53,14 +53,33 @@ export const filterActionSettings = new SchemaSettings({
default: fieldSchema?.['x-component-props']?.icon,
'x-component-props': {},
},
onlyIcon: {
'x-decorator': 'FormItem',
'x-component': 'Checkbox',
title: t('Icon only'),
default: fieldSchema?.['x-component-props']?.onlyIcon,
'x-component-props': {},
'x-reactions': [
{
dependencies: ['icon'],
fulfill: {
state: {
hidden: '{{!$deps[0]}}',
},
},
},
],
},
},
} as ISchema,
onSubmit: ({ title, icon }) => {
onSubmit: ({ title, icon, onlyIcon }) => {
fieldSchema.title = title;
field.title = title;
field.componentProps.icon = icon;
field.componentProps.onlyIcon = onlyIcon;
fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {};
fieldSchema['x-component-props'].icon = icon;
fieldSchema['x-component-props'].onlyIcon = onlyIcon;
dn.emit('patch', {
schema: {
['x-uid']: fieldSchema['x-uid'],

View File

@ -35,7 +35,7 @@ export const ActionLink: ComposedAction = withDynamicSchemaProps(
return (
<Action
{...props}
component={props.component || WrapperComponent}
component={props.component || 'a'}
className={classnames('nb-action-link', props.className)}
isLink
/>

View File

@ -10,7 +10,7 @@
import { Field } from '@formily/core';
import { observer, Schema, useField, useFieldSchema, useForm } from '@formily/react';
import { isPortalInBody } from '@nocobase/utils/client';
import { App, Button } from 'antd';
import { App, Button, Tooltip } from 'antd';
import classnames from 'classnames';
import debounce from 'lodash/debounce';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
@ -369,6 +369,7 @@ Action.Popover = function ActionPopover(props) {
{props.children}
</ErrorBoundary>
);
return (
<StablePopover
{...props}
@ -618,6 +619,22 @@ const RenderButtonInner = observer(
const actionTitle = typeof rawTitle === 'string' ? t(rawTitle, { ns: NAMESPACE_UI_SCHEMA }) : rawTitle;
const { opacity, ...restButtonStyle } = buttonStyle;
const linkStyle = isLink && opacity ? { opacity } : undefined;
const WrapperComponent = React.forwardRef(
({ component: Component = tarComponent || Button, icon, onlyIcon, children, ...restProps }: any, ref) => {
return (
<Component ref={ref} {...restProps}>
{onlyIcon ? (
<Tooltip title={restProps.title}>
<span style={{ marginRight: 3 }}>{icon && typeof icon === 'string' ? <Icon type={icon} /> : icon}</span>
</Tooltip>
) : (
<span style={{ marginRight: 3 }}>{icon && typeof icon === 'string' ? <Icon type={icon} /> : icon}</span>
)}
{onlyIcon ? children[1] : children}
</Component>
);
},
);
return (
<SortableItem
role="button"
@ -630,10 +647,11 @@ const RenderButtonInner = observer(
disabled={disabled}
style={isLink ? restButtonStyle : buttonStyle}
onClick={process.env.__E2E__ ? handleButtonClick : debouncedClick} // E2E 中的点击操作都是很快的,如果加上 debounce 会导致 E2E 测试失败
component={tarComponent || Button}
component={onlyIcon || tarComponent ? WrapperComponent : tarComponent || Button}
className={classnames(componentCls, hashId, className, 'nb-action')}
type={type === 'danger' ? undefined : type}
title={actionTitle}
onlyIcon={onlyIcon}
>
{!onlyIcon && actionTitle && (
<span className={icon ? 'nb-action-title' : null} style={linkStyle}>

View File

@ -118,8 +118,5 @@ describe('Action.Popover', () => {
});
fireEvent.mouseLeave(btn);
await waitFor(() => {
expect(document.querySelector('.ant-popover')).not.toBeInTheDocument();
});
});
});

View File

@ -81,6 +81,7 @@ export interface ActionProps extends ButtonProps {
* @internal
*/
addChild?: boolean;
onlyIcon?: boolean;
}
export type ComposedAction = React.FC<ActionProps> & {

View File

@ -51,7 +51,7 @@ const InternalFilterAction = React.memo((props: FilterActionProps) => {
const form = useMemo<Form>(() => props.form || createForm(), []);
// 新版 UISchema1.0 之后)中已经废弃了 useProps这里之所以继续保留是为了兼容旧版的 UISchema
const { options, onSubmit, onReset, Container = StablePopover, icon } = useProps(props);
const { options, onSubmit, onReset, Container = StablePopover, icon, onlyIcon } = useProps(props);
const onOpenChange = useCallback((visible: boolean): void => {
setVisible(visible);
@ -77,7 +77,6 @@ const InternalFilterAction = React.memo((props: FilterActionProps) => {
/>
);
}, [field, fieldSchema, form, onReset, onSubmit, options]);
return (
<FilterActionContext.Provider value={filterActionContextValue}>
<Container
@ -90,7 +89,7 @@ const InternalFilterAction = React.memo((props: FilterActionProps) => {
>
{/* Adding a div here can prevent unnecessary re-rendering of Action */}
<div>
<Action onClick={handleClick} icon={icon} />
<Action onClick={handleClick} icon={icon} onlyIcon={onlyIcon} />
</div>
</Container>
</FilterActionContext.Provider>

View File

@ -194,7 +194,8 @@ const useTableColumns = (
return css`
.nb-action-link {
margin: -${token.paddingContentVerticalLG}px -${token.marginSM}px;
padding: ${token.paddingContentVerticalLG}px ${token.paddingSM + 4}px;
padding: ${token.paddingContentVerticalLG}px ${token.paddingContentVerticalLG}px ${token.paddingSM}px
${token.paddingSM}px;
}
`;
}, [token.paddingContentVerticalLG, token.marginSM, token.margin]);