fix(sharing): migrate QuickShareSelect to NcActions

Signed-off-by: Grigorii K. Shartsev <me@shgk.me>
pull/43213/head
Grigorii K. Shartsev 4 months ago
parent 0395cd8716
commit 66ba4c1e1b

@ -29,7 +29,7 @@
:menu-position="'left'"
:url="share.shareWithAvatar" />
<div class="sharing-entry__summary" @click.prevent="toggleQuickShareSelect">
<div class="sharing-entry__summary">
<component :is="share.shareWithLink ? 'a' : 'div'"
:title="tooltip"
:aria-label="tooltip"
@ -41,14 +41,13 @@
<small v-if="hasStatus && share.status.message">({{ share.status.message }})</small>
</span>
</component>
<QuickShareSelect :share="share"
<SharingEntryQuickShareSelect :share="share"
:file-info="fileInfo"
:toggle="showDropdown"
@open-sharing-details="openShareDetailsForCustomSettings(share)" />
</div>
<NcButton class="sharing-entry__action"
:aria-label="t('files_sharing', 'Open Sharing Details')"
type="tertiary-no-background"
type="tertiary"
@click="openSharingDetails(share)">
<template #icon>
<DotsHorizontalIcon :size="20" />
@ -63,7 +62,7 @@ import NcSelect from '@nextcloud/vue/dist/Components/NcSelect.js'
import NcAvatar from '@nextcloud/vue/dist/Components/NcAvatar.js'
import DotsHorizontalIcon from 'vue-material-design-icons/DotsHorizontal.vue'
import QuickShareSelect from './SharingEntryQuickShareSelect.vue'
import SharingEntryQuickShareSelect from './SharingEntryQuickShareSelect.vue'
import SharesMixin from '../mixins/SharesMixin.js'
import ShareDetails from '../mixins/ShareDetails.js'
@ -76,16 +75,11 @@ export default {
NcAvatar,
DotsHorizontalIcon,
NcSelect,
QuickShareSelect,
SharingEntryQuickShareSelect,
},
mixins: [SharesMixin, ShareDetails],
data() {
return {
showDropdown: false,
}
},
computed: {
title() {
let title = this.share.shareWithDisplayName
@ -140,9 +134,6 @@ export default {
onMenuClose() {
this.onNoteSubmit()
},
toggleQuickShareSelect() {
this.showDropdown = !this.showDropdown
},
},
}
</script>
@ -158,6 +149,7 @@ export default {
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-start;
flex: 1 0;
min-width: 0;

@ -27,17 +27,16 @@
class="sharing-entry__avatar" />
<div class="sharing-entry__summary">
<div class="sharing-entry__desc" @click.prevent="toggleQuickShareSelect">
<div class="sharing-entry__desc">
<span class="sharing-entry__title" :title="title">
{{ title }}
</span>
<p v-if="subtitle">
{{ subtitle }}
</p>
<QuickShareSelect v-if="share && share.permissions !== undefined"
<SharingEntryQuickShareSelect v-if="share && share.permissions !== undefined"
:share="share"
:file-info="fileInfo"
:toggle="showDropdown"
@open-sharing-details="openShareDetailsForCustomSettings(share)" />
</div>
@ -199,7 +198,7 @@ import NcAvatar from '@nextcloud/vue/dist/Components/NcAvatar.js'
import Tune from 'vue-material-design-icons/Tune.vue'
import QuickShareSelect from './SharingEntryQuickShareSelect.vue'
import SharingEntryQuickShareSelect from './SharingEntryQuickShareSelect.vue'
import ExternalShareAction from './ExternalShareAction.vue'
import GeneratePassword from '../utils/GeneratePassword.js'
@ -220,7 +219,7 @@ export default {
NcActionSeparator,
NcAvatar,
Tune,
QuickShareSelect,
SharingEntryQuickShareSelect,
},
mixins: [SharesMixin, ShareDetails],
@ -238,7 +237,6 @@ export default {
data() {
return {
showDropdown: false,
copySuccess: true,
copied: false,
@ -730,10 +728,6 @@ export default {
// YET. We can safely delete the share :)
this.$emit('remove:share', this.share)
},
toggleQuickShareSelect() {
this.showDropdown = !this.showDropdown
},
},
}
</script>

@ -1,32 +1,25 @@
<template>
<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 }}
<NcActions ref="quickShareActions"
class="share-select"
:menu-name="selectedOption"
:aria-label="ariaLabel"
type="tertiary-no-background"
force-name>
<template #icon>
<DropdownIcon :size="15" />
</span>
<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 }}
</button>
</div>
</div>
</template>
<NcActionButton v-for="option in options"
:key="option.label"
type="radio"
:model-value="option.label === selectedOption"
close-after-click
@click="selectOption(option.label)">
<template #icon>
<component :is="option.icon" />
</template>
{{ option.label }}
</NcActionButton>
</NcActions>
</template>
<script>
@ -34,37 +27,48 @@ import DropdownIcon from 'vue-material-design-icons/TriangleSmallDown.vue'
import SharesMixin from '../mixins/SharesMixin.js'
import ShareDetails from '../mixins/ShareDetails.js'
import ShareTypes from '../mixins/ShareTypes.js'
import NcActions from '@nextcloud/vue/dist/Components/NcActions.js'
import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'
import IconEyeOutline from 'vue-material-design-icons/EyeOutline.vue'
import IconPencil from 'vue-material-design-icons/Pencil.vue'
import IconFileUpload from 'vue-material-design-icons/FileUpload.vue'
import IconTune from 'vue-material-design-icons/Tune.vue'
import {
BUNDLED_PERMISSIONS,
ATOMIC_PERMISSIONS,
} from '../lib/SharePermissionsToolBox.js'
import { createFocusTrap } from 'focus-trap'
export default {
name: 'SharingEntryQuickShareSelect',
components: {
DropdownIcon,
NcActions,
NcActionButton,
},
mixins: [SharesMixin, ShareDetails, ShareTypes],
props: {
share: {
type: Object,
required: true,
},
toggle: {
type: Boolean,
default: false,
},
},
emits: ['open-sharing-details'],
data() {
return {
selectedOption: '',
showDropdown: this.toggle,
focusTrap: null,
}
},
computed: {
ariaLabel() {
return t('files_sharing', 'Quick share options, the current selected is "{selectedOption}"', { selectedOption: this.selectedOption })
},
canViewText() {
return t('files_sharing', 'View only')
},
@ -91,11 +95,23 @@ export default {
},
options() {
const options = [this.canViewText, this.canEditText]
const options = [{
label: this.canViewText,
icon: IconEyeOutline,
}, {
label: this.canEditText,
icon: IconPencil,
}]
if (this.supportsFileDrop) {
options.push(this.fileDropText)
options.push({
label: this.fileDropText,
icon: IconFileUpload,
})
}
options.push(this.customPermissionsText)
options.push({
label: this.customPermissionsText,
icon: IconTune,
})
return options
},
@ -119,96 +135,23 @@ 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) {
this.showDropdown = toggleValue
},
},
mounted() {
this.initializeComponent()
window.addEventListener('click', this.handleClickOutside)
},
beforeDestroy() {
// Remove the global click event listener to prevent memory leaks
window.removeEventListener('click', this.handleClickOutside)
created() {
this.selectedOption = this.preSelectedOption
},
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
if (option === this.customPermissionsText) {
selectOption(optionLabel) {
this.selectedOption = optionLabel
if (optionLabel === this.customPermissionsText) {
this.$emit('open-sharing-details')
} else {
this.share.permissions = this.dropDownPermissionValue
this.queueUpdate('permissions')
// TODO: Add a focus method to NcActions or configurable returnFocus enabling to NcActionButton with closeAfterClick
this.$refs.quickShareActions.$refs.menuButton.$el.focus()
}
this.showDropdown = false
},
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()
},
},
@ -217,65 +160,31 @@ export default {
<style lang="scss" scoped>
.share-select {
position: relative;
cursor: pointer;
.trigger-text {
display: flex;
flex-direction: row;
align-items: center;
font-size: 12.5px;
gap: 2px;
color: var(--color-primary-element);
}
.share-select-dropdown {
position: absolute;
display: flex;
flex-direction: column;
top: 100%;
left: 0;
background-color: var(--color-main-background);
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
border: 1px solid var(--color-border);
padding: 4px 0;
z-index: 1;
.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: var(--color-background-dark);
}
&.selected {
background-color: var(--color-background-dark);
}
display: block;
// TODO: NcActions should have a slot for custom trigger button like NcPopover
// Overrider NcActionms button to make it small
:deep(.action-item__menutoggle) {
color: var(--color-primary-element) !important;
font-size: 12.5px !important;
height: auto !important;
min-height: auto !important;
.button-vue__text {
font-weight: normal !important;
}
}
/* Optional: Add a transition effect for smoother dropdown animation */
.share-select-dropdown {
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease;
}
.button-vue__icon {
height: 24px !important;
min-height: 24px !important;
width: 24px !important;
min-width: 24px !important;
}
&.active .share-select-dropdown {
max-height: 200px;
/* Adjust the value to your desired height */
.button-vue__wrapper {
// Emulate NcButton's alignment=center-reverse
flex-direction: row-reverse !important;
}
}
}
</style>

Loading…
Cancel
Save