Sink/app/components/spark-ui/AnimatedList.vue
2025-05-21 19:54:54 +08:00

100 lines
2.1 KiB
Vue

<script lang="ts" setup>
import { computed, onMounted, ref, useSlots } from 'vue'
import { cn } from '@/lib/utils'
const props = withDefaults(defineProps<{
class?: string
delay?: number
}>(), {
delay: 1000,
})
const emit = defineEmits(['update:items'])
const slots = useSlots()
const index = ref(0)
const slotsArray = ref<any>([])
const maxShowItems = 100
const itemsToShow = computed(() => {
const start = index.value - maxShowItems < 0 ? 0 : index.value - maxShowItems
return slotsArray.value.slice(start, index.value)
})
watch([itemsToShow], () => {
emit('update:items', itemsToShow.value.at(-1), props)
})
async function loadComponents() {
slotsArray.value = slots.default ? slots.default()?.[0]?.children : []
while (index.value < slotsArray.value.length) {
index.value++
await delay(props.delay)
}
}
async function delay(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms))
}
function getInitial(idx: number) {
return idx === index.value - 1
? {
scale: 0,
opacity: 0,
}
: undefined
}
function getEnter(idx: number) {
return idx === index.value - 1
? {
scale: 1,
opacity: 1,
y: 0,
transition: {
type: 'spring',
stiffness: 250,
damping: 40,
},
}
: undefined
}
function getLeave() {
return {
scale: 0,
opacity: 0,
y: 0,
transition: {
type: 'spring',
stiffness: 350,
damping: 40,
},
}
};
onMounted(() => loadComponents())
</script>
<template>
<div :class="cn('overflow-auto', $props.class)">
<transition-group name="list" tag="div" class="flex flex-col-reverse items-center p-2" move-class="move">
<div
v-for="(item, idx) in itemsToShow"
:key="item.props.key"
v-motion
:initial="getInitial(idx)" :enter="getEnter(idx)" :leave="getLeave()"
:class="cn('mx-auto w-full')"
>
<component :is="item" />
</div>
</transition-group>
</div>
</template>
<style scoped>
.move {
transition: transform 0.4s ease-out;
}
</style>