Allow setting custom tabIndex on the <Switch /> component (#2966)
* allow setting a custom `tabIndex` on the `<Switch />` component * update changelog
This commit is contained in:
@@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Ensure `children` prop of `Field` component can be a render prop ([#2941](https://github.com/tailwindlabs/headlessui/pull/2941))
|
||||
- Add `hidden` attribute to internal `<Hidden />` component when the `Features.Hidden` feature is used ([#2955](https://github.com/tailwindlabs/headlessui/pull/2955))
|
||||
- Attempt form submission when pressing `Enter` on `Checkbox` component ([#2962](https://github.com/tailwindlabs/headlessui/pull/2962))
|
||||
- Allow setting custom `tabIndex` on the `<Switch />` component ([#2966](https://github.com/tailwindlabs/headlessui/pull/2966))
|
||||
|
||||
## [2.0.0-alpha.4] - 2024-01-03
|
||||
|
||||
|
||||
@@ -59,6 +59,47 @@ describe('Rendering', () => {
|
||||
assertSwitch({ state: SwitchState.Off, label: 'Enable notifications' })
|
||||
})
|
||||
|
||||
describe('`tabIndex` attribute', () => {
|
||||
it('should have a default tabIndex of `0`', () => {
|
||||
render(
|
||||
<Switch checked={false} onChange={console.log}>
|
||||
<span>Enable notifications</span>
|
||||
</Switch>
|
||||
)
|
||||
assertSwitch({
|
||||
state: SwitchState.Off,
|
||||
label: 'Enable notifications',
|
||||
attributes: { tabindex: '0' },
|
||||
})
|
||||
})
|
||||
|
||||
it('should be possible to override the `tabIndex`', () => {
|
||||
render(
|
||||
<Switch checked={false} onChange={console.log} tabIndex={3}>
|
||||
<span>Enable notifications</span>
|
||||
</Switch>
|
||||
)
|
||||
assertSwitch({
|
||||
state: SwitchState.Off,
|
||||
label: 'Enable notifications',
|
||||
attributes: { tabindex: '3' },
|
||||
})
|
||||
})
|
||||
|
||||
it('should not be possible to override the `tabIndex` to `-1`', () => {
|
||||
render(
|
||||
<Switch checked={false} onChange={console.log} tabIndex={-1}>
|
||||
<span>Enable notifications</span>
|
||||
</Switch>
|
||||
)
|
||||
assertSwitch({
|
||||
state: SwitchState.Off,
|
||||
label: 'Enable notifications',
|
||||
attributes: { tabindex: '0' },
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('`type` attribute', () => {
|
||||
it('should set the `type` to "button" by default', async () => {
|
||||
render(
|
||||
|
||||
@@ -116,12 +116,7 @@ type SwitchRenderPropArg = {
|
||||
changing: boolean
|
||||
disabled: boolean
|
||||
}
|
||||
type SwitchPropsWeControl =
|
||||
| 'aria-checked'
|
||||
| 'aria-describedby'
|
||||
| 'aria-labelledby'
|
||||
| 'role'
|
||||
| 'tabIndex'
|
||||
type SwitchPropsWeControl = 'aria-checked' | 'aria-describedby' | 'aria-labelledby' | 'role'
|
||||
|
||||
export type SwitchProps<TTag extends ElementType = typeof DEFAULT_SWITCH_TAG> = Props<
|
||||
TTag,
|
||||
@@ -136,6 +131,7 @@ export type SwitchProps<TTag extends ElementType = typeof DEFAULT_SWITCH_TAG> =
|
||||
form?: string
|
||||
autoFocus?: boolean
|
||||
disabled?: boolean
|
||||
tabIndex?: number
|
||||
}
|
||||
>
|
||||
|
||||
@@ -220,7 +216,7 @@ function SwitchFn<TTag extends ElementType = typeof DEFAULT_SWITCH_TAG>(
|
||||
ref: switchRef,
|
||||
role: 'switch',
|
||||
type: useResolveButtonType(props, internalSwitchRef),
|
||||
tabIndex: 0,
|
||||
tabIndex: props.tabIndex === -1 ? 0 : props.tabIndex ?? 0,
|
||||
'aria-checked': checked,
|
||||
'aria-labelledby': labelledBy,
|
||||
'aria-describedby': describedBy,
|
||||
|
||||
@@ -1145,6 +1145,7 @@ export function assertSwitch(
|
||||
textContent?: string
|
||||
label?: string
|
||||
description?: string
|
||||
attributes?: Record<string, string | null>
|
||||
},
|
||||
switchElement = getSwitch()
|
||||
) {
|
||||
@@ -1152,7 +1153,8 @@ export function assertSwitch(
|
||||
if (switchElement === null) return expect(switchElement).not.toBe(null)
|
||||
|
||||
expect(switchElement).toHaveAttribute('role', 'switch')
|
||||
expect(switchElement).toHaveAttribute('tabindex', '0')
|
||||
let tabIndex = Number(switchElement.getAttribute('tabindex') ?? '0')
|
||||
expect(tabIndex).toBeGreaterThanOrEqual(0)
|
||||
|
||||
if (options.textContent) {
|
||||
expect(switchElement).toHaveTextContent(options.textContent)
|
||||
@@ -1182,6 +1184,11 @@ export function assertSwitch(
|
||||
default:
|
||||
assertNever(options.state)
|
||||
}
|
||||
|
||||
// Ensure disclosure button has the following attributes
|
||||
for (let attributeName in options.attributes) {
|
||||
expect(switchElement).toHaveAttribute(attributeName, options.attributes[attributeName])
|
||||
}
|
||||
} catch (err) {
|
||||
if (err instanceof Error) Error.captureStackTrace(err, assertSwitch)
|
||||
throw err
|
||||
|
||||
@@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Prevent default behaviour when clicking outside of a `DialogPanel` ([#2919](https://github.com/tailwindlabs/headlessui/pull/2919))
|
||||
- Don’t override explicit `disabled` prop for components inside `<MenuItem>` ([#2929](https://github.com/tailwindlabs/headlessui/pull/2929))
|
||||
- Add `hidden` attribute to internal `<Hidden />` component when the `Features.Hidden` feature is used ([#2955](https://github.com/tailwindlabs/headlessui/pull/2955))
|
||||
- Allow setting custom `tabIndex` on the `<Switch />` component ([#2966](https://github.com/tailwindlabs/headlessui/pull/2966))
|
||||
|
||||
## [1.7.17] - 2024-01-08
|
||||
|
||||
|
||||
@@ -327,6 +327,38 @@ describe('Rendering', () => {
|
||||
expect(handleChange).toHaveBeenNthCalledWith(3, true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('`tabIndex` attribute', () => {
|
||||
it('should have a default tabIndex of `0`', () => {
|
||||
renderTemplate(html`<Switch :checked="false" :tabIndex="0">Enable notifications</Switch>`)
|
||||
|
||||
assertSwitch({
|
||||
state: SwitchState.Off,
|
||||
label: 'Enable notifications',
|
||||
attributes: { tabindex: '0' },
|
||||
})
|
||||
})
|
||||
|
||||
it('should be possible to override the `tabIndex`', () => {
|
||||
renderTemplate(html`<Switch :checked="false" :tabIndex="3">Enable notifications</Switch>`)
|
||||
|
||||
assertSwitch({
|
||||
state: SwitchState.Off,
|
||||
label: 'Enable notifications',
|
||||
attributes: { tabindex: '3' },
|
||||
})
|
||||
})
|
||||
|
||||
it('should not be possible to override the `tabIndex` to `-1`', () => {
|
||||
renderTemplate(html`<Switch :checked="false" :tabIndex="-1">Enable notifications</Switch>`)
|
||||
|
||||
assertSwitch({
|
||||
state: SwitchState.Off,
|
||||
label: 'Enable notifications',
|
||||
attributes: { tabindex: '0' },
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Render composition', () => {
|
||||
|
||||
@@ -78,6 +78,7 @@ export let Switch = defineComponent({
|
||||
name: { type: String, optional: true },
|
||||
value: { type: String, optional: true },
|
||||
id: { type: String, default: () => `headlessui-switch-${useId()}` },
|
||||
tabIndex: { type: Number, default: 0 },
|
||||
},
|
||||
inheritAttrs: false,
|
||||
setup(props, { emit, attrs, slots, expose }) {
|
||||
@@ -143,14 +144,14 @@ export let Switch = defineComponent({
|
||||
})
|
||||
|
||||
return () => {
|
||||
let { id, name, value, form, ...theirProps } = props
|
||||
let { id, name, value, form, tabIndex, ...theirProps } = props
|
||||
let slot = { checked: checked.value }
|
||||
let ourProps = {
|
||||
id,
|
||||
ref: switchRef,
|
||||
role: 'switch',
|
||||
type: type.value,
|
||||
tabIndex: 0,
|
||||
tabIndex: tabIndex === -1 ? 0 : tabIndex,
|
||||
'aria-checked': checked.value,
|
||||
'aria-labelledby': api?.labelledby.value,
|
||||
'aria-describedby': api?.describedby.value,
|
||||
|
||||
@@ -978,6 +978,7 @@ export function assertSwitch(
|
||||
textContent?: string
|
||||
label?: string
|
||||
description?: string
|
||||
attributes?: Record<string, string | null>
|
||||
},
|
||||
switchElement = getSwitch()
|
||||
) {
|
||||
@@ -985,7 +986,8 @@ export function assertSwitch(
|
||||
if (switchElement === null) return expect(switchElement).not.toBe(null)
|
||||
|
||||
expect(switchElement).toHaveAttribute('role', 'switch')
|
||||
expect(switchElement).toHaveAttribute('tabindex', '0')
|
||||
let tabIndex = Number(switchElement.getAttribute('tabindex') ?? '0')
|
||||
expect(tabIndex).toBeGreaterThanOrEqual(0)
|
||||
|
||||
if (options.textContent) {
|
||||
expect(switchElement).toHaveTextContent(options.textContent)
|
||||
@@ -1015,6 +1017,11 @@ export function assertSwitch(
|
||||
default:
|
||||
assertNever(options.state)
|
||||
}
|
||||
|
||||
// Ensure disclosure button has the following attributes
|
||||
for (let attributeName in options.attributes) {
|
||||
expect(switchElement).toHaveAttribute(attributeName, options.attributes[attributeName])
|
||||
}
|
||||
} catch (err) {
|
||||
if (err instanceof Error) Error.captureStackTrace(err, assertSwitch)
|
||||
throw err
|
||||
|
||||
Reference in New Issue
Block a user