Commit Graph

26 Commits

Author SHA1 Message Date
Robin Malfait 9807e2ba7e prepare 1.6.7 2022-07-12 15:59:01 +02:00
Robin Malfait fc7def3295 Revert "prepare 1.6.6"
This reverts commit 06df02a158.
2022-07-07 23:06:18 +02:00
Robin Malfait 06df02a158 prepare 1.6.6 2022-07-07 22:57:45 +02:00
Robin Malfait bc0b64a8c9 Revert "prepare 1.6.5"
This reverts commit c31136032b.
2022-06-20 18:00:31 +02:00
Robin Malfait c31136032b prepare 1.6.5 2022-06-20 16:22:05 +02:00
Robin Malfait 1d53ac312f Revert "prepare 1.6.4"
This reverts commit 842d07146e.
2022-05-29 14:57:35 +02:00
Robin Malfait 842d07146e prepare 1.6.4 2022-05-29 14:51:38 +02:00
Robin Malfait 21bdf529de Fix event handlers with arity > 1 (#1515)
* fix event handlers with arity > 1

* update changelog
2022-05-28 17:32:00 +02:00
Robin Malfait 08b419e9b7 Revert "prepare 1.6.3"
This reverts commit 3aaf20b9ac.
2022-05-25 15:25:48 +02:00
Robin Malfait 3aaf20b9ac prepare 1.6.3 2022-05-25 15:13:09 +02:00
Robin Malfait 39c5bd3230 Add @headlessui/tailwindcss plugin (#1487)
* add `@headlessui/tailwindcss` plugin

* expose `data-headlessui-state="..."` data attribute

All components that expose boolean props in their render prop / v-slot
will receive a `data-headlessui-state="..."` attribute.

If it exposes boolean values but all are false, then there will be an
empty `data-headlessui-state=""`. If the current component is rendering
a `Fragment` then we don't expose those attributes.

* use tailwindcss in `playground-react` and `playground-vue`

We were using the CDN, but now that we have the
`@headlessui/tailwindcss` plugin, it's a bit easier to configure it
natively and import the plugin.

* ensure to build the `@headlessui/tailwindcss` package before starting the playground

* refactor `listbox` example to use the @headlessui/tailwindcss plugin

* update changelog

* bump Tailwind CSS to latest insiders version

* correctly generate types

* type `tailwind.config.js` files for playgrounds

* add todo for when `:has()` is available
2022-05-24 22:51:02 +02:00
Robin Malfait 3e19aa5c97 Properly merge incoming props (#1265)
* rename inconsistent `passThroughProps` and `passthroughProps` to more
concise `incomingProps`

This is going to make a bit more sense in the next commits of this
branch, hold on!

* split props into `propsWeControl` and `propsTheyControl`

This will allow us to merge the props with a bit more control. Instead
of overriding every prop from the user' props with our props, we can now
merge event listeners.

* update `render` API to accept `propsWeControl` and `propsTheyControl`

* improve the merge logic

This will essentially do the exact same thing we were doing before:
```js
let props = { ...propsTheyControl, ...propsWeControl }
```

But instead of overriding everything, we will merge the event listener
related props like `onClick`, `onKeyDown`, ...

* fix typo in tests

* simplify naming

- Rename `propsWeControl` to `ourProps`
- Rename `propsTheyControl` to `theirProps`

* update changelog
2022-03-22 17:32:11 +01:00
Robin Malfait 7bb89871ba Add <form> compatibility (#1214)
* implement `objetToFormEntries` functionality

If we are working with more complex data structures then we have to
encode those data structures into a syntax that the HTML can understand.

This means that we have to use `<input type="hidden" name="..." value="...">` syntax.

To convert a simple array we can use the following syntax:
```js
// Assuming we have a `name` of `person`
let input = ['Alice', 'Bob', 'Charlie']
```

Results in:
```html
<input type="hidden" name="person[]" value="Alice" />
<input type="hidden" name="person[]" value="Bob" />
<input type="hidden" name="person[]" value="Charlie" />
```

Note: the additional `[]` in the name attribute.

---

A more complex object (even deeply nested) can be encoded like this:
```js
// Assuming we have a `name` of `person`
let input = {
  id: 1,
  name: {
    first: 'Jane',
    last: 'Doe'
  }
}
```

Results in:
```html
<input type="hidden" name="person[id]" value="1" />
<input type="hidden" name="person[name][first]" value="Jane" />
<input type="hidden" name="person[name][last]" value="Doe" />
```

* implement VisuallyHidden component

* implement and export some extra helper utilities

* implement form element for Switch

* implement form element for Combobox

* implement form element for RadioGroup

* implement form element for Listbox

* add combined forms example to the playground

* update changelog

* enable support for iterators

* ensure to compile dom iterables

* remove unused imports
2022-03-09 11:24:45 +01:00
Robin Malfait 26670d2768 Forward the ref to all components (#1116)
* forward ref to all components

* fix playground pages

This isn't a perfect fix of course. But the TypeScript changes required
to do it properly are a bit bigger and require more work.

Having this ready is a good step forward.

* update changelog
2022-02-24 16:13:56 +01:00
Robin Malfait fdd2629795 Improve overal codebase, use modern tech like esbuild and TypeScript 4! (#1055)
* use esbuild for React instead of tsdx

* remove tsdx from Vue

* use consistent names

* add jest and prettier

* update scripts

* ignore some folders for prettier

* run lint script instead of tsdx lint

* run prettier en-masse

This has a few changes because of the new prettier version.

* bump typescript to latest version

* make typescript happy

* cleanup playground package.json

* make esbuild a dev dependency

* make scripts consistent

* fix husky hooks

* add dedicated watch script

* add `yarn playground-react` and `yarn react-playground` (alias)

This will make sure to run a watcher for the actual @headlessui/react
package, and start a development server in the playground-react package.

* ignore formatting in the .next folder

* run prettier on playground-react package

* setup playground-vue

Still not 100% working, but getting there!

* add playground aliases in @headlessui/vue and @headlessui/react

This allows you to run `yarn react playground` or `yarn vue playground`
from the root.

* add `clean` script

* move examples folder in playground-vue to root

* ensure new lines for consistency in scripts

* fix typescript issue

* fix typescript issues in playgrounds

* make sure to run prettier on everything it can

* run prettier on all files

* improve error output

If you minify the code, then it could happen that the errors are a bit
obscure. This will hardcode the component name to improve errors.

* add the `prettier-plugin-tailwindcss` plugin, party!

* update changelog
2022-01-27 17:07:38 +01:00
Robin Malfait ce23edeee4 Next release (#431)
* Fixed typos (#350)

* chore: Fix typo in render.ts (#347)

* Better vue link (#353)

* Better vue link

* add better React link

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>

* Enable NoScroll feature for the initial useFocusTrap hook (#356)

* enable NoScroll feature for the initial useFocusTrap hook

Once you are using Tab and Shift+Tab it does the scrolling.

Fixes: #345

* update changelog

* Revert "Enable NoScroll feature for the initial useFocusTrap hook (#356)"

This reverts commit 19590b07624d7e3d751cbf11de869dfb0ea432ba.

Solution is not 100% correct, so will revert for now!

* Improve search (#385)

* make search case insensitive for the listbox

* make search case insensitive for the menu

* update changelog

* add `disabled` prop to RadioGroup and RadioGroup Option (#401)

* add `disabled` prop to RadioGroup and RadioGroup Option

Also did some general cleanup which in turn fixed an issue where the
RadioGroup is unreachable when a value is used that doesn't exist in the
list of options.

Fixes: #378

* update changelog

* Fix type of `RadioGroupOption` (#400)

Match RadioGroupOption value types to match modelValue allowed types for RadioGroup

* update changelog

* fix typo's

* chore(CI): update main workflow (#395)

* chore(CI): update main workflow

* Update main.yml

* fix dialog event propagation (#422)

* re-export the `screen` utility for quick debugging purposes

* stop event propagation when clicking inside a Dialog

Fixes: #414

* improve dialog escape (#430)

* Make sure that `Escape` only closes the top most Dialog

* update changelog

* add defaultOpen prop to Disclosure component (#447)

* add defaultOpen prop to Disclosure component

* update changelog

Co-authored-by: Shuvro Roy <shuvro.roy@northsouth.edu>
Co-authored-by: Alex Nault <nault.alex@gmail.com>
Co-authored-by: Eugene Kopich <github@web2033.com>
Co-authored-by: Nathan Shoemark <n.shoemark@gmail.com>
Co-authored-by: Michaël De Boey <info@michaeldeboey.be>
2021-04-26 15:44:10 +02:00
Robin Malfait 6f8225c437 Next batch of Vue components (#282)
* add little editor hack

By adding a html`..` to the template strings editors can get syntax
highlighting for these template strings. Even better, prettier can even
format the contents inside those because now it is "aware" of what kind
of content is inside of these template strings.

You might notice that for the Menu component I have a jsx`..`, this is
another little hack, this only provides us with syntax highlighting and
not with prettier support.

The reason why we have this is that for some reason, when you have:

html`
  <MenuItem>
`

It will be formatted as:

html`
  <menuitem>
`

There might exist a better name we can use instead of jsx, but for now,
this will do. Having syntax highlighting is already 10x better than what
we had before!

* add Alert component

* update changelog

* update REACT readme for Alert component

* expose Alert component

* add Disclosure component

* expose Disclosure component

* add FocusTrap component

* add FocusTrap example

* expose FocusTrap

* update test utils

We've been making some changes in the React utils, so we have to update
them here as well!

* add Popover component

* expose Popover

* drop unused state

* type Disclosure's API object

* add Portal component

* add Portal example

* expose Portal component

* use correct containElement assertion

* add useInertOthers hook

* add Dialog component

* fix various typo's

* expose Dialog

* add Popover example

* force focus on the Popover button on click

* drop own id when using labels

We are nesting the Label and Description components, if we also add the
id of ourselves we get strange results when using Voice Over.

First you would hear the contents (which includes the labels and
descriptions) then you would hear the labels and descriptions again.

We don't want to hear things twice!

* add Dialog example

* ensure to stop propagation

Otherwise if you nest a Menu inside a Dialog and you press `Escape` the
Dialog will close as well, which is not the expected behaviour.

* improve focus management

When you trigger a Popover using a `click` event, then start using `Tab`
the next `a`-tags do not contain the default focus styles. These only
happen when you trigger it using the keyboard first.

Using a tabindex="0" does make it "focusable" and the default browser
styles will be visible. If we remove the tabindex in a
requestAnimationFrame or a setTimeout then the focus styles will be
removed as well.

This should not cause to many issues (fingers crossed) because the
document.activeElement was already referring to the correct element!

* remove Alert component

There are a lot of unknowns and context dependendant questions to
implement Alerts in a good way. The current Alert component just had a
role set, and it had no JS attached.

We will revisit this, once we start working on Alert Dialogs,
Notification center notifications (dismissable, hide after x time, ...)

* ensure Popover.Overlay auto shows/hides based on Popover state

* enable focus trapping based on `open` prop

Only enable focus trapping in the Dialog when `open` is true, regardless
of the `static` prop.

* handle attrs on Dialog manually

* add low level Description component

* add low level Label component

* add RadioGroup component

* expose RadioGroup

* update README with links to new components

* update changelog with all the changes

* add RadioGroup example

* improve type in test

* cleanup internal Dialog Description

We have a low level Description component abstraction that can be used
instead of the Dialog specifiction Description.

* refactor raw window events to a shared useWindowEvent

* passthrough prop bag via context for abstract Description

The Description component is a generic low level component that is
re-used. This causes an issue that the render prop "bag"/"slot" doesn't
contain the data from let's say a Dialog component.

This commit will ensure that you can specify a bag (React) and slot
(Vue) on the DescriptionProvider, so that the Description component can
read it from the context.

* improve render function in React

These contain a few changes that are purely internal changes. Nothing
changes / breaks in the public API of the components.

- Instead of using multiple arguments in your `render()` functions, we now
use an object.
- `propsBag` / `bag` is renamed to `slot`.
- We also provide a `name` to the render function, so that we can use
  that to improve error messages.

* use the new internal render api (React)

* improve render function in Vue

* use the new internal render api (Vue)
2021-04-04 17:49:44 +02:00
Robin Malfait f4291112c5 Class name functions (#257)
* allow className to be a function

Every component that accepts a className should be able to pass in a
function. This function will retrieve the render prop arg for this
component. The function should resolve to a string in the end.

This makes the API a bit nicer if you just need to change the classNames
based on some internal state.

E.g.:

```js
// Before
<Menu.Button as={Fragment}>
  {({ open }) => (
    <button className={open ? 'font-bold' : 'font-normal'}>
      Hello
    </button>
  )}
</Menu.Button>

// After
<Menu.Button className={({ open }) => open ? 'font-bold' : 'font-normal'}>
  Hello
</Menu.Button>
```

* cleanup types

* merge React imports

* update changelog
2021-04-02 15:55:15 +02:00
Robin Malfait e43dd5b012 assign displayName instead of name (#247)
This allows us to read a custom `displayName`, and we can default to a
`name`. React Devtools will still be able to read this information.

Fixes: #246
2021-04-02 15:55:15 +02:00
Robin Malfait 648a2843e6 Multiple new components (#220)
* 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
2021-04-02 15:55:14 +02:00
Robin Malfait bb68793f08 correctly handle TypeScript render abstractions 2021-02-11 19:19:12 +01:00
Robin Malfait ef00732685 cleanup and consistency (#213)
- Made the use of `const` and `let` consistent
- import required functions and types from 'react' instead of using the
  `React.` namespace.
- Added `Expand` type, which can expand complex types to their "final"
  result.
- Ensured that we use `as const` for DEFAULT_XXX_TAG where we used a
  string. So that we have the type of `div` instead of `string` for
  example.
- Used `interface` over `type` where possible. I'm personally more of a
  `type` fan. But the TypeScript recommends `interfaces` where possible
  because they are faster, yield better error messages and so on.
2021-01-30 14:46:54 +01:00
Robin Malfait aab23c9077 feat: add render features + render strategy (#106)
* add unmount strategy to README (React)

* add unmount strategy to README (Vue)

* add different render features (React)

* use render features in Menu and Listbox (React)

* add different render features (Vue)

* use render features in Menu and Listbox (Vue)

* bump dependencies

* add ability to change the ref property using `refName`

Example use case:

```tsx
// Some components have this API with an `innerRef`. The suggested approach is to use
// `React.forwardRef` so that you get the actual `ref` value. However if you already have this
// `innerRef` API than we can use the `refName="innerRef"` to give the `ref` prop a good name. It
// defaults to `ref` so that it still works everywhere else.

function MyButton({ innerRef, ...props }) {
  return <button ref={innerRef} {...props} />
}

<Menu.Button as={MyButton} refName="innerRef" />
```

* small cleanup, move refs to props we control

* add tests for the render abstraction (Render)

+ use the unique __ symbol as a default value in the Props type for the
  omitable props.

* use render features in Transition (React)

* add/update Transition examples to also showcase the `unmount={false}` render strategy

* bump dependencies

* add example with nested unmount/hide transitions

* add unmount to Transition documentation
2020-10-18 15:34:05 +02:00
Robin Malfait 6e3d496998 feat: add Switch component (#26)
* add Switch component

* add tests to verify that we can click the label to toggle the Switch

* use onKeyUp to prevent triggering the onClick in firefox
2020-10-05 16:47:31 +02:00
Robin Malfait 2747745891 ensure that you can not click disabled items 2020-09-25 16:09:40 +02:00
Robin Malfait 76c24680c5 make render logic consistent with the Vue implementation 2020-09-16 23:44:38 +02:00