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)