Data display
Loading State code
Copy the implementation or inspect every file that belongs to this component unit.
resources/views/components/ui/loading-state.blade.php
@props([
'label' => 'Working',
'showElapsed' => true,
'elapsed' => 0,
'shape' => 'square',
])
@php
/*
| Compact loading state with a pixel-grid indicator, shimmering label and
| optional elapsed timer. Use the spinner for icon-sized loading feedback.
|
| Usage:
| <x-ui.loading-state label="Analysing data" />
| <x-ui.loading-state label="Connecting" shape="circle" :show-elapsed="false" />
| <x-ui.loading-state label="Resuming" :elapsed="12.4" />
*/
$pixelDelays = [90, 180, 270, 0, 90, 180, 90, 180, 270];
$resolvedShape = in_array($shape, ['square', 'dots', 'circle', 'diamond', 'ring'], true) ? $shape : 'square';
$circlePositions = [
'-top-px left-1/2 -translate-x-1/2',
'right-0 top-0.5',
'-right-px top-1/2 -translate-y-1/2',
'right-0 bottom-0.5',
'-bottom-px left-1/2 -translate-x-1/2',
'bottom-0.5 left-0',
'-left-px top-1/2 -translate-y-1/2',
'left-0 top-0.5',
];
$diamondPositions = [2, 4, 5, 6, 8];
@endphp
<span
x-data="{
elapsed: Number(@js($elapsed)),
startedAt: performance.now() - (Number(@js($elapsed)) * 1000),
timer: null,
init() {
if (! @js((bool) $showElapsed)) {
return;
}
this.timer = setInterval(() => {
this.elapsed = (performance.now() - this.startedAt) / 1000;
}, 100);
},
destroy() {
clearInterval(this.timer);
},
}"
role="status"
aria-live="polite"
aria-label="{{ $label }}"
{{ $attributes->merge(['class' => 'inline-flex w-fit items-center gap-2.5 text-brand-600 dark:text-brand-400']) }}
>
<span aria-hidden="true" data-loading-shape="{{ $resolvedShape }}" class="inline-grid size-[18px] shrink-0 place-items-center">
@if ($resolvedShape === 'dots')
<span class="flex items-center gap-1">
@foreach ([0, 120, 240] as $delay)
<span
class="loading-state-pixel size-1 rounded-full bg-current motion-reduce:animate-none"
style="--loading-state-delay: {{ $delay }}ms"
></span>
@endforeach
</span>
@elseif ($resolvedShape === 'circle')
<span class="relative block size-[18px]">
@foreach ($circlePositions as $index => $position)
<span
class="loading-state-pixel absolute size-[3px] rounded-full bg-current {{ $position }} motion-reduce:animate-none"
style="--loading-state-delay: {{ $index * 75 }}ms"
></span>
@endforeach
</span>
@elseif ($resolvedShape === 'diamond')
<span class="grid grid-cols-3 gap-[2px]">
@foreach (range(1, 9) as $position)
@if (in_array($position, $diamondPositions, true))
<span
class="loading-state-pixel size-[4px] rotate-45 rounded-[0.5px] bg-current motion-reduce:animate-none"
style="--loading-state-delay: {{ $position * 55 }}ms"
></span>
@else
<span class="size-[4px]"></span>
@endif
@endforeach
</span>
@elseif ($resolvedShape === 'ring')
<span class="relative block size-[18px] animate-spin rounded-full border border-current/25 motion-reduce:animate-none">
<span class="absolute -top-0.5 left-1/2 size-1 -translate-x-1/2 rounded-full bg-current"></span>
<span class="absolute -bottom-px left-1/2 size-0.5 -translate-x-1/2 rounded-full bg-current opacity-40"></span>
</span>
@else
<span class="grid grid-cols-3 gap-[1.5px]">
@foreach ($pixelDelays as $delay)
<span
class="loading-state-pixel size-1 rounded-[1px] bg-current motion-reduce:animate-none"
style="--loading-state-delay: {{ $delay }}ms"
></span>
@endforeach
</span>
@endif
</span>
<span class="loading-state-label bg-[length:200%_100%] bg-clip-text text-[13px] font-medium text-transparent motion-reduce:animate-none">
{{ $label }}
</span>
@if ($showElapsed)
<span
aria-hidden="true"
class="font-mono text-xs tabular-nums text-brand-700/70 dark:text-brand-300/70"
x-text="`${elapsed.toFixed(1)}s`"
>{{ number_format((float) $elapsed, 1) }}s</span>
@endif
</span>