summaryrefslogtreecommitdiffstats
path: root/apps
diff options
context:
space:
mode:
authorArthur Schiwon <blizzz@arthur-schiwon.de>2023-09-07 15:32:09 +0200
committerGitHub <noreply@github.com>2023-09-07 15:32:09 +0200
commit865e32e4317f97c30018d9938b404b68e9fd6710 (patch)
treed5bdecfb63da6ae24dfa38845e5dde438272274c /apps
parenta5f71a75023989d4faf77c925b0eeed14b13d0db (diff)
parentc0b97ea89a2f419fb8dd19b9cb260639c5d0109f (diff)
downloadnextcloud-server-865e32e4317f97c30018d9938b404b68e9fd6710.tar.gz
nextcloud-server-865e32e4317f97c30018d9938b404b68e9fd6710.zip
Merge pull request #40327 from nextcloud/manual/backport/stable27/40266
[stable27] Backport Polish new sharing flow : accesibility, expand bahavior, click outside behaviour
Diffstat (limited to 'apps')
-rw-r--r--apps/files_sharing/src/components/SharingEntryQuickShareSelect.vue110
-rw-r--r--apps/files_sharing/src/views/SharingDetailsTab.vue16
2 files changed, 109 insertions, 17 deletions
diff --git a/apps/files_sharing/src/components/SharingEntryQuickShareSelect.vue b/apps/files_sharing/src/components/SharingEntryQuickShareSelect.vue
index 8128b3925ee..a83ef503048 100644
--- a/apps/files_sharing/src/components/SharingEntryQuickShareSelect.vue
+++ b/apps/files_sharing/src/components/SharingEntryQuickShareSelect.vue
@@ -1,16 +1,30 @@
<template>
- <div :class="{ 'active': showDropdown, 'share-select': true }">
- <span class="trigger-text" @click="toggleDropdown">
+ <div ref="quickShareDropdownContainer"
+ :class="{ 'active': showDropdown, 'share-select': true }">
+ <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.down="handleArrowDown"
+ @keydown.up="handleArrowUp"
+ @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>
@@ -26,6 +40,8 @@ import {
ATOMIC_PERMISSIONS,
} from '../lib/SharePermissionsToolBox.js'
+import { createFocusTrap } from 'focus-trap'
+
export default {
components: {
DropdownIcon,
@@ -45,6 +61,7 @@ export default {
return {
selectedOption: '',
showDropdown: this.toggle,
+ focusTrap: null,
}
},
computed: {
@@ -102,6 +119,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,10 +131,26 @@ export default {
},
mounted() {
this.initializeComponent()
+ window.addEventListener('click', this.handleClickOutside)
+ },
+ beforeDestroy() {
+ // 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
@@ -128,6 +165,51 @@ export default {
initializeComponent() {
this.selectedOption = this.preSelectedOption
},
+ handleClickOutside(event) {
+ const dropdownContainer = this.$refs.quickShareDropdownContainer
+
+ if (dropdownContainer && !dropdownContainer.contains(event.target)) {
+ this.showDropdown = false
+ }
+ },
+ useFocusTrap() {
+ // Create global stack if undefined
+ // Use in with trapStack to avoid conflicting traps
+ Object.assign(window, { _nc_focus_trap: window._nc_focus_trap || [] })
+ const dropdownElement = this.$refs.quickShareDropdown
+ this.focusTrap = createFocusTrap(dropdownElement, {
+ allowOutsideClick: true,
+ trapStack: window._nc_focus_trap,
+ })
+
+ 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()
+ },
},
}
@@ -147,8 +229,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);
@@ -160,6 +244,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;
@@ -172,13 +266,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 */
}
diff --git a/apps/files_sharing/src/views/SharingDetailsTab.vue b/apps/files_sharing/src/views/SharingDetailsTab.vue
index 175812a0ff2..6368bb20b9f 100644
--- a/apps/files_sharing/src/views/SharingDetailsTab.vue
+++ b/apps/files_sharing/src/views/SharingDetailsTab.vue
@@ -61,7 +61,7 @@
name="sharing_permission_radio"
type="radio"
button-variant-grouped="vertical"
- @update:checked="toggleCustomPermissions">
+ @update:checked="expandCustomPermissions">
{{ t('files_sharing', 'Custom permissions') }}
<small>{{ t('files_sharing', customPermissionsList) }}</small>
<template #icon>
@@ -666,16 +666,14 @@ export default {
this.$set(this.share, 'hasDownloadPermission', isDownloadChecked)
}
},
-
- toggleCustomPermissions(selectedPermission) {
- if (this.sharingPermission === 'custom') {
+ expandCustomPermissions() {
+ if (!this.advancedSectionAccordionExpanded) {
this.advancedSectionAccordionExpanded = true
- this.setCustomPermissions = true
- } else {
- this.advancedSectionAccordionExpanded = false
- this.revertSharingPermission = selectedPermission
- this.setCustomPermissions = false
}
+ this.toggleCustomPermissions()
+ },
+ toggleCustomPermissions() {
+ this.setCustomPermissions = this.sharingPermission === 'custom'
},
initializeAttributes() {