Skip to content

Commit

Permalink
Add explicit multiple prop (#1355)
Browse files Browse the repository at this point in the history
* add explicit `multiple` prop to the `Combobox`

This allows you to set the value to a **tuple** in `single-value` mode,
which was not possible before the `multiple` prop was introduced,
because then it resulted in `multi-value` mode instead of `single-value`
mode.

* add explicit `multiple` prop to the `Listbox`

This allows you to set the value to a **tuple** in `single-value` mode,
which was not possible before the `multiple` prop was introduced,
because then it resulted in `multi-value` mode instead of `single-value`
mode.

* update changelog

* update playground to use `multiple` prop
  • Loading branch information
RobinMalfait authored Apr 22, 2022
1 parent 591b328 commit 0c34fe8
Show file tree
Hide file tree
Showing 13 changed files with 49 additions and 34 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Ensure that there is always an active option in the `Combobox` ([#1279](https://github.com/tailwindlabs/headlessui/pull/1279), [#1281](https://github.com/tailwindlabs/headlessui/pull/1281))
- Allow `Enter` for form submit in `RadioGroup`, `Switch` and `Combobox` improvements ([#1285](https://github.com/tailwindlabs/headlessui/pull/1285))
- add React 18 compatibility ([#1326](https://github.com/tailwindlabs/headlessui/pull/1326))
- Add explicit `multiple` prop ([#1355](https://github.com/tailwindlabs/headlessui/pull/1355))

### Added

Expand Down Expand Up @@ -75,6 +76,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Resolve `initialFocusRef` correctly ([#1276](https://github.com/tailwindlabs/headlessui/pull/1276))
- Ensure that there is always an active option in the `Combobox` ([#1279](https://github.com/tailwindlabs/headlessui/pull/1279), [#1281](https://github.com/tailwindlabs/headlessui/pull/1281))
- Allow `Enter` for form submit in `RadioGroup`, `Switch` and `Combobox` improvements ([#1285](https://github.com/tailwindlabs/headlessui/pull/1285))
- Add explicit `multiple` prop ([#1355](https://github.com/tailwindlabs/headlessui/pull/1355))

### Added

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4594,7 +4594,7 @@ describe('Multi-select', () => {
let [value, setValue] = useState<string[]>(['bob', 'charlie'])

return (
<Combobox value={value} onChange={setValue}>
<Combobox value={value} onChange={setValue} multiple>
<Combobox.Input onChange={() => {}} />
<Combobox.Button>Trigger</Combobox.Button>
<Combobox.Options>
Expand Down Expand Up @@ -4630,7 +4630,7 @@ describe('Multi-select', () => {
let [value, setValue] = useState<string[]>(['bob', 'charlie'])

return (
<Combobox value={value} onChange={setValue}>
<Combobox value={value} onChange={setValue} multiple>
<Combobox.Input onChange={() => {}} />
<Combobox.Button>Trigger</Combobox.Button>
<Combobox.Options>
Expand Down Expand Up @@ -4659,7 +4659,7 @@ describe('Multi-select', () => {
let [value, setValue] = useState<string[]>(['bob', 'charlie'])

return (
<Combobox value={value} onChange={setValue}>
<Combobox value={value} onChange={setValue} multiple>
<Combobox.Input onChange={() => {}} />
<Combobox.Button>Trigger</Combobox.Button>
<Combobox.Options>
Expand Down Expand Up @@ -4692,7 +4692,7 @@ describe('Multi-select', () => {
let [value, setValue] = useState<string[]>(['bob', 'charlie'])

return (
<Combobox value={value} onChange={setValue}>
<Combobox value={value} onChange={setValue} multiple>
<Combobox.Input onChange={() => {}} />
<Combobox.Button>Trigger</Combobox.Button>
<Combobox.Options>
Expand Down
10 changes: 6 additions & 4 deletions packages/@headlessui-react/src/components/combobox/combobox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -340,14 +340,15 @@ let ComboboxRoot = forwardRefWithAs(function Combobox<
props: Props<
TTag,
ComboboxRenderPropArg<TType>,
'value' | 'onChange' | 'disabled' | 'name' | 'nullable'
'value' | 'onChange' | 'disabled' | 'name' | 'nullable' | 'multiple'
> & {
value: TType
onChange(value: TType): void
disabled?: boolean
__demoMode?: boolean
name?: string
nullable?: boolean
multiple?: boolean
},
ref: Ref<TTag>
) {
Expand All @@ -358,20 +359,21 @@ let ComboboxRoot = forwardRefWithAs(function Combobox<
disabled = false,
__demoMode = false,
nullable = false,
multiple = false,
...theirProps
} = props
let defaultToFirstOption = useRef(false)

let comboboxPropsRef = useRef<StateDefinition['comboboxPropsRef']['current']>({
value,
mode: Array.isArray(value) ? ValueMode.Multi : ValueMode.Single,
mode: multiple ? ValueMode.Multi : ValueMode.Single,
onChange,
nullable,
__demoMode,
})

comboboxPropsRef.current.value = value
comboboxPropsRef.current.mode = Array.isArray(value) ? ValueMode.Multi : ValueMode.Single
comboboxPropsRef.current.mode = multiple ? ValueMode.Multi : ValueMode.Single
comboboxPropsRef.current.nullable = nullable

let optionsPropsRef = useRef<StateDefinition['optionsPropsRef']['current']>({
Expand Down Expand Up @@ -411,7 +413,7 @@ let ComboboxRoot = forwardRefWithAs(function Combobox<
let dataBag = useMemo<Exclude<ContextType<typeof ComboboxData>, null>>(
() => ({
value,
mode: Array.isArray(value) ? ValueMode.Multi : ValueMode.Single,
mode: multiple ? ValueMode.Multi : ValueMode.Single,
get activeOptionIndex() {
if (defaultToFirstOption.current && _activeOptionIndex === null && options.length > 0) {
let localActiveOptionIndex = options.findIndex(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3963,7 +3963,7 @@ describe('Multi-select', () => {
let [value, setValue] = useState<string[]>(['bob', 'charlie'])

return (
<Listbox value={value} onChange={setValue}>
<Listbox value={value} onChange={setValue} multiple>
<Listbox.Button>Trigger</Listbox.Button>
<Listbox.Options>
<Listbox.Option value="alice">alice</Listbox.Option>
Expand Down Expand Up @@ -3998,7 +3998,7 @@ describe('Multi-select', () => {
let [value, setValue] = useState<string[]>(['bob', 'charlie'])

return (
<Listbox value={value} onChange={setValue}>
<Listbox value={value} onChange={setValue} multiple>
<Listbox.Button>Trigger</Listbox.Button>
<Listbox.Options>
<Listbox.Option value="alice">alice</Listbox.Option>
Expand Down Expand Up @@ -4026,7 +4026,7 @@ describe('Multi-select', () => {
let [value, setValue] = useState<string[]>(['bob', 'charlie'])

return (
<Listbox value={value} onChange={setValue}>
<Listbox value={value} onChange={setValue} multiple>
<Listbox.Button>Trigger</Listbox.Button>
<Listbox.Options>
<Listbox.Option value="alice">alice</Listbox.Option>
Expand Down Expand Up @@ -4058,7 +4058,7 @@ describe('Multi-select', () => {
let [value, setValue] = useState<string[]>(['bob', 'charlie'])

return (
<Listbox value={value} onChange={setValue}>
<Listbox value={value} onChange={setValue} multiple>
<Listbox.Button>Trigger</Listbox.Button>
<Listbox.Options>
<Listbox.Option value="alice">alice</Listbox.Option>
Expand Down
17 changes: 13 additions & 4 deletions packages/@headlessui-react/src/components/listbox/listbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -304,24 +304,33 @@ let ListboxRoot = forwardRefWithAs(function Listbox<
props: Props<
TTag,
ListboxRenderPropArg,
'value' | 'onChange' | 'disabled' | 'horizontal' | 'name'
'value' | 'onChange' | 'disabled' | 'horizontal' | 'name' | 'multiple'
> & {
value: TType
onChange(value: TType): void
disabled?: boolean
horizontal?: boolean
name?: string
multiple?: boolean
},
ref: Ref<TTag>
) {
let { value, name, onChange, disabled = false, horizontal = false, ...theirProps } = props
let {
value,
name,
onChange,
disabled = false,
horizontal = false,
multiple = false,
...theirProps
} = props
const orientation = horizontal ? 'horizontal' : 'vertical'
let listboxRef = useSyncRefs(ref)

let reducerBag = useReducer(stateReducer, {
listboxState: ListboxStates.Closed,
propsRef: {
current: { value, onChange, mode: Array.isArray(value) ? ValueMode.Multi : ValueMode.Single },
current: { value, onChange, mode: multiple ? ValueMode.Multi : ValueMode.Single },
},
labelRef: createRef(),
buttonRef: createRef(),
Expand All @@ -336,7 +345,7 @@ let ListboxRoot = forwardRefWithAs(function Listbox<
let [{ listboxState, propsRef, optionsRef, buttonRef }, dispatch] = reducerBag

propsRef.current.value = value
propsRef.current.mode = Array.isArray(value) ? ValueMode.Multi : ValueMode.Single
propsRef.current.mode = multiple ? ValueMode.Multi : ValueMode.Single

useIsoMorphicEffect(() => {
propsRef.current.onChange = (value: unknown) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4821,7 +4821,7 @@ describe('Multi-select', () => {
suppressConsoleLogs(async () => {
renderTemplate({
template: html`
<Combobox v-model="value">
<Combobox v-model="value" multiple>
<ComboboxInput />
<ComboboxButton>Trigger</ComboboxButton>
<ComboboxOptions>
Expand Down Expand Up @@ -4854,7 +4854,7 @@ describe('Multi-select', () => {
suppressConsoleLogs(async () => {
renderTemplate({
template: html`
<Combobox v-model="value">
<Combobox v-model="value" multiple>
<ComboboxInput />
<ComboboxButton>Trigger</ComboboxButton>
<ComboboxOptions>
Expand All @@ -4880,7 +4880,7 @@ describe('Multi-select', () => {
suppressConsoleLogs(async () => {
renderTemplate({
template: html`
<Combobox v-model="value">
<Combobox v-model="value" multiple>
<ComboboxInput />
<ComboboxButton>Trigger</ComboboxButton>
<ComboboxOptions>
Expand Down Expand Up @@ -4910,7 +4910,7 @@ describe('Multi-select', () => {
suppressConsoleLogs(async () => {
renderTemplate({
template: html`
<Combobox v-model="value">
<Combobox v-model="value" multiple>
<ComboboxInput />
<ComboboxButton>Trigger</ComboboxButton>
<ComboboxOptions>
Expand Down Expand Up @@ -4954,7 +4954,7 @@ describe('Multi-select', () => {
suppressConsoleLogs(async () => {
renderTemplate({
template: html`
<Combobox v-model="value">
<Combobox v-model="value" multiple>
<ComboboxInput />
<ComboboxButton>Trigger</ComboboxButton>
<ComboboxOptions>
Expand Down
5 changes: 3 additions & 2 deletions packages/@headlessui-vue/src/components/combobox/combobox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ export let Combobox = defineComponent({
modelValue: { type: [Object, String, Number, Boolean] },
name: { type: String },
nullable: { type: Boolean, default: false },
multiple: { type: [Boolean], default: false },
},
setup(props, { slots, attrs, emit }) {
let comboboxState = ref<StateDefinition['comboboxState']['value']>(ComboboxStates.Closed)
Expand Down Expand Up @@ -163,7 +164,7 @@ export let Combobox = defineComponent({
}

let value = computed(() => props.modelValue)
let mode = computed(() => (Array.isArray(value.value) ? ValueMode.Multi : ValueMode.Single))
let mode = computed(() => (props.multiple ? ValueMode.Multi : ValueMode.Single))
let nullable = computed(() => props.nullable)

let api = {
Expand Down Expand Up @@ -444,7 +445,7 @@ export let Combobox = defineComponent({
)
: []),
render({
props: omit(incomingProps, ['nullable', 'onUpdate:modelValue']),
props: omit(incomingProps, ['nullable', 'multiple', 'onUpdate:modelValue']),
slot,
slots,
attrs,
Expand Down
10 changes: 5 additions & 5 deletions packages/@headlessui-vue/src/components/listbox/listbox.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4086,7 +4086,7 @@ describe('Multi-select', () => {
suppressConsoleLogs(async () => {
renderTemplate({
template: html`
<Listbox v-model="value">
<Listbox v-model="value" multiple>
<ListboxButton>Trigger</ListboxButton>
<ListboxOptions>
<ListboxOption value="alice">alice</ListboxOption>
Expand Down Expand Up @@ -4118,7 +4118,7 @@ describe('Multi-select', () => {
suppressConsoleLogs(async () => {
renderTemplate({
template: html`
<Listbox v-model="value">
<Listbox v-model="value" multiple>
<ListboxButton>Trigger</ListboxButton>
<ListboxOptions>
<ListboxOption value="alice">alice</ListboxOption>
Expand All @@ -4143,7 +4143,7 @@ describe('Multi-select', () => {
suppressConsoleLogs(async () => {
renderTemplate({
template: html`
<Listbox v-model="value">
<Listbox v-model="value" multiple>
<ListboxButton>Trigger</ListboxButton>
<ListboxOptions>
<ListboxOption value="alice">alice</ListboxOption>
Expand Down Expand Up @@ -4172,7 +4172,7 @@ describe('Multi-select', () => {
suppressConsoleLogs(async () => {
renderTemplate({
template: html`
<Listbox v-model="value">
<Listbox v-model="value" multiple>
<ListboxButton>Trigger</ListboxButton>
<ListboxOptions>
<ListboxOption value="alice">alice</ListboxOption>
Expand Down Expand Up @@ -4215,7 +4215,7 @@ describe('Multi-select', () => {
suppressConsoleLogs(async () => {
renderTemplate({
template: html`
<Listbox v-model="value">
<Listbox v-model="value" multiple>
<ListboxButton>Trigger</ListboxButton>
<ListboxOptions>
<ListboxOption v-for="person in people" :value="person"
Expand Down
5 changes: 3 additions & 2 deletions packages/@headlessui-vue/src/components/listbox/listbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ export let Listbox = defineComponent({
horizontal: { type: [Boolean], default: false },
modelValue: { type: [Object, String, Number, Boolean] },
name: { type: String, optional: true },
multiple: { type: [Boolean], default: false },
},
setup(props, { slots, attrs, emit }) {
let listboxState = ref<StateDefinition['listboxState']['value']>(ListboxStates.Closed)
Expand Down Expand Up @@ -156,7 +157,7 @@ export let Listbox = defineComponent({
}

let value = computed(() => props.modelValue)
let mode = computed(() => (Array.isArray(value.value) ? ValueMode.Multi : ValueMode.Single))
let mode = computed(() => (props.multiple ? ValueMode.Multi : ValueMode.Single))

let api = {
listboxState,
Expand Down Expand Up @@ -327,7 +328,7 @@ export let Listbox = defineComponent({
)
: []),
render({
props: omit(incomingProps, ['onUpdate:modelValue', 'horizontal']),
props: omit(incomingProps, ['onUpdate:modelValue', 'horizontal', 'multiple']),
slot,
slots,
attrs,
Expand Down
2 changes: 1 addition & 1 deletion packages/playground-react/pages/combobox/multi-select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ function MultiPeopleList() {
console.log([...new FormData(e.currentTarget).entries()])
}}
>
<Combobox value={activePersons} onChange={setActivePersons} name="people">
<Combobox value={activePersons} onChange={setActivePersons} name="people" multiple>
<Combobox.Label className="block text-sm font-medium leading-5 text-gray-700">
Assigned to
</Combobox.Label>
Expand Down
2 changes: 1 addition & 1 deletion packages/playground-react/pages/listbox/multi-select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ function MultiPeopleList() {
console.log([...new FormData(e.currentTarget).entries()])
}}
>
<Listbox value={activePersons} onChange={setActivePersons} name="people">
<Listbox value={activePersons} onChange={setActivePersons} name="people" multiple>
<Listbox.Label className="block text-sm font-medium leading-5 text-gray-700">
Assigned to
</Listbox.Label>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<div class="w-full max-w-4xl">
<div class="space-y-1">
<form @submit="onSubmit">
<Combobox v-model="activePersons" name="people">
<Combobox v-model="activePersons" name="people" multiple>
<ComboboxLabel class="block text-sm font-medium leading-5 text-gray-700">
Assigned to
</ComboboxLabel>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<div class="w-full max-w-4xl">
<div class="space-y-1">
<form @submit="onSubmit">
<Listbox v-model="activePersons" name="people">
<Listbox v-model="activePersons" name="people" multiple>
<ListboxLabel class="block text-sm font-medium leading-5 text-gray-700">
Assigned to
</ListboxLabel>
Expand Down

0 comments on commit 0c34fe8

Please sign in to comment.