nextcloud/core/src/views/PublicPageMenu.vue
Ferdinand Thiessen 44917b5ef1
chore(legacy): fix @stylistic/exp-list-style ESLint rule
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
2026-01-26 00:03:51 +01:00

141 lines
3.9 KiB
Vue

<!--
- 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"
variant="primary"
@click="openDialogIfNeeded">
<template v-if="primaryAction.icon" #icon>
<div class="icon public-page-menu__primary-icon" :class="[primaryAction.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 { loadState } from '@nextcloud/initial-state'
import { t } from '@nextcloud/l10n'
import { useIsSmallMobile } from '@nextcloud/vue/composables/useIsMobile'
import { spawnDialog } from '@nextcloud/vue/functions/dialog'
import {
type Ref,
computed,
ref,
} from 'vue'
import NcButton from '@nextcloud/vue/components/NcButton'
import NcHeaderMenu from '@nextcloud/vue/components/NcHeaderMenu'
import IconMore from 'vue-material-design-icons/DotsHorizontal.vue'
import PublicPageMenuCustomEntry from '../components/PublicPageMenu/PublicPageMenuCustomEntry.vue'
import PublicPageMenuEntry from '../components/PublicPageMenu/PublicPageMenuEntry.vue'
import PublicPageMenuExternalDialog from '../components/PublicPageMenu/PublicPageMenuExternalDialog.vue'
import PublicPageMenuExternalEntry from '../components/PublicPageMenu/PublicPageMenuExternalEntry.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>