[feat] Replace view mode toggle with settings dropdown menu (#8950)

This commit is contained in:
Jin Yi
2026-02-21 13:49:19 +09:00
committed by GitHub
parent c05644045f
commit fd2ffb7100
9 changed files with 125 additions and 170 deletions

View File

@@ -751,7 +751,8 @@
"filterVideo": "Video",
"filterAudio": "Audio",
"filter3D": "3D",
"filterText": "Text"
"filterText": "Text",
"viewSettings": "View settings"
},
"backToAssets": "Back to all assets",
"folderView": {

View File

@@ -11,7 +11,6 @@
<MediaAssetFilterButton
v-if="isCloud"
v-tooltip.top="{ value: $t('assetBrowser.filterBy') }"
size="md"
>
<template #default="{ close }">
<MediaAssetFilterMenu
@@ -24,7 +23,6 @@
<AssetSortButton
v-if="isCloud"
v-tooltip.top="{ value: $t('assetBrowser.sortBy') }"
size="md"
>
<template #default="{ close }">
<MediaAssetSortMenu
@@ -34,7 +32,13 @@
/>
</template>
</AssetSortButton>
<MediaAssetViewModeToggle v-model:view-mode="viewMode" />
<MediaAssetSettingsButton
v-tooltip.top="{ value: $t('sideToolbar.mediaAssets.viewSettings') }"
>
<template #default="{ close }">
<MediaAssetSettingsMenu v-model:view-mode="viewMode" :close />
</template>
</MediaAssetSettingsButton>
</div>
</div>
</template>
@@ -48,7 +52,8 @@ import MediaAssetFilterMenu from './MediaAssetFilterMenu.vue'
import AssetSortButton from './MediaAssetSortButton.vue'
import MediaAssetSortMenu from './MediaAssetSortMenu.vue'
import type { SortBy } from './MediaAssetSortMenu.vue'
import MediaAssetViewModeToggle from './MediaAssetViewModeToggle.vue'
import MediaAssetSettingsButton from './MediaAssetSettingsButton.vue'
import MediaAssetSettingsMenu from './MediaAssetSettingsMenu.vue'
const { showGenerationTimeSort = false } = defineProps<{
searchQuery: string

View File

@@ -1,58 +1,19 @@
<template>
<div class="relative inline-flex items-center">
<Button variant="secondary" size="icon" @click="toggle">
<i class="icon-[lucide--list-filter]" />
</Button>
<Popover
ref="popover"
append-to="#vue-app"
auto-z-index
:base-z-index="1000"
dismissable
close-on-escape
unstyled
:pt="pt"
@show="$emit('menuOpened')"
@hide="$emit('menuClosed')"
>
<div class="flex min-w-40 flex-col gap-2 p-2">
<slot :close="hide" />
</div>
<div class="inline-flex items-center">
<Popover>
<template #button>
<Button variant="secondary" size="icon">
<i class="icon-[lucide--list-filter]" />
</Button>
</template>
<template #default="{ close }">
<slot :close />
</template>
</Popover>
</div>
</template>
<script setup lang="ts">
import Popover from 'primevue/popover'
import { computed, ref } from 'vue'
import Button from '@/components/ui/button/Button.vue'
import { cn } from '@/utils/tailwindUtil'
const popover = ref<InstanceType<typeof Popover>>()
defineEmits<{
menuOpened: []
menuClosed: []
}>()
const toggle = (event: Event) => {
popover.value?.toggle(event)
}
const hide = () => {
popover.value?.hide()
}
const pt = computed(() => ({
root: {
class: cn('absolute z-50')
},
content: {
class: cn(
'mt-1 rounded-lg bg-base-background text-base-foreground border border-border-default shadow-lg'
)
}
}))
import Popover from '@/components/ui/Popover.vue'
</script>

View File

@@ -15,7 +15,7 @@ TODO: Extract checkbox pattern into reusable Checkbox component
<div
v-for="filter in filters"
:key="filter.type"
class="flex h-10 cursor-pointer items-center gap-2 rounded-lg px-2 hover:bg-secondary-background-hover"
class="flex h-10 min-w-32 cursor-pointer items-center gap-2 rounded-lg px-2 hover:bg-secondary-background-hover"
tabindex="0"
role="checkbox"
:aria-checked="mediaTypeFilters.includes(filter.type)"

View File

@@ -0,0 +1,23 @@
<template>
<div class="inline-flex items-center">
<Popover>
<template #button>
<Button
variant="secondary"
size="icon"
:aria-label="$t('sideToolbar.mediaAssets.viewSettings')"
>
<i class="icon-[lucide--settings-2]" />
</Button>
</template>
<template #default="{ close }">
<slot :close />
</template>
</Popover>
</div>
</template>
<script setup lang="ts">
import Button from '@/components/ui/button/Button.vue'
import Popover from '@/components/ui/Popover.vue'
</script>

View File

@@ -0,0 +1,48 @@
<template>
<div class="flex flex-col">
<Button
variant="textonly"
class="w-full"
@click="handleViewModeChange('list')"
>
<span class="flex items-center gap-2">
<i class="icon-[lucide--table-of-contents] size-4" />
<span>{{ $t('sideToolbar.queueProgressOverlay.viewList') }}</span>
</span>
<i
class="icon-[lucide--check] ml-auto size-4"
:class="viewMode !== 'list' && 'opacity-0'"
/>
</Button>
<Button
variant="textonly"
class="w-full"
@click="handleViewModeChange('grid')"
>
<span class="flex items-center gap-2">
<i class="icon-[lucide--layout-grid] size-4" />
<span>{{ $t('sideToolbar.queueProgressOverlay.viewGrid') }}</span>
</span>
<i
class="icon-[lucide--check] ml-auto size-4"
:class="viewMode !== 'grid' && 'opacity-0'"
/>
</Button>
</div>
</template>
<script setup lang="ts">
import Button from '@/components/ui/button/Button.vue'
const { close } = defineProps<{
close: () => void
}>()
const viewMode = defineModel<'list' | 'grid'>('viewMode', { required: true })
function handleViewModeChange(value: 'list' | 'grid') {
viewMode.value = value
close()
}
</script>

View File

@@ -1,60 +1,19 @@
<template>
<div class="relative inline-flex items-center">
<Button variant="secondary" size="icon" @click="toggle">
<i class="icon-[lucide--arrow-up-down]" />
</Button>
<Popover
ref="popover"
append-to="body"
:auto-z-index="true"
:base-z-index="1000"
:dismissable="true"
:close-on-escape="true"
unstyled
:pt="pt"
@show="$emit('menuOpened')"
@hide="$emit('menuClosed')"
>
<div class="flex min-w-40 flex-col gap-2 p-2">
<slot :close="hide" />
</div>
<div class="inline-flex items-center">
<Popover>
<template #button>
<Button variant="secondary" size="icon">
<i class="icon-[lucide--arrow-up-down]" />
</Button>
</template>
<template #default="{ close }">
<slot :close />
</template>
</Popover>
</div>
</template>
<script setup lang="ts">
import Popover from 'primevue/popover'
import { computed, ref } from 'vue'
import Button from '@/components/ui/button/Button.vue'
import { cn } from '@/utils/tailwindUtil'
const popover = ref<InstanceType<typeof Popover>>()
defineEmits<{
menuOpened: []
menuClosed: []
}>()
const toggle = (event: Event) => {
popover.value?.toggle(event)
}
const hide = () => {
popover.value?.hide()
}
const pt = computed(() => ({
root: {
class: cn('absolute z-50')
},
content: {
class: cn(
'mt-1 rounded-lg',
'bg-base-background text-base-foreground border border-border-default',
'shadow-lg'
)
}
}))
import Popover from '@/components/ui/Popover.vue'
</script>

View File

@@ -2,39 +2,51 @@
<div class="flex flex-col">
<Button
variant="textonly"
class="justify-start"
class="w-full"
@click="handleSortChange('newest')"
>
<span>{{ $t('sideToolbar.mediaAssets.sortNewestFirst') }}</span>
<i v-if="sortBy === 'newest'" class="icon-[lucide--check] size-4" />
<i
class="icon-[lucide--check] ml-auto size-4"
:class="sortBy !== 'newest' && 'opacity-0'"
/>
</Button>
<Button
variant="textonly"
class="justify-start"
class="w-full"
@click="handleSortChange('oldest')"
>
<span>{{ $t('sideToolbar.mediaAssets.sortOldestFirst') }}</span>
<i v-if="sortBy === 'oldest'" class="icon-[lucide--check] size-4" />
<i
class="icon-[lucide--check] ml-auto size-4"
:class="sortBy !== 'oldest' && 'opacity-0'"
/>
</Button>
<template v-if="showGenerationTimeSort">
<Button
variant="textonly"
class="justify-start"
class="w-full"
@click="handleSortChange('longest')"
>
<span>{{ $t('sideToolbar.mediaAssets.sortLongestFirst') }}</span>
<i v-if="sortBy === 'longest'" class="icon-[lucide--check] size-4" />
<i
class="icon-[lucide--check] ml-auto size-4"
:class="sortBy !== 'longest' && 'opacity-0'"
/>
</Button>
<Button
variant="textonly"
class="justify-start"
class="w-full"
@click="handleSortChange('fastest')"
>
<span>{{ $t('sideToolbar.mediaAssets.sortFastestFirst') }}</span>
<i v-if="sortBy === 'fastest'" class="icon-[lucide--check] size-4" />
<i
class="icon-[lucide--check] ml-auto size-4"
:class="sortBy !== 'fastest' && 'opacity-0'"
/>
</Button>
</template>
</div>

View File

@@ -1,54 +0,0 @@
<template>
<div
class="inline-flex items-center gap-1 rounded-lg bg-secondary-background p-1"
role="group"
>
<Button
type="button"
variant="muted-textonly"
size="icon"
:aria-label="t('sideToolbar.queueProgressOverlay.viewList')"
:aria-pressed="viewMode === 'list'"
:class="
cn(
'rounded-lg',
viewMode === 'list'
? 'bg-secondary-background-selected text-text-primary hover:bg-secondary-background-selected'
: 'text-text-secondary hover:bg-secondary-background-hover'
)
"
@click="viewMode = 'list'"
>
<i class="icon-[lucide--table-of-contents] size-4" />
</Button>
<Button
type="button"
variant="muted-textonly"
size="icon"
:aria-label="t('sideToolbar.queueProgressOverlay.viewGrid')"
:aria-pressed="viewMode === 'grid'"
:class="
cn(
'rounded-lg',
viewMode === 'grid'
? 'bg-secondary-background-selected text-text-primary hover:bg-secondary-background-selected'
: 'text-text-secondary hover:bg-secondary-background-hover'
)
"
@click="viewMode = 'grid'"
>
<i class="icon-[lucide--layout-grid] size-4" />
</Button>
</div>
</template>
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
import Button from '@/components/ui/button/Button.vue'
import { cn } from '@/utils/tailwindUtil'
const viewMode = defineModel<'list' | 'grid'>('viewMode', { required: true })
const { t } = useI18n()
</script>