Skip to content

Commit

Permalink
Allow setting custom tabIndex on the <Switch /> component (#2966)
Browse files Browse the repository at this point in the history
* allow setting a custom `tabIndex` on the `<Switch />` component

* update changelog
  • Loading branch information
RobinMalfait committed Apr 15, 2024
1 parent 04695b2 commit e9df8dd
Show file tree
Hide file tree
Showing 8 changed files with 97 additions and 11 deletions.
1 change: 1 addition & 0 deletions packages/@headlessui-react/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Expose `disabled` state on `<Tab />` component ([#2918](https://github.com/tailwindlabs/headlessui/pull/2918))
- Prevent default behaviour when clicking outside of a `Dialog.Panel` ([#2919](https://github.com/tailwindlabs/headlessui/pull/2919))
- 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.18] - 2024-01-08

Expand Down
41 changes: 41 additions & 0 deletions packages/@headlessui-react/src/components/switch/switch.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
10 changes: 3 additions & 7 deletions packages/@headlessui-react/src/components/switch/switch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,12 +102,7 @@ let DEFAULT_SWITCH_TAG = 'button' as const
interface SwitchRenderPropArg {
checked: 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> = Props<
TTag,
Expand All @@ -120,6 +115,7 @@ export type SwitchProps<TTag extends ElementType> = Props<
name?: string
value?: string
form?: string
tabIndex?: number
}
>

Expand Down Expand Up @@ -172,7 +168,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': groupContext?.labelledby,
'aria-describedby': groupContext?.describedby,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -978,14 +978,16 @@ export function assertSwitch(
textContent?: string
label?: string
description?: string
attributes?: Record<string, string | null>
},
switchElement = getSwitch()
) {
try {
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)
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions packages/@headlessui-vue/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,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.19] - 2024-02-07

Expand Down
32 changes: 32 additions & 0 deletions packages/@headlessui-vue/src/components/switch/switch.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
5 changes: 3 additions & 2 deletions packages/@headlessui-vue/src/components/switch/switch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export let Switch = defineComponent({
name: { type: String, optional: true },
value: { type: String, optional: true },
id: { type: String, default: null },
tabIndex: { type: Number, default: 0 },
},
inheritAttrs: false,
setup(props, { emit, attrs, slots, expose }) {
Expand Down Expand Up @@ -145,14 +146,14 @@ export let Switch = defineComponent({
})

return () => {
let { name, value, form, ...theirProps } = props
let { 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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -978,14 +978,16 @@ export function assertSwitch(
textContent?: string
label?: string
description?: string
attributes?: Record<string, string | null>
},
switchElement = getSwitch()
) {
try {
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)
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit e9df8dd

Please sign in to comment.