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 form prop to form-like components such as RadioGroup, Switch, Listbox, and Combobox #2356

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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