mirror of
https://github.com/nextcloud/server.git
synced 2026-05-28 04:32:30 -04:00
Merge pull request #40266 from nextcloud/40172-polish-new-sharing-flow
40172 Polish new sharing flow : accesibility, expand bahavior, click outside behaviour
This commit is contained in:
commit
a0c4935e32
9 changed files with 116 additions and 24 deletions
|
|
@ -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 */
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
||||
|
|
|
|||
3
dist/2079-2079.js
vendored
Normal file
3
dist/2079-2079.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/2079-2079.js.map
vendored
Normal file
1
dist/2079-2079.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
3
dist/894-894.js
vendored
3
dist/894-894.js
vendored
File diff suppressed because one or more lines are too long
1
dist/894-894.js.map
vendored
1
dist/894-894.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/files_sharing-files_sharing_tab.js
vendored
4
dist/files_sharing-files_sharing_tab.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files_sharing-files_sharing_tab.js.map
vendored
2
dist/files_sharing-files_sharing_tab.js.map
vendored
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue