Skip to main content

Navigation

Pagination code

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

resources/views/components/ui/pagination.blade.php

@props([
    'paginator' => null,
    'currentPage' => 1,
    'lastPage' => 1,
    'onEachSide' => 1,
    'ajax' => false,
    'variant' => 'full',
])

@php
    /*
    | Page navigation. Pass a Laravel `paginator` (LengthAwarePaginator) and it
    | wires up the links automatically, or pass `currentPage` / `lastPage` plus a
    | `href` callback via the `links` slot for manual control.
    |
    | variant: full (numbered window) or simple (prev/next with "Page X of Y").
    | A CursorPaginator automatically renders as simple prev/next.
    |
    | Usage:
    |   <x-ui.pagination :paginator="$users" />
    |   <x-ui.pagination :current-page="3" :last-page="10" />
    |   <x-ui.pagination variant="simple" :current-page="3" :last-page="10" />
    |   <x-ui.pagination :paginator="$users->cursorPaginate(15)" />
    */
    use Illuminate\Contracts\Pagination\CursorPaginator as CursorPaginatorContract;

    $isCursor = $paginator instanceof CursorPaginatorContract;

    if ($isCursor) {
        $variant = 'simple';
        $prevUrl = $paginator->previousPageUrl();
        $nextUrl = $paginator->nextPageUrl();
        $currentPage = null;
        $lastPage = null;
    } else {
        if ($paginator) {
            $currentPage = $paginator->currentPage();
            $lastPage = $paginator->lastPage();
        }

        $currentPage = (int) $currentPage;
        $lastPage = (int) $lastPage;
        $prevUrl = $currentPage > 1 ? ($paginator ? $paginator->url($currentPage - 1) : '#') : '#';
        $nextUrl = $currentPage < $lastPage ? ($paginator ? $paginator->url($currentPage + 1) : '#') : '#';
    }

    // Build a compact window of page numbers with ellipsis gaps.
    $window = [];
    if (! $isCursor && $variant === 'full') {
        if ($lastPage <= 7 + ($onEachSide * 2)) {
            $window = range(1, max(1, $lastPage));
        } else {
            $window[] = 1;
            $start = max(2, $currentPage - $onEachSide);
            $end = min($lastPage - 1, $currentPage + $onEachSide);
            if ($start > 2) { $window[] = '...'; }
            for ($i = $start; $i <= $end; $i++) { $window[] = $i; }
            if ($end < $lastPage - 1) { $window[] = '...'; }
            $window[] = $lastPage;
        }
    }

    $hasPrev = $isCursor ? filled($prevUrl) : $currentPage > 1;
    $hasNext = $isCursor ? filled($nextUrl) : $currentPage < $lastPage;

    $linkBase = 'inline-flex items-center justify-center min-w-9 h-9 px-2 rounded-lg text-sm font-medium transition-[box-shadow,color]';
    $idle = 'text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800';
    $active = 'bg-brand-600 text-white';
    $disabled = 'text-gray-300 dark:text-gray-600 pointer-events-none';
@endphp

<nav
    role="navigation"
    aria-label="Pagination"
    @if ($ajax) data-ajax-pagination @endif
    {{ $attributes->merge(['class' => 'flex items-center justify-between gap-2']) }}
>
    @if ($variant === 'simple')
        <a href="{{ $hasPrev ? $prevUrl : '#' }}"
           class="{{ $linkBase }} px-3 {{ $hasPrev ? $idle : $disabled }}"
           @if (! $hasPrev) aria-disabled="true" @endif>
            <x-ui.icon name="arrow-left" weight="bold" size="sm" class="me-1" /> Previous
        </a>

        @if (! $isCursor)
            <span class="text-sm text-gray-500 dark:text-gray-400">
                Page <span class="font-semibold text-gray-900 dark:text-white">{{ $currentPage }}</span> of {{ $lastPage }}
            </span>
        @endif

        <a href="{{ $hasNext ? $nextUrl : '#' }}"
           class="{{ $linkBase }} px-3 {{ $hasNext ? $idle : $disabled }}"
           @if (! $hasNext) aria-disabled="true" @endif>
            Next <x-ui.icon name="arrow-right" weight="bold" size="sm" class="ms-1" />
        </a>
    @else
        {{-- Mobile: prev / next --}}
        <div class="flex flex-1 items-center justify-between sm:hidden">
            <a href="{{ $hasPrev ? $prevUrl : '#' }}"
               class="{{ $linkBase }} px-3 {{ $hasPrev ? $idle : $disabled }}"
               @if (! $hasPrev) aria-disabled="true" @endif>
                <x-ui.icon name="arrow-left" weight="bold" size="sm" class="me-1" /> Previous
            </a>
            <a href="{{ $hasNext ? $nextUrl : '#' }}"
               class="{{ $linkBase }} px-3 {{ $hasNext ? $idle : $disabled }}"
               @if (! $hasNext) aria-disabled="true" @endif>
                Next <x-ui.icon name="arrow-right" weight="bold" size="sm" class="ms-1" />
            </a>
        </div>

        {{-- Desktop: full range --}}
        <div class="hidden w-full items-center justify-center gap-1 sm:flex">
            <a href="{{ $hasPrev ? $prevUrl : '#' }}"
               class="{{ $linkBase }} {{ $hasPrev ? $idle : $disabled }}"
               aria-label="Previous page" @if (! $hasPrev) aria-disabled="true" @endif>
                <x-ui.icon name="caret-left" weight="bold" size="sm" />
            </a>

            @foreach ($window as $page)
                @if ($page === '...')
                    <span class="inline-flex h-9 min-w-9 items-center justify-center text-sm text-gray-400 dark:text-gray-500">&hellip;</span>
                @else
                    <a href="{{ $paginator ? $paginator->url($page) : '#' }}"
                       class="{{ $linkBase }} {{ $page === $currentPage ? $active : $idle }}"
                       @if ($page === $currentPage) aria-current="page" @endif>{{ $page }}</a>
                @endif
            @endforeach

            <a href="{{ $hasNext ? $nextUrl : '#' }}"
               class="{{ $linkBase }} {{ $hasNext ? $idle : $disabled }}"
               aria-label="Next page" @if (! $hasNext) aria-disabled="true" @endif>
                <x-ui.icon name="caret-right" weight="bold" size="sm" />
            </a>
        </div>
    @endif
</nav>