-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Multiple new components #220
Conversation
This pull request is being automatically deployed with Vercel (learn more). headlessui-react – ./packages/@headlessui-react🔍 Inspect: https://vercel.com/tailwindlabs/headlessui-react/q8qu5m3kp headlessui-vue – ./packages/@headlessui-vue🔍 Inspect: https://vercel.com/tailwindlabs/headlessui-vue/p4r4zyvz3 |
3dd74c1
to
07a8363
Compare
07a8363
to
7fce110
Compare
7fce110
to
95c8cc7
Compare
95c8cc7
to
53942cf
Compare
53942cf
to
3610a48
Compare
3610a48
to
03d412f
Compare
03d412f
to
35a97cf
Compare
35a97cf
to
1da8e49
Compare
d21d1eb
to
2739234
Compare
2739234
to
0235587
Compare
0235587
to
2aeaf1a
Compare
* add Disclosure component * expose the Disclosure component * add Disclosure example component page * temporary fix selector because of JSDOM bug * add useFocusTrap hook * add FocusTrap component * expose FocusTrap * add Dialog component * add Dialog example component page * expose Dialog * random cleanup * make TypeScript a bit more happy * add Switch.Description component for React * add Switch.Description component for Vue * ensure focus event is triggered on click when element is focusable * remove Dialog.Button and Dialog.Panel from accessibility assertions * add Portal component * expose Portal * always render Dialog in a Portal * add useInertOthers hook This will allow us to mark everything but the current ref as "inert". This is important for screenreaders, to ensure that screenreaders and assistive technology can't interact with other content but the current ref. This implementation is not ideal yet. It doesn't take into account that you can use the hook in 2 different components. For now this is fine, since we only use it in a Dialog and you should also probably only have a single Dialog open at a time. Will improve this in the future! * use the useInertOthers hook * add scroll lock to the dialog * ensure we respect autoFocus on form elements within the Dialog If we have an autoFocus on an input, that input will receive focus. Once we try to focus the first focusable element in the Dialog this could be lead to unwanted behaviour. Therefore we check if the focus already is within the Dialog, if it is, keep it like that. * only mark aria-modal when Dialog is open * add initialFocus option to Dialog, FocusTrap & useFocusTrap * add tests and a few fixes for the initialFocusRef functionality * forward ref to underlying Dialog component * close Dialog when it becomes hidden Could happen when this is in md:hidden for example * prevent infinite loop When we `Tab` in a FocusTrap it will try and focus the Next element. If we are in a state where none of the elements inside the FocusTrap can be focused, then we keep trying to focus the next one in line. This results in an infinite loop... To mitigate this issue, we check if we looped around, if we did, it means that we tried all the other focusable elements, therefore we can stop. * isIntersecting doesn't work in every scenario When page is scrollable, when dialog is translated of the page. Now just checking for sizes, which should be enough for md:hiden cases * render Portal contents in a div Otherwise you can't use multiple Portal components if you render multiple children inside each Portal * ensure the props bag is typed * add getByText and assertContainsActiveElement helpers * add Popover component * expose Popover * add Popover example component page * add quick checks to prevent useless renders * drop incorrect close function * update Changelog * make test error more readable when comparing DOM nodes * actually call .focus() on the element This ensures that the document.activeElement becomes the focused element. * improve useSyncRefs, because ...refs is *always* different * add dedicated focus management utilities * refactor useFocusTrap, use focus management utilities * fix regression while using outside click There might be a chance that you didn't even notice this *bug*. The idea is that when you click outside, that the Menu or Listbox closes. However there is another step that happens: 1. When you click on a focusable item, keep the focus on that item. 2. When you click on a non-focusable item, move focus back to the Menu.Button or Listbox.Button We broke part 2, we never returned to the Menu.Button or Listbox.Button. This is (might) be important for screenreaders so that they don't "get lost", because if you click on a non-focusable item, the document.body becomes the active element. Confusing. * add outside-click to Dialog itself * update docs
This looks very promising! If the work isn't quite ready and you can spare the time, do you think it would be possible to release a beta on npm? |
@Zertz You can already try it using |
* add Disclosure component * expose the Disclosure component * add Disclosure example component page * temporary fix selector because of JSDOM bug * add useFocusTrap hook * add FocusTrap component * expose FocusTrap * add Dialog component * add Dialog example component page * expose Dialog * random cleanup * make TypeScript a bit more happy * add Switch.Description component for React * add Switch.Description component for Vue * ensure focus event is triggered on click when element is focusable * remove Dialog.Button and Dialog.Panel from accessibility assertions * add Portal component * expose Portal * always render Dialog in a Portal * add useInertOthers hook This will allow us to mark everything but the current ref as "inert". This is important for screenreaders, to ensure that screenreaders and assistive technology can't interact with other content but the current ref. This implementation is not ideal yet. It doesn't take into account that you can use the hook in 2 different components. For now this is fine, since we only use it in a Dialog and you should also probably only have a single Dialog open at a time. Will improve this in the future! * use the useInertOthers hook * add scroll lock to the dialog * ensure we respect autoFocus on form elements within the Dialog If we have an autoFocus on an input, that input will receive focus. Once we try to focus the first focusable element in the Dialog this could be lead to unwanted behaviour. Therefore we check if the focus already is within the Dialog, if it is, keep it like that. * only mark aria-modal when Dialog is open * add initialFocus option to Dialog, FocusTrap & useFocusTrap * add tests and a few fixes for the initialFocusRef functionality * forward ref to underlying Dialog component * close Dialog when it becomes hidden Could happen when this is in md:hidden for example * prevent infinite loop When we `Tab` in a FocusTrap it will try and focus the Next element. If we are in a state where none of the elements inside the FocusTrap can be focused, then we keep trying to focus the next one in line. This results in an infinite loop... To mitigate this issue, we check if we looped around, if we did, it means that we tried all the other focusable elements, therefore we can stop. * isIntersecting doesn't work in every scenario When page is scrollable, when dialog is translated of the page. Now just checking for sizes, which should be enough for md:hiden cases * render Portal contents in a div Otherwise you can't use multiple Portal components if you render multiple children inside each Portal * ensure the props bag is typed * add getByText and assertContainsActiveElement helpers * add Popover component * expose Popover * add Popover example component page * add quick checks to prevent useless renders * drop incorrect close function * update Changelog * make test error more readable when comparing DOM nodes * actually call .focus() on the element This ensures that the document.activeElement becomes the focused element. * improve useSyncRefs, because ...refs is *always* different * add dedicated focus management utilities * refactor useFocusTrap, use focus management utilities * fix regression while using outside click There might be a chance that you didn't even notice this *bug*. The idea is that when you click outside, that the Menu or Listbox closes. However there is another step that happens: 1. When you click on a focusable item, keep the focus on that item. 2. When you click on a non-focusable item, move focus back to the Menu.Button or Listbox.Button We broke part 2, we never returned to the Menu.Button or Listbox.Button. This is (might) be important for screenreaders so that they don't "get lost", because if you click on a non-focusable item, the document.body becomes the active element. Confusing. * add outside-click to Dialog itself * update docs
* add Disclosure component * expose the Disclosure component * add Disclosure example component page * temporary fix selector because of JSDOM bug * add useFocusTrap hook * add FocusTrap component * expose FocusTrap * add Dialog component * add Dialog example component page * expose Dialog * random cleanup * make TypeScript a bit more happy * add Switch.Description component for React * add Switch.Description component for Vue * ensure focus event is triggered on click when element is focusable * remove Dialog.Button and Dialog.Panel from accessibility assertions * add Portal component * expose Portal * always render Dialog in a Portal * add useInertOthers hook This will allow us to mark everything but the current ref as "inert". This is important for screenreaders, to ensure that screenreaders and assistive technology can't interact with other content but the current ref. This implementation is not ideal yet. It doesn't take into account that you can use the hook in 2 different components. For now this is fine, since we only use it in a Dialog and you should also probably only have a single Dialog open at a time. Will improve this in the future! * use the useInertOthers hook * add scroll lock to the dialog * ensure we respect autoFocus on form elements within the Dialog If we have an autoFocus on an input, that input will receive focus. Once we try to focus the first focusable element in the Dialog this could be lead to unwanted behaviour. Therefore we check if the focus already is within the Dialog, if it is, keep it like that. * only mark aria-modal when Dialog is open * add initialFocus option to Dialog, FocusTrap & useFocusTrap * add tests and a few fixes for the initialFocusRef functionality * forward ref to underlying Dialog component * close Dialog when it becomes hidden Could happen when this is in md:hidden for example * prevent infinite loop When we `Tab` in a FocusTrap it will try and focus the Next element. If we are in a state where none of the elements inside the FocusTrap can be focused, then we keep trying to focus the next one in line. This results in an infinite loop... To mitigate this issue, we check if we looped around, if we did, it means that we tried all the other focusable elements, therefore we can stop. * isIntersecting doesn't work in every scenario When page is scrollable, when dialog is translated of the page. Now just checking for sizes, which should be enough for md:hiden cases * render Portal contents in a div Otherwise you can't use multiple Portal components if you render multiple children inside each Portal * ensure the props bag is typed * add getByText and assertContainsActiveElement helpers * add Popover component * expose Popover * add Popover example component page * add quick checks to prevent useless renders * drop incorrect close function * update Changelog * make test error more readable when comparing DOM nodes * actually call .focus() on the element This ensures that the document.activeElement becomes the focused element. * improve useSyncRefs, because ...refs is *always* different * add dedicated focus management utilities * refactor useFocusTrap, use focus management utilities * fix regression while using outside click There might be a chance that you didn't even notice this *bug*. The idea is that when you click outside, that the Menu or Listbox closes. However there is another step that happens: 1. When you click on a focusable item, keep the focus on that item. 2. When you click on a non-focusable item, move focus back to the Menu.Button or Listbox.Button We broke part 2, we never returned to the Menu.Button or Listbox.Button. This is (might) be important for screenreaders so that they don't "get lost", because if you click on a non-focusable item, the document.body becomes the active element. Confusing. * add outside-click to Dialog itself * update docs
* add Disclosure component * expose the Disclosure component * add Disclosure example component page * temporary fix selector because of JSDOM bug * add useFocusTrap hook * add FocusTrap component * expose FocusTrap * add Dialog component * add Dialog example component page * expose Dialog * random cleanup * make TypeScript a bit more happy * add Switch.Description component for React * add Switch.Description component for Vue * ensure focus event is triggered on click when element is focusable * remove Dialog.Button and Dialog.Panel from accessibility assertions * add Portal component * expose Portal * always render Dialog in a Portal * add useInertOthers hook This will allow us to mark everything but the current ref as "inert". This is important for screenreaders, to ensure that screenreaders and assistive technology can't interact with other content but the current ref. This implementation is not ideal yet. It doesn't take into account that you can use the hook in 2 different components. For now this is fine, since we only use it in a Dialog and you should also probably only have a single Dialog open at a time. Will improve this in the future! * use the useInertOthers hook * add scroll lock to the dialog * ensure we respect autoFocus on form elements within the Dialog If we have an autoFocus on an input, that input will receive focus. Once we try to focus the first focusable element in the Dialog this could be lead to unwanted behaviour. Therefore we check if the focus already is within the Dialog, if it is, keep it like that. * only mark aria-modal when Dialog is open * add initialFocus option to Dialog, FocusTrap & useFocusTrap * add tests and a few fixes for the initialFocusRef functionality * forward ref to underlying Dialog component * close Dialog when it becomes hidden Could happen when this is in md:hidden for example * prevent infinite loop When we `Tab` in a FocusTrap it will try and focus the Next element. If we are in a state where none of the elements inside the FocusTrap can be focused, then we keep trying to focus the next one in line. This results in an infinite loop... To mitigate this issue, we check if we looped around, if we did, it means that we tried all the other focusable elements, therefore we can stop. * isIntersecting doesn't work in every scenario When page is scrollable, when dialog is translated of the page. Now just checking for sizes, which should be enough for md:hiden cases * render Portal contents in a div Otherwise you can't use multiple Portal components if you render multiple children inside each Portal * ensure the props bag is typed * add getByText and assertContainsActiveElement helpers * add Popover component * expose Popover * add Popover example component page * add quick checks to prevent useless renders * drop incorrect close function * update Changelog * make test error more readable when comparing DOM nodes * actually call .focus() on the element This ensures that the document.activeElement becomes the focused element. * improve useSyncRefs, because ...refs is *always* different * add dedicated focus management utilities * refactor useFocusTrap, use focus management utilities * fix regression while using outside click There might be a chance that you didn't even notice this *bug*. The idea is that when you click outside, that the Menu or Listbox closes. However there is another step that happens: 1. When you click on a focusable item, keep the focus on that item. 2. When you click on a non-focusable item, move focus back to the Menu.Button or Listbox.Button We broke part 2, we never returned to the Menu.Button or Listbox.Button. This is (might) be important for screenreaders so that they don't "get lost", because if you click on a non-focusable item, the document.body becomes the active element. Confusing. * add outside-click to Dialog itself * update docs
* add Disclosure component * expose the Disclosure component * add Disclosure example component page * temporary fix selector because of JSDOM bug * add useFocusTrap hook * add FocusTrap component * expose FocusTrap * add Dialog component * add Dialog example component page * expose Dialog * random cleanup * make TypeScript a bit more happy * add Switch.Description component for React * add Switch.Description component for Vue * ensure focus event is triggered on click when element is focusable * remove Dialog.Button and Dialog.Panel from accessibility assertions * add Portal component * expose Portal * always render Dialog in a Portal * add useInertOthers hook This will allow us to mark everything but the current ref as "inert". This is important for screenreaders, to ensure that screenreaders and assistive technology can't interact with other content but the current ref. This implementation is not ideal yet. It doesn't take into account that you can use the hook in 2 different components. For now this is fine, since we only use it in a Dialog and you should also probably only have a single Dialog open at a time. Will improve this in the future! * use the useInertOthers hook * add scroll lock to the dialog * ensure we respect autoFocus on form elements within the Dialog If we have an autoFocus on an input, that input will receive focus. Once we try to focus the first focusable element in the Dialog this could be lead to unwanted behaviour. Therefore we check if the focus already is within the Dialog, if it is, keep it like that. * only mark aria-modal when Dialog is open * add initialFocus option to Dialog, FocusTrap & useFocusTrap * add tests and a few fixes for the initialFocusRef functionality * forward ref to underlying Dialog component * close Dialog when it becomes hidden Could happen when this is in md:hidden for example * prevent infinite loop When we `Tab` in a FocusTrap it will try and focus the Next element. If we are in a state where none of the elements inside the FocusTrap can be focused, then we keep trying to focus the next one in line. This results in an infinite loop... To mitigate this issue, we check if we looped around, if we did, it means that we tried all the other focusable elements, therefore we can stop. * isIntersecting doesn't work in every scenario When page is scrollable, when dialog is translated of the page. Now just checking for sizes, which should be enough for md:hiden cases * render Portal contents in a div Otherwise you can't use multiple Portal components if you render multiple children inside each Portal * ensure the props bag is typed * add getByText and assertContainsActiveElement helpers * add Popover component * expose Popover * add Popover example component page * add quick checks to prevent useless renders * drop incorrect close function * update Changelog * make test error more readable when comparing DOM nodes * actually call .focus() on the element This ensures that the document.activeElement becomes the focused element. * improve useSyncRefs, because ...refs is *always* different * add dedicated focus management utilities * refactor useFocusTrap, use focus management utilities * fix regression while using outside click There might be a chance that you didn't even notice this *bug*. The idea is that when you click outside, that the Menu or Listbox closes. However there is another step that happens: 1. When you click on a focusable item, keep the focus on that item. 2. When you click on a non-focusable item, move focus back to the Menu.Button or Listbox.Button We broke part 2, we never returned to the Menu.Button or Listbox.Button. This is (might) be important for screenreaders so that they don't "get lost", because if you click on a non-focusable item, the document.body becomes the active element. Confusing. * add outside-click to Dialog itself * update docs
This PR adds a few new components:
aria-describedby
to theSwitch
component)This PR also has a few fixes and perforamnce improvements:
useReducer
a lot, and state updates were always causing re-renders. Now we will only re-render when something actually changed. This change has not been applied to all components yet, but didn't want to bloat the scope to much in this very big PRMenu
andListbox
componentsuseSyncRefs
, we created a new callback when therefs
changed, but they changed alwaysfocus management
Disclosure
componentA component for showing/hiding content.
Disclosure
Props
as
React.Fragment
(no wrapper element)Disclosure
should render as.Render prop object
open
Disclosure.Button
Props
as
button
Disclosure.Button
should render as.Render prop object
open
Disclosure.Panel
Props
as
div
Disclosure.Panel
should render as.static
false
unmount
true
Render prop object
open
FocusTrap
componentA component for making sure that you can't Tab out of the contents of this
component.
Focus strategy:
initialFocus
prop can be passed in, this is aref
object, which is a ref to the element that should receive initial focus.autoFocus
prop, it will receive initial focus.Once the
FocusTrap
will unmount, the focus will be restored to the element that was focused before theFocusTrap
was rendered.FocusTrap
Props
as
div
FocusTrap
should render as.initialFocus
undefined
Portal
componentA component for rendering your contents within a Portal (at the end of
document.body
).Portal
Dialog
componentThis component can be used to render content inside a Dialog/Modal. This contains a ton of features:
Portal
FocusTrap
with its features (Focus first focusable element,autoFocus
orinitialFocus
ref)inert
(hides other elements from screen readers)escape
md:hidden
) it will also trigger theonClose
Dialog
Props
open
Dialog
is open or not.onClose
Dialog
should close. For convenience we pass in aonClose(false)
so that you can use:onClose={setIsOpen}
.initialFocus
as
div
Dialog
should render as.static
false
unmount
true
Render prop object
open
Dialog.Overlay
This can be used to create an overlay for your Dialog component. Clicking on the overlay will close the Dialog.
Props
as
div
Dialog.Overlay
should render as.Render prop object
open
Dialog.Title
This is the title for your Dialog. When this is used, it will set the
aria-labelledby
on the Dialog.Props
as
h2
Dialog.Title
should render as.Render prop object
open
Dialog.Description
This is the description for your Dialog. When this is used, it will set the
aria-describedby
on the Dialog.Props
as
p
Dialog.Description
should render as.Render prop object
open
Popover
componentThis component can be used for navigation menu's, mobile menu's and flyout menu's.
Popover
Props
as
div
Popover
should render as.Render prop object
open
Popover.Overlay
This can be used to create an overlay for your Popover component. Clicking on the overlay will close the Popover.
Props
as
div
Popover.Overlay
should render as.Render prop object
open
Popover.Button
This is the trigger component to open a Popover. You can also use this
Popover.Button
component inside aPopover.Panel
, if you do so, then it willbehave as a
close
button. We will also make sure to provide the correctaria-*
attributes onto the button.Props
as
button
Popover.Button
should render as.Render prop object
open
Popover.Panel
This component contains the contents of your Popover.
Props
as
div
Popover.Panel
should render as.focus
false
Popover.Panel
when thePopover
is open. It will also close thePopover
if focus left this component.static
false
unmount
true
Render prop object
open
Popover.Group
This allows you to wrap multiple elements and Popover's inside a group.
Popover.Panel
, it will focus the nextPopover.Button
in line.Popover.Group
it will close all thePopover
's.Props
as
div
Popover.Group
should render as.