diff options
Diffstat (limited to 'core/src/components/HeaderMenu.vue')
-rw-r--r-- | core/src/components/HeaderMenu.vue | 206 |
1 files changed, 206 insertions, 0 deletions
diff --git a/core/src/components/HeaderMenu.vue b/core/src/components/HeaderMenu.vue new file mode 100644 index 00000000000..2cc5b79d6dd --- /dev/null +++ b/core/src/components/HeaderMenu.vue @@ -0,0 +1,206 @@ + <!-- + - @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 v-click-outside="closeMenu" :class="{ 'header-menu--opened': opened }" class="header-menu"> + <a class="header-menu__trigger" + href="#" + :aria-controls="`header-menu-${id}`" + :aria-expanded="opened" + aria-haspopup="true" + @click.prevent="toggleMenu"> + <slot name="trigger" /> + </a> + <div v-if="opened" + :id="`header-menu-${id}`" + class="header-menu__wrapper" + role="menu"> + <div class="header-menu__carret" /> + <div class="header-menu__content"> + <slot /> + </div> + </div> + </div> +</template> + +<script> +import { directive as ClickOutside } from 'v-click-outside' +import { emit, subscribe, unsubscribe } from '@nextcloud/event-bus' + +export default { + name: 'HeaderMenu', + + directives: { + ClickOutside, + }, + + props: { + id: { + type: String, + required: true, + }, + open: { + type: Boolean, + default: false, + }, + }, + + data() { + return { + opened: this.open, + } + }, + + watch: { + open(newVal) { + this.opened = newVal + this.$nextTick(() => { + if (this.opened) { + this.openMenu() + } else { + this.closeMenu() + } + }) + }, + }, + + mounted() { + document.addEventListener('keydown', this.onKeyDown) + }, + + beforeMount() { + subscribe(`header-menu-${this.id}-close`, this.closeMenu) + subscribe(`header-menu-${this.id}-open`, this.openMenu) + }, + + beforeDestroy() { + unsubscribe(`header-menu-${this.id}-close`, this.closeMenu) + unsubscribe(`header-menu-${this.id}-open`, this.openMenu) + }, + + 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) + emit(`header-menu-${this.id}-close`) + }, + + /** + * Open the current menu + */ + openMenu() { + if (this.opened) { + return + } + + this.opened = true + this.$emit('open') + this.$emit('update:open', true) + emit(`header-menu-${this.id}-open`) + }, + + onKeyDown(event) { + // If opened and escape pressed, close + if (event.key === 'Escape' && this.opened) { + event.preventDefault() + this.closeMenu() + } + }, + }, +} +</script> + +<style lang="scss" scoped> +.header-menu { + &__trigger { + display: flex; + align-items: center; + justify-content: center; + width: 50px; + height: 100%; + margin: 0; + padding: 0; + cursor: pointer; + opacity: .6; + } + + &--opened &__trigger, + &__trigger:hover, + &__trigger:focus, + &__trigger:active { + opacity: 1; + } + + &__wrapper { + position: absolute; + z-index: 2000; + top: 50px; + right: 5px; + box-sizing: border-box; + margin: 0; + 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)); + } + + &__carret { + position: absolute; + right: 10px; + bottom: 100%; + 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: 350px; + min-height: calc(44px * 1.5); + max-height: calc(100vh - 50px * 2); + } +} + +</style> |