UISpin
UISpin
A versatile loading indicator component that provides various spinner styles and configurations for indicating loading states in your application.
<template> <UISpin :spinning="loading" :delay="300" tip="Loading data..." > <template #spinner> <UISpinner size="lg" class="text-primary-500" /> </template>
<div class="p-6 space-y-4"> <h3 class="text-lg font-medium"> Content Title </h3> <p class="text-gray-600"> Content description goes here... </p> </div> </UISpin></template>
<script setup lang="ts">const loading = ref(true)
onMounted(() => { // Simulate loading setTimeout(() => { loading.value = false }, 2000)})</script>Props
spinning(boolean): Show spinnerdelay(number): Delay showing spinnertip(string): Loading tip textsize(‘sm’ | ‘md’ | ‘lg’): Spinner sizefullscreen(boolean): Show fullscreen spinneroverlay(boolean): Show overlay backgroundblur(boolean): Blur content when loadingwrapperClass(string): Wrapper CSS classcontentClass(string): Content CSS classindicator(string): Spinner indicator typecolor(string): Spinner color
Events
visibleChange: Spinner visibility changed
Slots
default: Content to wrapspinner: Custom spinner contenttip: Custom loading tip contenticon: Custom icon content
Usage Examples
- Basic Spinner:
<template> <UISpin :spinning="loading"> <div class="p-4"> Content here </div> </UISpin></template>
<script setup>const loading = ref(true)</script>- Advanced Loading Container:
<template> <div class="space-y-4"> <div class="flex items-center justify-between"> <h2 class="text-lg font-medium"> Dashboard </h2>
<UIButton @click="refreshData" > Refresh </UIButton> </div>
<UISpin :spinning="loading" :delay="300" tip="Loading dashboard data..." > <template #spinner> <div class="flex flex-col items-center space-y-2"> <UISpinner size="lg" class="text-primary-500" /> <div class="text-sm text-gray-500"> {{ loadingMessage }} </div> </div> </template>
<div class="grid gap-4 md:grid-cols-2 lg:grid-cols-3"> <DashboardCard v-for="card in cards" :key="card.id" :data="card" /> </div> </UISpin> </div></template>
<script setup>const loading = ref(true)const cards = ref([])const loadingMessage = ref('Fetching latest data...')
const refreshData = async () => { loading.value = true try { cards.value = await fetchDashboardData() } finally { loading.value = false }}
onMounted(() => { refreshData()})</script>- Form Submission:
<template> <form @submit.prevent="handleSubmit"> <UISpin :spinning="submitting" :blur="true" tip="Saving changes..." > <div class="space-y-4"> <div class="space-y-2"> <label class="text-sm font-medium"> Name </label>
<UIInput v-model="form.name" :disabled="submitting" /> </div>
<div class="space-y-2"> <label class="text-sm font-medium"> Email </label>
<UIInput v-model="form.email" type="email" :disabled="submitting" /> </div>
<div class="flex justify-end"> <UIButton type="primary" :loading="submitting" :disabled="!isValid" > Save Changes </UIButton> </div> </div> </UISpin> </form></template>
<script setup>const form = reactive({ name: '', email: ''})
const submitting = ref(false)
const handleSubmit = async () => { submitting.value = true try { await saveChanges(form) } finally { submitting.value = false }}</script>Best Practices
-
User Experience:
- Appropriate delays
- Clear feedback
- Content preservation
- Smooth transitions
-
Performance:
- Minimal reflows
- Efficient updates
- Memory management
- Optimized rendering
-
Accessibility:
- ARIA labels
- Focus management
- Screen reader support
- Loading announcements
-
Mobile:
- Touch feedback
- Responsive layout
- Clear indicators
- Performance optimization
Common Use Cases
- Data Loading:
<template> <div class="space-y-4"> <UISpin :spinning="loading" :delay="300" > <UITable :data="tableData" :columns="columns" /> </UISpin>
<div class="flex justify-center"> <UIPagination v-model="page" :total="total" :disabled="loading" /> </div> </div></template>- Image Loading:
<template> <div class="relative"> <UISpin :spinning="imageLoading" :overlay="true" class="absolute inset-0" > <template #spinner> <div class="flex flex-col items-center space-y-2"> <UISpinner size="sm" /> <span class="text-xs"> Loading image... </span> </div> </template> </UISpin>
<img :src="imageUrl" :alt="imageAlt" class="w-full h-full object-cover" @load="imageLoading = false" @error="handleImageError" /> </div></template>- Async Operations:
<template> <div class="relative min-h-[200px]"> <UISpin :spinning="processing" :fullscreen="false" :blur="true" tip="Processing..." > <template #spinner> <div class="flex flex-col items-center space-y-4"> <UISpinner size="lg" /> <div class="text-sm"> {{ processingStep }} </div> <UIProgress :percent="progress" :showInfo="true" /> </div> </template>
<div class="space-y-4"> <FileList :files="files" @select="selectFile" />
<div class="flex justify-end space-x-2"> <UIButton variant="outline" :disabled="processing" @click="cancel" > Cancel </UIButton>
<UIButton type="primary" :disabled="!files.length || processing" @click="processFiles" > Process Files </UIButton> </div> </div> </UISpin> </div></template>
<script setup>const processing = ref(false)const progress = ref(0)const processingStep = ref('')
const processFiles = async () => { processing.value = true progress.value = 0
try { for (const file of files.value) { processingStep.value = `Processing ${file.name}...` await processFile(file) progress.value += (100 / files.value.length) } } finally { processing.value = false }}</script>Component Composition
The UISpin component works well with:
- UISpinner for loading indicators
- UIProgress for progress tracking
- UIButton for actions
- UITooltip for loading tips
- UIIcon for custom indicators
- UIOverlay for backgrounds
- UICard for loading containers
- UITable for data loading