fix: collapse view buttons into dropdown when overflowing (#2306)
This commit is contained in:
parent
aed93b9389
commit
7b6b432301
|
|
@ -11,12 +11,48 @@
|
|||
</h1>
|
||||
|
||||
<div
|
||||
ref="switchViewContainerRef"
|
||||
class="switch-view-container d-print-none"
|
||||
:class="{'is-justify-content-flex-end': views.length === 1}"
|
||||
>
|
||||
<!-- Dropdown mode when buttons overflow -->
|
||||
<Dropdown
|
||||
v-if="isOverflowing && views.length > 1"
|
||||
class="switch-view-dropdown"
|
||||
>
|
||||
<template #trigger="{ toggleOpen }">
|
||||
<BaseButton
|
||||
class="switch-view switch-view-dropdown-trigger"
|
||||
@click="toggleOpen"
|
||||
>
|
||||
{{ activeViewTitle }}
|
||||
<Icon
|
||||
icon="chevron-down"
|
||||
class="dropdown-icon"
|
||||
/>
|
||||
</BaseButton>
|
||||
</template>
|
||||
<template #default="{ close }">
|
||||
<div @click="close">
|
||||
<DropdownItem
|
||||
v-for="view in views"
|
||||
:key="view.id"
|
||||
:to="getViewRoute(view)"
|
||||
:class="{'is-active': view.id === viewId}"
|
||||
>
|
||||
{{ getViewTitle(view) }}
|
||||
</DropdownItem>
|
||||
</div>
|
||||
</template>
|
||||
</Dropdown>
|
||||
|
||||
<!-- Inline buttons, hidden when overflowing but kept in DOM for width measurement -->
|
||||
<div
|
||||
v-if="views.length > 1"
|
||||
ref="switchViewRef"
|
||||
class="switch-view"
|
||||
:class="{'switch-view--hidden': isOverflowing || !overflowChecked}"
|
||||
:aria-hidden="isOverflowing || undefined"
|
||||
>
|
||||
<BaseButton
|
||||
v-for="view in views"
|
||||
|
|
@ -24,6 +60,7 @@
|
|||
class="switch-view-button"
|
||||
:class="{'is-active': view.id === viewId}"
|
||||
:to="getViewRoute(view)"
|
||||
:tabindex="isOverflowing ? -1 : undefined"
|
||||
>
|
||||
{{ getViewTitle(view) }}
|
||||
</BaseButton>
|
||||
|
|
@ -45,10 +82,14 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {computed} from 'vue'
|
||||
import {computed, ref, watch, nextTick, onMounted} from 'vue'
|
||||
import {useResizeObserver} from '@vueuse/core'
|
||||
import {useI18n} from 'vue-i18n'
|
||||
|
||||
import BaseButton from '@/components/base/BaseButton.vue'
|
||||
import Dropdown from '@/components/misc/Dropdown.vue'
|
||||
import DropdownItem from '@/components/misc/DropdownItem.vue'
|
||||
import Icon from '@/components/misc/Icon'
|
||||
import Message from '@/components/misc/Message.vue'
|
||||
import CustomTransition from '@/components/misc/CustomTransition.vue'
|
||||
|
||||
|
|
@ -74,6 +115,29 @@ const baseStore = useBaseStore()
|
|||
const projectStore = useProjectStore()
|
||||
const viewFiltersStore = useViewFiltersStore()
|
||||
|
||||
const switchViewContainerRef = ref<HTMLElement>()
|
||||
const switchViewRef = ref<HTMLElement>()
|
||||
const isOverflowing = ref(false)
|
||||
const overflowChecked = ref(false)
|
||||
|
||||
function checkOverflow() {
|
||||
if (!switchViewRef.value || !switchViewContainerRef.value) {
|
||||
return
|
||||
}
|
||||
const buttonsWidth = switchViewRef.value.scrollWidth
|
||||
const containerWidth = switchViewContainerRef.value.clientWidth
|
||||
isOverflowing.value = buttonsWidth > containerWidth
|
||||
overflowChecked.value = true
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
checkOverflow()
|
||||
})
|
||||
|
||||
useResizeObserver(switchViewContainerRef, () => {
|
||||
requestAnimationFrame(() => checkOverflow())
|
||||
})
|
||||
|
||||
const currentProject = computed<IProject>(() => {
|
||||
return baseStore.currentProject || {
|
||||
id: 0,
|
||||
|
|
@ -86,6 +150,16 @@ useTitle(() => currentProject.value?.id ? getProjectTitle(currentProject.value)
|
|||
|
||||
const views = computed(() => projectStore.projects[props.projectId]?.views)
|
||||
|
||||
const activeViewTitle = computed(() => {
|
||||
const activeView = views.value?.find((v: IProjectView) => v.id === props.viewId)
|
||||
return activeView ? getViewTitle(activeView) : ''
|
||||
})
|
||||
|
||||
// Re-check overflow when views change
|
||||
watch(views, () => {
|
||||
nextTick(() => checkOverflow())
|
||||
})
|
||||
|
||||
function getViewTitle(view: IProjectView) {
|
||||
switch (view.title) {
|
||||
case 'List':
|
||||
|
|
@ -113,6 +187,7 @@ function getViewRoute(view: IProjectView) {
|
|||
|
||||
<style lang="scss" scoped>
|
||||
.switch-view-container {
|
||||
position: relative;
|
||||
min-block-size: $switch-view-height;
|
||||
margin-block-end: 1rem;
|
||||
|
||||
|
|
@ -136,9 +211,34 @@ function getViewRoute(view: IProjectView) {
|
|||
padding: .5rem;
|
||||
}
|
||||
|
||||
.switch-view--hidden {
|
||||
position: absolute;
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
white-space: nowrap;
|
||||
inset-inline-start: 0;
|
||||
inset-inline-end: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.switch-view-dropdown-trigger {
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: .25rem;
|
||||
font-weight: bold;
|
||||
color: var(--switch-view-color);
|
||||
background: var(--primary);
|
||||
}
|
||||
|
||||
.dropdown-icon {
|
||||
font-size: .6rem;
|
||||
}
|
||||
|
||||
.switch-view-button {
|
||||
padding: .25rem .5rem;
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
border-radius: $radius;
|
||||
transition: all 100ms;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue