Skip to content

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 spinner
  • delay (number): Delay showing spinner
  • tip (string): Loading tip text
  • size (‘sm’ | ‘md’ | ‘lg’): Spinner size
  • fullscreen (boolean): Show fullscreen spinner
  • overlay (boolean): Show overlay background
  • blur (boolean): Blur content when loading
  • wrapperClass (string): Wrapper CSS class
  • contentClass (string): Content CSS class
  • indicator (string): Spinner indicator type
  • color (string): Spinner color

Events

  • visibleChange: Spinner visibility changed

Slots

  • default: Content to wrap
  • spinner: Custom spinner content
  • tip: Custom loading tip content
  • icon: Custom icon content

Usage Examples

  1. Basic Spinner:
<template>
<UISpin :spinning="loading">
<div class="p-4">
Content here
</div>
</UISpin>
</template>
<script setup>
const loading = ref(true)
</script>
  1. 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>
  1. 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

  1. User Experience:

    • Appropriate delays
    • Clear feedback
    • Content preservation
    • Smooth transitions
  2. Performance:

    • Minimal reflows
    • Efficient updates
    • Memory management
    • Optimized rendering
  3. Accessibility:

    • ARIA labels
    • Focus management
    • Screen reader support
    • Loading announcements
  4. Mobile:

    • Touch feedback
    • Responsive layout
    • Clear indicators
    • Performance optimization

Common Use Cases

  1. 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>
  1. 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>
  1. 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