Skip to content

Commit

Permalink
Add accesibility features to quick share dropdown
Browse files Browse the repository at this point in the history
- Adds appropriate aria attributes
- Uses button element for dropdown items as it's more semantically correct
- Uses trap-focus lib to trap focus when the drowpdown is active
- Adds custom handling for arrow up and down

Signed-off-by: fenn-cs <fenn25.fn@gmail.com>
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
  • Loading branch information
Fenn-CS committed Sep 6, 2023
1 parent 8570fe7 commit 8226964
Show file tree
Hide file tree
Showing 8 changed files with 102 additions and 23 deletions.
111 changes: 95 additions & 16 deletions apps/files_sharing/src/components/SharingEntryQuickShareSelect.vue
@@ -1,16 +1,31 @@
<template>
<div :class="{ 'active': showDropdown, 'share-select': true }" ref="quickShareDropdown">
<span class="trigger-text" @click="toggleDropdown">
<div ref="quickShareDropdownContainer"
:class="{ 'active': showDropdown, 'share-select': true }"
tabindex="0"
@keydown.down="handleArrowDown"
@keydown.up="handleArrowUp">
<span :id="dropdownId"
class="trigger-text"
:aria-expanded="showDropdown"
:aria-haspopup="true"
aria-label="Quick share options dropdown"
@click="toggleDropdown">
{{ selectedOption }}
<DropdownIcon :size="15" />
</span>
<div v-if="showDropdown" class="share-select-dropdown-container">
<div v-for="option in options"
<div v-if="showDropdown"
ref="quickShareDropdown"
class="share-select-dropdown"
:aria-labelledby="dropdownId"
tabindex="0"
@keydown.esc="closeDropdown">
<button v-for="option in options"
:key="option"
:class="{ 'dropdown-item': true, 'selected': option === selectedOption }"
:aria-selected="option === selectedOption"
@click="selectOption(option)">
{{ option }}
</div>
</button>
</div>
</div>
</template>
Expand All @@ -26,6 +41,8 @@ import {
ATOMIC_PERMISSIONS,
} from '../lib/SharePermissionsToolBox.js'
import { createFocusTrap } from 'focus-trap'
export default {
components: {
DropdownIcon,
Expand All @@ -45,6 +62,7 @@ export default {
return {
selectedOption: '',
showDropdown: this.toggle,
focusTrap: null,
}
},
computed: {
Expand Down Expand Up @@ -102,6 +120,10 @@ export default {
return BUNDLED_PERMISSIONS.READ_ONLY
}
},
dropdownId() {
// Generate a unique ID for ARIA attributes
return `dropdown-${Math.random().toString(36).substr(2, 9)}`
},
},
watch: {
toggle(toggleValue) {
Expand All @@ -110,15 +132,26 @@ export default {
},
mounted() {
this.initializeComponent()
window.addEventListener('click', this.handleClickOutside);
window.addEventListener('click', this.handleClickOutside)
},
beforeDestroy() {
// Remove the global click event listener to prevent memory leaks
window.removeEventListener('click', this.handleClickOutside);
},
// Remove the global click event listener to prevent memory leaks
window.removeEventListener('click', this.handleClickOutside)
},
methods: {
toggleDropdown() {
this.showDropdown = !this.showDropdown
if (this.showDropdown) {
this.$nextTick(() => {
this.useFocusTrap()
})
} else {
this.clearFocusTrap()
}
},
closeDropdown() {
this.clearFocusTrap()
this.showDropdown = false
},
selectOption(option) {
this.selectedOption = option
Expand All @@ -134,12 +167,46 @@ export default {
this.selectedOption = this.preSelectedOption
},
handleClickOutside(event) {
const dropdownElement = this.$refs.quickShareDropdown;
const dropdownContainer = this.$refs.quickShareDropdownContainer
if (dropdownContainer && !dropdownContainer.contains(event.target)) {
this.showDropdown = false
}
},
useFocusTrap() {
const dropdownElement = this.$refs.quickShareDropdown
this.focusTrap = createFocusTrap(dropdownElement, {
allowOutsideClick: true,
})
if (dropdownElement && !dropdownElement.contains(event.target)) {
this.showDropdown = false;
this.focusTrap.activate()
},
clearFocusTrap() {
this.focusTrap?.deactivate()
this.focusTrap = null
},
shiftFocusForward() {
const currentElement = document.activeElement
let nextElement = currentElement.nextElementSibling
if (!nextElement) {
nextElement = this.$refs.quickShareDropdown.firstElementChild
}
nextElement.focus()
},
shiftFocusBackward() {
const currentElement = document.activeElement
let previousElement = currentElement.previousElementSibling
if (!previousElement) {
previousElement = this.$refs.quickShareDropdown.lastElementChild
}
},
previousElement.focus()
},
handleArrowUp() {
this.shiftFocusBackward()
},
handleArrowDown() {
this.shiftFocusForward()
},
},
}
Expand All @@ -159,8 +226,10 @@ export default {
color: var(--color-primary-element);
}
.share-select-dropdown-container {
.share-select-dropdown {
position: absolute;
display: flex;
flex-direction: column;
top: 100%;
left: 0;
background-color: var(--color-main-background);
Expand All @@ -172,6 +241,16 @@ export default {
.dropdown-item {
padding: 8px;
font-size: 12px;
background: none;
border: none;
border-radius: 0;
font: inherit;
cursor: pointer;
color: inherit;
outline: none;
width: 100%;
white-space: nowrap;
text-align: left;
&:hover {
background-color: #f2f2f2;
Expand All @@ -184,13 +263,13 @@ export default {
}
/* Optional: Add a transition effect for smoother dropdown animation */
.share-select-dropdown-container {
.share-select-dropdown {
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease;
}
&.active .share-select-dropdown-container {
&.active .share-select-dropdown {
max-height: 200px;
/* Adjust the value to your desired height */
}
Expand Down
3 changes: 0 additions & 3 deletions dist/894-894.js

This file was deleted.

1 change: 0 additions & 1 deletion dist/894-894.js.map

This file was deleted.

3 changes: 3 additions & 0 deletions dist/9875-9875.js

Large diffs are not rendered by default.

File renamed without changes.
1 change: 1 addition & 0 deletions dist/9875-9875.js.map

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions dist/files_sharing-files_sharing_tab.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/files_sharing-files_sharing_tab.js.map

Large diffs are not rendered by default.

0 comments on commit 8226964

Please sign in to comment.