Skip to content

Commit

Permalink
Merge pull request #41199 from nextcloud/fix/apporder-accessible-details
Browse files Browse the repository at this point in the history
fix(theming): Add accessible information to app order settings
  • Loading branch information
susnux authored Oct 31, 2023
2 parents b4e7070 + 0219bfa commit 16e97f0
Show file tree
Hide file tree
Showing 7 changed files with 98 additions and 25 deletions.
71 changes: 59 additions & 12 deletions apps/theming/src/components/AppOrderSelector.vue
Original file line number Diff line number Diff line change
@@ -1,21 +1,34 @@
<template>
<ol ref="listElement" data-cy-app-order class="order-selector">
<AppOrderSelectorElement v-for="app,index in appList"
:key="`${app.id}${renderCount}`"
ref="selectorElements"
:app="app"
:is-first="index === 0 || !!appList[index - 1].default"
:is-last="index === value.length - 1"
v-on="app.default ? {} : {
'move:up': () => moveUp(index),
'move:down': () => moveDown(index),
}" />
</ol>
<Fragment>
<div :id="statusInfoId"
aria-live="polite"
class="hidden-visually"
role="status">
{{ statusInfo }}
</div>
<ol ref="listElement" data-cy-app-order class="order-selector">
<AppOrderSelectorElement v-for="app,index in appList"
:key="`${app.id}${renderCount}`"
ref="selectorElements"
:app="app"
:aria-details="ariaDetails"
:aria-describedby="statusInfoId"
:is-first="index === 0 || !!appList[index - 1].default"
:is-last="index === value.length - 1"
v-on="app.default ? {} : {
'move:up': () => moveUp(index),
'move:down': () => moveDown(index),
'update:focus': () => updateStatusInfo(index),
}" />
</ol>
</Fragment>
</template>

<script lang="ts">
import { translate as t } from '@nextcloud/l10n'
import { useSortable } from '@vueuse/integrations/useSortable'
import { PropType, computed, defineComponent, onUpdated, ref } from 'vue'
import { Fragment } from 'vue-frag'
import AppOrderSelectorElement from './AppOrderSelectorElement.vue'
Expand All @@ -32,8 +45,16 @@ export default defineComponent({
name: 'AppOrderSelector',
components: {
AppOrderSelectorElement,
Fragment,
},
props: {
/**
* Details like status information that need to be forwarded to the interactive elements
*/
ariaDetails: {
type: String,
default: null,
},
/**
* List of apps to reorder
*/
Expand Down Expand Up @@ -125,13 +146,39 @@ export default defineComponent({
emit('update:value', [...before, props.value[index], ...after])
}
/**
* Additional status information to show to screen reader users for accessibility
*/
const statusInfo = ref('')
/**
* ID to be used on the status info element
*/
const statusInfoId = `sorting-status-info-${(Math.random() + 1).toString(36).substring(7)}`
/**
* Update the status information for the currently selected app
* @param index Index of the app that is currently selected
*/
const updateStatusInfo = (index: number) => {
statusInfo.value = t('theming', 'Current selected app: {app}, position {position} of {total}', {
app: props.value[index].label,
position: index + 1,
total: props.value.length,
})
}
return {
appList,
listElement,
moveDown,
moveUp,
statusInfoId,
statusInfo,
updateStatusInfo,
renderCount,
selectorElements,
}
Expand Down
22 changes: 21 additions & 1 deletion apps/theming/src/components/AppOrderSelectorElement.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
:class="{
'order-selector-element': true,
'order-selector-element--disabled': app.default
}">
}"
@focusin="$emit('update:focus')">
<svg width="20"
height="20"
viewBox="0 0 20 20"
Expand All @@ -25,6 +26,8 @@
<NcButton v-show="!isFirst && !app.default"
ref="buttonUp"
:aria-label="t('settings', 'Move up')"
:aria-describedby="ariaDescribedby"
:aria-details="ariaDetails"
data-cy-app-order-button="up"
type="tertiary-no-background"
@click="moveUp">
Expand All @@ -36,6 +39,8 @@
<NcButton v-show="!isLast && !app.default"
ref="buttonDown"
:aria-label="t('settings', 'Move down')"
:aria-describedby="ariaDescribedby"
:aria-details="ariaDetails"
data-cy-app-order-button="down"
type="tertiary-no-background"
@click="moveDown">
Expand Down Expand Up @@ -73,6 +78,17 @@ export default defineComponent({
NcButton,
},
props: {
/**
* Needs to be forwarded to the buttons (as interactive elements)
*/
ariaDescribedby: {
type: String,
default: null,
},
ariaDetails: {
type: String,
default: null,
},
app: {
type: Object as PropType<IApp>,
required: true,
Expand All @@ -89,6 +105,10 @@ export default defineComponent({
emits: {
'move:up': () => true,
'move:down': () => true,
/**
* We need this as Sortable.js removes all native focus event listeners
*/
'update:focus': () => true,
},
setup(props, { emit }) {
const buttonUp = ref()
Expand Down
18 changes: 12 additions & 6 deletions cypress/e2e/theming/navigation-bar-settings.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,21 +241,27 @@ describe('User theming app order list accessibility', () => {
})

it('click the first button', () => {
cy.get('[data-cy-app-order] [data-cy-app-order-element]:first-of-type [data-cy-app-order-button="down"]').should('be.visible').click()
cy.get('[data-cy-app-order] [data-cy-app-order-element="dashboard"] [data-cy-app-order-button="down"]').should('be.visible').focus()
cy.get('[data-cy-app-order] [data-cy-app-order-element="dashboard"] [data-cy-app-order-button="down"]').click()
})

it('see the same app kept the focus', () => {
cy.get('[data-cy-app-order] [data-cy-app-order-element]:first-of-type [data-cy-app-order-button="down"]').should('not.have.focus')
cy.get('[data-cy-app-order] [data-cy-app-order-element]:last-of-type [data-cy-app-order-button="up"]').should('have.focus')
cy.get('[data-cy-app-order] [data-cy-app-order-element="files"] [data-cy-app-order-button="down"]').should('not.have.focus')
cy.get('[data-cy-app-order] [data-cy-app-order-element="files"] [data-cy-app-order-button="up"]').should('not.have.focus')
cy.get('[data-cy-app-order] [data-cy-app-order-element="dashboard"] [data-cy-app-order-button="down"]').should('not.have.focus')
cy.get('[data-cy-app-order] [data-cy-app-order-element="dashboard"] [data-cy-app-order-button="up"]').should('have.focus')
})

it('click the last button', () => {
cy.get('[data-cy-app-order] [data-cy-app-order-element]:last-of-type [data-cy-app-order-button="up"]').should('be.visible').click()
cy.get('[data-cy-app-order] [data-cy-app-order-element="dashboard"] [data-cy-app-order-button="up"]').should('be.visible').focus()
cy.get('[data-cy-app-order] [data-cy-app-order-element="dashboard"] [data-cy-app-order-button="up"]').click()
})

it('see the same app kept the focus', () => {
cy.get('[data-cy-app-order] [data-cy-app-order-element]:first-of-type [data-cy-app-order-button="down"]').should('have.focus')
cy.get('[data-cy-app-order] [data-cy-app-order-element]:last-of-type [data-cy-app-order-button="up"]').should('not.have.focus')
cy.get('[data-cy-app-order] [data-cy-app-order-element="files"] [data-cy-app-order-button="down"]').should('not.have.focus')
cy.get('[data-cy-app-order] [data-cy-app-order-element="files"] [data-cy-app-order-button="up"]').should('not.have.focus')
cy.get('[data-cy-app-order] [data-cy-app-order-element="dashboard"] [data-cy-app-order-button="up"]').should('not.have.focus')
cy.get('[data-cy-app-order] [data-cy-app-order-element="dashboard"] [data-cy-app-order-button="down"]').should('have.focus')
})
})

Expand Down
4 changes: 2 additions & 2 deletions dist/theming-admin-theming.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/theming-admin-theming.js.map

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions dist/theming-personal-theming.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/theming-personal-theming.js.map

Large diffs are not rendered by default.

0 comments on commit 16e97f0

Please sign in to comment.