Files
zammad/app/frontend/apps/desktop/components/CommonFloatingToolbar/CommonFloatingToolbar.vue
Benjamin Scharf 3a08058c1d Feature: Desktop view - View an answer in the knowledge base
Approved-by: Marcel Bialas <mb@zammad.com>
Approved-by: Benjamin Scharf <bs@zammad.com>
Approved-by: Dusan Vuckovic <dv@zammad.com>

Co-authored-by: Dominik Klein <dk@zammad.com>
Co-authored-by: Dusan Vuckovic <dv@zammad.com>
Co-authored-by: Marcel Bialas <mb@zammad.com>
Co-authored-by: Rene Reimann <rr@zammad.com>
2026-08-17 08:01:05 +02:00

118 lines
3.8 KiB
Vue

<!-- Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/ -->
<script setup lang="ts">
import { Comment, computed, useSlots } from 'vue'
import CommonButton from '#desktop/components/CommonButton/CommonButton.vue'
import { useTransitionConfig } from '#desktop/composables/useTransitionConfig.ts'
export interface Props {
label?: string
orientation?: 'horizontal' | 'vertical'
size?: 'normal' | 'large'
isReachingBottom?: boolean
isReachingTop?: boolean
hidePrimaryAction?: boolean
unreadCount?: number
unreadTooltip?: string
}
const props = withDefaults(defineProps<Props>(), {
orientation: 'vertical',
unreadCount: 0,
unreadTooltip: __('Scroll to unread item'),
label: __('Scroll actions'),
})
const emit = defineEmits<{
'scroll-to-start': []
'scroll-to-end': []
'scroll-to-unread': []
}>()
const slots = useSlots()
const countDisplay = computed(() => (props.unreadCount > 9 ? '9+' : props.unreadCount))
const showUnreadCount = computed(() => props.unreadCount > 0)
// Generic actions replace the scroll controls, they are always visible.
// Comment-only slot content does not count, some consumers pass placeholder comments.
const hasGenericActions = computed(() =>
Boolean(slots.default?.().some((vnode) => vnode.type !== Comment)),
)
const showElement = computed(
() =>
hasGenericActions.value ||
!props.isReachingBottom ||
!props.isReachingTop ||
showUnreadCount.value,
)
const { transitions } = useTransitionConfig()
</script>
<template>
<div
v-if="showElement"
role="toolbar"
:aria-orientation="orientation"
:aria-label="label"
class="grid w-fit gap-1 rounded-(--toolbar-radius) border border-neutral-100 bg-neutral-75/80 p-(--toolbar-p) backdrop-blur-xs [--toolbar-p:0.25rem] [--toolbar-radius:0.75rem] dark:border-gray-900 dark:bg-gray-500/80"
:class="{ 'grid-flow-col items-center': orientation === 'horizontal' }"
>
<slot />
<Transition v-if="!hasGenericActions" :name="transitions.collapseHeight">
<div v-if="!hidePrimaryAction && slots['primary-action']" class="flex">
<slot name="primary-action" />
</div>
</Transition>
<Transition v-if="!hasGenericActions" :name="transitions.collapseHeight">
<div v-if="!isReachingTop">
<div class="flex min-h-0">
<CommonButton
ref="scroll-up-button"
v-tooltip="$t('Scroll to start')"
size="medium"
variant="tertiary"
icon="arrow-up-short"
class="rounded-[(--toolbar-radius)-(--toolbar-p)]! border! border-neutral-100 text-gray-100 dark:border-gray-900 dark:text-neutral-400"
@click="emit('scroll-to-start')"
/>
</div>
</div>
</Transition>
<Transition v-if="!hasGenericActions" :name="transitions.collapseHeight">
<div v-if="!isReachingBottom">
<div class="relative flex min-h-0">
<CommonButton
ref="scroll-down-button"
v-tooltip="showUnreadCount ? unreadTooltip : $t('Scroll to end')"
size="medium"
variant="tertiary"
icon="arrow-down-short"
class="rounded-[(--toolbar-radius)-(--toolbar-p)]! border! border-neutral-100 text-gray-100 dark:border-gray-900 dark:text-neutral-400"
@click="showUnreadCount ? emit('scroll-to-unread') : emit('scroll-to-end')"
/>
<CommonBadge
v-if="showUnreadCount"
size="xs"
class="pointer-events-none absolute inset-e-0 -top-1.5 aspect-square size-4 p-0! ltr:translate-x-1/2 rtl:-translate-x-1/2"
variant="highlight"
rounded
:aria-label="$t('Unread messages count')"
role="status"
>
{{ countDisplay }}
</CommonBadge>
</div>
</div>
</Transition>
</div>
</template>