Skip to content

Commit 958e3ea

Browse files
committed
bug fixes (#261)
* apply re-focus bug fix to Popover * force focus in Menu.Items from within Menu.Items component itself * force focus in Listbox.Options from within Listbox.Options component itself * fix undefined values in id's We were setting the element in state, but updates to the id were not taken into account * update the caniuse db * ensure useInertOthers works in multiple places Previously each hook call would take care of the whole tree. However when multiple calls to this hook are happening we need to make sure that you are not removing the aria-hidden when another hook is still used. This will fix that by keeping track of a list of "interactable" items, and updating the parents (root of the body) accordingly. * add the concept of a Stack When you are rendering a Dialog, we will make sure that this Dialog is rendered inside a Portal. However, when you are also rendering a Menu, there is a chance that your Menu doesn't fit within the Dialog, therefore you will likely render the Menu.Items inside a Portal so that you can style it as if it is rendered inside but overflows the Dialog correctly. This introduces an interesting/annoying problem. Your Menu.Items are now rendered in a Portal, as a *sibling* to the Dialog. This means that autoFocus, focusTrap, ... all these features don't work as expected. Introducing this Stack will allow us to register DOM nodes into a list of contains that we consider being part of the main container. In other words, the sibling Menu.Items will now be considered part of the Dialog. Even though it is rendered *outside* of the Dialog. This concept also allows for some fun stuff, for example, nesting Dialog's is no problem with this approach. Dialogs are technically rendered as siblings in the Portal, but the FocusTrap, and all that just works as expected. * capture keyboard events in the capturing phase This will allow us to use event.stopPropagation() in the code (which will be required, probably) but still see the keystrokes in the playground. * stop propagating keyboard events This looks a bit silly, and ideally we can solve this in a more elegant way. However when you nest a Menu inside a Dialog, both of those components have a `close on escape` functionality built in. However when your Menu is open, and you press escape, you only want to close the Menu, not the Dialog. Therefore if we `event.stopPropagation()` it allows us to stop the `escape` keystroke in the Menu from reaching all the way to the Dialog itself. * update Dialog example that showcases nested Dialogs, and nested Menu * update changelog
1 parent f429111 commit 958e3ea

File tree

16 files changed

+801
-137
lines changed

16 files changed

+801
-137
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
- Fixed `outside click` not re-focusing the `Menu.Button` ([#220](https://github.com/tailwindlabs/headlessui/pull/220), [#256](https://github.com/tailwindlabs/headlessui/pull/256))
1313
- Fixed `outside click` not re-focusing the `Listbox.Button` ([#220](https://github.com/tailwindlabs/headlessui/pull/220), [#256](https://github.com/tailwindlabs/headlessui/pull/256))
14+
- Fixed `outside click` not re-focusing the `Popover` ([#261](https://github.com/tailwindlabs/headlessui/pull/220), [#256](https://github.com/tailwindlabs/headlessui/pull/261))
1415
- Fix incorrect type error `unique symbol` ([#248](https://github.com/tailwindlabs/headlessui/pull/248), [#240](https://github.com/tailwindlabs/headlessui/issues/240))
16+
- Force focus in `Menu.Items` and `Listbox.Options` from within the component itself ([#261](https://github.com/tailwindlabs/headlessui/pull/261))
17+
- Fix `undefined` values in id's for the `Dialog` component ([#261](https://github.com/tailwindlabs/headlessui/pull/261))
18+
- Ensue `useInertOthers` works when used in a shared parent ([#261](https://github.com/tailwindlabs/headlessui/pull/261))
19+
- Stop propagating keyboard/mouse events ([#261](https://github.com/tailwindlabs/headlessui/pull/261))
1520

1621
### Added
1722

packages/@headlessui-react/pages/_app.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,8 @@ function KeyCaster() {
8585
d.setTimeout(() => setKeys(current => tap(current.slice(), clone => clone.pop())), 2000)
8686
}
8787

88-
window.addEventListener('keydown', handler)
89-
return () => window.removeEventListener('keydown', handler)
88+
window.addEventListener('keydown', handler, true)
89+
return () => window.removeEventListener('keydown', handler, true)
9090
}, [d, KeyDisplay])
9191

9292
if (keys.length <= 0) return null

packages/@headlessui-react/pages/dialog/dialog.tsx

Lines changed: 150 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,60 @@
11
import React, { useState, Fragment } from 'react'
2-
import { Dialog, Transition } from '@headlessui/react'
2+
import { Dialog, Menu, Portal, Transition } from '@headlessui/react'
3+
import { usePopper } from '../../playground-utils/hooks/use-popper'
4+
5+
function classNames(...classes) {
6+
return classes.filter(Boolean).join(' ')
7+
}
8+
9+
function resolveClass({ active, disabled }) {
10+
return classNames(
11+
'flex justify-between w-full px-4 py-2 text-sm leading-5 text-left',
12+
active ? 'bg-gray-100 text-gray-900' : 'text-gray-700',
13+
disabled && 'cursor-not-allowed opacity-50'
14+
)
15+
}
16+
17+
function Nested({ onClose, level = 0 }) {
18+
let [showChild, setShowChild] = useState(false)
19+
20+
return (
21+
<>
22+
<Dialog open={true} onClose={onClose} className="fixed z-10 inset-0 pointer-events-none">
23+
{true && <Dialog.Overlay className="fixed inset-0 bg-gray-500 opacity-25" />}
24+
<div
25+
className="z-10 fixed left-12 top-24 bg-white w-96 p-4 pointer-events-auto"
26+
style={{
27+
transform: `translate(calc(50px * ${level}), calc(50px * ${level}))`,
28+
}}
29+
>
30+
<p>Level: {level}</p>
31+
<div className="space-x-4">
32+
<button className="bg-gray-200 px-2 py-1 rounded" onClick={() => setShowChild(true)}>
33+
Open (1)
34+
</button>
35+
<button className="bg-gray-200 px-2 py-1 rounded" onClick={() => setShowChild(true)}>
36+
Open (2)
37+
</button>
38+
<button className="bg-gray-200 px-2 py-1 rounded" onClick={() => setShowChild(true)}>
39+
Open (3)
40+
</button>
41+
</div>
42+
</div>
43+
{showChild && <Nested onClose={() => setShowChild(false)} level={level + 1} />}
44+
</Dialog>
45+
</>
46+
)
47+
}
348

449
export default function Home() {
550
let [isOpen, setIsOpen] = useState(false)
51+
let [nested, setNested] = useState(false)
52+
53+
let [trigger, container] = usePopper({
54+
placement: 'bottom-end',
55+
strategy: 'fixed',
56+
modifiers: [{ name: 'offset', options: { offset: [0, 10] } }],
57+
})
658

759
return (
860
<>
@@ -14,6 +66,9 @@ export default function Home() {
1466
Toggle!
1567
</button>
1668

69+
<button onClick={() => setNested(true)}>Show nested</button>
70+
{nested && <Nested onClose={() => setNested(false)} />}
71+
1772
<Transition show={isOpen} as={Fragment}>
1873
<Dialog open={isOpen} onClose={setIsOpen} static>
1974
<div className="fixed z-10 inset-0 overflow-y-auto">
@@ -79,6 +134,100 @@ export default function Home() {
79134
Are you sure you want to deactivate your account? All of your data will
80135
be permanently removed. This action cannot be undone.
81136
</p>
137+
<div className="relative inline-block text-left mt-10">
138+
<Menu>
139+
{({ open }) => (
140+
<>
141+
<span className="rounded-md shadow-sm">
142+
<Menu.Button
143+
ref={trigger}
144+
className="inline-flex justify-center w-full px-4 py-2 text-sm font-medium leading-5 text-gray-700 transition duration-150 ease-in-out bg-white border border-gray-300 rounded-md hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-50 active:text-gray-800"
145+
>
146+
<span>Choose a reason</span>
147+
<svg
148+
className="w-5 h-5 ml-2 -mr-1"
149+
viewBox="0 0 20 20"
150+
fill="currentColor"
151+
>
152+
<path
153+
fillRule="evenodd"
154+
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
155+
clipRule="evenodd"
156+
/>
157+
</svg>
158+
</Menu.Button>
159+
</span>
160+
161+
<Transition
162+
show={open}
163+
enter="transition duration-100 ease-out"
164+
enterFrom="transform scale-95 opacity-0"
165+
enterTo="transform scale-100 opacity-100"
166+
leave="transition duration-75 ease-out"
167+
leaveFrom="transform scale-100 opacity-100"
168+
leaveTo="transform scale-95 opacity-0"
169+
>
170+
<Portal>
171+
<Menu.Items
172+
ref={container}
173+
static
174+
className="z-20 w-56 mt-2 origin-top-right bg-white border border-gray-200 divide-y divide-gray-100 rounded-md shadow-lg outline-none"
175+
>
176+
<div className="px-4 py-3">
177+
<p className="text-sm leading-5">Signed in as</p>
178+
<p className="text-sm font-medium leading-5 text-gray-900 truncate">
179+
180+
</p>
181+
</div>
182+
183+
<div className="py-1">
184+
<Menu.Item
185+
as="a"
186+
href="#account-settings"
187+
className={resolveClass}
188+
>
189+
Account settings
190+
</Menu.Item>
191+
<Menu.Item
192+
as="a"
193+
href="#support"
194+
className={resolveClass}
195+
>
196+
Support
197+
</Menu.Item>
198+
<Menu.Item
199+
as="a"
200+
disabled
201+
href="#new-feature"
202+
className={resolveClass}
203+
>
204+
New feature (soon)
205+
</Menu.Item>
206+
<Menu.Item
207+
as="a"
208+
href="#license"
209+
className={resolveClass}
210+
>
211+
License
212+
</Menu.Item>
213+
</div>
214+
215+
<div className="py-1">
216+
<Menu.Item
217+
as="a"
218+
href="#sign-out"
219+
className={resolveClass}
220+
>
221+
Sign out
222+
</Menu.Item>
223+
</div>
224+
</Menu.Items>
225+
</Portal>
226+
</Transition>
227+
</>
228+
)}
229+
</Menu>
230+
</div>
82231
</div>
83232
</div>
84233
</div>

0 commit comments

Comments
 (0)