feat: improve image browsing experience
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
open: boolean
|
||||
imageUrl: string
|
||||
imageAlt: string
|
||||
title: string
|
||||
subtitle?: string
|
||||
badgeLabel?: string
|
||||
badgeClass?: string
|
||||
panelWidth?: string
|
||||
}>(), {
|
||||
subtitle: '',
|
||||
badgeLabel: '',
|
||||
badgeClass: '',
|
||||
panelWidth: '20rem',
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
close: []
|
||||
}>()
|
||||
|
||||
const collapsed = ref(false)
|
||||
|
||||
const rootStyle = computed(() => ({
|
||||
'--detail-panel-width': props.panelWidth,
|
||||
}) as Record<string, string>)
|
||||
|
||||
const imageClass = computed(() => {
|
||||
return collapsed.value
|
||||
? 'block h-full w-auto max-w-full object-contain lg:max-w-[92vw]'
|
||||
: 'block h-full w-auto max-w-full object-contain lg:max-w-[calc(92vw-var(--detail-panel-width))]'
|
||||
})
|
||||
|
||||
watch(() => props.open, open => {
|
||||
if (open) {
|
||||
collapsed.value = false
|
||||
}
|
||||
})
|
||||
|
||||
function close() {
|
||||
emit('close')
|
||||
}
|
||||
|
||||
function togglePanel() {
|
||||
collapsed.value = !collapsed.value
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<div
|
||||
v-if="open"
|
||||
class="fixed inset-0 z-[110] flex items-center justify-center bg-black/85 px-4 py-6"
|
||||
@click="close"
|
||||
>
|
||||
<div
|
||||
class="relative h-[80vh] w-fit max-w-[92vw] overflow-hidden rounded-[30px] shadow-2xl lg:flex"
|
||||
:style="rootStyle"
|
||||
@click.stop
|
||||
>
|
||||
<div class="flex min-h-0 flex-1 items-center justify-center overflow-hidden lg:flex-none">
|
||||
<img :src="imageUrl" :alt="imageAlt" :class="imageClass" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="relative h-[38vh] transition-[width] duration-300 lg:h-full lg:shrink-0"
|
||||
:class="collapsed ? 'lg:w-0' : 'lg:w-[var(--detail-panel-width)]'"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
@click="togglePanel"
|
||||
class="absolute left-0 top-1/2 z-10 hidden h-14 w-8 -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full border border-slate-200 bg-white text-slate-500 shadow-lg transition-colors hover:text-slate-800 lg:flex"
|
||||
:aria-label="collapsed ? '展开详细信息' : '收起详细信息'"
|
||||
:title="collapsed ? '展开详细信息' : '收起详细信息'"
|
||||
>
|
||||
<span class="text-lg leading-none">{{ collapsed ? '‹' : '›' }}</span>
|
||||
</button>
|
||||
|
||||
<div
|
||||
class="flex h-full flex-col overflow-hidden border-t border-slate-200 bg-white transition-[border-color] duration-300 lg:border-t-0"
|
||||
:class="collapsed ? 'lg:border-l-0' : 'lg:border-l'"
|
||||
>
|
||||
<div class="flex items-start justify-between border-b border-slate-200 px-6 py-5">
|
||||
<div>
|
||||
<div class="flex items-center gap-3">
|
||||
<h2 class="text-2xl font-bold text-slate-900">{{ title }}</h2>
|
||||
<span
|
||||
v-if="badgeLabel"
|
||||
class="rounded-full border px-3 py-1 text-xs font-medium"
|
||||
:class="badgeClass"
|
||||
>
|
||||
{{ badgeLabel }}
|
||||
</span>
|
||||
</div>
|
||||
<p v-if="subtitle" class="mt-2 text-sm text-slate-500">{{ subtitle }}</p>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
@click="close"
|
||||
class="rounded-full p-2 text-slate-400 transition-colors hover:bg-slate-100 hover:text-slate-700"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 overflow-y-auto px-6 py-5">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
Reference in New Issue
Block a user