Skip to content
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

Add multi value support for Listbox & Combobox #1243

Merged
merged 11 commits into from
Mar 16, 2022
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Improve some internal code ([#1221](https://github.com/tailwindlabs/headlessui/pull/1221))
- Use `ownerDocument` instead of `document` ([#1158](https://github.com/tailwindlabs/headlessui/pull/1158))
- Ensure focus trap, Tabs and Dialog play well together ([#1231](https://github.com/tailwindlabs/headlessui/pull/1231))
- Add `multi` value support for Listbox & Combobox ([#1243](https://github.com/tailwindlabs/headlessui/pull/1243))

### Added

Expand Down Expand Up @@ -54,6 +55,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Use `ownerDocument` instead of `document` ([#1158](https://github.com/tailwindlabs/headlessui/pull/1158))
- Re-expose `el` ([#1230](https://github.com/tailwindlabs/headlessui/pull/1230))
- Ensure focus trap, Tabs and Dialog play well together ([#1231](https://github.com/tailwindlabs/headlessui/pull/1231))
- Add `multi` value support for Listbox & Combobox ([#1243](https://github.com/tailwindlabs/headlessui/pull/1243))

### Added

Expand Down
8 changes: 4 additions & 4 deletions packages/@headlessui-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@
},
"devDependencies": {
"@testing-library/react": "^11.2.3",
"@types/react": "^16.14.2",
"@types/react-dom": "^16.9.10",
"@types/react": "16.14.21",
"@types/react-dom": "^16.9.0",
"esbuild": "^0.11.18",
"react": "^16.14.0",
"react-dom": "^16.14.0",
"snapshot-diff": "^0.8.1",
"esbuild": "^0.11.18"
"snapshot-diff": "^0.8.1"
}
}
149 changes: 149 additions & 0 deletions packages/@headlessui-react/src/components/combobox/combobox.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ import {
ComboboxState,
getByText,
getComboboxes,
assertCombobox,
ComboboxMode,
} from '../../test-utils/accessibility-assertions'
import { Transition } from '../transitions/transition'

Expand Down Expand Up @@ -4393,6 +4395,153 @@ describe('Mouse interactions', () => {
)
})

describe('Multi-select', () => {
it(
'should be possible to pass multiple values to the Combobox component',
suppressConsoleLogs(async () => {
function Example() {
let [value, setValue] = useState<string[]>(['bob', 'charlie'])

return (
<Combobox value={value} onChange={setValue}>
<Combobox.Input onChange={() => {}} />
<Combobox.Button>Trigger</Combobox.Button>
<Combobox.Options>
<Combobox.Option value="alice">alice</Combobox.Option>
<Combobox.Option value="bob">bob</Combobox.Option>
<Combobox.Option value="charlie">charlie</Combobox.Option>
</Combobox.Options>
</Combobox>
)
}

render(<Example />)

// Open combobox
await click(getComboboxButton())

// Verify that we have an open combobox with multiple mode
assertCombobox({ state: ComboboxState.Visible, mode: ComboboxMode.Multiple })

// Verify that we have multiple selected combobox options
let options = getComboboxOptions()

assertComboboxOption(options[0], { selected: false })
assertComboboxOption(options[1], { selected: true })
assertComboboxOption(options[2], { selected: true })
})
)

it(
'should make the first selected option the active item',
suppressConsoleLogs(async () => {
function Example() {
let [value, setValue] = useState<string[]>(['bob', 'charlie'])

return (
<Combobox value={value} onChange={setValue}>
<Combobox.Input onChange={() => {}} />
<Combobox.Button>Trigger</Combobox.Button>
<Combobox.Options>
<Combobox.Option value="alice">alice</Combobox.Option>
<Combobox.Option value="bob">bob</Combobox.Option>
<Combobox.Option value="charlie">charlie</Combobox.Option>
</Combobox.Options>
</Combobox>
)
}

render(<Example />)

// Open combobox
await click(getComboboxButton())

// Verify that bob is the active option
assertActiveComboboxOption(getComboboxOptions()[1])
})
)

it(
'should keep the combobox open when selecting an item via the keyboard',
suppressConsoleLogs(async () => {
function Example() {
let [value, setValue] = useState<string[]>(['bob', 'charlie'])

return (
<Combobox value={value} onChange={setValue}>
<Combobox.Input onChange={() => {}} />
<Combobox.Button>Trigger</Combobox.Button>
<Combobox.Options>
<Combobox.Option value="alice">alice</Combobox.Option>
<Combobox.Option value="bob">bob</Combobox.Option>
<Combobox.Option value="charlie">charlie</Combobox.Option>
</Combobox.Options>
</Combobox>
)
}

render(<Example />)

// Open combobox
await click(getComboboxButton())
assertCombobox({ state: ComboboxState.Visible })

// Verify that bob is the active option
await click(getComboboxOptions()[0])

// Verify that the combobox is still open
assertCombobox({ state: ComboboxState.Visible })
})
)

it(
'should toggle the selected state of an option when clicking on it',
suppressConsoleLogs(async () => {
function Example() {
let [value, setValue] = useState<string[]>(['bob', 'charlie'])

return (
<Combobox value={value} onChange={setValue}>
<Combobox.Input onChange={() => {}} />
<Combobox.Button>Trigger</Combobox.Button>
<Combobox.Options>
<Combobox.Option value="alice">alice</Combobox.Option>
<Combobox.Option value="bob">bob</Combobox.Option>
<Combobox.Option value="charlie">charlie</Combobox.Option>
</Combobox.Options>
</Combobox>
)
}

render(<Example />)

// Open combobox
await click(getComboboxButton())
assertCombobox({ state: ComboboxState.Visible })

let options = getComboboxOptions()

assertComboboxOption(options[0], { selected: false })
assertComboboxOption(options[1], { selected: true })
assertComboboxOption(options[2], { selected: true })

// Click on bob
await click(getComboboxOptions()[1])

assertComboboxOption(options[0], { selected: false })
assertComboboxOption(options[1], { selected: false })
assertComboboxOption(options[2], { selected: true })

// Click on bob again
await click(getComboboxOptions()[1])

assertComboboxOption(options[0], { selected: false })
assertComboboxOption(options[1], { selected: true })
assertComboboxOption(options[2], { selected: true })
})
)
})

describe('Form compatibility', () => {
it('should be possible to submit a form with a value', async () => {
let submits = jest.fn()
Expand Down
Loading