From 49bcd0c95e67850a09b454cd2f027bed42be5666 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Fri, 10 Jul 2026 12:20:57 +0800 Subject: [PATCH] feat: support disabled prop --- README.md | 1 + README.zh-CN.md | 1 + docs/demos/disabled.md | 8 ++++++ docs/examples/disabled.tsx | 38 +++++++++++++++++++++++++ src/index.tsx | 5 +++- tests/basic.test.jsx | 57 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 docs/demos/disabled.md create mode 100644 docs/examples/disabled.tsx diff --git a/README.md b/README.md index 5aa7ad57..a66da5b6 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,7 @@ Then open `http://localhost:8000`. | blurDelay | Delay before hiding on blur, in seconds. | `number` | - | | builtinPlacements | Named placement presets. | `BuildInPlacements` | `{}` | | defaultPopupVisible | Initial uncontrolled visibility. | `boolean` | `false` | +| disabled | Temporarily suppress popup visibility without resetting the current open state. | `boolean` | `false` | | focusDelay | Delay before showing on focus, in seconds. | `number` | - | | forceRender | Render popup before it is first shown. | `boolean` | `false` | | fresh | Keep popup content updated while closed. | `boolean` | - | diff --git a/README.zh-CN.md b/README.zh-CN.md index 06e32684..d99a82f3 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -75,6 +75,7 @@ npm start | blurDelay | `blur` 后隐藏前的延迟,单位为秒。 | `number` | - | | builtinPlacements | 命名位置预设。 | `BuildInPlacements` | `{}` | | defaultPopupVisible | 非受控初始显示状态。 | `boolean` | `false` | +| disabled | 临时隐藏弹层,但不主动重置当前打开状态。 | `boolean` | `false` | | focusDelay | `focus` 后显示前的延迟,单位为秒。 | `number` | - | | forceRender | 首次显示前渲染弹层。 | `boolean` | `false` | | fresh | 关闭时仍保持弹层内容更新。 | `boolean` | - | diff --git a/docs/demos/disabled.md b/docs/demos/disabled.md new file mode 100644 index 00000000..839e77d4 --- /dev/null +++ b/docs/demos/disabled.md @@ -0,0 +1,8 @@ +--- +title: Disabled +nav: + title: Demo + path: /demo +--- + + diff --git a/docs/examples/disabled.tsx b/docs/examples/disabled.tsx new file mode 100644 index 00000000..3be77891 --- /dev/null +++ b/docs/examples/disabled.tsx @@ -0,0 +1,38 @@ +import Trigger from '@rc-component/trigger'; +import React, { useState } from 'react'; +import '../../assets/index.less'; + +const builtinPlacements = { + top: { + points: ['bc', 'tc'], + offset: [0, -8], + }, +}; + +const DisabledDemo = () => { + const [disabled, setDisabled] = useState(false); + + return ( +
+ Tooltip content} + popupPlacement="top" + popupStyle={{ + padding: '6px 8px', + color: '#fff', + background: '#1f1f1f', + borderRadius: 4, + }} + > + + +
+ ); +}; + +export default DisabledDemo; diff --git a/src/index.tsx b/src/index.tsx index bb5fb838..90f8202d 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -65,6 +65,8 @@ export interface TriggerProps { action?: ActionType | ActionType[]; showAction?: ActionType[]; hideAction?: ActionType[]; + /** Temporarily suppress popup visibility without resetting the current open state. */ + disabled?: boolean; prefixCls?: string; @@ -159,6 +161,7 @@ export function generateTrigger( action = 'hover', showAction, hideAction, + disabled = false, // Open popupVisible, @@ -319,7 +322,7 @@ export function generateTrigger( popupVisible, ); - const mergedOpen = internalOpen || false; + const mergedOpen = (internalOpen || false) && !disabled; // ========================== Children ========================== const child = React.useMemo(() => { diff --git a/tests/basic.test.jsx b/tests/basic.test.jsx index 231b16ec..edebbd61 100644 --- a/tests/basic.test.jsx +++ b/tests/basic.test.jsx @@ -179,6 +179,35 @@ describe('Trigger.Basic', () => { trigger(document, '.rc-trigger-popup', 'pointerEnter'); expect(isPopupHidden()).toBeFalsy(); }); + + it('temporarily hides while disabled and restores without mouse leave', () => { + const onOpenChange = jest.fn(); + const Demo = ({ disabled = false }) => ( + trigger} + > +
hover
+
+ ); + + const { container, rerender } = render(); + + trigger(container, '.target', 'mouseEnter'); + expect(isPopupHidden()).toBeFalsy(); + expect(onOpenChange).toHaveBeenCalledWith(true); + onOpenChange.mockReset(); + + rerender(); + expect(isPopupHidden()).toBeTruthy(); + expect(onOpenChange).not.toHaveBeenCalled(); + + rerender(); + expect(isPopupHidden()).toBeFalsy(); + expect(onOpenChange).not.toHaveBeenCalled(); + }); }); it('contextMenu works', () => { @@ -1009,6 +1038,34 @@ describe('Trigger.Basic', () => { expect(document.querySelector('.rc-trigger-popup')).toBeTruthy(); }); + it('temporarily hides a controlled popup without changing its open state', () => { + const onOpenChange = jest.fn(); + const Demo = ({ disabled = false }) => ( + trigger} + popupVisible + > + {({ open }) =>
} + + ); + + const { container, rerender } = render(); + + expect(container.firstChild).toHaveAttribute('data-open', 'true'); + expect(isPopupHidden()).toBeFalsy(); + + rerender(); + expect(container.firstChild).toHaveAttribute('data-open', 'false'); + expect(isPopupHidden()).toBeTruthy(); + + rerender(); + expect(container.firstChild).toHaveAttribute('data-open', 'true'); + expect(isPopupHidden()).toBeFalsy(); + expect(onOpenChange).not.toHaveBeenCalled(); + }); + describe('click window to hide', () => { it('should hide', async () => { const onOpenChange = jest.fn();