feat(stage-ui): skeleton component

This commit is contained in:
Neko Ayaka
2025-03-18 12:57:56 +08:00
parent d4c08a2c26
commit 7a417fbae1
5 changed files with 213 additions and 0 deletions
+4
View File
@@ -38,6 +38,10 @@ export default defineConfig({
id: 'providers',
title: 'Providers',
},
{
id: 'misc',
title: 'Misc',
},
{
id: 'physics',
title: 'Physics',
@@ -0,0 +1,135 @@
<script setup lang="ts">
import Skeleton from './Skeleton.vue'
</script>
<template>
<Story
title="Skeleton"
group="misc"
:layout="{ type: 'grid', width: 600 }"
>
<template #controls>
<ThemeColorsHueControl />
</template>
<Variant
id="basic"
title="Basic Skeleton"
>
<div class="space-y-4">
<Skeleton class="h-6 w-full rounded-lg" />
<Skeleton class="h-6 w-3/4 rounded-lg" />
<Skeleton class="h-6 w-1/2 rounded-lg" />
</div>
</Variant>
<Variant
id="pulse-animation"
title="Pulse Animation"
>
<div class="space-y-4">
<Skeleton animation="pulse" class="h-6 w-full rounded-lg" />
<Skeleton animation="pulse" class="h-6 w-3/4 rounded-lg" />
<Skeleton animation="pulse" class="h-6 w-1/2 rounded-lg" />
</div>
</Variant>
<Variant
id="wave-animation"
title="Wave Animation"
>
<div class="space-y-4">
<Skeleton animation="wave" class="h-6 w-full rounded-lg" />
<Skeleton animation="wave" class="h-6 w-3/4 rounded-lg" />
<Skeleton animation="wave" class="h-6 w-1/2 rounded-lg" />
</div>
</Variant>
<Variant
id="no-animation"
title="No Animation"
>
<div class="space-y-4">
<Skeleton animation="none" class="h-6 w-full rounded-lg" />
<Skeleton animation="none" class="h-6 w-3/4 rounded-lg" />
<Skeleton animation="none" class="h-6 w-1/2 rounded-lg" />
</div>
</Variant>
<Variant
id="card-loading"
title="Card Loading State"
>
<div class="border border-neutral-200 rounded-xl p-4 space-y-4 dark:border-neutral-800">
<Skeleton class="h-40 w-full rounded-lg" />
<Skeleton class="h-5 w-3/4 rounded-md" />
<Skeleton class="h-16 w-full rounded-md" />
<div class="flex gap-2">
<Skeleton class="h-8 w-16 rounded-md" />
<Skeleton class="h-8 w-16 rounded-md" />
</div>
</div>
</Variant>
<Variant
id="profile-loading"
title="Profile Loading State"
>
<div class="flex items-center gap-4">
<Skeleton class="h-16 w-16 rounded-full" />
<div class="flex-1 space-y-2">
<Skeleton class="h-5 w-1/3 rounded-md" />
<Skeleton class="h-4 w-2/3 rounded-md" />
<Skeleton class="h-4 w-1/4 rounded-md" />
</div>
</div>
</Variant>
<Variant
id="form-loading"
title="Form Loading State"
>
<div class="space-y-6">
<div class="space-y-1">
<Skeleton class="h-5 w-24 rounded-md" />
<Skeleton class="h-4 w-32 rounded-md" />
<Skeleton class="mt-2 h-10 w-full rounded-lg" />
</div>
<div class="space-y-1">
<Skeleton class="h-5 w-32 rounded-md" />
<Skeleton class="h-4 w-40 rounded-md" />
<Skeleton class="mt-2 h-10 w-full rounded-lg" />
</div>
<div class="space-y-1">
<Skeleton class="h-5 w-36 rounded-md" />
<Skeleton class="h-4 w-28 rounded-md" />
<Skeleton class="mt-2 h-24 w-full rounded-lg" />
</div>
<Skeleton class="h-10 w-24 rounded-md" />
</div>
</Variant>
<Variant
id="table-loading"
title="Table Loading State"
>
<div class="overflow-hidden border border-neutral-200 rounded-lg dark:border-neutral-800">
<div class="flex border-b border-neutral-200 bg-neutral-100 p-4 dark:border-neutral-800 dark:bg-neutral-900">
<Skeleton class="h-6 w-1/4 rounded-md" />
<Skeleton class="ml-4 h-6 w-1/4 rounded-md" />
<Skeleton class="ml-4 h-6 w-1/4 rounded-md" />
<Skeleton class="ml-4 h-6 w-1/4 rounded-md" />
</div>
<div v-for="i in 5" :key="i" class="flex border-b border-neutral-200 p-4 dark:border-neutral-800">
<Skeleton class="h-6 w-1/4 rounded-md" />
<Skeleton class="ml-4 h-6 w-1/4 rounded-md" />
<Skeleton class="ml-4 h-6 w-1/4 rounded-md" />
<Skeleton class="ml-4 h-6 w-1/4 rounded-md" />
</div>
</div>
</Variant>
</Story>
</template>
@@ -0,0 +1,72 @@
<script setup lang="ts">
const props = withDefaults(defineProps<{
animation?: 'pulse' | 'wave' | 'none'
}>(), {
animation: 'pulse',
})
</script>
<template>
<div
class="skeleton"
:class="props.animation !== 'none' ? `skeleton-${props.animation}` : ''"
bg="neutral-200 dark:neutral-800"
overflow="hidden"
>
<slot />
</div>
</template>
<style scoped>
.skeleton {
position: relative;
transition: all 0.2s ease-in-out;
}
/* Pulse animation */
.skeleton-pulse {
animation: skeleton-pulse 1.5s ease-in-out 0.5s infinite;
}
@keyframes skeleton-pulse {
0% {
opacity: 1;
}
50% {
opacity: 0.5;
}
100% {
opacity: 1;
}
}
/* Wave animation */
.skeleton-wave::after {
content: '';
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
transform: translateX(-100%);
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
animation: skeleton-wave 1.5s infinite;
border-radius: inherit;
}
.dark .skeleton-wave::after {
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.1), transparent);
}
@keyframes skeleton-wave {
0% {
transform: translateX(-100%);
}
60% {
transform: translateX(100%);
}
100% {
transform: translateX(100%);
}
}
</style>
@@ -0,0 +1 @@
export { default as Skeleton } from './Skeleton.vue'
@@ -6,6 +6,7 @@ export * from './Graphics'
export { default as Live2DCanvas } from './Live2D/Canvas.vue'
export { default as Live2DModel } from './Live2D/Model.vue'
export * from './Menu'
export * from './Misc'
export * from './Physics'
export * from './Providers'
export { default as SceneLive2D } from './Scenes/Live2D.vue'