Skip to content
Brok UI

Loading…

No results

Saved Views

Open source

A named view switcher with a menu mode for many views and a tab-strip mode for few, distinguishing system, owned, and shared views.

Version
v1.2.1
Stability
stable
License
MIT
Related
Badge
Dropdown Menu
Active Filters
Filters

Preview

Mode
previews.components.saved-views.default.blade.php Blade
<x-ui.saved-views
    :views="[
        ['key' => 'all', 'label' => __('All orders'), 'href' => '?view=all', 'active' => true, 'system' => true],
        ['key' => 'unpaid', 'label' => __('Awaiting payment'), 'href' => '?view=unpaid', 'count' => 12, 'tone' => 'attention', 'system' => true],
        ['key' => 'shipped', 'label' => __('Shipped'), 'href' => '?view=shipped', 'count' => 48, 'system' => true],
        [
            'key' => 'my-wholesale',
            'label' => __('Wholesale, this week'),
            'href' => '?view=my-wholesale',
            'owned' => true,
            'renameUrl' => '#',
            'deleteUrl' => '#',
        ],
        [
            'key' => 'team-backorders',
            'label' => __('Team backorders'),
            'href' => '?view=team-backorders',
            'owned' => false,
            'shared' => true,
            'owner' => 'Priya',
        ],
    ]"
    save-url="#"
/>

Installation

terminal
php artisan ui:add saved-views

Note

This component ships an Alpine behavior module at resources/js/ui/saved-views.js. Import it once from your bundle so it registers on alpine:init:

resources/js/ui/index.js JS
import './saved-views.js';

Registry contract

php artisan ui:add saved-views writes only the files below. The CLI validates each file hash before writing and asks before it replaces a local change, unless you pass --force.

  • blade resources/views/components/ui/saved-views.blade.php
Registry dependencies
badge button dropdown
Packages
composer: jml/brok:^0.2

Use with AI

A brief for your coding agent: install command, usage, props, guidance and the rules. Copy it, or open a prompt about this component in an assistant.

saved-views.md
# Brok UI: Saved Views (`saved-views`)

A named view switcher with a menu mode for many views and a tab-strip mode for few, distinguishing system, owned, and shared views.

Brok UI is a Laravel Blade component registry. Installed components are plain Blade files the app owns.

## Install

```bash
php artisan ui:add saved-views
```

## Usage

```blade
<x-ui.saved-views
    :views="[
        ['key' => 'all', 'label' => __('All orders'), 'href' => '?view=all', 'active' => true, 'system' => true],
        ['key' => 'unpaid', 'label' => __('Awaiting payment'), 'href' => '?view=unpaid', 'count' => 12, 'tone' => 'attention', 'system' => true],
        ['key' => 'shipped', 'label' => __('Shipped'), 'href' => '?view=shipped', 'count' => 48, 'system' => true],
        [
            'key' => 'my-wholesale',
            'label' => __('Wholesale, this week'),
            'href' => '?view=my-wholesale',
            'owned' => true,
            'renameUrl' => '#',
            'deleteUrl' => '#',
        ],
        [
            'key' => 'team-backorders',
            'label' => __('Team backorders'),
            'href' => '?view=team-backorders',
            'owned' => false,
            'shared' => true,
            'owner' => 'Priya',
        ],
    ]"
    save-url="#"
/>
```

## Props

- `views` (array, default `[]`) — Views to render: each entry is ['key','label','href','event','payload','count','tone','active','system','owned','shared','owner','renameUrl','deleteUrl']. href must carry the view's full state (filters, sort, tab); pass event (+ optional payload) instead to dispatch rather than navigate, for a list that owns its own state. An entry with neither is inert. Renaming, deleting and saving stay server-only (renameUrl/deleteUrl/saveUrl).
- `mode` (menu|tabs, default `menu`) — menu opens a dropdown split into Standard/Saved sections with rename, delete, and save-current; tabs renders a flat, always-visible strip with no editing.
- `heading` (string|null, default `null`) — Accessible name for the switcher. Defaults to a localized "Views".
- `saveUrl` (string|null, default `null`) — POST target that saves the list's current state as a new view. The save-current form is hidden without it.
- `saveLabel` (string|null, default `null`) — Label for the save-current submit button. Defaults to a localized "Save view".
- `anchored` (bool, default `true`) — Forwards to the menu-mode dropdown's anchored/teleported placement so a clipping toolbar ancestor never cuts the panel off.

## Use when

- Use to orient users and help them move across pages, sections, or commands.
- A list's filters, sort, and tab together form named, revisitable states worth switching between.
- The list has enough saved/system views that a fixed tab strip would wrap or crowd the toolbar (use mode="menu"); a handful of system views fit as a strip instead (mode="tabs").

## Avoid when

- Do not hide primary wayfinding in novelty interactions or deep nested structures if straightforward navigation would be clearer.
- There is only one way to look at the list — a saved-views switcher with a single entry is a control with nothing to switch to.
- Picking or editing filter VALUES — use <x-ui.filters>; this switches between whole saved combinations.

## Anti-patterns

- Hiding primary wayfinding in novelty interactions

## Rules

- Use the `<brok:saved-views>` tag (or `<x-ui.saved-views>`) in Blade; do not rewrite the component.
- Prefer the documented props and variants over utility-class overrides; when a utility must win, use the `!` important modifier.
- Use semantic design tokens (`bg-primary`, `text-muted-foreground`), never raw colour utilities.
- Keep the `data-slot` attributes; they are the styling and test hooks.

## Links

- Docs: https://brokui.dev/docs/components/saved-views
- Registry JSON (files, props, contract): https://brokui.dev/r/open/saved-views.json

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

Examples

client.blade.php Blade
{{-- Each view decides for itself: a system view can still navigate while a
     personal view (in a list that owns its own state) dispatches instead —
     mixed in the same menu. Rename/delete/save stay server-only. --}}
<div
    x-data="{ last: null }"
    x-on:orders:select-view="last = JSON.stringify($event.detail)"
    class="flex flex-col gap-3"
>
    <x-ui.saved-views
        :views="[
            ['key' => 'all', 'label' => __('All orders'), 'count' => 128, 'system' => true, 'active' => true, 'href' => '?view=all'],
            ['key' => 'unpaid', 'label' => __('Awaiting payment'), 'count' => 12, 'tone' => 'attention', 'system' => true, 'href' => '?view=unpaid'],
            [
                'key' => 'mine',
                'label' => __('My queue'),
                'owned' => true,
                'event' => 'orders:select-view',
                'payload' => ['key' => 'mine'],
            ],
        ]"
    />

    <p class="text-xs text-muted-foreground">
        {{ __('Host received:') }}
        <span data-slot="preview-event-log" class="font-mono text-foreground" x-text="last ?? '—'"></span>
    </p>
</div>
long-content.blade.php Blade
<div class="max-w-xs">
    <x-ui.saved-views
        :views="[
            [
                'key' => 'my-view',
                'label' => __('A deliberately long saved-view name that verifies truncation instead of layout overflow'),
                'href' => '?view=my-view',
                'active' => true,
                'owned' => true,
                'renameUrl' => '#',
                'deleteUrl' => '#',
            ],
            ['key' => 'all', 'label' => __('All records'), 'href' => '?view=all', 'system' => true],
        ]"
        save-url="#"
    />
</div>

API

manifest knowledge + registry-derived coverage

Props

Props accepted by this component: name, type, default value and description.
Prop Type Default Description
views array [] Views to render: each entry is ['key','label','href','event','payload','count','tone','active','system','owned','shared','owner','renameUrl','deleteUrl']. href must carry the view's full state (filters, sort, tab); pass event (+ optional payload) instead to dispatch rather than navigate, for a list that owns its own state. An entry with neither is inert. Renaming, deleting and saving stay server-only (renameUrl/deleteUrl/saveUrl).
mode menu | tabs menu menu opens a dropdown split into Standard/Saved sections with rename, delete, and save-current; tabs renders a flat, always-visible strip with no editing.
heading string | null null Accessible name for the switcher. Defaults to a localized "Views".
saveUrl string | null null POST target that saves the list's current state as a new view. The save-current form is hidden without it.
saveLabel string | null null Label for the save-current submit button. Defaults to a localized "Save view".
anchored bool true Forwards to the menu-mode dropdown's anchored/teleported placement so a clipping toolbar ancestor never cuts the panel off.

Slots

Default Blade slot only.

Data slots

Stable hooks for CSS overrides and browser tests.

saved-views saved-views-item

Behavior

  • Each view decides for itself: href renders a plain link, so applying it survives a middle-click, a shared address, and a page with no JavaScript; event dispatches instead (payload defaults to the whole entry) for a list that owns its own state; neither renders the view inert (listed, not switchable).
  • Rename and delete post real forms to each view's own renameUrl/deleteUrl; there is no client-side persistence for either, or for save.
  • Rename and delete are only offered on a view whose owned field is not false; system views are fixed.
  • In tabs mode there is no rename, delete, or save-current — that mode is for a handful of fixed views only.
  • A dispatched event fires from the component's own root (found by id), not from the click site, so it still reaches a host listener when menu mode teleports its content to <body>.
  • Installs a JavaScript behavior module when the registry item includes resources/js/ui files.
  • Declares registry capability flags: a11y, interactive, authoredStateFixtures, responsive, rtl, darkMode, localized.

Guidance

Navigation and orientation

Orient users and move between destinations.

Use when

  • Use to orient users and help them move across pages, sections, or commands.
  • A list's filters, sort, and tab together form named, revisitable states worth switching between.
  • The list has enough saved/system views that a fixed tab strip would wrap or crowd the toolbar (use mode="menu"); a handful of system views fit as a strip instead (mode="tabs").

Avoid when

  • Do not hide primary wayfinding in novelty interactions or deep nested structures if straightforward navigation would be clearer.
  • There is only one way to look at the list — a saved-views switcher with a single entry is a control with nothing to switch to.
  • Picking or editing filter VALUES — use <x-ui.filters>; this switches between whole saved combinations.

Use instead

  • Visible links and local navigation

Anti-patterns

  • Hiding primary wayfinding in novelty interactions
Anatomy
saved-views saved-views-item
Theming hooks
saved-views saved-views-item

Accessibility

WCAG 2.2 AA Keyboard focus-visible RTL-ready Localized labels Dark mode
Keyboard
Escape
Focus
managed
  • Meet the WCAG 2.2 AA target declared in meta.a11y.
  • The active tab exposes aria-current="page"; the menu trigger names the current view rather than a bare chevron.
  • Semantic HTML and a stable data-slot attribute for styling and scripting hooks.
  • Focus-visible rings use the ring token, so keyboard focus is always visible.
  • Disabled and invalid states are conveyed to assistive tech, not by color alone.
  • Targets WCAG 2.2 AA; verify contrast in light, dark, admin and customer surfaces in the preview.
  • Labels go through __() and layout uses logical properties (ms-*, text-start), so it mirrors under dir="rtl" — flip the preview to RTL to confirm.
  • Dark mode uses the same semantic tokens under the dark class; high contrast follows forced-color system tokens.

Livewire

Needs wire:key

Add a stable wire:key when Livewire can reorder this interactive component.

livewire-component.blade.php Blade
<div wire:key="saved-views-{{ $record->id }}">
    <x-ui.saved-views
        :views="[
            ['key' => 'all', 'label' => __('All orders'), 'href' => '?view=all', 'active' => true, 'system' => true],
            ['key' => 'unpaid', 'label' => __('Awaiting payment'), 'href' => '?view=unpaid', 'count' => 12, 'tone' => 'attention', 'system' => true],
            ['key' => 'shipped', 'label' => __('Shipped'), 'href' => '?view=shipped', 'count' => 48, 'system' => true],
            [
                'key' => 'my-wholesale',
                'label' => __('Wholesale, this week'),
                'href' => '?view=my-wholesale',
                'owned' => true,
                'renameUrl' => '#',
                'deleteUrl' => '#',
            ],
            [
                'key' => 'team-backorders',
                'label' => __('Team backorders'),
                'href' => '?view=team-backorders',
                'owned' => false,
                'shared' => true,
                'owner' => 'Priya',
            ],
        ]"
        save-url="#"
    />
</div>

Source

The exact, editable file ui:add writes into your app. Previews render this same code; there are no preview-only components.

resources/views/components/ui/saved-views.blade.php Blade
{{--
    Saved Views — a named view switcher over a list's own state.

    A view is a bookmark for everything the URL would restore: filters, sort,
    tab. Storing only a label would make it a bookmark that lies about where
    it goes, so `views[].href` is expected to carry the full query state — or,
    for a list that owns its own state, `views[].event` (+ optional
    `payload`) dispatches instead of navigating. Each view decides for
    itself, so a menu may legitimately mix system views that navigate with a
    personal view that doesn't. A view with neither is inert: listed, but not
    a thing you can switch to.

    Two modes for two shapes of the same idea:
      `menu`  — a trigger naming the current view, opening a menu split into
                system-shipped "Standard" views (fixed, uncounted-editable)
                and personal/shared "Saved" views (rename, delete, save
                current). Use this once there are enough views that a fixed
                strip would wrap or force the operator to hunt.
      `tabs`  — a flat, always-visible strip of links (no rename/delete/save):
                the right shape for a handful of system views, one always
                active.

    Names are never truncated: the point of naming a view is defeated by
    cutting the name to fit the control, so the menu is sized for names
    rather than names cut to fit the menu.

    A view's `owned`/`shared`/`owner` fields distinguish a personal view (which
    only its owner can rename/delete) from one shared by a colleague (whose
    byline says so, because changing it affects them too) from a system view
    (fixed, carries a live `count`). Renaming, deleting and saving stay
    server-only (`renameUrl`/`deleteUrl`/`saveUrl`) — only *switching* views
    gained a client-dispatch alternative; a list that needs the edit
    affordances too still wires them to real endpoints.

    A dispatched event fires from this component's own root, not from
    `$el` at the click site: menu mode's content panel teleports to <body>
    (`anchored`, see dropdown's own docs), so a plain `$dispatch` from inside
    it would bubble through <body> and never reach a host listening around
    where this component was placed. Firing from the root — found by `id`,
    real or generated — reaches that host in both modes identically.

    Generalised from `orders/saved-views.blade.php` and `files/views.blade.php`
    (~19 admin modules use one of the two shapes). Renaming/deleting/saving
    post real forms to per-view `renameUrl`/`deleteUrl` and the top-level
    `saveUrl` — no fetch layer is assumed, so this works identically under
    Livewire or a plain controller. The client's own `v2-orders-saved-views.js`
    anchor math is replaced by dropdown's built-in `anchored` teleport mode.
--}}
@props([
    // [['key','label','href','event'=>null,'payload'=>null,'count'=>null,
    //   'tone'=>null,'active'=>false,'system'=>false,'owned'=>true,
    //   'shared'=>false,'owner'=>null,'renameUrl'=>null,'deleteUrl'=>null]]
    // href XOR event pick how switching to that view behaves; neither makes
    // it inert. event's payload defaults to the whole view entry.
    'views' => [],
    // menu (default, for many views) | tabs (a flat strip, for few).
    'mode' => 'menu',
    'heading' => null,
    // POST target that saves the list's current state as a new view.
    'saveUrl' => null,
    'saveLabel' => null,
    'anchored' => true,
])

@php
    $views = array_values(array_filter((array) $views, 'is_array'));
    $mode = in_array($mode, ['menu', 'tabs'], true) ? $mode : 'menu';
    $heading ??= __('Views');
    $saveLabel ??= __('Save view');
    $current = collect($views)->firstWhere('active', true);
    $standard = array_values(array_filter($views, static fn (array $v): bool => (bool) ($v['system'] ?? false)));
    $saved = array_values(array_filter($views, static fn (array $v): bool => ! ($v['system'] ?? false)));

    // A stable id to dispatch from (see the header note on teleported
    // content) — the caller's own id when given, so two instances never
    // collide, otherwise one generated once per render.
    $instanceId = (string) ($attributes->get('id') ?: 'saved-views-'.\Illuminate\Support\Str::random(8));

    // The payload is encoded with Js::from, never string-interpolated.
    $dispatchExpr = static function (string $event, $payload) use ($instanceId): string {
        return 'document.getElementById('.\Illuminate\Support\Js::from($instanceId).')'
            .'?.dispatchEvent(new CustomEvent('.\Illuminate\Support\Js::from($event).', '
            .'{ detail: '.\Illuminate\Support\Js::from($payload).', bubbles: true }))';
    };

    // Precomputed per view, because Blade's component-tag parser reads a tag
    // with a regex: a directive written between its attributes is not seen,
    // so each mode has to arrive as a bound attribute that is null when
    // unused — a null-valued bound attribute is dropped from the tag rather
    // than rendered empty.
    $viewTag = static fn (array $view): string => match (true) {
        ! empty($view['href']) => 'a',
        ! empty($view['event']) => 'button',
        default => 'span',
    };
    $viewHref = static fn (array $view): ?string => $view['href'] ?? null;
    $viewHandler = static fn (array $view): ?string => (empty($view['href']) && ! empty($view['event']))
        ? $dispatchExpr((string) $view['event'], $view['payload'] ?? $view)
        : null;

    // menu mode already gets an Alpine root from <x-ui.dropdown>; tabs mode
    // does not, so it only grows one (and the id it dispatches from) when a
    // view actually needs to dispatch.
    $needsDispatch = collect($views)->contains(
        static fn (array $view): bool => empty($view['href']) && ! empty($view['event'])
    );

    // Icon-button recipe shared by the row actions (rename, delete, save, cancel).
    $actionClass = 'inline-flex size-7 shrink-0 items-center justify-center rounded-sm text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-[length:var(--ring-width)] focus-visible:ring-ring motion-reduce:transition-none';

    $countTone = static function (array $view): string {
        $count = $view['count'] ?? null;

        return ($view['tone'] ?? null) === 'attention' && (int) $count > 0 ? 'soft-warning' : 'soft-neutral';
    };
@endphp

@if ($mode === 'tabs')
    <nav
        data-slot="saved-views"
        aria-label="{{ $heading }}"
        @if ($needsDispatch) x-data="{}" @endif
        {{ $attributes->merge(array_filter(['class' => 'flex flex-wrap items-center gap-2', 'id' => $needsDispatch ? $instanceId : null])) }}
    >
        @foreach ($views as $view)
            @php $active = (bool) ($view['active'] ?? false); @endphp
            <{{ $viewTag($view) }}
                @if ($viewHref($view))
                    href="{{ $viewHref($view) }}"
                @elseif ($viewHandler($view))
                    type="button"
                    x-on:click="{{ $viewHandler($view) }}"
                @else
                    aria-disabled="true"
                    tabindex="-1"
                @endif
                @if ($active) aria-current="page" @endif
                @class([
                    'inline-flex h-9 items-center gap-2 rounded-md px-2 text-sm transition-colors focus-visible:outline-none focus-visible:ring-[length:var(--ring-width)] focus-visible:ring-ring',
                    'bg-secondary font-medium text-foreground' => $active,
                    'text-muted-foreground hover:bg-secondary/60 hover:text-foreground' => ! $active,
                ])
            >
                <span class="min-w-0 truncate">{{ $view['label'] ?? '' }}</span>
                @if (($view['count'] ?? null) !== null)
                    <x-ui.badge :variant="$countTone($view)" size="sm" class="tabular-nums">
                        {{ $view['count'] }}
                    </x-ui.badge>
                @endif
            </{{ $viewTag($view) }}>
        @endforeach
    </nav>
@else
    <x-ui.dropdown data-slot="saved-views" :id="$needsDispatch ? $instanceId : null" :anchored="$anchored" {{ $attributes->except('data-slot') }}>
        <x-ui.dropdown.trigger class="inline-flex h-9 max-w-[19rem] items-center gap-2 rounded-md border border-border px-2 text-sm transition-colors hover:bg-secondary focus-visible:outline-none focus-visible:ring-[length:var(--ring-width)] focus-visible:ring-ring">
            <span class="min-w-0 truncate font-medium">{{ $current['label'] ?? __('All') }}</span>
            @if (($current['count'] ?? null) !== null)
                <x-ui.badge :variant="$countTone($current)" size="sm" class="shrink-0 tabular-nums">
                    {{ $current['count'] }}
                </x-ui.badge>
            @endif
            <svg aria-hidden="true" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.75" class="size-3 shrink-0 text-muted-foreground">
                <path d="m4 6 4 4 4-4" stroke-linecap="round" stroke-linejoin="round" />
            </svg>
        </x-ui.dropdown.trigger>

        <x-ui.dropdown.content align="start" class="w-96">
            @if ($standard !== [])
                <x-ui.dropdown.label>{{ __('Standard') }}</x-ui.dropdown.label>
                <x-ui.dropdown.group>
                    @foreach ($standard as $view)
                        <x-ui.dropdown.item :href="$viewHref($view)" :x-on:click="$viewHandler($view)">
                            <span class="min-w-0 flex-1 truncate">{{ $view['label'] ?? '' }}</span>
                            @if (($view['count'] ?? null) !== null)
                                <x-ui.badge :variant="$countTone($view)" size="sm" class="tabular-nums">
                                    {{ $view['count'] }}
                                </x-ui.badge>
                            @endif
                        </x-ui.dropdown.item>
                    @endforeach
                </x-ui.dropdown.group>
            @endif

            @if ($saved !== [])
                @if ($standard !== [])
                    <x-ui.dropdown.separator />
                @endif
                <x-ui.dropdown.label>{{ __('Saved') }}</x-ui.dropdown.label>
                <x-ui.dropdown.group>
                    @foreach ($saved as $view)
                        @php $owned = (bool) ($view['owned'] ?? true); @endphp
                        <div
                            x-data="{ editing: false, draft: {{ Illuminate\Support\Js::from($view['label'] ?? '') }} }"
                            data-slot="saved-views-item"
                            class="group/view flex min-h-9 items-center gap-2 rounded-sm px-2 py-1 hover:bg-accent"
                        >
                            <template x-if="!editing">
                                <{{ $viewTag($view) }}
                                    @if ($viewHref($view))
                                        href="{{ $viewHref($view) }}"
                                    @elseif ($viewHandler($view))
                                        type="button"
                                        x-on:click="{{ $viewHandler($view) }}"
                                    @else
                                        aria-disabled="true"
                                        tabindex="-1"
                                    @endif
                                    role="menuitem"
                                    class="min-w-0 flex-1 text-start text-sm text-popover-foreground outline-none"
                                >
                                    <span class="block truncate">{{ $view['label'] ?? '' }}</span>
                                    @if (! empty($view['shared']) && ! empty($view['owner']))
                                        <span class="mt-0.5 block truncate text-xs text-muted-foreground">
                                            {{ __('Shared by :owner', ['owner' => $view['owner']]) }}
                                        </span>
                                    @endif
                                </{{ $viewTag($view) }}>
                            </template>

                            @if ($owned && ! empty($view['renameUrl']))
                                <template x-if="editing">
                                    <form method="POST" action="{{ $view['renameUrl'] }}" class="flex min-w-0 flex-1 items-center gap-1">
                                        @csrf
                                        <input
                                            type="text"
                                            name="label"
                                            x-model="draft"
                                            x-on:keydown.escape="editing = false"
                                            aria-label="{{ __('Rename :label', ['label' => $view['label'] ?? '']) }}"
                                            class="me-1 h-8 w-full min-w-0 rounded-md border border-input bg-background px-2 text-sm focus-visible:outline-none focus-visible:ring-[length:var(--ring-width)] focus-visible:ring-ring"
                                        />
                                        <button type="submit" aria-label="{{ __('Save name') }}" title="{{ __('Save') }}" class="{{ $actionClass }} text-foreground">
                                            <svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m5 12 5 5L20 7" /></svg>
                                        </button>
                                        <button type="button" x-on:click="editing = false" aria-label="{{ __('Cancel') }}" title="{{ __('Cancel') }}" class="{{ $actionClass }}">
                                            <svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 6 6 18M6 6l12 12" /></svg>
                                        </button>
                                    </form>
                                </template>
                            @endif

                            @if ($owned)
                                {{-- Row actions: icon buttons that surface on hover/focus (always on touch). --}}
                                <div class="flex shrink-0 items-center gap-1 opacity-0 transition-opacity group-hover/view:opacity-100 group-focus-within/view:opacity-100 [@media(hover:none)]:opacity-100 motion-reduce:transition-none" x-show="!editing">
                                    @if (! empty($view['renameUrl']))
                                        <button type="button" x-on:click.stop="editing = true; $nextTick(() => $root.querySelector('input[name=label]')?.select())" aria-label="{{ __('Rename :label', ['label' => $view['label'] ?? '']) }}" title="{{ __('Rename') }}" class="{{ $actionClass }}">
                                            <svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 20h9" /><path d="M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4Z" /></svg>
                                        </button>
                                    @endif
                                    @if (! empty($view['deleteUrl']))
                                        <form method="POST" action="{{ $view['deleteUrl'] }}" class="contents">
                                            @csrf
                                            @method('DELETE')
                                            <button type="submit" aria-label="{{ __('Delete :label', ['label' => $view['label'] ?? '']) }}" title="{{ __('Delete') }}" class="{{ $actionClass }} hover:text-destructive-text">
                                                <svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 6h18" /><path d="M8 6V4h8v2" /><path d="M19 6l-1 14H6L5 6" /><path d="M10 11v6M14 11v6" /></svg>
                                            </button>
                                        </form>
                                    @endif
                                </div>
                            @endif
                        </div>
                    @endforeach
                </x-ui.dropdown.group>
            @endif

            @if ($saveUrl)
                <x-ui.dropdown.separator />
                <form method="POST" action="{{ $saveUrl }}" class="p-2">
                    @csrf
                    <label for="saved-views-new-name" class="block text-xs text-muted-foreground">
                        {{ __('Save current as a view') }}
                    </label>
                    <div class="mt-2 flex items-center gap-2">
                        <input
                            id="saved-views-new-name"
                            type="text"
                            name="label"
                            required
                            placeholder="{{ __('View name') }}"
                            class="h-9 w-full min-w-0 rounded-md border border-input bg-background px-2 text-sm focus-visible:outline-none focus-visible:ring-[length:var(--ring-width)] focus-visible:ring-ring"
                        />
                        <x-ui.button type="submit" size="sm" class="shrink-0">{{ $saveLabel }}</x-ui.button>
                    </div>
                </form>
            @endif
        </x-ui.dropdown.content>
    </x-ui.dropdown>
@endif

Ownership & lifecycle

Owner, release state, review evidence and adoption for this item.
Owner
Platform UI (@JoshJML)
Current version
1.2.1
Status
Stable
License
open
Accessibility reviewed
No review date recorded
Last breaking change
No date recorded
Deprecation
Not deprecated
Contract
v6
Foundation
≥ 1.0.0