diff --git a/packages/@headlessui-react/CHANGELOG.md b/packages/@headlessui-react/CHANGELOG.md index 1c6ba07023..4988bc105d 100644 --- a/packages/@headlessui-react/CHANGELOG.md +++ b/packages/@headlessui-react/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Fix `ListboxOptions` being incorrectly marked as `inert` ([#3466](https://github.com/tailwindlabs/headlessui/pull/3466)) +- Fix crash when using `DisclosureButton` inside of a `DisclosurePanel` when the `Disclosure` is open by default ([#3465](https://github.com/tailwindlabs/headlessui/pull/3465)) ## [2.1.5] - 2024-09-04 diff --git a/packages/@headlessui-react/src/components/disclosure/disclosure.test.tsx b/packages/@headlessui-react/src/components/disclosure/disclosure.test.tsx index d32844943c..6b6626878d 100644 --- a/packages/@headlessui-react/src/components/disclosure/disclosure.test.tsx +++ b/packages/@headlessui-react/src/components/disclosure/disclosure.test.tsx @@ -337,6 +337,49 @@ describe('Rendering', () => { }) ) + it('should behave as a close button when used inside of the Disclosure.Panel', async () => { + render( + + Open + + Close + + + ) + + assertDisclosureButton({ state: DisclosureState.InvisibleUnmounted }) + assertDisclosurePanel({ state: DisclosureState.InvisibleUnmounted }) + + await click(getByText('Open')) + + assertDisclosureButton({ state: DisclosureState.Visible }) + assertDisclosurePanel({ state: DisclosureState.Visible }) + + await click(getByText('Close')) + + assertDisclosureButton({ state: DisclosureState.InvisibleUnmounted }) + assertDisclosurePanel({ state: DisclosureState.InvisibleUnmounted }) + }) + + it('should behave as a close button when used inside of the Disclosure.Panel (defaultOpen)', async () => { + render( + + Open + + Close + + + ) + + assertDisclosureButton({ state: DisclosureState.Visible }) + assertDisclosurePanel({ state: DisclosureState.Visible }) + + await click(getByText('Close')) + + assertDisclosureButton({ state: DisclosureState.InvisibleUnmounted }) + assertDisclosurePanel({ state: DisclosureState.InvisibleUnmounted }) + }) + describe('`type` attribute', () => { it('should set the `type` to "button" by default', async () => { render( diff --git a/packages/@headlessui-react/src/components/disclosure/disclosure.tsx b/packages/@headlessui-react/src/components/disclosure/disclosure.tsx index 2f3e522627..d7a5dd1425 100644 --- a/packages/@headlessui-react/src/components/disclosure/disclosure.tsx +++ b/packages/@headlessui-react/src/components/disclosure/disclosure.tsx @@ -299,9 +299,10 @@ function ButtonFn( let buttonRef = useSyncRefs( internalButtonRef, ref, - !isWithinPanel - ? useEvent((element) => dispatch({ type: ActionTypes.SetButtonElement, element })) - : null + useEvent((element) => { + if (isWithinPanel) return + return dispatch({ type: ActionTypes.SetButtonElement, element }) + }) ) let mergeRefs = useMergeRefsFn() diff --git a/packages/@headlessui-react/src/components/popover/popover.tsx b/packages/@headlessui-react/src/components/popover/popover.tsx index b1efa7eaf6..49a02f742d 100644 --- a/packages/@headlessui-react/src/components/popover/popover.tsx +++ b/packages/@headlessui-react/src/components/popover/popover.tsx @@ -535,24 +535,23 @@ function ButtonFn( internalButtonRef, ref, useFloatingReference(), - isWithinPanel - ? null - : useEvent((button) => { - if (button) { - state.buttons.current.push(uniqueIdentifier) - } else { - let idx = state.buttons.current.indexOf(uniqueIdentifier) - if (idx !== -1) state.buttons.current.splice(idx, 1) - } + useEvent((button) => { + if (isWithinPanel) return + if (button) { + state.buttons.current.push(uniqueIdentifier) + } else { + let idx = state.buttons.current.indexOf(uniqueIdentifier) + if (idx !== -1) state.buttons.current.splice(idx, 1) + } - if (state.buttons.current.length > 1) { - console.warn( - 'You are already using a but only 1 is supported.' - ) - } + if (state.buttons.current.length > 1) { + console.warn( + 'You are already using a but only 1 is supported.' + ) + } - button && dispatch({ type: ActionTypes.SetButton, button }) - }) + button && dispatch({ type: ActionTypes.SetButton, button }) + }) ) let withinPanelButtonRef = useSyncRefs(internalButtonRef, ref) let ownerDocument = useOwnerDocument(internalButtonRef)