Skip to content

Commit

Permalink
Add form prop to form-like components such as RadioGroup, `Switch…
Browse files Browse the repository at this point in the history
…`, `Listbox`, and `Combobox` (#2356)

* Adds form prop to Switch component

* add `form` prop to `Switch` component in Vue

+ tests for both React and Vue

* add `form` prop to `Combobox` component

* add `form` prop to `Listbox` comopnent

* add `form` prop to `RadioGroup` component

* update changelog

* add Oxford comma

* cleanup `screen` import

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
  • Loading branch information
asylejmani and RobinMalfait committed Mar 14, 2023
1 parent 0c0601f commit fb612f7
Show file tree
Hide file tree
Showing 18 changed files with 366 additions and 5 deletions.
4 changes: 4 additions & 0 deletions packages/@headlessui-react/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Fix focus styles showing up when using the mouse ([#2347](https://github.com/tailwindlabs/headlessui/pull/2347))

### Added

- Add `form` prop to form-like components such as `RadioGroup`, `Switch`, `Listbox`, and `Combobox` ([#2356](https://github.com/tailwindlabs/headlessui/pull/2356))

## [1.7.13] - 2023-03-03

### Fixed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5700,6 +5700,51 @@ describe('Multi-select', () => {
})

describe('Form compatibility', () => {
it('should be possible to set the `form`, which is forwarded to the hidden inputs', async () => {
let submits = jest.fn()

function Example() {
let [value, setValue] = useState(null)
return (
<div>
<Combobox form="my-form" value={value} onChange={setValue} name="delivery">
<Combobox.Input onChange={NOOP} />
<Combobox.Button>Trigger</Combobox.Button>
<Combobox.Label>Pizza Delivery</Combobox.Label>
<Combobox.Options>
<Combobox.Option value="pickup">Pickup</Combobox.Option>
<Combobox.Option value="home-delivery">Home delivery</Combobox.Option>
<Combobox.Option value="dine-in">Dine in</Combobox.Option>
</Combobox.Options>
</Combobox>

<form
id="my-form"
onSubmit={(event) => {
event.preventDefault()
submits([...new FormData(event.currentTarget).entries()])
}}
>
<button>Submit</button>
</form>
</div>
)
}

render(<Example />)

// Open combobox
await click(getComboboxButton())

// Choose pickup
await click(getByText('Pickup'))

// Submit the form
await click(getByText('Submit'))

expect(submits).lastCalledWith([['delivery', 'pickup']])
})

it('should be possible to submit a form with a value', async () => {
let submits = jest.fn()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,7 @@ export type ComboboxProps<
> = ComboboxValueProps<TValue, TNullable, TMultiple, TTag> & {
disabled?: boolean
__demoMode?: boolean
form?: string
name?: string
}

Expand Down Expand Up @@ -408,6 +409,7 @@ function ComboboxFn<TValue, TTag extends ElementType = typeof DEFAULT_COMBOBOX_T
value: controlledValue,
defaultValue,
onChange: controlledOnChange,
form: formName,
name,
by = (a: TValue, z: TValue) => a === z,
disabled = false,
Expand Down Expand Up @@ -671,6 +673,7 @@ function ComboboxFn<TValue, TTag extends ElementType = typeof DEFAULT_COMBOBOX_T
type: 'hidden',
hidden: true,
readOnly: true,
form: formName,
name,
value,
})}
Expand Down
44 changes: 44 additions & 0 deletions packages/@headlessui-react/src/components/listbox/listbox.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4716,6 +4716,50 @@ describe('Multi-select', () => {
})

describe('Form compatibility', () => {
it('should be possible to set the `form`, which is forwarded to the hidden inputs', async () => {
let submits = jest.fn()

function Example() {
let [value, setValue] = useState(null)
return (
<div>
<Listbox form="my-form" value={value} onChange={setValue} name="delivery">
<Listbox.Button>Trigger</Listbox.Button>
<Listbox.Label>Pizza Delivery</Listbox.Label>
<Listbox.Options>
<Listbox.Option value="pickup">Pickup</Listbox.Option>
<Listbox.Option value="home-delivery">Home delivery</Listbox.Option>
<Listbox.Option value="dine-in">Dine in</Listbox.Option>
</Listbox.Options>
</Listbox>

<form
id="my-form"
onSubmit={(event) => {
event.preventDefault()
submits([...new FormData(event.currentTarget).entries()])
}}
>
<button>Submit</button>
</form>
</div>
)
}

render(<Example />)

// Open listbox
await click(getListboxButton())

// Choose pickup
await click(getByText('Pickup'))

// Submit the form
await click(getByText('Submit'))

expect(submits).lastCalledWith([['delivery', 'pickup']])
})

it('should be possible to submit a form with a value', async () => {
let submits = jest.fn()

Expand Down
3 changes: 3 additions & 0 deletions packages/@headlessui-react/src/components/listbox/listbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,7 @@ export type ListboxProps<TTag extends ElementType, TType, TActualType> = Props<
by?: (keyof TActualType & string) | ((a: TActualType, z: TActualType) => boolean)
disabled?: boolean
horizontal?: boolean
form?: string
name?: string
multiple?: boolean
}
Expand All @@ -355,6 +356,7 @@ function ListboxFn<
let {
value: controlledValue,
defaultValue,
form: formName,
name,
onChange: controlledOnChange,
by = (a: TActualType, z: TActualType) => a === z,
Expand Down Expand Up @@ -565,6 +567,7 @@ function ListboxFn<
type: 'hidden',
hidden: true,
readOnly: true,
form: formName,
name,
value,
})}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1356,6 +1356,47 @@ describe('Mouse interactions', () => {
})

describe('Form compatibility', () => {
it(
'should be possible to set the `form`, which is forwarded to the hidden inputs',
suppressConsoleLogs(async () => {
let submits = jest.fn()

function Example() {
let [value, setValue] = useState(null)
return (
<div>
<RadioGroup form="my-form" value={value} onChange={setValue} name="delivery">
<RadioGroup.Label>Pizza Delivery</RadioGroup.Label>
<RadioGroup.Option value="pickup">Pickup</RadioGroup.Option>
<RadioGroup.Option value="home-delivery">Home delivery</RadioGroup.Option>
<RadioGroup.Option value="dine-in">Dine in</RadioGroup.Option>
</RadioGroup>

<form
id="my-form"
onSubmit={(event) => {
event.preventDefault()
submits([...new FormData(event.currentTarget).entries()])
}}
>
<button>Submit</button>
</form>
</div>
)
}

render(<Example />)

// Choose pickup
await click(getByText('Pickup'))

// Submit the form
await click(getByText('Submit'))

expect(submits).lastCalledWith([['delivery', 'pickup']])
})
)

it(
'should be possible to submit a form with a value',
suppressConsoleLogs(async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ export type RadioGroupProps<TTag extends ElementType, TType> = Props<
onChange?(value: TType): void
by?: (keyof TType & string) | ((a: TType, z: TType) => boolean)
disabled?: boolean
form?: string
name?: string
}
>
Expand All @@ -160,6 +161,7 @@ function RadioGroupFn<TTag extends ElementType = typeof DEFAULT_RADIO_GROUP_TAG,
id = `headlessui-radiogroup-${internalId}`,
value: controlledValue,
defaultValue,
form: formName,
name,
onChange: controlledOnChange,
by = (a: TType, z: TType) => a === z,
Expand Down Expand Up @@ -343,6 +345,7 @@ function RadioGroupFn<TTag extends ElementType = typeof DEFAULT_RADIO_GROUP_TAG,
checked: value != null,
hidden: true,
readOnly: true,
form: formName,
name,
value,
})}
Expand Down
37 changes: 37 additions & 0 deletions packages/@headlessui-react/src/components/switch/switch.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,43 @@ describe('Mouse interactions', () => {
})

describe('Form compatibility', () => {
it('should be possible to set the `form`, which is forwarded to the hidden inputs', async () => {
let submits = jest.fn()

function Example() {
let [state, setState] = useState(false)
return (
<div>
<Switch.Group>
<Switch form="my-form" checked={state} onChange={setState} name="notifications" />
<Switch.Label>Enable notifications</Switch.Label>
</Switch.Group>

<form
id="my-form"
onSubmit={(event) => {
event.preventDefault()
submits([...new FormData(event.currentTarget).entries()])
}}
>
<button>Submit</button>
</form>
</div>
)
}

render(<Example />)

// Toggle
await click(getSwitchLabel())

// Submit the form again
await click(getByText('Submit'))

// Verify that the form has been submitted
expect(submits).lastCalledWith([['notifications', 'on']])
})

it('should be possible to submit a form with an boolean value', async () => {
let submits = jest.fn()

Expand Down
3 changes: 3 additions & 0 deletions packages/@headlessui-react/src/components/switch/switch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ export type SwitchProps<TTag extends ElementType> = Props<
onChange?(checked: boolean): void
name?: string
value?: string
form?: string
}
>

Expand All @@ -127,6 +128,7 @@ function SwitchFn<TTag extends ElementType = typeof DEFAULT_SWITCH_TAG>(
onChange: controlledOnChange,
name,
value,
form,
...theirProps
} = props
let groupContext = useContext(GroupContext)
Expand Down Expand Up @@ -193,6 +195,7 @@ function SwitchFn<TTag extends ElementType = typeof DEFAULT_SWITCH_TAG>(
type: 'checkbox',
hidden: true,
readOnly: true,
form,
checked,
name,
value,
Expand Down
4 changes: 4 additions & 0 deletions packages/@headlessui-vue/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Fix focus styles showing up when using the mouse ([#2347](https://github.com/tailwindlabs/headlessui/pull/2347))

### Added

- Add `form` prop to form-like components such as `RadioGroup`, `Switch`, `Listbox`, and `Combobox` ([#2356](https://github.com/tailwindlabs/headlessui/pull/2356))

## [1.7.12] - 2023-03-03

### Fixed
Expand Down
44 changes: 44 additions & 0 deletions packages/@headlessui-vue/src/components/combobox/combobox.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5926,6 +5926,50 @@ describe('Multi-select', () => {
})

describe('Form compatibility', () => {
it('should be possible to set the `form`, which is forwarded to the hidden inputs', async () => {
let submits = jest.fn()

renderTemplate({
template: html`
<div>
<Combobox form="my-form" v-model="value" name="delivery">
<ComboboxInput />
<ComboboxButton>Trigger</ComboboxButton>
<ComboboxOptions>
<ComboboxOption value="pickup">Pickup</ComboboxOption>
<ComboboxOption value="home-delivery">Home delivery</ComboboxOption>
<ComboboxOption value="dine-in">Dine in</ComboboxOption>
</ComboboxOptions>
</Combobox>
<form id="my-form" @submit="handleSubmit">
<button>Submit</button>
</form>
</div>
`,
setup: () => {
let value = ref(null)
return {
value,
handleSubmit(event: SubmitEvent) {
event.preventDefault()
submits([...new FormData(event.currentTarget as HTMLFormElement).entries()])
},
}
},
})

// Open combobox
await click(getComboboxButton())

// Choose pickup
await click(getByText('Pickup'))

// Submit the form
await click(getByText('Submit'))

expect(submits).lastCalledWith([['delivery', 'pickup']])
})

it('should be possible to submit a form with a value', async () => {
let submits = jest.fn()

Expand Down
6 changes: 4 additions & 2 deletions packages/@headlessui-vue/src/components/combobox/combobox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,8 @@ export let Combobox = defineComponent({
>,
default: undefined,
},
name: { type: String },
form: { type: String, optional: true },
name: { type: String, optional: true },
nullable: { type: Boolean, default: false },
multiple: { type: [Boolean], default: false },
},
Expand Down Expand Up @@ -466,7 +467,7 @@ export let Combobox = defineComponent({
})

return () => {
let { name, disabled, ...theirProps } = props
let { name, disabled, form, ...theirProps } = props
let slot = {
open: comboboxState.value === ComboboxStates.Open,
disabled,
Expand All @@ -487,6 +488,7 @@ export let Combobox = defineComponent({
type: 'hidden',
hidden: true,
readOnly: true,
form,
name,
value,
})
Expand Down
Loading

0 comments on commit fb612f7

Please sign in to comment.