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

fix(a11y): Remove nav and radios in sidebar tabs #4456

Merged
merged 2 commits into from Aug 26, 2023
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/components/NcAppSidebar/NcAppSidebarTabs.vue
Expand Up @@ -28,7 +28,7 @@
<div class="app-sidebar-tabs">
<!-- tabs navigation -->
<!-- 33 and 34 code is for page up and page down -->
<nav v-if="hasMultipleTabs"
<div v-if="hasMultipleTabs"
role="tablist"
class="app-sidebar-tabs__nav"
@keydown.left.exact.prevent="focusPreviousTab"
Expand All @@ -50,7 +50,7 @@
class="app-sidebar-tabs__tab"
:class="{ active: tab.id === activeTab }"
role="tab"
type="radio"
type="button"
susnux marked this conversation as resolved.
Show resolved Hide resolved
@update:checked="setActive(tab.id)">
<span class="app-sidebar-tabs__tab-caption">
{{ tab.name }}
Expand All @@ -61,7 +61,7 @@
</NcVNodes>
</template>
</NcCheckboxRadioSwitch>
</nav>
</div>

<!-- tabs content -->
<div :class="{'app-sidebar-tabs__content--multiple': hasMultipleTabs}"
Expand Down
55 changes: 45 additions & 10 deletions src/components/NcCheckboxRadioSwitch/NcCheckboxRadioSwitch.vue
Expand Up @@ -271,14 +271,12 @@ export default {
:style="cssVars"
class="checkbox-radio-switch">
<input :id="id"
:checked="isChecked"
class="checkbox-radio-switch__input"
:disabled="disabled"
:indeterminate="indeterminate"
:name="name"
:type="inputType"
:value="value"
class="checkbox-radio-switch__input"
@change="onToggle">
v-bind="inputProps"
v-on="inputListeners">
<label :for="id" class="checkbox-radio-switch__label">
<div class="checkbox-radio-switch__icon">
<!-- @slot The checkbox/radio icon, you can use it for adding an icon to the button variant
Expand Down Expand Up @@ -317,6 +315,7 @@ import ToggleSwitch from 'vue-material-design-icons/ToggleSwitch.vue'
export const TYPE_CHECKBOX = 'checkbox'
export const TYPE_RADIO = 'radio'
export const TYPE_SWITCH = 'switch'
export const TYPE_BUTTON = 'button'

export default {
name: 'NcCheckboxRadioSwitch',
Expand All @@ -339,20 +338,29 @@ export default {
},

/**
* Input name. Required for radio, optional for checkbox
* Input name. Required for radio, optional for checkbox, and ignored
* for button.
*/
name: {
type: String,
default: null,
},

/**
* Type of the input. checkbox, radio or switch
* Type of the input. checkbox, radio, switch, or button.
*
* Only use button when used in a `tablist` container and the
* `tab` role is set.
*/
type: {
type: String,
default: 'checkbox',
validator: type => type === TYPE_CHECKBOX || type === TYPE_RADIO || type === TYPE_SWITCH,
validator: type => [
TYPE_CHECKBOX,
TYPE_RADIO,
TYPE_SWITCH,
TYPE_BUTTON,
].includes(type),
},

/**
Expand Down Expand Up @@ -426,6 +434,28 @@ export default {
emits: ['update:checked'],

computed: {
inputProps() {
if (this.type === TYPE_BUTTON) {
return null
}
return {
checked: this.isChecked,
indeterminate: this.indeterminate,
name: this.name,
}
},

inputListeners() {
if (this.type === TYPE_BUTTON) {
return {
click: this.onToggle,
}
}
return {
change: this.onToggle,
}
},

/**
* Icon size
*
Expand Down Expand Up @@ -455,8 +485,13 @@ export default {
* @return {string}
*/
inputType() {
if (this.type === TYPE_RADIO) {
return TYPE_RADIO
const nativeTypes = [
TYPE_CHECKBOX,
TYPE_RADIO,
TYPE_BUTTON,
]
if (nativeTypes.includes(this.type)) {
return this.type
}
return TYPE_CHECKBOX
},
Expand Down
32 changes: 16 additions & 16 deletions tests/unit/components/NcAppSidebar/NcAppSidebarTabs.spec.js
Expand Up @@ -63,8 +63,8 @@ describe('NcAppSidebarTabs.vue', () => {
expect(onWarning).toHaveBeenCalledTimes(0)
expect(consoleDebug).toHaveBeenCalledTimes(0)
})
it('does not display the nav element', () => {
expect(wrapper.find('nav').exists()).toBe(false)
it('does not display the tablist element', () => {
expect(wrapper.find('div[role="tablist"]').exists()).toBe(false)
})
})
describe('with div and secondary action', () => {
Expand All @@ -88,8 +88,8 @@ describe('NcAppSidebarTabs.vue', () => {
})
})

it('does not display the nav element', () => {
expect(wrapper.find('nav').exists()).toBe(false)
it('does not display the tablist element', () => {
expect(wrapper.find('div[role="tablist"]').exists()).toBe(false)
})
})
describe('when only children of type AppSidebarTab is used', () => {
Expand All @@ -113,25 +113,25 @@ describe('NcAppSidebarTabs.vue', () => {
it('Issues no warning.', () => {
expect(onWarning).toHaveBeenCalledTimes(0)
})
it('display the nav element', () => {
expect(wrapper.find('nav').exists()).toBe(true)
it('display the tablist element', () => {
expect(wrapper.find('div[role="tablist"]').exists()).toBe(true)
})
it('display all the 3 elements', () => {
const liList = wrapper.findAll('nav>.checkbox-radio-switch')
const liList = wrapper.findAll('div[role="tablist"]>.checkbox-radio-switch')
expect(liList.length).toEqual(3)
})
it('emit "update:active" event with the selected tab id when clicking on a tab', () => {
const lastLink = wrapper.find('nav>.checkbox-radio-switch:last-of-type>label')
const lastLink = wrapper.find('div[role="tablist"]>.checkbox-radio-switch:last-of-type>label')
lastLink.trigger('click')
expect(wrapper.emitted('update:active')[0]).toEqual(['last'])
})
it('emit "update:active" event with the first tab id when keydown pageup is pressed', () => {
const lastLink = wrapper.find('nav>.checkbox-radio-switch:last-of-type>label')
const lastLink = wrapper.find('div[role="tablist"]>.checkbox-radio-switch:last-of-type>label')
lastLink.trigger('keydown.pageup')
expect(wrapper.emitted('update:active')[0]).toEqual(['first'])
})
it('emit "update:active" event with the last tab id when keydown pagedown is pressed', () => {
const lastLink = wrapper.find('nav>.checkbox-radio-switch:last-of-type>label')
const lastLink = wrapper.find('div[role="tablist"]>.checkbox-radio-switch:last-of-type>label')
lastLink.trigger('keydown.pagedown')
expect(wrapper.emitted('update:active')[0]).toEqual(['last'])
})
Expand All @@ -141,12 +141,12 @@ describe('NcAppSidebarTabs.vue', () => {
})
it('does not emit "update:active" event when keydown left is pressed', () => {
expect(wrapper.emitted('update:active')).toBeFalsy()
const firstLink = wrapper.find('nav>.checkbox-radio-switch>label')
const firstLink = wrapper.find('div[role="tablist"]>.checkbox-radio-switch>label')
firstLink.trigger('keydown.left')
expect(wrapper.emitted('update:active')).toBeFalsy()
})
it('emit "update:active" event with the next tab id when keydown right is pressed', () => {
const firstLink = wrapper.find('nav>.checkbox-radio-switch>label')
const firstLink = wrapper.find('div[role="tablist"]>.checkbox-radio-switch>label')
firstLink.trigger('keydown.right')
expect(wrapper.emitted('update:active')[0]).toEqual(['second'])
})
Expand All @@ -156,13 +156,13 @@ describe('NcAppSidebarTabs.vue', () => {
wrapper.setData({ activeTab: 'last' })
})
it('emit "update:active" event with the previous tab id when keydown left is pressed', () => {
const lastLink = wrapper.find('nav>.checkbox-radio-switch:last-of-type>label')
const lastLink = wrapper.find('div[role="tablist"]>.checkbox-radio-switch:last-of-type>label')
lastLink.trigger('keydown.left')
expect(wrapper.emitted('update:active')[0]).toEqual(['second'])
})
it('does not emit "update:active" event when keydown right is pressed', () => {
expect(wrapper.emitted('update:active')).toBeFalsy()
const lastLink = wrapper.find('nav>.checkbox-radio-switch:last-of-type>label')
const lastLink = wrapper.find('div[role="tablist"]>.checkbox-radio-switch:last-of-type>label')
lastLink.trigger('keydown.right')
expect(wrapper.emitted('update:active')).toBeFalsy()
})
Expand All @@ -185,8 +185,8 @@ describe('NcAppSidebarTabs.vue', () => {
it('Issues no warning.', () => {
expect(onWarning).toHaveBeenCalledTimes(0)
})
it('does not display the nav element', () => {
expect(wrapper.find('nav').exists()).toBe(false)
it('does not display the tablist element', () => {
expect(wrapper.find('div[role="tablist"]').exists()).toBe(false)
})
})
})
Expand Down