Skip to content
Brok UI

Loading…

No results

Resource Pages

Open source

Authenticated resource workflows — searchable and selectable index, create, edit, and detail pages with activity history, plus the operator set: an alert-led index, a waiting-for work queue, a stepped record and one create/edit form with save blockers.

1 of 8

Preview

resources/views/pages/resource-page-index.blade.php Blade
@props([
    'title'          => 'Projects',
    'description'    => 'Search, review, and manage active work.',
    'rows'           => null,
    'queryAction'    => null,
    'searchValue'    => '',
    'filterValue'    => '',
    'sortValue'      => 'updated_desc',
    'activeFilters'  => null,
    'clearHref'      => null,
    'createHref'     => '#',
    'bulkAction'     => '#',
    'pagination'     => null,
    // Opt-in operational enrichments below. Every one defaults to null (or
    // false), so a caller who passes none of them gets the exact worklist
    // this page has always rendered — see CHANGELOG for the byte-identical
    // proof. Generalised from the Noord-C admin's orders/fulfilment/returns
    // index screens (`v2/orders/index.blade.php` and siblings).
    //
    // [['label','value','delta'=>null,'tone'=>null,'description'=>null,'href'=>null], ...]
    // — composes the `stat` primitive; a tile with an href filters into the list it summarises.
    'kpis'           => null,
    // [['label','href'=>null,'linkLabel'=>null,'tone'=>'attention'|'negative'|'info'], ...]
    // — an operational banner zone above the KPIs. Renders nothing when empty.
    'alerts'         => null,
    // ['syncedAt'=>'12 minutes ago','stale'=>false,'source'=>null] — an honest
    // "synced N minutes ago" signal for data owned by an upstream system.
    'freshness'      => null,
    // Forwarded to the `saved-views` primitive — see that item for the view shape.
    'savedViews'     => null,
    // Forwarded to the `active-filters` primitive — distinct from `activeFilters`
    // above, which feeds the header's own inline filter chips
    // (`label`/`removeHref`). This is the standalone readout row rendered by
    // the new `active-filters` primitive (`key`/`label`/`value`/`href`).
    'appliedFilters' => null,
    // Forwarded to the `bulk-job` primitive for async bulk-action progress. See that
    // item for the full prop shape (action/state/done/total/failed/…).
    'bulkJob'        => null,
    // compact (default) | comfortable | dense — forwarded to the table.
    'density'        => null,
])

@php
    $rows ??= [
        ['id' => 'project_101', 'name' => 'Mobile application', 'owner' => 'Maya Chen', 'status' => 'Active', 'tone' => 'success', 'updated' => '12 minutes ago'],
        ['id' => 'project_102', 'name' => 'Design system', 'owner' => 'Noah Williams', 'status' => 'Active', 'tone' => 'success', 'updated' => 'Yesterday'],
        ['id' => 'project_103', 'name' => 'Partner portal', 'owner' => 'Ava Patel', 'status' => 'Draft', 'tone' => 'secondary', 'updated' => '3 days ago'],
        ['id' => 'project_104', 'name' => 'Research archive', 'owner' => 'Leo Martin', 'status' => 'Archived', 'tone' => 'outline', 'updated' => '8 days ago'],
    ];

    $columns = [
        ['key' => 'name', 'label' => __('Name'), 'sortable' => false],
        ['key' => 'owner', 'label' => __('Owner'), 'sortable' => false],
        ['key' => 'status', 'label' => __('Status'), 'sortable' => false],
        ['key' => 'updated', 'label' => __('Updated'), 'type' => 'date', 'sortable' => false, 'tier' => 2],
        ['key' => 'actions', 'label' => __('Actions'), 'align' => 'end', 'sortable' => false],
    ];

    $tableDensity = $density ?? 'compact';

    $freshnessStale = $freshness ? (bool) ($freshness['stale'] ?? false) : false;
    $freshnessSyncedAt = $freshness['syncedAt'] ?? '';
@endphp

<main
    id="main-content"
    data-slot="resource-index-page"
    data-surface="admin"
    data-visual-contracts="VC-005"
    {{ $attributes->merge(['class' => 'min-h-screen bg-background text-foreground']) }}
>
    <x-blocks.app-header-resource-toolbar
        :title="$title"
        :count="trans_choice(':count record|:count records', count($rows), ['count' => count($rows)])"
        :description="$description"
        :action="$queryAction"
        search-placeholder="Search projects…"
        :search-value="$searchValue"
        :filter-value="$filterValue"
        :sort-value="$sortValue"
        :filters="$activeFilters"
        :clear-href="$clearHref"
        :create-href="$createHref"
        create-label="New project"
    />

    <div class="mx-auto max-w-screen-2xl px-4 py-6 sm:px-6">
@if ($savedViews || $freshness)
        <div data-slot="resource-index-meta" class="mb-4 flex flex-wrap items-center justify-between gap-4">
            <div class="flex min-w-0 flex-wrap items-center gap-2">
                @if ($savedViews)
                    <x-ui.saved-views :views="$savedViews" />
                @endif
            </div>
            @if ($freshness)
                <span
                    data-slot="resource-index-freshness"
                    data-stale="{{ $freshnessStale ? 'true' : 'false' }}"
                    title="{{ $freshness['source'] ?? '' ? __(':source synced :time', ['source' => $freshness['source'], 'time' => $freshnessSyncedAt]) : __('Synced :time', ['time' => $freshnessSyncedAt]) }}"
                    @class([
                        'inline-flex h-8 shrink-0 items-center gap-2 rounded-md border px-2 text-xs',
                        'border-warning-line bg-warning-soft text-warning-text' => $freshnessStale,
                        'border-border text-muted-foreground' => ! $freshnessStale,
                    ])
                >
                    <span
                        @class(['size-2 shrink-0 rounded-full', 'bg-warning' => $freshnessStale, 'bg-success' => ! $freshnessStale])
                        aria-hidden="true"
                    ></span>
                    {{ $freshnessStale
                        ? __('Data may be stale — synced :time', ['time' => $freshnessSyncedAt])
                        : __('Synced :time', ['time' => $freshnessSyncedAt]) }}
                </span>
            @endif
        </div>
@endif
@if (! empty($alerts))
        <div data-slot="resource-index-alerts" class="mb-4 space-y-2">
            @foreach ($alerts as $alert)
                @php
                    $alertTone = in_array($alert['tone'] ?? null, ['negative', 'info', 'attention'], true) ? $alert['tone'] : 'attention';
                    $alertClasses = match ($alertTone) {
                        'negative' => 'border-destructive-line bg-destructive-soft text-destructive-text',
                        'info' => 'border-info-line bg-info-soft text-info-text',
                        default => 'border-warning-line bg-warning-soft text-warning-text',
                    };
                @endphp
                <div data-slot="resource-index-alert" data-tone="{{ $alertTone }}" class="{{ $alertClasses }} flex flex-wrap items-center gap-4 rounded-md border px-4 py-2 text-sm">
                    <span class="min-w-0 break-words font-medium">{{ $alert['label'] ?? '' }}</span>
                    @if (! empty($alert['href']))
                        <x-ui.button :href="$alert['href']" variant="ghost" size="sm" class="ms-auto shrink-0">
                            {{ $alert['linkLabel'] ?? __('Review') }}
                        </x-ui.button>
                    @endif
                </div>
            @endforeach
        </div>
@endif
@if (! empty($kpis))
        <div data-slot="resource-index-kpis" class="mb-6 grid grid-cols-2 gap-4 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5">
            @foreach ($kpis as $kpi)
                <x-ui.stat
                    :label="$kpi['label'] ?? null"
                    :value="$kpi['value'] ?? null"
                    :delta="$kpi['delta'] ?? null"
                    :tone="$kpi['tone'] ?? null"
                    :description="$kpi['description'] ?? null"
                    :href="$kpi['href'] ?? null"
                />
            @endforeach
        </div>
@endif
@if ($bulkJob)
        <div class="mb-4">
            <x-ui.bulk-job
                :action="$bulkJob['action'] ?? ''"
                :state="$bulkJob['state'] ?? 'running'"
                :done="$bulkJob['done'] ?? 0"
                :total="$bulkJob['total'] ?? 0"
                :failed="$bulkJob['failed'] ?? []"
                :message="$bulkJob['message'] ?? null"
                :retry-url="$bulkJob['retryUrl'] ?? null"
                :download-url="$bulkJob['downloadUrl'] ?? null"
                :report-url="$bulkJob['reportUrl'] ?? null"
            />
        </div>
@endif
@if (! empty($appliedFilters))
        <div class="mb-4">
            <x-ui.active-filters :filters="$appliedFilters" :clear-href="$clearHref" />
        </div>
@endif
        <x-ui.admin-table
            :columns="$columns"
            :selectable="true"
            :pinned="true"
            :density="$tableDensity"
            :summary="trans_choice('Showing :count record|Showing :count records', count($rows), ['count' => count($rows)])"
        >
            @forelse ($rows as $row)
                <x-ui.admin-table.row :key="$row['id']" select-label="Select :record">
                    <x-ui.admin-table.cell column="name" :pinned="true" class="py-4 font-medium">
                        {{ $row['name'] }}
                    </x-ui.admin-table.cell>
                    <x-ui.admin-table.cell column="owner" class="py-4 text-muted-foreground">
                        {{ $row['owner'] }}
                    </x-ui.admin-table.cell>
                    <x-ui.admin-table.cell column="status" class="py-4">
                        <x-ui.badge :variant="$row['tone'] ?? 'secondary'">{{ __($row['status']) }}</x-ui.badge>
                    </x-ui.admin-table.cell>
                    <x-ui.admin-table.cell column="updated" :tier="2" class="py-4 text-muted-foreground">
                        {{ __($row['updated']) }}
                    </x-ui.admin-table.cell>
                    <x-ui.admin-table.cell column="actions" align="end" class="py-4">
                        <x-ui.button :href="$row['href'] ?? '#'" variant="ghost" size="sm">{{ __('View') }}</x-ui.button>
                    </x-ui.admin-table.cell>
                </x-ui.admin-table.row>
            @empty
                <x-ui.admin-table.empty>
                    <x-ui.empty variant="plain" size="sm">
                        <x-ui.empty.header>
                        <x-ui.empty.title as="h2">{{ __('No projects found') }}</x-ui.empty.title>
                            <x-ui.empty.description>{{ __('Change the search or filters, or create the first project.') }}</x-ui.empty.description>
                        </x-ui.empty.header>
                        <x-ui.empty.content>
                            <x-ui.button :href="$createHref">{{ __('New project') }}</x-ui.button>
                        </x-ui.empty.content>
                    </x-ui.empty>
                </x-ui.admin-table.empty>
            @endforelse

            <x-slot:footer>
                @if ($pagination && method_exists($pagination, 'links'))
                    {{ $pagination->links() }}
                @else
                    <x-ui.button variant="ghost" size="sm" disabled>{{ __('Previous') }}</x-ui.button>
                    <span aria-current="page" class="text-foreground">{{ __('Page 1') }}</span>
                    <x-ui.button variant="ghost" size="sm" disabled>{{ __('Next') }}</x-ui.button>
                @endif
            </x-slot:footer>

            <x-slot:bulk>
                <x-ui.button type="button" variant="secondary" size="sm" class="min-h-11 sm:min-h-9" x-on:click="$dispatch('resource:bulk-archive', { ids: Array.from(selected) })">
                    {{ __('Archive') }}
                </x-ui.button>

                <x-ui.alert-dialog>
                    <x-ui.alert-dialog.trigger class="min-h-11 rounded-md bg-destructive px-4 text-sm font-medium text-destructive-foreground hover:bg-destructive/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring sm:min-h-9">
                        {{ __('Delete') }}
                    </x-ui.alert-dialog.trigger>
                    <x-ui.alert-dialog.content>
                        <x-ui.alert-dialog.header>
                            <x-ui.alert-dialog.title>{{ __('Delete selected resources?') }}</x-ui.alert-dialog.title>
                            <x-ui.alert-dialog.description>{{ __('This action cannot be undone. Only the selected resources will be deleted.') }}</x-ui.alert-dialog.description>
                        </x-ui.alert-dialog.header>
                        <x-ui.alert-dialog.footer>
                            <x-ui.alert-dialog.cancel>{{ __('Cancel') }}</x-ui.alert-dialog.cancel>
                            <x-ui.form method="DELETE" :action="$bulkAction" class="!space-y-0">
                                <template x-for="key in Array.from(selected)" :key="key">
                                    <input type="hidden" name="selected[]" :value="key" />
                                </template>
                                <x-ui.button type="submit" variant="destructive">{{ __('Delete selected') }}</x-ui.button>
                            </x-ui.form>
                        </x-ui.alert-dialog.footer>
                    </x-ui.alert-dialog.content>
                </x-ui.alert-dialog>
            </x-slot:bulk>
        </x-ui.admin-table>
    </div>
</main>

Installation

terminal
php artisan ui:add pages/resource-page-index

Registry contract

Install confidence

php artisan ui:add pages/resource-page-index writes only the generated targets below. The CLI validates each file hash before writing and prompts before replacing local changes unless --force is used.

Version
1.2.2
License
open
Stability
stable
Contract
v2
Foundation
≥ 1.0.0
Type Generated target
blade resources/views/pages/resource-page-index.blade.php

Registry dependencies

blocks/app-header-resource-toolbar admin-table badge button empty alert-dialog form stat active-filters bulk-job saved-views

Package dependencies

composer: jml/brok:^0.2

Use with AI

A brief for your coding agent: what the page is, the install command, how to render it, its props and the rules. Copy it, or open a prompt about this page in an assistant.

resource-page-index.md
# Brok UI page: Resource Index (`resource-page-index`)

An authenticated resource index with a server-ready search, filter, and sort toolbar, a selectable paginated admin table, bulk actions, and an empty state. Optionally composes KPI tiles, an operational alert zone, a data-freshness indicator, saved views, an applied-filters readout, async bulk-job progress, and table density.

Brok UI is a Laravel Blade registry. `ui:add` copies this page into the app as plain Blade the app owns; it composes installed `<brok:*>` primitives and semantic design tokens.

## Install

```bash
php artisan ui:add pages/resource-page-index
```

## Render it

```blade
<x-pages.resource-page-index />
```

## Props

- `title` (string, default `Projects`) — Page heading.
- `description` (string, default `Search, review, and manage active work.`) — Page description.
- `rows` (array|null, default `null`) — Resource records with stable id, name, owner, status, tone, updated, and optional href values.
- `query-action` (string|null, default `null`) — GET action for search, filter, and sort.
- `search-value` (string) — Current search query.
- `filter-value` (string) — Current status filter.
- `sort-value` (string, default `updated_desc`) — Current sort order.
- `active-filters` (array|null, default `null`) — Active filter labels and removal links rendered inline in the resource toolbar header. Distinct from `appliedFilters` below.
- `clear-href` (string|null, default `null`) — Destination that clears all query state. Also used as the clear-all target for `appliedFilters`.
- `create-href` (string, default `#`) — Create-page destination.
- `bulk-action` (string, default `#`) — DELETE action that receives selected[] identifiers.
- `pagination` (object|null, default `null`) — Optional Laravel paginator whose links are rendered in the table footer.
- `kpis` (array|null, default `null`) — Summary tiles rendered above the table: [['label','value','delta'=>null,'tone'=>null,'description'=>null,'href'=>null]]. Composes <x-ui.stat>; a tile with an href filters into the list it summarises.
- `alerts` (array|null, default `null`) — Operational banner zone above the KPIs, for conditions needing attention: [['label','href'=>null,'linkLabel'=>null,'tone'=>'attention'|'negative'|'info']]. Renders nothing when null/empty.
- `freshness` (array|null, default `null`) — ['syncedAt','stale'=>false,'source'=>null] — an honest "synced N minutes ago" signal, with a stale state, for data owned by an upstream system.
- `saved-views` (array|null, default `null`) — Forwarded to <x-ui.saved-views> as `views`.
- `applied-filters` (array|null, default `null`) — Forwarded to <x-ui.active-filters> as `filters` (shape: key/label/value/href). Distinct from `activeFilters`, which feeds the header's own inline chips.
- `bulk-job` (array|null, default `null`) — Forwarded to <x-ui.bulk-job> for async bulk-action progress: ['action','state'=>'running','done'=>0,'total'=>0,'failed'=>[],'message'=>null,'retryUrl'=>null,'downloadUrl'=>null,'reportUrl'=>null].
- `density` (string|null, default `null`) — Table density tier forwarded to the admin table: compact (the implicit default when null), comfortable, or dense.

## Use when

- Use as the index route for an application resource that operators search, filter, sort, select, and manage.

## Avoid when

- Use a simple list when records do not need operational table controls or bulk actions.

## Rules

- Render the installed page with `<x-pages.resource-page-index />` and pass data through its props; edit the copied file only for structural changes.
- Keep the semantic design tokens (`bg-background`, `text-muted-foreground`); never swap in raw colour utilities.
- Keep the `data-slot` attributes and the logical (start/end) spacing so the markup still mirrors under `dir="rtl"`.

## Links

- Docs: https://brokui.dev/pages/resource-page-index
- Registry JSON (files, props, contract): https://brokui.dev/r/open/pages/resource-page-index.json

Working in Claude Code, Cursor or Codex? Give the agent the whole registry through the MCP server or the Brok UI skill.

Guidance

Use when

  • Use as the index route for an application resource that operators search, filter, sort, select, and manage.

Avoid when

  • Use a simple list when records do not need operational table controls or bulk actions.

Anti-patterns

  • Do not replace semantic props with conflicting utility classes.
  • Do not remove labels, focus styles, or state attributes.
Anatomy
resource toolbar saved views freshness indicator operational alerts kpi tiles bulk job progress applied filters selectable data table row actions empty state pagination bulk action bar bulk delete confirmation
Theming hooks
Pins the pattern to the admin surface and uses semantic design tokens.

Usage

Render the page as a component, or wire it to a route. Pass data where useful:

resources/views/welcome.blade.php Blade
<x-pages.resource-page-index />

Built from blocks & primitives

This page composes installed <brok:*> primitives and semantic design tokens only — it does not reimplement any primitive. Re-theme it (light, dark, admin, customer) by editing CSS variables; flip the preview to RTL to confirm it mirrors.

Source

The exact, editable file ui:add writes into your app. The preview above renders this same source — there are no preview-only pages.

resources/views/pages/resource-page-index.blade.php Blade
@props([
    'title'          => 'Projects',
    'description'    => 'Search, review, and manage active work.',
    'rows'           => null,
    'queryAction'    => null,
    'searchValue'    => '',
    'filterValue'    => '',
    'sortValue'      => 'updated_desc',
    'activeFilters'  => null,
    'clearHref'      => null,
    'createHref'     => '#',
    'bulkAction'     => '#',
    'pagination'     => null,
    // Opt-in operational enrichments below. Every one defaults to null (or
    // false), so a caller who passes none of them gets the exact worklist
    // this page has always rendered — see CHANGELOG for the byte-identical
    // proof. Generalised from the Noord-C admin's orders/fulfilment/returns
    // index screens (`v2/orders/index.blade.php` and siblings).
    //
    // [['label','value','delta'=>null,'tone'=>null,'description'=>null,'href'=>null], ...]
    // — composes the `stat` primitive; a tile with an href filters into the list it summarises.
    'kpis'           => null,
    // [['label','href'=>null,'linkLabel'=>null,'tone'=>'attention'|'negative'|'info'], ...]
    // — an operational banner zone above the KPIs. Renders nothing when empty.
    'alerts'         => null,
    // ['syncedAt'=>'12 minutes ago','stale'=>false,'source'=>null] — an honest
    // "synced N minutes ago" signal for data owned by an upstream system.
    'freshness'      => null,
    // Forwarded to the `saved-views` primitive — see that item for the view shape.
    'savedViews'     => null,
    // Forwarded to the `active-filters` primitive — distinct from `activeFilters`
    // above, which feeds the header's own inline filter chips
    // (`label`/`removeHref`). This is the standalone readout row rendered by
    // the new `active-filters` primitive (`key`/`label`/`value`/`href`).
    'appliedFilters' => null,
    // Forwarded to the `bulk-job` primitive for async bulk-action progress. See that
    // item for the full prop shape (action/state/done/total/failed/…).
    'bulkJob'        => null,
    // compact (default) | comfortable | dense — forwarded to the table.
    'density'        => null,
])

@php
    $rows ??= [
        ['id' => 'project_101', 'name' => 'Mobile application', 'owner' => 'Maya Chen', 'status' => 'Active', 'tone' => 'success', 'updated' => '12 minutes ago'],
        ['id' => 'project_102', 'name' => 'Design system', 'owner' => 'Noah Williams', 'status' => 'Active', 'tone' => 'success', 'updated' => 'Yesterday'],
        ['id' => 'project_103', 'name' => 'Partner portal', 'owner' => 'Ava Patel', 'status' => 'Draft', 'tone' => 'secondary', 'updated' => '3 days ago'],
        ['id' => 'project_104', 'name' => 'Research archive', 'owner' => 'Leo Martin', 'status' => 'Archived', 'tone' => 'outline', 'updated' => '8 days ago'],
    ];

    $columns = [
        ['key' => 'name', 'label' => __('Name'), 'sortable' => false],
        ['key' => 'owner', 'label' => __('Owner'), 'sortable' => false],
        ['key' => 'status', 'label' => __('Status'), 'sortable' => false],
        ['key' => 'updated', 'label' => __('Updated'), 'type' => 'date', 'sortable' => false, 'tier' => 2],
        ['key' => 'actions', 'label' => __('Actions'), 'align' => 'end', 'sortable' => false],
    ];

    $tableDensity = $density ?? 'compact';

    $freshnessStale = $freshness ? (bool) ($freshness['stale'] ?? false) : false;
    $freshnessSyncedAt = $freshness['syncedAt'] ?? '';
@endphp

<main
    id="main-content"
    data-slot="resource-index-page"
    data-surface="admin"
    data-visual-contracts="VC-005"
    {{ $attributes->merge(['class' => 'min-h-screen bg-background text-foreground']) }}
>
    <x-blocks.app-header-resource-toolbar
        :title="$title"
        :count="trans_choice(':count record|:count records', count($rows), ['count' => count($rows)])"
        :description="$description"
        :action="$queryAction"
        search-placeholder="Search projects…"
        :search-value="$searchValue"
        :filter-value="$filterValue"
        :sort-value="$sortValue"
        :filters="$activeFilters"
        :clear-href="$clearHref"
        :create-href="$createHref"
        create-label="New project"
    />

    <div class="mx-auto max-w-screen-2xl px-4 py-6 sm:px-6">
@if ($savedViews || $freshness)
        <div data-slot="resource-index-meta" class="mb-4 flex flex-wrap items-center justify-between gap-4">
            <div class="flex min-w-0 flex-wrap items-center gap-2">
                @if ($savedViews)
                    <x-ui.saved-views :views="$savedViews" />
                @endif
            </div>
            @if ($freshness)
                <span
                    data-slot="resource-index-freshness"
                    data-stale="{{ $freshnessStale ? 'true' : 'false' }}"
                    title="{{ $freshness['source'] ?? '' ? __(':source synced :time', ['source' => $freshness['source'], 'time' => $freshnessSyncedAt]) : __('Synced :time', ['time' => $freshnessSyncedAt]) }}"
                    @class([
                        'inline-flex h-8 shrink-0 items-center gap-2 rounded-md border px-2 text-xs',
                        'border-warning-line bg-warning-soft text-warning-text' => $freshnessStale,
                        'border-border text-muted-foreground' => ! $freshnessStale,
                    ])
                >
                    <span
                        @class(['size-2 shrink-0 rounded-full', 'bg-warning' => $freshnessStale, 'bg-success' => ! $freshnessStale])
                        aria-hidden="true"
                    ></span>
                    {{ $freshnessStale
                        ? __('Data may be stale — synced :time', ['time' => $freshnessSyncedAt])
                        : __('Synced :time', ['time' => $freshnessSyncedAt]) }}
                </span>
            @endif
        </div>
@endif
@if (! empty($alerts))
        <div data-slot="resource-index-alerts" class="mb-4 space-y-2">
            @foreach ($alerts as $alert)
                @php
                    $alertTone = in_array($alert['tone'] ?? null, ['negative', 'info', 'attention'], true) ? $alert['tone'] : 'attention';
                    $alertClasses = match ($alertTone) {
                        'negative' => 'border-destructive-line bg-destructive-soft text-destructive-text',
                        'info' => 'border-info-line bg-info-soft text-info-text',
                        default => 'border-warning-line bg-warning-soft text-warning-text',
                    };
                @endphp
                <div data-slot="resource-index-alert" data-tone="{{ $alertTone }}" class="{{ $alertClasses }} flex flex-wrap items-center gap-4 rounded-md border px-4 py-2 text-sm">
                    <span class="min-w-0 break-words font-medium">{{ $alert['label'] ?? '' }}</span>
                    @if (! empty($alert['href']))
                        <x-ui.button :href="$alert['href']" variant="ghost" size="sm" class="ms-auto shrink-0">
                            {{ $alert['linkLabel'] ?? __('Review') }}
                        </x-ui.button>
                    @endif
                </div>
            @endforeach
        </div>
@endif
@if (! empty($kpis))
        <div data-slot="resource-index-kpis" class="mb-6 grid grid-cols-2 gap-4 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5">
            @foreach ($kpis as $kpi)
                <x-ui.stat
                    :label="$kpi['label'] ?? null"
                    :value="$kpi['value'] ?? null"
                    :delta="$kpi['delta'] ?? null"
                    :tone="$kpi['tone'] ?? null"
                    :description="$kpi['description'] ?? null"
                    :href="$kpi['href'] ?? null"
                />
            @endforeach
        </div>
@endif
@if ($bulkJob)
        <div class="mb-4">
            <x-ui.bulk-job
                :action="$bulkJob['action'] ?? ''"
                :state="$bulkJob['state'] ?? 'running'"
                :done="$bulkJob['done'] ?? 0"
                :total="$bulkJob['total'] ?? 0"
                :failed="$bulkJob['failed'] ?? []"
                :message="$bulkJob['message'] ?? null"
                :retry-url="$bulkJob['retryUrl'] ?? null"
                :download-url="$bulkJob['downloadUrl'] ?? null"
                :report-url="$bulkJob['reportUrl'] ?? null"
            />
        </div>
@endif
@if (! empty($appliedFilters))
        <div class="mb-4">
            <x-ui.active-filters :filters="$appliedFilters" :clear-href="$clearHref" />
        </div>
@endif
        <x-ui.admin-table
            :columns="$columns"
            :selectable="true"
            :pinned="true"
            :density="$tableDensity"
            :summary="trans_choice('Showing :count record|Showing :count records', count($rows), ['count' => count($rows)])"
        >
            @forelse ($rows as $row)
                <x-ui.admin-table.row :key="$row['id']" select-label="Select :record">
                    <x-ui.admin-table.cell column="name" :pinned="true" class="py-4 font-medium">
                        {{ $row['name'] }}
                    </x-ui.admin-table.cell>
                    <x-ui.admin-table.cell column="owner" class="py-4 text-muted-foreground">
                        {{ $row['owner'] }}
                    </x-ui.admin-table.cell>
                    <x-ui.admin-table.cell column="status" class="py-4">
                        <x-ui.badge :variant="$row['tone'] ?? 'secondary'">{{ __($row['status']) }}</x-ui.badge>
                    </x-ui.admin-table.cell>
                    <x-ui.admin-table.cell column="updated" :tier="2" class="py-4 text-muted-foreground">
                        {{ __($row['updated']) }}
                    </x-ui.admin-table.cell>
                    <x-ui.admin-table.cell column="actions" align="end" class="py-4">
                        <x-ui.button :href="$row['href'] ?? '#'" variant="ghost" size="sm">{{ __('View') }}</x-ui.button>
                    </x-ui.admin-table.cell>
                </x-ui.admin-table.row>
            @empty
                <x-ui.admin-table.empty>
                    <x-ui.empty variant="plain" size="sm">
                        <x-ui.empty.header>
                        <x-ui.empty.title as="h2">{{ __('No projects found') }}</x-ui.empty.title>
                            <x-ui.empty.description>{{ __('Change the search or filters, or create the first project.') }}</x-ui.empty.description>
                        </x-ui.empty.header>
                        <x-ui.empty.content>
                            <x-ui.button :href="$createHref">{{ __('New project') }}</x-ui.button>
                        </x-ui.empty.content>
                    </x-ui.empty>
                </x-ui.admin-table.empty>
            @endforelse

            <x-slot:footer>
                @if ($pagination && method_exists($pagination, 'links'))
                    {{ $pagination->links() }}
                @else
                    <x-ui.button variant="ghost" size="sm" disabled>{{ __('Previous') }}</x-ui.button>
                    <span aria-current="page" class="text-foreground">{{ __('Page 1') }}</span>
                    <x-ui.button variant="ghost" size="sm" disabled>{{ __('Next') }}</x-ui.button>
                @endif
            </x-slot:footer>

            <x-slot:bulk>
                <x-ui.button type="button" variant="secondary" size="sm" class="min-h-11 sm:min-h-9" x-on:click="$dispatch('resource:bulk-archive', { ids: Array.from(selected) })">
                    {{ __('Archive') }}
                </x-ui.button>

                <x-ui.alert-dialog>
                    <x-ui.alert-dialog.trigger class="min-h-11 rounded-md bg-destructive px-4 text-sm font-medium text-destructive-foreground hover:bg-destructive/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring sm:min-h-9">
                        {{ __('Delete') }}
                    </x-ui.alert-dialog.trigger>
                    <x-ui.alert-dialog.content>
                        <x-ui.alert-dialog.header>
                            <x-ui.alert-dialog.title>{{ __('Delete selected resources?') }}</x-ui.alert-dialog.title>
                            <x-ui.alert-dialog.description>{{ __('This action cannot be undone. Only the selected resources will be deleted.') }}</x-ui.alert-dialog.description>
                        </x-ui.alert-dialog.header>
                        <x-ui.alert-dialog.footer>
                            <x-ui.alert-dialog.cancel>{{ __('Cancel') }}</x-ui.alert-dialog.cancel>
                            <x-ui.form method="DELETE" :action="$bulkAction" class="!space-y-0">
                                <template x-for="key in Array.from(selected)" :key="key">
                                    <input type="hidden" name="selected[]" :value="key" />
                                </template>
                                <x-ui.button type="submit" variant="destructive">{{ __('Delete selected') }}</x-ui.button>
                            </x-ui.form>
                        </x-ui.alert-dialog.footer>
                    </x-ui.alert-dialog.content>
                </x-ui.alert-dialog>
            </x-slot:bulk>
        </x-ui.admin-table>
    </div>
</main>

Ownership & lifecycle

Owner, release state, review evidence and adoption for this item.
Owner
Platform UI (@JoshJML)
Current version
1.2.2
Status
Stable
License
open
Deprecation
Not deprecated
Contract
v2
Foundation
≥ 1.0.0