Skip to main content

Feedback & status

Page Loader code

Copy the implementation or inspect every file that belongs to this component unit.

resources/views/components/ui/page-loader.blade.php

@props([
    'name' => 'page',
    'show' => false,
    'fixed' => true,
    'variant' => 'spinner',
    'title' => null,
    'message' => null,
    'progress' => null,
    'backdrop' => true,
    'blur' => true,
])

@php
    /*
    | Full-page / overlay loading state. Covers the viewport (fixed) or its
    | nearest positioned ancestor (fixed="false", parent must be relative) with a
    | backdrop, a loading indicator and optional title, message and determinate
    | progress. For small inline cases use <x-ui.spinner> instead.
    |
    | Toggle it by dispatching window events with the matching name:
    |   $dispatch('show-loader', 'page')  /  $dispatch('hide-loader', 'page')
    |
    | variant: spinner, dots, bars
    |
    | Usage:
    |   <x-ui.page-loader title="Loading workspace" message="Fetching your data..." />
    |   <x-ui.page-loader :fixed="false" variant="dots" />            {{-- container overlay --}}
    |   <x-ui.page-loader name="save" :show="true" :progress="60" title="Uploading" />
    */
    $position = $fixed ? 'fixed' : 'absolute';

    $surface = $backdrop
        ? 'bg-white/80 dark:bg-gray-950/80'.($blur ? ' backdrop-blur-sm' : '')
        : '';
@endphp

<div
    x-data="{
        show: @js((bool) $show),
        fixed: @js((bool) $fixed),
    }"
    x-on:show-loader.window="$event.detail === @js($name) ? (show = true) : null"
    x-on:hide-loader.window="$event.detail === @js($name) ? (show = false) : null"
    x-effect="fixed ? document.body.classList.toggle('overflow-hidden', show) : null"
    x-show="show"
    x-cloak
    x-transition:enter="ease-out duration-200" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100"
    x-transition:leave="ease-in duration-150" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0"
    {{ $attributes->merge(['class' => "{$position} inset-0 z-50 flex items-center justify-center {$surface}"]) }}
    role="status"
    aria-live="polite"
    :aria-busy="show.toString()"
>
    <div class="flex flex-col items-center gap-4 px-6 text-center">
        {{-- Indicator --}}
        @if ($variant === 'dots')
            <span class="flex items-center gap-2" aria-hidden="true">
                @foreach ([0, 150, 300] as $delay)
                    <span class="h-3 w-3 animate-bounce rounded-full bg-brand-500" style="animation-delay: {{ $delay }}ms"></span>
                @endforeach
            </span>
        @elseif ($variant === 'bars')
            <span class="flex items-end gap-1.5" aria-hidden="true">
                @foreach ([0, 120, 240, 360, 480] as $delay)
                    <span class="h-8 w-1.5 animate-pulse rounded-full bg-brand-500" style="animation-delay: {{ $delay }}ms"></span>
                @endforeach
            </span>
        @else
            <x-ui.spinner size="xl" class="text-brand-500 dark:text-brand-400" :label="$title ?? 'Loading'" />
        @endif

        {{-- Text --}}
        @if ($title)
            <p class="text-base font-semibold text-gray-900 dark:text-white">{{ $title }}</p>
        @endif

        @if ($message)
            <p class="max-w-xs text-sm text-gray-500 dark:text-gray-400">{{ $message }}</p>
        @endif

        {{-- Determinate progress --}}
        @if ($progress !== null)
            <div class="w-56 max-w-full">
                <x-ui.progress :value="$progress" size="sm" show-value />
            </div>
        @endif

        {{-- Extra content --}}
        @if (trim($slot) !== '')
            <div class="text-sm text-gray-500 dark:text-gray-400">{{ $slot }}</div>
        @endif
    </div>
</div>