Skip to content

Commit ba1bd52

Browse files
authored
Expose close functionality via render prop (#697)
* expose a `close` function via the render prop for the `Popover` and `Popover.Panel` components (React) * expose a `close` function via the render prop for the `Disclosure` and `Disclosure.Panel` components (React) * expose a `close` function via the render prop for the `Popover` and `PopoverPanel` components (Vue) * expose a `close` function via the render prop for the `Disclosure` and `DisclosurePanel` components (Vue)
1 parent e830338 commit ba1bd52

File tree

8 files changed

+1062
-43
lines changed

8 files changed

+1062
-43
lines changed

packages/@headlessui-react/src/components/disclosure/disclosure.test.tsx

Lines changed: 231 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { createElement, useEffect } from 'react'
1+
import React, { createElement, useEffect, useRef } from 'react'
22
import { render } from '@testing-library/react'
33

44
import { Disclosure } from './disclosure'
@@ -115,6 +115,127 @@ describe('Rendering', () => {
115115

116116
assertDisclosureButton({ state: DisclosureState.InvisibleUnmounted })
117117
})
118+
119+
it(
120+
'should expose a close function that closes the disclosure',
121+
suppressConsoleLogs(async () => {
122+
render(
123+
<Disclosure>
124+
{({ close }) => (
125+
<>
126+
<Disclosure.Button>Trigger</Disclosure.Button>
127+
<Disclosure.Panel>
128+
<button onClick={() => close()}>Close me</button>
129+
</Disclosure.Panel>
130+
</>
131+
)}
132+
</Disclosure>
133+
)
134+
135+
// Focus the button
136+
getDisclosureButton()?.focus()
137+
138+
// Ensure the button is focused
139+
assertActiveElement(getDisclosureButton())
140+
141+
// Open the disclosure
142+
await click(getDisclosureButton())
143+
144+
// Ensure we can click the close button
145+
await click(getByText('Close me'))
146+
147+
// Ensure the disclosure is closed
148+
assertDisclosurePanel({ state: DisclosureState.InvisibleUnmounted })
149+
150+
// Ensure the Disclosure.Button got the restored focus
151+
assertActiveElement(getByText('Trigger'))
152+
})
153+
)
154+
155+
it(
156+
'should expose a close function that closes the disclosure and restores to a specific element',
157+
suppressConsoleLogs(async () => {
158+
render(
159+
<>
160+
<button id="test">restoreable</button>
161+
<Disclosure>
162+
{({ close }) => (
163+
<>
164+
<Disclosure.Button>Trigger</Disclosure.Button>
165+
<Disclosure.Panel>
166+
<button onClick={() => close(document.getElementById('test')!)}>
167+
Close me
168+
</button>
169+
</Disclosure.Panel>
170+
</>
171+
)}
172+
</Disclosure>
173+
</>
174+
)
175+
176+
// Focus the button
177+
getDisclosureButton()?.focus()
178+
179+
// Ensure the button is focused
180+
assertActiveElement(getDisclosureButton())
181+
182+
// Open the disclosure
183+
await click(getDisclosureButton())
184+
185+
// Ensure we can click the close button
186+
await click(getByText('Close me'))
187+
188+
// Ensure the disclosure is closed
189+
assertDisclosurePanel({ state: DisclosureState.InvisibleUnmounted })
190+
191+
// Ensure the restoreable button got the restored focus
192+
assertActiveElement(getByText('restoreable'))
193+
})
194+
)
195+
196+
it(
197+
'should expose a close function that closes the disclosure and restores to a ref',
198+
suppressConsoleLogs(async () => {
199+
function Example() {
200+
let elementRef = useRef(null)
201+
return (
202+
<>
203+
<button ref={elementRef}>restoreable</button>
204+
<Disclosure>
205+
{({ close }) => (
206+
<>
207+
<Disclosure.Button>Trigger</Disclosure.Button>
208+
<Disclosure.Panel>
209+
<button onClick={() => close(elementRef)}>Close me</button>
210+
</Disclosure.Panel>
211+
</>
212+
)}
213+
</Disclosure>
214+
</>
215+
)
216+
}
217+
218+
render(<Example />)
219+
220+
// Focus the button
221+
getDisclosureButton()?.focus()
222+
223+
// Ensure the button is focused
224+
assertActiveElement(getDisclosureButton())
225+
226+
// Open the disclosure
227+
await click(getDisclosureButton())
228+
229+
// Ensure we can click the close button
230+
await click(getByText('Close me'))
231+
232+
// Ensure the disclosure is closed
233+
assertDisclosurePanel({ state: DisclosureState.InvisibleUnmounted })
234+
235+
// Ensure the restoreable button got the restored focus
236+
assertActiveElement(getByText('restoreable'))
237+
})
238+
)
118239
})
119240

120241
describe('Disclosure.Button', () => {
@@ -242,6 +363,115 @@ describe('Rendering', () => {
242363
assertDisclosureButton({ state: DisclosureState.InvisibleHidden })
243364
assertDisclosurePanel({ state: DisclosureState.InvisibleHidden })
244365
})
366+
367+
it(
368+
'should expose a close function that closes the disclosure',
369+
suppressConsoleLogs(async () => {
370+
render(
371+
<Disclosure>
372+
<Disclosure.Button>Trigger</Disclosure.Button>
373+
<Disclosure.Panel>
374+
{({ close }) => <button onClick={() => close()}>Close me</button>}
375+
</Disclosure.Panel>
376+
</Disclosure>
377+
)
378+
379+
// Focus the button
380+
getDisclosureButton()?.focus()
381+
382+
// Ensure the button is focused
383+
assertActiveElement(getDisclosureButton())
384+
385+
// Open the disclosure
386+
await click(getDisclosureButton())
387+
388+
// Ensure we can click the close button
389+
await click(getByText('Close me'))
390+
391+
// Ensure the disclosure is closed
392+
assertDisclosurePanel({ state: DisclosureState.InvisibleUnmounted })
393+
394+
// Ensure the Disclosure.Button got the restored focus
395+
assertActiveElement(getByText('Trigger'))
396+
})
397+
)
398+
399+
it(
400+
'should expose a close function that closes the disclosure and restores to a specific element',
401+
suppressConsoleLogs(async () => {
402+
render(
403+
<>
404+
<button id="test">restoreable</button>
405+
<Disclosure>
406+
<Disclosure.Button>Trigger</Disclosure.Button>
407+
<Disclosure.Panel>
408+
{({ close }) => (
409+
<button onClick={() => close(document.getElementById('test')!)}>Close me</button>
410+
)}
411+
</Disclosure.Panel>
412+
</Disclosure>
413+
</>
414+
)
415+
416+
// Focus the button
417+
getDisclosureButton()?.focus()
418+
419+
// Ensure the button is focused
420+
assertActiveElement(getDisclosureButton())
421+
422+
// Open the disclosure
423+
await click(getDisclosureButton())
424+
425+
// Ensure we can click the close button
426+
await click(getByText('Close me'))
427+
428+
// Ensure the disclosure is closed
429+
assertDisclosurePanel({ state: DisclosureState.InvisibleUnmounted })
430+
431+
// Ensure the restoreable button got the restored focus
432+
assertActiveElement(getByText('restoreable'))
433+
})
434+
)
435+
436+
it(
437+
'should expose a close function that closes the disclosure and restores to a ref',
438+
suppressConsoleLogs(async () => {
439+
function Example() {
440+
let elementRef = useRef(null)
441+
return (
442+
<>
443+
<button ref={elementRef}>restoreable</button>
444+
<Disclosure>
445+
<Disclosure.Button>Trigger</Disclosure.Button>
446+
<Disclosure.Panel>
447+
{({ close }) => <button onClick={() => close(elementRef)}>Close me</button>}
448+
</Disclosure.Panel>
449+
</Disclosure>
450+
</>
451+
)
452+
}
453+
454+
render(<Example />)
455+
456+
// Focus the button
457+
getDisclosureButton()?.focus()
458+
459+
// Ensure the button is focused
460+
assertActiveElement(getDisclosureButton())
461+
462+
// Open the disclosure
463+
await click(getDisclosureButton())
464+
465+
// Ensure we can click the close button
466+
await click(getByText('Close me'))
467+
468+
// Ensure the disclosure is closed
469+
assertDisclosurePanel({ state: DisclosureState.InvisibleUnmounted })
470+
471+
// Ensure the restoreable button got the restored focus
472+
assertActiveElement(getByText('restoreable'))
473+
})
474+
)
245475
})
246476
})
247477

0 commit comments

Comments
 (0)