fix: collapse view buttons into dropdown when overflowing (#2306)

This commit is contained in:
Weijie Zhao 2026-03-19 07:09:29 +08:00 committed by GitHub
parent aed93b9389
commit 7b6b432301
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 101 additions and 1 deletions

View File

@ -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;