diff options
author | Ferdinand Thiessen <opensource@fthiessen.de> | 2024-08-28 13:10:25 +0200 |
---|---|---|
committer | Ferdinand Thiessen <opensource@fthiessen.de> | 2024-09-03 16:07:49 +0200 |
commit | 04b25ba59d48ea3ab1d34d9f05ede33fc7ad7fbc (patch) | |
tree | d16c43eaeea478254009d7d2a87b75af0bc553ce /core/src/views | |
parent | 5118f6684bd2f4f8d693ada710e845b6af5c5395 (diff) | |
download | nextcloud-server-04b25ba59d48ea3ab1d34d9f05ede33fc7ad7fbc.tar.gz nextcloud-server-04b25ba59d48ea3ab1d34d9f05ede33fc7ad7fbc.zip |
feat: Implement Vue UI for public page menu
This adds a Vue implementation of the public page menu,
that is the menu that can be added using `PublicTemplateResponse::setHeaderActions`.
Co-authored-by: Ferdinand Thiessen <opensource@fthiessen.de>
Co-authored-by: Louis <louis@chmn.me>
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
Diffstat (limited to 'core/src/views')
-rw-r--r-- | core/src/views/PublicPageMenu.vue | 131 |
1 files changed, 131 insertions, 0 deletions
diff --git a/core/src/views/PublicPageMenu.vue b/core/src/views/PublicPageMenu.vue new file mode 100644 index 00000000000..a9ff78a7c5f --- /dev/null +++ b/core/src/views/PublicPageMenu.vue @@ -0,0 +1,131 @@ +<!-- + - SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + - SPDX-License-Identifier: AGPL-3.0-or-later + --> +<template> + <div class="public-page-menu__wrapper"> + <NcButton v-if="primaryAction" + id="public-page-menu--primary" + class="public-page-menu__primary" + :href="primaryAction.href" + type="primary" + @click="openDialogIfNeeded"> + <template v-if="primaryAction.icon" #icon> + <div :class="['icon', primaryAction.icon, 'public-page-menu__primary-icon']" /> + </template> + {{ primaryAction.label }} + </NcButton> + + <NcHeaderMenu v-if="secondaryActions.length > 0" + id="public-page-menu" + :aria-label="t('core', 'More actions')" + :open.sync="showMenu"> + <template #trigger> + <IconMore :size="20" /> + </template> + <ul :aria-label="t('core', 'More actions')" + class="public-page-menu" + role="menu"> + <component :is="getComponent(entry)" + v-for="entry, index in secondaryActions" + :key="index" + v-bind="entry" + @click="showMenu = false" /> + </ul> + </NcHeaderMenu> + </div> +</template> + +<script setup lang="ts"> +import { spawnDialog } from '@nextcloud/dialogs' +import { loadState } from '@nextcloud/initial-state' +import { t } from '@nextcloud/l10n' +import { useIsSmallMobile } from '@nextcloud/vue/dist/Composables/useIsMobile.js' +import { computed, ref, type Ref } from 'vue' +import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' +import NcHeaderMenu from '@nextcloud/vue/dist/Components/NcHeaderMenu.js' +import IconMore from 'vue-material-design-icons/DotsHorizontal.vue' +import PublicPageMenuEntry from '../components/PublicPageMenu/PublicPageMenuEntry.vue' +import PublicPageMenuCustomEntry from '../components/PublicPageMenu/PublicPageMenuCustomEntry.vue' +import PublicPageMenuExternalEntry from '../components/PublicPageMenu/PublicPageMenuExternalEntry.vue' +import PublicPageMenuExternalDialog from '../components/PublicPageMenu/PublicPageMenuExternalDialog.vue' +import PublicPageMenuLinkEntry from '../components/PublicPageMenu/PublicPageMenuLinkEntry.vue' + +interface IPublicPageMenu { + id: string + label: string + href: string + icon?: string + html?: string + details?: string +} + +const menuEntries = loadState<Array<IPublicPageMenu>>('core', 'public-page-menu') + +/** used to conditionally close the menu when clicking entry */ +const showMenu = ref(false) + +const isMobile = useIsSmallMobile() as Readonly<Ref<boolean>> +/** The primary menu action - only showed when not on mobile */ +const primaryAction = computed(() => isMobile.value ? undefined : menuEntries[0]) +/** All other secondary actions (including primary action on mobile) */ +const secondaryActions = computed(() => isMobile.value ? menuEntries : menuEntries.slice(1)) + +/** + * Get the render component for an entry + * @param entry The entry to get the component for + */ +function getComponent(entry: IPublicPageMenu) { + if ('html' in entry) { + return PublicPageMenuCustomEntry + } + switch (entry.id) { + case 'save': + return PublicPageMenuExternalEntry + case 'directLink': + return PublicPageMenuLinkEntry + default: + return PublicPageMenuEntry + } +} + +/** + * Open the "federated share" dialog if needed + */ +function openDialogIfNeeded() { + if (primaryAction.value?.id !== 'save') { + return + } + spawnDialog(PublicPageMenuExternalDialog, { label: primaryAction.value.label }) +} +</script> + +<style scoped lang="scss"> +.public-page-menu { + box-sizing: border-box; + + > :deep(*) { + box-sizing: border-box; + } + + &__wrapper { + display: flex; + flex-direction: row; + gap: var(--default-grid-baseline); + } + + &__primary { + height: var(--default-clickable-area); + margin-block: calc((var(--header-height) - var(--default-clickable-area)) / 2); + + // Ensure the correct focus-visible color is used (as this is rendered directly on the background(-image)) + &:focus-visible { + border-color: var(--color-background-plain-text) !important; + } + } + + &__primary-icon { + filter: var(--primary-invert-if-bright); + } +} +</style> |