feat: implement LazyImage component for thumbnails and search bar

This commit is contained in:
Johnpaul
2025-07-30 01:14:14 +01:00
parent 939b94f85f
commit 0b2d985fbf
4 changed files with 104 additions and 42 deletions

View File

@@ -0,0 +1,64 @@
<template>
<div class="relative w-full p-4">
<div class="h-12 flex items-center gap-4 justify-between">
<div class="flex-1 max-w-md">
<AutoComplete
v-model.lazy="searchQuery"
:placeholder="$t('templateWorkflows.searchPlaceholder')"
:complete-on-focus="false"
:delay="200"
class="w-full"
:pt="{
pcInputText: {
root: {
class: 'w-full rounded-2xl'
}
},
loader: {
style: 'display: none'
}
}"
:show-empty-message="false"
@complete="() => {}"
/>
</div>
</div>
<div class="flex items-center gap-4 mt-2">
<small
v-if="searchQuery && filteredCount !== null"
class="text-color-secondary"
>
{{ $t('g.resultsCount', { count: filteredCount }) }}
</small>
<Button
v-if="searchQuery"
text
size="small"
icon="pi pi-times"
:label="$t('g.clearFilters')"
@click="clearFilters"
/>
</div>
</div>
</template>
<script setup lang="ts">
import AutoComplete from 'primevue/autocomplete'
import Button from 'primevue/button'
const { filteredCount } = defineProps<{
filteredCount?: number | null
}>()
const searchQuery = defineModel<string>('searchQuery', { default: '' })
const emit = defineEmits<{
clearFilters: []
}>()
const clearFilters = () => {
searchQuery.value = ''
emit('clearFilters')
}
</script>

View File

@@ -1,24 +1,24 @@
<template>
<BaseThumbnail :is-hovered="isHovered">
<img
<LazyImage
:src="baseImageSrc"
:alt="alt"
:class="
:image-class="
isVideoType
? 'w-full h-full object-cover'
: 'max-w-full max-h-64 object-contain'
"
/>
<div ref="containerRef" class="absolute inset-0">
<img
<LazyImage
:src="overlayImageSrc"
:alt="alt"
:class="
:image-class="
isVideoType
? 'w-full h-full object-cover'
: 'max-w-full max-h-64 object-contain'
"
:style="{
:image-style="{
clipPath: `inset(0 ${100 - sliderPosition}% 0 0)`
}"
/>
@@ -36,6 +36,7 @@
import { useMouseInElement } from '@vueuse/core'
import { ref, watch } from 'vue'
import LazyImage from '@/components/common/LazyImage.vue'
import BaseThumbnail from '@/components/templates/thumbnails/BaseThumbnail.vue'
const SLIDER_START_POSITION = 50

View File

@@ -1,25 +1,23 @@
<template>
<BaseThumbnail :hover-zoom="hoverZoom" :is-hovered="isHovered">
<div class="overflow-hidden w-full h-full flex items-center justify-center">
<img
:src="src"
:alt="alt"
draggable="false"
:class="[
'transform-gpu transition-transform duration-300 ease-out',
isVideoType
? 'w-full h-full object-cover'
: 'max-w-full max-h-64 object-contain'
]"
:style="
isHovered ? { transform: `scale(${1 + hoverZoom / 100})` } : undefined
"
/>
</div>
<LazyImage
:src="src"
:alt="alt"
:image-class="[
'transform-gpu transition-transform duration-300 ease-out',
isVideoType
? 'w-full h-full object-cover'
: 'max-w-full max-h-64 object-contain'
]"
:image-style="
isHovered ? { transform: `scale(${1 + hoverZoom / 100})` } : undefined
"
/>
</BaseThumbnail>
</template>
<script setup lang="ts">
import LazyImage from '@/components/common/LazyImage.vue'
import BaseThumbnail from '@/components/templates/thumbnails/BaseThumbnail.vue'
const { src, isVideo } = defineProps<{

View File

@@ -1,37 +1,23 @@
<template>
<BaseThumbnail :is-hovered="isHovered">
<div class="relative w-full h-full">
<img
:src="baseImageSrc"
:alt="alt"
draggable="false"
class="absolute inset-0"
:class="
isVideoType
? 'w-full h-full object-cover'
: 'max-w-full max-h-64 object-contain'
"
/>
<img
<LazyImage :src="baseImageSrc" :alt="alt" :image-class="baseImageClass" />
<LazyImage
:src="overlayImageSrc"
:alt="alt"
draggable="false"
class="absolute inset-0 transition-opacity duration-300"
:class="[
isVideoType
? 'w-full h-full object-cover'
: 'max-w-full max-h-64 object-contain',
{ 'opacity-100': isHovered, 'opacity-0': !isHovered }
]"
:image-class="overlayImageClass"
/>
</div>
</BaseThumbnail>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import LazyImage from '@/components/common/LazyImage.vue'
import BaseThumbnail from '@/components/templates/thumbnails/BaseThumbnail.vue'
const { baseImageSrc, overlayImageSrc, isVideo } = defineProps<{
const { baseImageSrc, overlayImageSrc, isVideo, isHovered } = defineProps<{
baseImageSrc: string
overlayImageSrc: string
alt: string
@@ -44,4 +30,17 @@ const isVideoType =
baseImageSrc?.toLowerCase().endsWith('.webp') ||
overlayImageSrc?.toLowerCase().endsWith('.webp') ||
false
const baseImageClass = computed(() => {
return `absolute inset-0 ${isVideoType ? 'w-full h-full object-cover' : 'max-w-full max-h-64 object-contain'}`
})
const overlayImageClass = computed(() => {
const baseClasses = 'absolute inset-0 transition-opacity duration-300'
const sizeClasses = isVideoType
? 'w-full h-full object-cover'
: 'max-w-full max-h-64 object-contain'
const opacityClasses = isHovered ? 'opacity-100' : 'opacity-0'
return `${baseClasses} ${sizeClasses} ${opacityClasses}`
})
</script>