Skip to content
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

feat[float-button]: add clickOutAutoClose feature #39501

Merged
merged 13 commits into from Dec 14, 2022
70 changes: 46 additions & 24 deletions components/float-button/FloatButtonGroup.tsx
@@ -1,4 +1,4 @@
import React, { useRef, memo, useContext } from 'react';
import React, { useRef, memo, useContext, useEffect, useCallback, useMemo } from 'react';
import CloseOutlined from '@ant-design/icons/CloseOutlined';
import FileTextOutlined from '@ant-design/icons/FileTextOutlined';
import classNames from 'classnames';
Expand All @@ -23,6 +23,7 @@ const FloatButtonGroup: React.FC<FloatButtonGroupProps> = (props) => {
description,
trigger,
children,
clickOutAutoClose = true,
onOpenChange,
} = props;

Expand All @@ -41,37 +42,58 @@ const FloatButtonGroup: React.FC<FloatButtonGroupProps> = (props) => {

const [open, setOpen] = useMergedState(false, { value: props.open });

const clickAction = useRef<React.HTMLAttributes<HTMLAnchorElement | HTMLButtonElement>>({});
const floatButtonGroupRef = useRef<HTMLDivElement>(null);
const floatButtonRef = useRef<HTMLButtonElement>(null);

const hoverAction = useRef<React.HTMLAttributes<HTMLDivElement>>({});
const hoverTypeAction = {
li-jia-nan marked this conversation as resolved.
Show resolved Hide resolved
onMouseEnter() {
setOpen(true);
onOpenChange?.(true);
},
onMouseLeave() {
setOpen(false);
onOpenChange?.(false);
},
};
const hoverAction = useMemo(() => {
if (trigger === 'hover') {
return hoverTypeAction;
li-jia-nan marked this conversation as resolved.
Show resolved Hide resolved
}
}, [trigger]);

if (trigger === 'click') {
clickAction.current = {
onClick() {
setOpen((prevState) => {
onOpenChange?.(!prevState);
return !prevState;
});
},
};
}
const handleOpenChange = () => {
setOpen((prevState) => {
onOpenChange?.(!prevState);
return !prevState;
});
};

if (trigger === 'hover') {
hoverAction.current = {
onMouseEnter() {
setOpen(true);
onOpenChange?.(true);
},
onMouseLeave() {
const onClick = useCallback(
(e: MouseEvent) => {
if (trigger !== 'click') return;
MadCcc marked this conversation as resolved.
Show resolved Hide resolved
li-jia-nan marked this conversation as resolved.
Show resolved Hide resolved
if (floatButtonGroupRef.current!.contains(e.target as Node)) {
if (!floatButtonRef.current!.contains(e.target as Node)) return;
handleOpenChange();
return;
MadCcc marked this conversation as resolved.
Show resolved Hide resolved
}
if (clickOutAutoClose) {
setOpen(false);
onOpenChange?.(false);
},
}
},
[clickOutAutoClose, trigger],
);

useEffect(() => {
document.addEventListener('click', onClick);
li-jia-nan marked this conversation as resolved.
Show resolved Hide resolved
return () => {
document.removeEventListener('click', onClick);
};
}
}, []);

return wrapSSR(
<FloatButtonGroupProvider value={shape}>
<div className={groupCls} style={style} {...hoverAction.current}>
<div ref={floatButtonGroupRef} className={groupCls} style={style} {...hoverAction}>
{trigger && ['click', 'hover'].includes(trigger) ? (
<>
<CSSMotion visible={open} motionName={`${groupPrefixCls}-wrap`}>
Expand All @@ -80,11 +102,11 @@ const FloatButtonGroup: React.FC<FloatButtonGroupProps> = (props) => {
)}
</CSSMotion>
<FloatButton
ref={floatButtonRef}
type={type}
shape={shape}
icon={open ? closeIcon : icon}
description={description}
{...clickAction.current}
/>
</>
) : (
Expand Down
26 changes: 26 additions & 0 deletions components/float-button/__tests__/group.test.tsx
Expand Up @@ -58,4 +58,30 @@ describe('FloatButtonGroup', () => {
fireEvent.mouseLeave(container.querySelector('.ant-float-btn-group')!);
expect(onOpenChange).toHaveBeenCalled();
});
it('support click floatButtonGroup not close', () => {
const onOpenChange = jest.fn();
const { container } = render(
<FloatButton.Group trigger="click" onOpenChange={onOpenChange}>
<FloatButton />
<FloatButton />
<FloatButton />
</FloatButton.Group>,
);
fireEvent.click(container.querySelector('.ant-float-btn')!);
fireEvent.click(container.querySelector('.ant-float-btn-group')!);
expect(onOpenChange).toHaveBeenCalledTimes(1);
});
it('support click out auto close', () => {
const onOpenChange = jest.fn();
const { container } = render(
<FloatButton.Group trigger="click" onOpenChange={onOpenChange}>
<FloatButton />
<FloatButton />
<FloatButton />
</FloatButton.Group>,
);
fireEvent.click(container.querySelector('.ant-float-btn')!);
fireEvent.click(container);
expect(onOpenChange).toHaveBeenCalledTimes(2);
});
});
4 changes: 2 additions & 2 deletions components/float-button/demo/group-menu.md
@@ -1,7 +1,7 @@
## zh-CN

设置 `trigger` 属性即可开启菜单模式。提供 `hover` 和 `click` 两种触发方式
设置 `trigger` 属性即可开启菜单模式。提供 `hover` 和 `click` 两种触发方式。若设置为 `click`,点击外侧会**自动关闭** `menu`

## en-US

Open menu mode with `trigger`, which could be `hover` or `click`.
Open menu mode with `trigger`, which could be `hover` or `click`. if set to `click`, `clickOutAutoClose` is enabled by default
li-jia-nan marked this conversation as resolved.
Show resolved Hide resolved
2 changes: 1 addition & 1 deletion components/float-button/demo/group-menu.tsx
Expand Up @@ -3,7 +3,7 @@ import { FloatButton } from 'antd';
import { CustomerServiceOutlined, CommentOutlined } from '@ant-design/icons';

const App: React.FC = () => (
<FloatButton.Group icon={<CustomerServiceOutlined />} type="primary" trigger="click">
<FloatButton.Group icon={<CustomerServiceOutlined />} type="primary" trigger='click'>
li-jia-nan marked this conversation as resolved.
Show resolved Hide resolved
<FloatButton />
<FloatButton icon={<CommentOutlined />} />
</FloatButton.Group>
Expand Down
2 changes: 2 additions & 0 deletions components/float-button/interface.ts
Expand Up @@ -36,6 +36,8 @@ export interface FloatButtonGroupProps extends FloatButtonProps {
children: React.ReactNode;
// 触发方式 (有触发方式为菜单模式)
trigger?: FloatButtonGroupTrigger;
// 触发方式为 click 时,是否开启点击外侧自动关闭
clickOutAutoClose?: boolean;
li-jia-nan marked this conversation as resolved.
Show resolved Hide resolved
// 受控展开
open?: boolean;
// 关闭按钮自定义图标
Expand Down