From 258f5eb5f089da767e54f861bb56a02ef23b8b74 Mon Sep 17 00:00:00 2001 From: fenn-cs Date: Tue, 5 Sep 2023 16:51:58 +0100 Subject: [PATCH] Add accesibility features to quick share dropdown - 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 --- .../SharingEntryQuickShareSelect.vue | 111 +++++++++++++++--- 1 file changed, 95 insertions(+), 16 deletions(-) diff --git a/apps/files_sharing/src/components/SharingEntryQuickShareSelect.vue b/apps/files_sharing/src/components/SharingEntryQuickShareSelect.vue index 673038b620f43..0fed9be47aaf0 100644 --- a/apps/files_sharing/src/components/SharingEntryQuickShareSelect.vue +++ b/apps/files_sharing/src/components/SharingEntryQuickShareSelect.vue @@ -1,16 +1,31 @@ @@ -26,6 +41,8 @@ import { ATOMIC_PERMISSIONS, } from '../lib/SharePermissionsToolBox.js' +import { createFocusTrap } from 'focus-trap' + export default { components: { DropdownIcon, @@ -45,6 +62,7 @@ export default { return { selectedOption: '', showDropdown: this.toggle, + focusTrap: null, } }, computed: { @@ -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) { @@ -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 @@ -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() + }, }, } @@ -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); @@ -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; @@ -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 */ }