mirror of
https://github.com/nextcloud/server.git
synced 2026-04-24 15:53:36 -04:00
Port global search menu to focus trapped NcHeaderMenu
Signed-off-by: Christopher Ng <chrng8@gmail.com> Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
This commit is contained in:
parent
1c517215d7
commit
3a49400944
7 changed files with 22 additions and 260 deletions
|
|
@ -1,238 +0,0 @@
|
|||
<!--
|
||||
- @copyright Copyright (c) 2020 John Molakvoæ <skjnldsv@protonmail.com>
|
||||
-
|
||||
- @author John Molakvoæ <skjnldsv@protonmail.com>
|
||||
-
|
||||
- @license GNU AGPL version 3 or any later version
|
||||
-
|
||||
- This program is free software: you can redistribute it and/or modify
|
||||
- it under the terms of the GNU Affero General Public License as
|
||||
- published by the Free Software Foundation, either version 3 of the
|
||||
- License, or (at your option) any later version.
|
||||
-
|
||||
- This program is distributed in the hope that it will be useful,
|
||||
- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
- GNU Affero General Public License for more details.
|
||||
-
|
||||
- You should have received a copy of the GNU Affero General Public License
|
||||
- along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-
|
||||
-->
|
||||
<template>
|
||||
<div :id="id"
|
||||
v-click-outside="clickOutsideConfig"
|
||||
:class="{ 'header-menu--opened': opened }"
|
||||
class="header-menu">
|
||||
<a class="header-menu__trigger"
|
||||
href="#"
|
||||
:aria-label="ariaLabel"
|
||||
:aria-controls="`header-menu-${id}`"
|
||||
:aria-expanded="opened.toString()"
|
||||
@click.prevent="toggleMenu">
|
||||
<slot name="trigger" />
|
||||
</a>
|
||||
<div v-show="opened" class="header-menu__carret" />
|
||||
<div v-show="opened"
|
||||
:id="`header-menu-${id}`"
|
||||
class="header-menu__wrapper"
|
||||
role="menu"
|
||||
@focusout="handleFocusOut">
|
||||
<div class="header-menu__content">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { directive as ClickOutside } from 'v-click-outside'
|
||||
import excludeClickOutsideClasses from '@nextcloud/vue/dist/Mixins/excludeClickOutsideClasses'
|
||||
|
||||
export default {
|
||||
name: 'HeaderMenu',
|
||||
|
||||
directives: {
|
||||
ClickOutside,
|
||||
},
|
||||
|
||||
mixins: [
|
||||
excludeClickOutsideClasses,
|
||||
],
|
||||
|
||||
props: {
|
||||
id: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
ariaLabel: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
open: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
opened: this.open,
|
||||
clickOutsideConfig: {
|
||||
handler: this.closeMenu,
|
||||
middleware: this.clickOutsideMiddleware,
|
||||
},
|
||||
shortcutsDisabled: OCP.Accessibility.disableKeyboardShortcuts(),
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
open(newVal) {
|
||||
this.opened = newVal
|
||||
this.$nextTick(() => {
|
||||
if (this.opened) {
|
||||
this.openMenu()
|
||||
} else {
|
||||
this.closeMenu()
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
document.addEventListener('keydown', this.onKeyDown)
|
||||
},
|
||||
beforeDestroy() {
|
||||
document.removeEventListener('keydown', this.onKeyDown)
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Toggle the current menu open state
|
||||
*/
|
||||
toggleMenu() {
|
||||
// Toggling current state
|
||||
if (!this.opened) {
|
||||
this.openMenu()
|
||||
} else {
|
||||
this.closeMenu()
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Close the current menu
|
||||
*/
|
||||
closeMenu() {
|
||||
if (!this.opened) {
|
||||
return
|
||||
}
|
||||
|
||||
this.opened = false
|
||||
this.$emit('close')
|
||||
this.$emit('update:open', false)
|
||||
},
|
||||
|
||||
/**
|
||||
* Open the current menu
|
||||
*/
|
||||
openMenu() {
|
||||
if (this.opened) {
|
||||
return
|
||||
}
|
||||
|
||||
this.opened = true
|
||||
this.$emit('open')
|
||||
this.$emit('update:open', true)
|
||||
},
|
||||
|
||||
onKeyDown(event) {
|
||||
if (this.shortcutsDisabled) {
|
||||
return
|
||||
}
|
||||
|
||||
// If opened and escape pressed, close
|
||||
if (event.key === 'Escape' && this.opened) {
|
||||
event.preventDefault()
|
||||
|
||||
/** user cancelled the menu by pressing escape */
|
||||
this.$emit('cancel')
|
||||
|
||||
/** we do NOT fire a close event to differentiate cancel and close */
|
||||
this.opened = false
|
||||
this.$emit('update:open', false)
|
||||
}
|
||||
},
|
||||
|
||||
handleFocusOut(event) {
|
||||
if (!event.currentTarget.contains(event.relatedTarget)) {
|
||||
this.closeMenu()
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$externalMargin: 8px;
|
||||
|
||||
.header-menu {
|
||||
&__trigger {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 50px;
|
||||
height: 44px;
|
||||
margin: 2px 0;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
opacity: .85;
|
||||
}
|
||||
|
||||
&--opened &__trigger,
|
||||
&__trigger:hover,
|
||||
&__trigger:focus,
|
||||
&__trigger:active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&__trigger:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&__wrapper {
|
||||
position: fixed;
|
||||
z-index: 2000;
|
||||
top: 50px;
|
||||
right: 0;
|
||||
box-sizing: border-box;
|
||||
margin: 0 $externalMargin;
|
||||
border-radius: 0 0 var(--border-radius) var(--border-radius);
|
||||
background-color: var(--color-main-background);
|
||||
filter: drop-shadow(0 1px 5px var(--color-box-shadow));
|
||||
padding: 8px;
|
||||
border-radius: var(--border-radius-large);
|
||||
}
|
||||
|
||||
&__carret {
|
||||
position: absolute;
|
||||
z-index: 2001; // Because __wrapper is 2000.
|
||||
left: calc(50% - 10px);
|
||||
bottom: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
content: ' ';
|
||||
pointer-events: none;
|
||||
border: 10px solid transparent;
|
||||
border-bottom-color: var(--color-main-background);
|
||||
}
|
||||
|
||||
&__content {
|
||||
overflow: auto;
|
||||
width: 350px;
|
||||
max-width: calc(100vw - 2 * $externalMargin);
|
||||
min-height: calc(44px * 1.5);
|
||||
max-height: calc(100vh - 50px * 2);
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
@ -20,7 +20,7 @@
|
|||
-
|
||||
-->
|
||||
<template>
|
||||
<HeaderMenu id="unified-search"
|
||||
<NcHeaderMenu id="unified-search"
|
||||
class="unified-search"
|
||||
exclude-click-outside-classes="popover"
|
||||
:open.sync="open"
|
||||
|
|
@ -150,24 +150,26 @@
|
|||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
</HeaderMenu>
|
||||
</NcHeaderMenu>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import debounce from 'debounce'
|
||||
import { emit, subscribe, unsubscribe } from '@nextcloud/event-bus'
|
||||
import { minSearchLength, getTypes, search, defaultLimit, regexFilterIn, regexFilterNot, enableLiveSearch } from '../services/UnifiedSearchService'
|
||||
import { showError } from '@nextcloud/dialogs'
|
||||
|
||||
import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton'
|
||||
import NcActions from '@nextcloud/vue/dist/Components/NcActions'
|
||||
import debounce from 'debounce'
|
||||
import NcEmptyContent from '@nextcloud/vue/dist/Components/NcEmptyContent'
|
||||
import NcHighlight from '@nextcloud/vue/dist/Components/NcHighlight'
|
||||
import Magnify from 'vue-material-design-icons/Magnify'
|
||||
import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'
|
||||
import NcActions from '@nextcloud/vue/dist/Components/NcActions.js'
|
||||
import NcEmptyContent from '@nextcloud/vue/dist/Components/NcEmptyContent.js'
|
||||
import NcHeaderMenu from '@nextcloud/vue/dist/Components/NcHeaderMenu.js'
|
||||
import NcHighlight from '@nextcloud/vue/dist/Components/NcHighlight.js'
|
||||
|
||||
import HeaderMenu from '../components/HeaderMenu'
|
||||
import SearchResult from '../components/UnifiedSearch/SearchResult'
|
||||
import SearchResultPlaceholders from '../components/UnifiedSearch/SearchResultPlaceholders'
|
||||
import Magnify from 'vue-material-design-icons/Magnify.vue'
|
||||
|
||||
import SearchResult from '../components/UnifiedSearch/SearchResult.vue'
|
||||
import SearchResultPlaceholders from '../components/UnifiedSearch/SearchResultPlaceholders.vue'
|
||||
|
||||
import { minSearchLength, getTypes, search, defaultLimit, regexFilterIn, regexFilterNot, enableLiveSearch } from '../services/UnifiedSearchService.js'
|
||||
|
||||
const REQUEST_FAILED = 0
|
||||
const REQUEST_OK = 1
|
||||
|
|
@ -177,12 +179,12 @@ export default {
|
|||
name: 'UnifiedSearch',
|
||||
|
||||
components: {
|
||||
Magnify,
|
||||
NcActionButton,
|
||||
NcActions,
|
||||
NcEmptyContent,
|
||||
HeaderMenu,
|
||||
NcHeaderMenu,
|
||||
NcHighlight,
|
||||
Magnify,
|
||||
SearchResult,
|
||||
SearchResultPlaceholders,
|
||||
},
|
||||
|
|
|
|||
4
dist/core-common.js
vendored
4
dist/core-common.js
vendored
File diff suppressed because one or more lines are too long
2
dist/core-common.js.LICENSE.txt
vendored
2
dist/core-common.js.LICENSE.txt
vendored
|
|
@ -382,8 +382,6 @@
|
|||
|
||||
/*! For license information please see Tooltip.js.LICENSE.txt */
|
||||
|
||||
/*! For license information please see excludeClickOutsideClasses.js.LICENSE.txt */
|
||||
|
||||
/*! For license information please see index.module.js.LICENSE.txt */
|
||||
|
||||
/*! For license information please see ncvuecomponents.js.LICENSE.txt */
|
||||
|
|
|
|||
2
dist/core-common.js.map
vendored
2
dist/core-common.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/core-unified-search.js
vendored
4
dist/core-unified-search.js
vendored
File diff suppressed because one or more lines are too long
2
dist/core-unified-search.js.map
vendored
2
dist/core-unified-search.js.map
vendored
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue