Repository: tailwindlabs/headlessui (TypeScript)

Issue:
Popover: nested popovers don't open
**What package within Headless UI are you using?**
@headlessui/react

**What version of that package are you using?**
1.7.8 - 1.7.10

**What browser are you using?**
Firefox, Chrome, Safari

**Reproduction URL**
https://stackblitz.com/edit/react-ts-tqzsk8
for the working version change the version of @headlessui/react in `package.json` to 1.7.7 

**Describe your issue**
Starting with `@headlessui/react v1.7.8` you cannot open a Popover from an existing `Popover.Panel`. It worked fine in 1.7.7.
I've attached a minimal example. Just for context, in our scenario we have a drill down UX where the user can navigate a few levels deep. 


Discussion:
Popovers doesnt open even when not nested. Might wanna edit your title.

For those who cant get it to work even after downgrading to `1.7.7`,  might wanna check your lock file. Doing this to a fixed version should solve it.
```diff
-"@headlessui/react": "^1.7.7",
+"@headlessui/react": "1.7.7",
```
Hey! Thank you for your bug report!
Much appreciated! 🙏

This should be fixed by #2293, and will be available in the next release.

You can already try it using:
- `npm install @headlessui/react@insiders`.
- `npm install @headlessui/vue@insiders`.



Patch:
diff --git a/packages/@headlessui-react/CHANGELOG.md b/packages/@headlessui-react/CHANGELOG.md
index 6ce4c25dbc..85e823dc38 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
 
 - Ensure the main tree and parent `Dialog` components are marked as `inert` ([#2290](https://github.com/tailwindlabs/headlessui/pull/2290))
+- Fix nested `Popover` components not opening ([#2293](https://github.com/tailwindlabs/headlessui/pull/2293))
 
 ## [1.7.11] - 2023-02-15
 
diff --git a/packages/@headlessui-react/src/components/popover/popover.tsx b/packages/@headlessui-react/src/components/popover/popover.tsx
index e14ae8a20a..96d9ad6e1b 100644
--- a/packages/@headlessui-react/src/components/popover/popover.tsx
+++ b/packages/@headlessui-react/src/components/popover/popover.tsx
@@ -393,7 +393,12 @@ let Button = forwardRefWithAs(function Button<TTag extends ElementType = typeof
   let closeOthers = groupContext?.closeOthers
 
   let panelContext = usePopoverPanelContext()
-  let isWithinPanel = panelContext !== null
+
+  // A button inside a panel will just have "close" functionality, no "open" functionality. However,
+  // if a `Popover.Button` is rendered inside a `Popover` which in turn is rendered inside a
+  // `Popover.Panel` (aka nested popovers), then we need to make sure that the button is able to
+  // open the nested popover.
+  let isWithinPanel = panelContext === null ? false : panelContext === state.panelId
 
   useEffect(() => {
     if (isWithinPanel) return
diff --git a/packages/@headlessui-vue/CHANGELOG.md b/packages/@headlessui-vue/CHANGELOG.md
index 5deee342b3..f3fc70e817 100644
--- a/packages/@headlessui-vue/CHANGELOG.md
+++ b/packages/@headlessui-vue/CHANGELOG.md
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ### Fixed
 
 - Ensure the main tree and parent `Dialog` components are marked as `inert` ([#2290](https://github.com/tailwindlabs/headlessui/pull/2290))
+- Fix nested `Popover` components not opening ([#2293](https://github.com/tailwindlabs/headlessui/pull/2293))
 
 ## [1.7.10] - 2023-02-15
 
diff --git a/packages/@headlessui-vue/src/components/popover/popover.ts b/packages/@headlessui-vue/src/components/popover/popover.ts
index ff7a47015f..eb7d94830f 100644
--- a/packages/@headlessui-vue/src/components/popover/popover.ts
+++ b/packages/@headlessui-vue/src/components/popover/popover.ts
@@ -288,6 +288,11 @@ export let PopoverButton = defineComponent({
     let closeOthers = groupContext?.closeOthers
 
     let panelContext = usePopoverPanelContext()
+
+    // A button inside a panel will just have "close" functionality, no "open" functionality.
+    // However, if a `Popover.Button` is rendered inside a `Popover` which in turn is rendered
+    // inside a `Popover.Panel` (aka nested popovers), then we need to make sure that the button is
+    // able to open the nested popover.
     let isWithinPanel = computed(() =>
       panelContext === null ? false : panelContext.value === api.panelId.value
     )


Test patch:
diff --git a/packages/@headlessui-react/src/components/popover/popover.test.tsx b/packages/@headlessui-react/src/components/popover/popover.test.tsx
index fb6d760a5b..4f6c755dca 100644
--- a/packages/@headlessui-react/src/components/popover/popover.test.tsx
+++ b/packages/@headlessui-react/src/components/popover/popover.test.tsx
@@ -2481,3 +2481,64 @@ describe('Mouse interactions', () => {
     })
   )
 })
+
+describe('Nested popovers', () => {
+  it(
+    'should be possible to nest Popover components and control them individually',
+    suppressConsoleLogs(async () => {
+      render(
+        <Popover data-testid="popover-a">
+          <Popover.Button>Toggle A</Popover.Button>
+          <Popover.Panel>
+            <span>Contents A</span>
+            <Popover data-testid="popover-b">
+              <Popover.Button>Toggle B</Popover.Button>
+              <Popover.Panel>
+                <span>Contents B</span>
+              </Popover.Panel>
+            </Popover>
+          </Popover.Panel>
+        </Popover>
+      )
+
+      // Verify that Popover B is not there yet
+      expect(document.querySelector('[data-testid="popover-b"]')).toBeNull()
+
+      // Open Popover A
+      await click(getByText('Toggle A'))
+
+      // Ensure Popover A is visible
+      assertPopoverPanel(
+        { state: PopoverState.Visible },
+        document.querySelector(
+          '[data-testid="popover-a"] [id^="headlessui-popover-panel-"]'
+        ) as HTMLElement
+      )
+
+      // Ensure Popover B is visible
+      assertPopoverPanel(
+        { state: PopoverState.InvisibleUnmounted },
+        document.querySelector(
+          '[data-testid="popover-b"] [id^="headlessui-popover-panel-"]'
+        ) as HTMLElement
+      )
+
+      // Open Popover B
+      await click(getByText('Toggle B'))
+
+      // Ensure both popovers are open
+      assertPopoverPanel(
+        { state: PopoverState.Visible },
+        document.querySelector(
+          '[data-testid="popover-a"] [id^="headlessui-popover-panel-"]'
+        ) as HTMLElement
+      )
+      assertPopoverPanel(
+        { state: PopoverState.Visible },
+        document.querySelector(
+          '[data-testid="popover-b"] [id^="headlessui-popover-panel-"]'
+        ) as HTMLElement
+      )
+    })
+  )
+})
diff --git a/packages/@headlessui-vue/src/components/popover/popover.test.ts b/packages/@headlessui-vue/src/components/popover/popover.test.ts
index f0484606cd..825f2b33b4 100644
--- a/packages/@headlessui-vue/src/components/popover/popover.test.ts
+++ b/packages/@headlessui-vue/src/components/popover/popover.test.ts
@@ -2574,3 +2574,64 @@ describe('Mouse interactions', () => {
     })
   )
 })
+
+describe('Nested popovers', () => {
+  it(
+    'should be possible to nest Popover components and control them individually',
+    suppressConsoleLogs(async () => {
+      renderTemplate(html`
+        <Popover data-testid="popover-a">
+          <PopoverButton>Toggle A</PopoverButton>
+          <PopoverPanel>
+            <span>Contents A</span>
+            <Popover data-testid="popover-b">
+              <PopoverButton>Toggle B</PopoverButton>
+              <PopoverPanel>
+                <span>Contents B</span>
+              </PopoverPanel>
+            </Popover>
+          </PopoverPanel>
+        </Popover>
+      `)
+
+      // Verify that Popover B is not there yet
+      expect(document.querySelector('[data-testid="popover-b"]')).toBeNull()
+
+      // Open Popover A
+      await click(getByText('Toggle A'))
+
+      // Ensure Popover A is visible
+      assertPopoverPanel(
+        { state: PopoverState.Visible },
+        document.querySelector(
+          '[data-testid="popover-a"] [id^="headlessui-popover-panel-"]'
+        ) as HTMLElement
+      )
+
+      // Ensure Popover B is visible
+      assertPopoverPanel(
+        { state: PopoverState.InvisibleUnmounted },
+        document.querySelector(
+          '[data-testid="popover-b"] [id^="headlessui-popover-panel-"]'
+        ) as HTMLElement
+      )
+
+      // Open Popover B
+      await click(getByText('Toggle B'))
+
+      // Ensure both popovers are open
+      assertPopoverPanel(
+        { state: PopoverState.Visible },
+        document.querySelector(
+          '[data-testid="popover-a"] [id^="headlessui-popover-panel-"]'
+        ) as HTMLElement
+      )
+      assertPopoverPanel(
+        { state: PopoverState.Visible },
+        document.querySelector(
+          '[data-testid="popover-b"] [id^="headlessui-popover-panel-"]'
+        ) as HTMLElement
+      )
+    })
+  )
+})