fix(ui): unify dropdown menu styles and drop sort buttons that duplicate table headers

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Andras Bacsai
2026-10-30 15:16:41 +01:00
co-authored by Claude Opus 5.5
parent 34e872482d
commit c1fc9466b2
11 changed files with 153 additions and 211 deletions
@@ -18,7 +18,6 @@
's3' => $backup->save_s3 ? ($backup->s3Storages->isNotEmpty() ? $backup->s3Storages : collect([$backup->s3])->filter())->count() : -1,
'lastRunAt' => $backup->latestExecution?->finished_at?->timestamp ?? ($backup->latestExecution?->status === 'running' ? now()->timestamp : 0),
'executions' => $backup->executions_count,
'createdAt' => $backup->created_at?->timestamp ?? 0,
])->values()),
filterOptions: @js(collect([['value' => 'all', 'label' => 'All targets']])->merge(
$backups->map(fn ($backup) => [
@@ -26,12 +25,6 @@
'label' => $backup->targetType(),
])->unique('value')->values()
)->values()),
sortOptions: [
{ value: 'target_asc', label: 'Target A–Z' },
{ value: 'target_desc', label: 'Target Z–A' },
{ value: 'newest', label: 'Newest first' },
{ value: 'oldest', label: 'Oldest first' },
],
get filteredBackups() {
const query = this.search.toLowerCase();
const filtered = this.backups.filter((backup) => {
@@ -52,8 +45,6 @@
const sortValue = sortValues[column] || sortValues.target;
const order = direction === 'desc' ? -1 : 1;
return filtered.sort((left, right) => {
if (this.sortBy === 'newest') return right.createdAt - left.createdAt;
if (this.sortBy === 'oldest') return left.createdAt - right.createdAt;
const leftValue = sortValue(left);
const rightValue = sortValue(right);
return order * (typeof leftValue === 'number' ? leftValue - rightValue : leftValue.localeCompare(rightValue)) ||
@@ -144,7 +135,7 @@
</button></x-slot:trigger>
<template x-for="option in filterOptions" :key="option.value">
<button type="button"
class="flex h-8 w-full items-center rounded-md px-2 text-left text-[12px] text-neutral-600 transition-colors hover:bg-neutral-100 hover:text-black dark:text-fg-dim dark:hover:bg-white/[0.06] dark:hover:text-fg"
class="listbox-option"
x-on:click="typeFilter = option.value; close()">
<span class="flex-1" x-text="option.label"></span>
<svg x-show="typeFilter === option.value" class="size-3.5 text-black dark:text-fg"
@@ -155,28 +146,6 @@
</button>
</template>
</x-table.dropdown>
<x-table.dropdown panel-class="w-48!">
<x-slot:trigger><button type="button" class="button" aria-haspopup="listbox" :aria-expanded="open">
<svg class="size-3.5 opacity-65" viewBox="0 0 24 24" fill="none" aria-hidden="true">
<path d="M8 5v14m0 0-3-3m3 3 3-3M16 19V5m0 0-3 3m3-3 3 3" stroke="currentColor"
stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" />
</svg>
Sort
</button></x-slot:trigger>
<template x-for="option in sortOptions" :key="option.value">
<button type="button"
class="flex h-8 w-full items-center rounded-md px-2 text-left text-[12px] text-neutral-600 transition-colors hover:bg-neutral-100 hover:text-black dark:text-fg-dim dark:hover:bg-white/[0.06] dark:hover:text-fg"
x-on:click="sortBy = option.value; close()">
<span class="flex-1" x-text="option.label"></span>
<svg x-show="sortBy === option.value" class="size-3.5 text-black dark:text-fg"
viewBox="0 0 12 12" fill="none" aria-hidden="true">
<path d="m2.5 6.25 2.1 2.1 4.9-5" stroke="currentColor" stroke-width="1.4"
stroke-linecap="round" stroke-linejoin="round" />
</svg>
</button>
</template>
</x-table.dropdown>
</div>
</div>
@@ -158,26 +158,29 @@
@endforeach
@endif
</x-table.filter>
<x-table.sort>
@foreach ([
'newest' => 'Newest first',
'oldest' => 'Oldest first',
] as $sortValue => $sortLabel)
<button type="button" class="listbox-option" role="option"
aria-selected="{{ $deploymentSort === $sortValue ? 'true' : 'false' }}"
wire:click="setDeploymentSort('{{ $sortValue }}')"
@click="open = false">
<span>{{ $sortLabel }}</span>
@if ($deploymentSort === $sortValue)
<svg class="size-3.5 shrink-0" viewBox="0 0 24 24" fill="none">
<path d="m4.5 12.75 6 6 9-13.5" stroke="currentColor"
stroke-width="2.5" stroke-linecap="round"
stroke-linejoin="round" />
</svg>
@endif
</button>
@endforeach
</x-table.sort>
{{-- The Started header sorts the full table; the embedded history has no such header. --}}
@if ($embedded)
<x-table.sort>
@foreach ([
'newest' => 'Newest first',
'oldest' => 'Oldest first',
] as $sortValue => $sortLabel)
<button type="button" class="listbox-option" role="option"
aria-selected="{{ $deploymentSort === $sortValue ? 'true' : 'false' }}"
wire:click="setDeploymentSort('{{ $sortValue }}')"
@click="open = false">
<span>{{ $sortLabel }}</span>
@if ($deploymentSort === $sortValue)
<svg class="size-3.5 shrink-0" viewBox="0 0 24 24" fill="none">
<path d="m4.5 12.75 6 6 9-13.5" stroke="currentColor"
stroke-width="2.5" stroke-linecap="round"
stroke-linejoin="round" />
</svg>
@endif
</button>
@endforeach
</x-table.sort>
@endif
</x-table.toolbar>
@if ($deployments->isNotEmpty())
@@ -231,9 +231,12 @@
<x-reicon name="play-circle" class="size-3.5 opacity-70" />
{{ $previewIsStopped ? 'Deploy' : 'Redeploy' }}
</button>
@if (!$previewIsStopped || auth()->user()->can('delete', $application))
<div class="my-1 border-t border-neutral-200 dark:border-white/[0.08]"></div>
@endif
@if (!$previewIsStopped)
<button type="button"
class="listbox-option justify-start! gap-2.5! text-error!"
class="listbox-option justify-start! gap-2.5! text-error! hover:text-error!"
@click="open = false; document.getElementById('preview-stop-trigger-{{ data_get($preview, 'pull_request_id') }}')?.click()"
role="menuitem">
<x-reicon name="stop" class="size-3.5" />
@@ -243,7 +246,7 @@
@endcan
@can('delete', $application)
<button type="button"
class="listbox-option justify-start! gap-2.5! text-error!"
class="listbox-option justify-start! gap-2.5! text-error! hover:text-error!"
@click="open = false; document.getElementById('preview-delete-trigger-{{ data_get($preview, 'pull_request_id') }}')?.click()"
role="menuitem">
<x-reicon name="trash" class="size-3.5" />
@@ -49,31 +49,33 @@
</div>
<div class="flex flex-wrap items-center gap-2">
<x-table.dropdown panel-class="w-52!">
<x-slot:trigger>
<button type="button" class="button" aria-haspopup="listbox" :aria-expanded="open">
<svg class="size-3.5 opacity-65" viewBox="0 0 24 24" fill="none"
aria-hidden="true">
<path d="M8 5v14m0 0-3-3m3 3 3-3M16 19V5m0 0-3 3m3-3 3 3"
stroke="currentColor" stroke-width="1.7" stroke-linecap="round"
stroke-linejoin="round" />
</svg>
Sort
</button>
</x-slot:trigger>
<template x-for="option in sortOptions" :key="option.value">
<button type="button"
class="flex h-9 w-full items-center rounded-md px-2 text-left text-[12px] text-neutral-600 transition-colors hover:bg-neutral-100 hover:text-black dark:text-fg-dim dark:hover:bg-white/[0.06] dark:hover:text-fg"
x-on:click="sortBy = option.value; close(); page = 1">
<span class="flex-1" x-text="option.label"></span>
<svg x-show="sortBy === option.value" class="size-3.5 text-black dark:text-fg"
viewBox="0 0 12 12" fill="none" aria-hidden="true">
<path d="m2.5 6.25 2.1 2.1 4.9-5" stroke="currentColor"
stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" />
<div :class="viewMode === 'grid' ? '' : 'min-[641px]:hidden'">
<x-table.dropdown panel-class="w-52!">
<x-slot:trigger>
<button type="button" class="button" aria-haspopup="listbox" :aria-expanded="open">
<svg class="size-3.5 opacity-65" viewBox="0 0 24 24" fill="none"
aria-hidden="true">
<path d="M8 5v14m0 0-3-3m3 3 3-3M16 19V5m0 0-3 3m3-3 3 3"
stroke="currentColor" stroke-width="1.7" stroke-linecap="round"
stroke-linejoin="round" />
</svg>
Sort
</button>
</template>
</x-table.dropdown>
</x-slot:trigger>
<template x-for="option in sortOptions" :key="option.value">
<button type="button"
class="listbox-option"
x-on:click="sortBy = option.value; close(); page = 1">
<span class="flex-1" x-text="option.label"></span>
<svg x-show="sortBy === option.value" class="size-3.5 text-black dark:text-fg"
viewBox="0 0 12 12" fill="none" aria-hidden="true">
<path d="m2.5 6.25 2.1 2.1 4.9-5" stroke="currentColor"
stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</button>
</template>
</x-table.dropdown>
</div>
<x-canvas-view-toggle :list-url="route('project.index')"
:canvas-url="route('project.canvas.index')" />
@@ -132,30 +132,32 @@
</div>
</x-table.dropdown>
<x-table.dropdown panel-class="w-48!">
<x-slot:trigger>
<button type="button" class="button" aria-haspopup="listbox" :aria-expanded="open">
<svg class="size-3.5 opacity-65" viewBox="0 0 24 24" fill="none" aria-hidden="true">
<path d="M8 5v14m0 0-3-3m3 3 3-3M16 19V5m0 0-3 3m3-3 3 3"
stroke="currentColor" stroke-width="1.7" stroke-linecap="round"
stroke-linejoin="round" />
</svg>
Sort
</button>
</x-slot:trigger>
<template x-for="option in sortOptions" :key="option.value">
<button type="button"
class="flex h-9 w-full items-center rounded-md px-2 text-left text-[12px] text-neutral-600 transition-colors hover:bg-neutral-100 hover:text-black dark:text-fg-dim dark:hover:bg-white/[0.06] dark:hover:text-fg"
x-on:click="sortBy = option.value; close(); page = 1">
<span class="flex-1" x-text="option.label"></span>
<svg x-show="sortBy === option.value" class="size-3.5 text-black dark:text-fg"
viewBox="0 0 12 12" fill="none" aria-hidden="true">
<path d="m2.5 6.25 2.1 2.1 4.9-5" stroke="currentColor"
stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" />
</svg>
<div :class="viewMode === 'grid' ? '' : 'min-[769px]:hidden'">
<x-table.dropdown panel-class="w-48!">
<x-slot:trigger>
<button type="button" class="button" aria-haspopup="listbox" :aria-expanded="open">
<svg class="size-3.5 opacity-65" viewBox="0 0 24 24" fill="none" aria-hidden="true">
<path d="M8 5v14m0 0-3-3m3 3 3-3M16 19V5m0 0-3 3m3-3 3 3"
stroke="currentColor" stroke-width="1.7" stroke-linecap="round"
stroke-linejoin="round" />
</svg>
Sort
</button>
</template>
</x-table.dropdown>
</x-slot:trigger>
<template x-for="option in sortOptions" :key="option.value">
<button type="button"
class="listbox-option"
x-on:click="sortBy = option.value; close(); page = 1">
<span class="flex-1" x-text="option.label"></span>
<svg x-show="sortBy === option.value" class="size-3.5 text-black dark:text-fg"
viewBox="0 0 12 12" fill="none" aria-hidden="true">
<path d="m2.5 6.25 2.1 2.1 4.9-5" stroke="currentColor"
stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</button>
</template>
</x-table.dropdown>
</div>
<x-canvas-view-toggle
:list-url="route('project.resource.index', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid])"
@@ -17,7 +17,6 @@
},
's3' => $backup->save_s3 ? ($backup->s3Storages->isNotEmpty() ? $backup->s3Storages : collect([$backup->s3])->filter())->where('team_id', $serviceTeamId)->count() : -1,
'lastRunAt' => $backup->latestExecution?->finished_at?->timestamp ?? ($backup->latestExecution?->status === 'running' ? now()->timestamp : 0),
'createdAt' => $backup->created_at?->timestamp ?? 0,
])->concat($databaseBackups->map(fn ($backup) => [
'id' => 'database:'.$backup->id,
'name' => strtolower($backup->database->human_name ?: $backup->database->name),
@@ -31,7 +30,6 @@
},
's3' => $backup->save_s3 ? ($backup->s3Storages->isNotEmpty() ? $backup->s3Storages : collect([$backup->s3])->filter())->where('team_id', $serviceTeamId)->count() : -1,
'lastRunAt' => $backup->latest_log?->finished_at?->timestamp ?? ($backup->latest_log?->status === 'running' ? now()->timestamp : 0),
'createdAt' => $backup->created_at?->timestamp ?? 0,
]))->values()),
filterOptions: @js(collect([['value' => 'all', 'label' => 'All targets']])->merge(
$backups->map(fn ($backup) => [
@@ -39,12 +37,6 @@
'label' => $backup->targetType(),
])->push(['value' => 'database', 'label' => 'Database'])->unique('value')->values()
)->values()),
sortOptions: [
{ value: 'target_asc', label: 'Target A–Z' },
{ value: 'target_desc', label: 'Target Z–A' },
{ value: 'newest', label: 'Newest first' },
{ value: 'oldest', label: 'Oldest first' },
],
get filteredBackups() {
const query = this.search.toLowerCase();
const filtered = this.backups.filter((backup) => {
@@ -64,8 +56,6 @@
const sortValue = sortValues[column] || sortValues.target;
const order = direction === 'desc' ? -1 : 1;
return filtered.sort((left, right) => {
if (this.sortBy === 'newest') return right.createdAt - left.createdAt;
if (this.sortBy === 'oldest') return left.createdAt - right.createdAt;
const leftValue = sortValue(left);
const rightValue = sortValue(right);
return order * (typeof leftValue === 'number' ? leftValue - rightValue : leftValue.localeCompare(rightValue)) ||
@@ -220,7 +210,7 @@
</button></x-slot:trigger>
<template x-for="option in filterOptions" :key="option.value">
<button type="button"
class="flex h-8 w-full items-center rounded-md px-2 text-left text-[12px] text-neutral-600 transition-colors hover:bg-neutral-100 hover:text-black dark:text-fg-dim dark:hover:bg-white/[0.06] dark:hover:text-fg"
class="listbox-option"
x-on:click="typeFilter = option.value; close()">
<span class="flex-1" x-text="option.label"></span>
<svg x-show="typeFilter === option.value" class="size-3.5 text-black dark:text-fg"
@@ -231,28 +221,6 @@
</button>
</template>
</x-table.dropdown>
<x-table.dropdown panel-class="w-48!">
<x-slot:trigger><button type="button" class="button" aria-haspopup="listbox" :aria-expanded="open">
<svg class="size-3.5 opacity-65" viewBox="0 0 24 24" fill="none" aria-hidden="true">
<path d="M8 5v14m0 0-3-3m3 3 3-3M16 19V5m0 0-3 3m3-3 3 3" stroke="currentColor"
stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" />
</svg>
Sort
</button></x-slot:trigger>
<template x-for="option in sortOptions" :key="option.value">
<button type="button"
class="flex h-8 w-full items-center rounded-md px-2 text-left text-[12px] text-neutral-600 transition-colors hover:bg-neutral-100 hover:text-black dark:text-fg-dim dark:hover:bg-white/[0.06] dark:hover:text-fg"
x-on:click="sortBy = option.value; close()">
<span class="flex-1" x-text="option.label"></span>
<svg x-show="sortBy === option.value" class="size-3.5 text-black dark:text-fg"
viewBox="0 0 12 12" fill="none" aria-hidden="true">
<path d="m2.5 6.25 2.1 2.1 4.9-5" stroke="currentColor" stroke-width="1.4"
stroke-linecap="round" stroke-linejoin="round" />
</svg>
</button>
</template>
</x-table.dropdown>
</div>
</div>
@@ -170,14 +170,17 @@
@endforeach
@endif
</x-table.filter>
<x-table.sort>
@foreach (['default' => 'Default order', 'name_asc' => 'Name A–Z', 'name_desc' => 'Name Z–A'] as $value => $label)
<button type="button" class="listbox-option" wire:click="setTableSort('{{ $value }}')" @click="open = false">
<span>{{ $label }}</span>
@if ($tableSort === $value)<span>✓</span>@endif
</button>
@endforeach
</x-table.sort>
{{-- The table header, with its Name sort, is hidden on small screens (app.css, max-width: 768px). --}}
<div class="min-[769px]:hidden">
<x-table.sort>
@foreach (['default' => 'Default order', 'name_asc' => 'Name A–Z', 'name_desc' => 'Name Z–A'] as $value => $label)
<button type="button" class="listbox-option" wire:click="setTableSort('{{ $value }}')" @click="open = false">
<span>{{ $label }}</span>
@if ($tableSort === $value)<span>✓</span>@endif
</button>
@endforeach
</x-table.sort>
</div>
@can('manageEnvironment', $resource)
{{-- Do not disable Add based on readyToLoad: modal-input uses wire:ignore, so a
disabled attribute painted on first load would never re-enable. --}}
+26 -24
View File
@@ -61,31 +61,33 @@
</div>
<div class="flex flex-wrap items-center gap-2">
<x-table.dropdown panel-class="w-48!">
<x-slot:trigger>
<button type="button" class="button" aria-haspopup="listbox" :aria-expanded="open">
<svg class="size-3.5 opacity-65" viewBox="0 0 24 24" fill="none"
aria-hidden="true">
<path d="M8 5v14m0 0-3-3m3 3 3-3M16 19V5m0 0-3 3m3-3 3 3"
stroke="currentColor" stroke-width="1.7" stroke-linecap="round"
stroke-linejoin="round" />
</svg>
Sort
</button>
</x-slot:trigger>
<template x-for="option in sortOptions" :key="option.value">
<button type="button"
class="flex h-9 w-full items-center rounded-md px-2 text-left text-[12px] text-neutral-600 transition-colors hover:bg-neutral-100 hover:text-black dark:text-fg-dim dark:hover:bg-white/[0.06] dark:hover:text-fg"
x-on:click="sortBy = option.value; close(); page = 1">
<span class="flex-1" x-text="option.label"></span>
<svg x-show="sortBy === option.value" class="size-3.5 text-black dark:text-fg"
viewBox="0 0 12 12" fill="none" aria-hidden="true">
<path d="m2.5 6.25 2.1 2.1 4.9-5" stroke="currentColor"
stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" />
</svg>
<div :class="viewMode === 'grid' ? '' : 'min-[641px]:hidden'">
<x-table.dropdown panel-class="w-48!">
<x-slot:trigger>
<button type="button" class="button" aria-haspopup="listbox" :aria-expanded="open">
<svg class="size-3.5 opacity-65" viewBox="0 0 24 24" fill="none"
aria-hidden="true">
<path d="M8 5v14m0 0-3-3m3 3 3-3M16 19V5m0 0-3 3m3-3 3 3"
stroke="currentColor" stroke-width="1.7" stroke-linecap="round"
stroke-linejoin="round" />
</svg>
Sort
</button>
</template>
</x-table.dropdown>
</x-slot:trigger>
<template x-for="option in sortOptions" :key="option.value">
<button type="button"
class="listbox-option"
x-on:click="sortBy = option.value; close(); page = 1">
<span class="flex-1" x-text="option.label"></span>
<svg x-show="sortBy === option.value" class="size-3.5 text-black dark:text-fg"
viewBox="0 0 12 12" fill="none" aria-hidden="true">
<path d="m2.5 6.25 2.1 2.1 4.9-5" stroke="currentColor"
stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</button>
</template>
</x-table.dropdown>
</div>
<x-canvas-view-toggle
:list-url="route('project.show', ['project_uuid' => $project->uuid])"
@@ -196,12 +196,12 @@
</x-slot:trigger>
<button type="button" role="menuitem"
x-on:click="close(); editToken(@js(App\Livewire\Forms\ApiTokenForm::valuesOf($token)))"
class="flex h-8 w-full items-center rounded-md px-2 text-left text-[12px] transition-colors text-neutral-600 hover:bg-neutral-100 hover:text-black dark:text-fg-dim dark:hover:bg-white/[0.06] dark:hover:text-fg">
class="listbox-option justify-start!">
Edit
</button>
<button type="button" role="menuitem" x-on:click="close()"
wire:click="setPaused({{ $token->id }}, {{ $token->isPaused() ? 'false' : 'true' }})"
class="flex h-8 w-full items-center rounded-md px-2 text-left text-[12px] transition-colors text-neutral-600 hover:bg-neutral-100 hover:text-black dark:text-fg-dim dark:hover:bg-white/[0.06] dark:hover:text-fg">
class="listbox-option justify-start!">
{{ $token->isPaused() ? 'Resume' : 'Pause' }}
</button>
<x-modal-confirmation title="Roll API Token?" buttonFullWidth
@@ -215,11 +215,12 @@
:confirmWithPassword="false" step2ButtonText="Roll token">
<x-slot:trigger>
<button type="button" role="menuitem" x-on:click="close()"
class="flex h-8 w-full items-center rounded-md px-2 text-left text-[12px] transition-colors text-neutral-600 hover:bg-neutral-100 hover:text-black dark:text-fg-dim dark:hover:bg-white/[0.06] dark:hover:text-fg">
class="listbox-option justify-start!">
Roll
</button>
</x-slot:trigger>
</x-modal-confirmation>
<div class="my-1 border-t border-neutral-200 dark:border-white/[0.08]"></div>
<x-modal-confirmation title="Confirm API Token Revocation?" buttonFullWidth
submitAction="revoke({{ $token->id }})" :actions="[
'This API token will be permanently revoked.',
@@ -230,7 +231,7 @@
:confirmWithPassword="false" step2ButtonText="Revoke token">
<x-slot:trigger>
<button type="button" role="menuitem" x-on:click="close()"
class="flex h-8 w-full items-center rounded-md px-2 text-left text-[12px] transition-colors text-error hover:bg-error/10">
class="listbox-option justify-start! text-error! hover:text-error!">
Revoke
</button>
</x-slot:trigger>
+26 -24
View File
@@ -57,31 +57,33 @@
</div>
<div class="flex flex-wrap items-center gap-2">
<x-table.dropdown panel-class="w-52!">
<x-slot:trigger>
<button type="button" class="button" aria-haspopup="listbox" :aria-expanded="open">
<svg class="size-3.5 opacity-65" viewBox="0 0 24 24" fill="none"
aria-hidden="true">
<path d="M8 5v14m0 0-3-3m3 3 3-3M16 19V5m0 0-3 3m3-3 3 3"
stroke="currentColor" stroke-width="1.7" stroke-linecap="round"
stroke-linejoin="round" />
</svg>
Sort
</button>
</x-slot:trigger>
<template x-for="option in sortOptions" :key="option.value">
<button type="button"
class="flex h-9 w-full items-center rounded-md px-2 text-left text-[12px] text-neutral-600 transition-colors hover:bg-neutral-100 hover:text-black dark:text-fg-dim dark:hover:bg-white/[0.06] dark:hover:text-fg"
x-on:click="sortBy = option.value; close(); page = 1">
<span class="flex-1" x-text="option.label"></span>
<svg x-show="sortBy === option.value" class="size-3.5 text-black dark:text-fg"
viewBox="0 0 12 12" fill="none" aria-hidden="true">
<path d="m2.5 6.25 2.1 2.1 4.9-5" stroke="currentColor"
stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" />
</svg>
<div :class="viewMode === 'grid' ? '' : 'min-[641px]:hidden'">
<x-table.dropdown panel-class="w-52!">
<x-slot:trigger>
<button type="button" class="button" aria-haspopup="listbox" :aria-expanded="open">
<svg class="size-3.5 opacity-65" viewBox="0 0 24 24" fill="none"
aria-hidden="true">
<path d="M8 5v14m0 0-3-3m3 3 3-3M16 19V5m0 0-3 3m3-3 3 3"
stroke="currentColor" stroke-width="1.7" stroke-linecap="round"
stroke-linejoin="round" />
</svg>
Sort
</button>
</template>
</x-table.dropdown>
</x-slot:trigger>
<template x-for="option in sortOptions" :key="option.value">
<button type="button"
class="listbox-option"
x-on:click="sortBy = option.value; close(); page = 1">
<span class="flex-1" x-text="option.label"></span>
<svg x-show="sortBy === option.value" class="size-3.5 text-black dark:text-fg"
viewBox="0 0 12 12" fill="none" aria-hidden="true">
<path d="m2.5 6.25 2.1 2.1 4.9-5" stroke="currentColor"
stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</button>
</template>
</x-table.dropdown>
</div>
<x-canvas-view-toggle :list-url="route('tags.show')"
:canvas-url="route('tags.canvas')" />
+5 -18
View File
@@ -13,11 +13,11 @@ beforeEach(function () {
});
/**
* Creates volume backups for bravo, alpha, and charlie, in that order from oldest to newest.
* Creates volume backups for bravo, alpha, and charlie.
*/
function createSortableVolumeBackups(Model $resource): void
{
foreach (['bravo', 'alpha', 'charlie'] as $index => $name) {
foreach (['bravo', 'alpha', 'charlie'] as $name) {
$volume = LocalPersistentVolume::create([
'uuid' => (string) new Cuid2,
'name' => $name.'-data',
@@ -26,15 +26,13 @@ function createSortableVolumeBackups(Model $resource): void
'resource_type' => $resource->getMorphClass(),
]);
$backup = ScheduledVolumeBackup::create([
ScheduledVolumeBackup::create([
'backupable_type' => $volume->getMorphClass(),
'backupable_id' => $volume->id,
'team_id' => 0,
'frequency' => '0 0 * * *',
'enabled' => true,
]);
ScheduledVolumeBackup::whereKey($backup->id)->update(['created_at' => now()->subDays(10 - $index)]);
}
}
@@ -55,24 +53,13 @@ function visibleBackupOrderScript(): string
function assertBackupSortOrders(mixed $page, string $screenshot): void
{
$page->assertSee('alpha-data')
->assertScript(visibleBackupOrderScript(), 'alpha-data,bravo-data,charlie-data')
->click('Sort')
->click('Target Z–A')
->assertScript(visibleBackupOrderScript(), 'charlie-data,bravo-data,alpha-data')
->click('Sort')
->click('Oldest first')
->assertScript(visibleBackupOrderScript(), 'bravo-data,alpha-data,charlie-data')
->click('Sort')
->click('Newest first')
->assertScript(visibleBackupOrderScript(), 'charlie-data,alpha-data,bravo-data')
->click('[aria-label^="Sort by target"]')
->assertScript(visibleBackupOrderScript(), 'alpha-data,bravo-data,charlie-data')
->click('[aria-label^="Sort by target"]')
->assertScript(visibleBackupOrderScript(), 'charlie-data,bravo-data,alpha-data')
->screenshot(filename: $screenshot);
}
it('reorders application storage backups when the sort option changes', function () {
it('reorders application storage backups when the target header is clicked', function () {
$application = createBrowserApplication($this->stack, [
'uuid' => 'app-browser-backup-sort',
'name' => 'Backup Sort App',
@@ -86,7 +73,7 @@ it('reorders application storage backups when the sort option changes', function
assertBackupSortOrders($page, 'application-backup-sort');
});
it('reorders service storage backups when the sort option changes', function () {
it('reorders service storage backups when the target header is clicked', function () {
['service' => $service, 'serviceApplication' => $serviceApplication] = createBrowserService($this->stack, [
'uuid' => 'svc-browser-backup-sort',
]);