Skip to content

Commit

Permalink
docs: combobox HTML correct structure in documentation (#867)
Browse files Browse the repository at this point in the history
* add Downshift docs page

* add the page to the menu

* readme changes

* more clarification in readme

* fix docsite links

* update example description

* use links instead of highlight
  • Loading branch information
silviuaavram committed Dec 26, 2019
1 parent 22c1051 commit e2404b7
Show file tree
Hide file tree
Showing 4 changed files with 232 additions and 7 deletions.
18 changes: 16 additions & 2 deletions README.md
Expand Up @@ -173,7 +173,7 @@ npm install --save downshift
## Usage

> [Try it out in the browser](https://codesandbox.io/s/n9095)
> [Try it out in the browser](https://codesandbox.io/s/simple-downshift-with-getrootprops-example-24s13)
```jsx
import React from 'react'
Expand Down Expand Up @@ -204,10 +204,16 @@ render(
inputValue,
highlightedIndex,
selectedItem,
getRootProps,
}) => (
<div>
<label {...getLabelProps()}>Enter a fruit</label>
<input {...getInputProps()} />
<div
style={{display: 'inline-block'}}
{...getRootProps({}, {suppressRefError: true})}
>
<input {...getInputProps()} />
</div>
<ul {...getMenuProps()}>
{isOpen
? items
Expand Down Expand Up @@ -237,6 +243,14 @@ render(
)
```

The previous example without `getRootProps` is
[here](https://codesandbox.io/s/n9095).

> Warning: The example without `getRootProps` is not fully accessible with
> screen readers as it's not possible to achieve a correct HTML structure for
> the combobox. Examples on how to use `Downshift` component with and without
> `getRootProps` are on the [docsite](https://downshift.netlify.com/).
`<Downshift />` is the only component exposed by this package. It doesn't render
anything itself, it just calls the render function and renders that. ["Use a
render prop!"][use-a-render-prop]!
Expand Down
202 changes: 202 additions & 0 deletions docs/downshift/downshift.mdx
@@ -0,0 +1,202 @@
---
name: Component
menu: Downshift
route: /downshift/component
---

import {useState} from 'react'
import {Playground} from 'docz'
import Downshift from '../../src'
import {items, menuStyles, comboboxStyles, playgroundStyles} from '../utils'

# Downshift

## Introduction

The `Downshift` component has been developed in order to provide accessibility
and functionality to a `combobox` component, described by the corresponding
[ARIA docs](https://www.w3.org/TR/wai-aria-practices/examples/combobox/aria1.1pattern/listbox-combo.html).

In the examples below, we use the `Downshift` hook and destructure from its
result the getter props and state variables. The hooks also has the
`onInputValueChange` prop passed in order to filter the items in the list
according to the input value. The getter props are used as follows:

| Returned prop | Element | Comments |
| ---------------------- | ---------- | ------------------------------------------------------------------------- |
| `getLabelProps` | `<label>` | Adds an `id` attribute to be used for `menu` and `toggleButton` |
| `getToggleButtonProps` | `<button>` | Controls the open state of the list. |
| `getRootProps` | `<div>` | Container for `input` and `toggleButton`. |
| `getInputProps` | `<input>` | Can be used to filter the options. Also displays the selected item. |
| `getMenuProps` | `<ul>` | Makes list focusable, adds ARIA attributes and event handlers. |
| `getItemProps` | `<li>` | Called with `index` and `item`, adds ARIA attributes and event listeners. |
| `isOpen` | | Only when it's true we render the `<li>` elements. |
| `highlightedIndex` | `<li>` | Used to style the highlighted item. |
| `selectedItem` | `<button>` | Used to render text equivalent of selected item on the button. |

For a complete documentation on all the returned props, component props and more
information check out the
[Github Page](https://github.com/downshift-js/downshift).

## Important

If you are new to `Downshift` then maybe you should fist check
[useCombobox](/hooks/use-combobox) which should provide the same functionality
but as a hook. If it's not suiting your use case then come back here. Also, if
you need just a `select` dropdown without a text input, then check
[useSelect](/hooks/use-select).

As far as the component is concerned, you can use it in two ways, both of them
illustrated below.

There is an _straightforward_ way, which allows you to wrap your whole
`combobox` HTML in `<Downshift>`. The drawback of this way is that the
`combobox` HTML structure is not correct, and screen readers will not widely
support it.

There is also the _not-so-straightforward-way_ which allows you to respect the
`combobox` HTML structure and you should aim for this one. Here you will use
`getRootProps` on the element that wraps your `<input>` and then you will add
the `<ul>` on the same level with the wrapper. More details on each of these
examples below.

A `combobox` element can be created with HTML elements such as: `<label>`,
`<ul>`, `<li>`, `<button>`, `<input>` and a `<div>` or something similar to
contain the input and the toggle button. It is absolutely important to follow
the HTML structure below, as it will allow all screen readers to properly work
with the widget. Most importantly, the `<input>` needs to be contained by the
combobox `<div>` and the `<ul>` needs to be at the same level with the combobox
`<div>`.

## Usage with `getRootProps`

It is possible to achieve the correct HTML structure only if you use
`getRootProps` getter prop from `Downshift`. You apply `getRootProps` on the
wrapper element that contains the `<input>` and optionally the trigger button.

[CodeSandbox](https://codesandbox.io/s/simple-downshift-with-getrootprops-example-24s13)

<Playground style={playgroundStyles}>
{() => {
const DropdownCombobox = () => {
const [inputItems, setInputItems] = useState(items)

return (
<Downshift onInputValueChange={inputValue => {
setInputItems(
items.filter(item =>
item.toLowerCase().startsWith(inputValue.toLowerCase()),
)
)
}}>
{({
getInputProps,
getItemProps,
getMenuProps,
getLabelProps,
getRootProps,
getToggleButtonProps,
highlightedIndex,
isOpen,
}) => (
<>
<label {...getLabelProps()}>Choose an element:</label>
<div style={comboboxStyles} {...getRootProps({}, {suppressRefError: true})}>
<input {...getInputProps()} />
<button {...getToggleButtonProps()} aria-label={'toggle menu'}>
&#8595;
</button>
</div>
<ul {...getMenuProps()} style={menuStyles}>
{isOpen &&
inputItems.map((item, index) => (
<li
style={
highlightedIndex === index
? {backgroundColor: '#bde4ff'}
: {}
}
key={`${item}${index}`}
{...getItemProps({item, index})}
>
{item}
</li>
))}
</ul>
</>
)}
</Downshift>
)}

return <DropdownCombobox />

}}

</Playground>

## Usage without `getRootProps`

Using `Downshift` without the `getRootProps` will add the `combobox` role to the
child element rendered. This way you are forced into having all elements (menu,
input, button, label) as children of the `combobox` which is not compatible with
the widget HTML structure. It will still work, and you can style it to look as a
normal combobox, but this structure is not supported by screen readers.
Accessibility will not be achieved in this case, so please migrate to either the
example above or to `useCombobox` hook.

[CodeSandbox](https://codesandbox.io/s/usecombobox-usage-evufg)

<Playground style={playgroundStyles}>
{() => {
const DropdownCombobox = () => {
const [inputItems, setInputItems] = useState(items)

return (
<Downshift onInputValueChange={inputValue => {
setInputItems(
items.filter(item =>
item.toLowerCase().startsWith(inputValue.toLowerCase()),
)
)
}}>
{({
getInputProps,
getItemProps,
getMenuProps,
getLabelProps,
getToggleButtonProps,
highlightedIndex,
isOpen,
}) => (
<div>
<label {...getLabelProps()}>Choose an element:</label>
<input {...getInputProps()} />
<button {...getToggleButtonProps()} aria-label={'toggle menu'}>
&#8595;
</button>
<ul {...getMenuProps()} style={menuStyles}>
{isOpen &&
inputItems.map((item, index) => (
<li
style={
highlightedIndex === index
? {backgroundColor: '#bde4ff'}
: {}
}
key={`${item}${index}`}
{...getItemProps({item, index})}
>
{item}
</li>
))}
</ul>
</div>
)}
</Downshift>
)}

return <DropdownCombobox />

}}

</Playground>
12 changes: 8 additions & 4 deletions docs/index.mdx
Expand Up @@ -6,10 +6,14 @@ order: 1

# Downshift

Work in progress for the docsite. Looking to centralize Downshift examples here. Also using it for Cypress tests.
Work in progress for the docsite. Looking to centralize Downshift examples here.
Also using it for Cypress tests.

For `docs` see the repo: [github.com/downshift-js/downshift](https://github.com/downshift-js/downshift)
For `docs` see the repo:
[github.com/downshift-js/downshift](https://github.com/downshift-js/downshift)

For `Downshift` examples see: [github.com/kentcdodds/downshift-examples](https://github.com/kentcdodds/downshift-examples)
For `Downshift` examples see [component section](/downshift/component) or
[github.com/kentcdodds/downshift-examples](https://github.com/kentcdodds/downshift-examples)

For `useSelect` examples see: [the docsite](/hooks/use-select/usage).
For `Downshift` hooks examples see [useSelect](/hooks/use-select) or
[useCombobox](/hooks/use-combobox).
7 changes: 6 additions & 1 deletion doczrc.js
@@ -1,5 +1,10 @@
export default {
port: 6006,
files: './docs/**/*.{md,markdown,mdx}',
menu: ['Home', {name: 'Hooks', menu: ['useSelect', 'useCombobox']}, 'Tests'],
menu: [
'Home',
'Downshift',
{name: 'Hooks', menu: ['useSelect', 'useCombobox']},
'Tests',
],
}

0 comments on commit e2404b7

Please sign in to comment.