elearning/Frontend-Learner/components/common/LoadingSkeleton.vue
2026-01-13 10:48:02 +07:00

108 lines
2.1 KiB
Vue

<script setup lang="ts">
defineProps<{
type?: 'text' | 'avatar' | 'card' | 'button'
width?: string
height?: string
count?: number
}>()
</script>
<template>
<div class="skeleton-wrapper">
<template v-if="type === 'card'">
<div v-for="i in (count || 1)" :key="i" class="skeleton-card">
<div class="skeleton skeleton-image"/>
<div class="skeleton-content">
<div class="skeleton skeleton-title"/>
<div class="skeleton skeleton-text"/>
<div class="skeleton skeleton-text short"/>
</div>
</div>
</template>
<template v-else-if="type === 'avatar'">
<div class="skeleton skeleton-avatar"/>
</template>
<template v-else-if="type === 'button'">
<div class="skeleton skeleton-button" :style="{ width: width || '120px' }"/>
</template>
<template v-else>
<div
v-for="i in (count || 1)"
:key="i"
class="skeleton skeleton-text"
:style="{ width: width || '100%', height: height || '16px' }"
/>
</template>
</div>
</template>
<style scoped>
.skeleton-wrapper {
display: flex;
flex-direction: column;
gap: 12px;
}
.skeleton {
background: linear-gradient(90deg, var(--neutral-100) 25%, var(--neutral-200) 50%, var(--neutral-100) 75%);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
border-radius: 8px;
}
.skeleton-card {
background: var(--bg-surface);
border-radius: var(--radius-xl);
border: 1px solid var(--border-color);
overflow: hidden;
}
.skeleton-image {
height: 160px;
border-radius: 0;
}
.skeleton-content {
padding: 16px;
display: flex;
flex-direction: column;
gap: 8px;
}
.skeleton-title {
height: 20px;
width: 80%;
}
.skeleton-text {
height: 14px;
width: 100%;
}
.skeleton-text.short {
width: 60%;
}
.skeleton-avatar {
width: 48px;
height: 48px;
border-radius: 50%;
}
.skeleton-button {
height: 40px;
border-radius: 8px;
}
@keyframes shimmer {
0% {
background-position: -200% 0;
}
100% {
background-position: 200% 0;
}
}
</style>