fix(ui): keep settings subnav in-page and persist sidebar collapse
Scroll active application settings sections with buttons instead of Livewire navigation, persist sidebar collapse even with auto-collapse, drop the unused modal id, and right-align destination status.
This commit is contained in:
parent
0cdb98d969
commit
0afa59bc02
8 changed files with 43 additions and 16 deletions
|
|
@ -318,13 +318,19 @@ class="absolute right-1 top-1 flex size-6 items-center justify-center rounded-md
|
|||
<div x-show="open" x-collapse.duration.200ms x-cloak
|
||||
class="nav-children flex flex-col gap-0.5 py-1">
|
||||
@foreach ($sections as $section)
|
||||
<a class="menu-subitem"
|
||||
:class="activeSection === '{{ $section['id'] }}' && 'menu-subitem-active'"
|
||||
href="{{ route($menuItem['route'], $applicationRouteParameters) }}#{{ $section['id'] }}"
|
||||
{{ wireNavigate() }}
|
||||
x-on:click="menuOpen = false; if (document.getElementById('{{ $section['id'] }}')) { $event.preventDefault(); activeSection = '{{ $section['id'] }}'; history.replaceState(null, '', '#{{ $section['id'] }}'); window.scrollToSettingsSection?.('{{ $section['id'] }}'); }">
|
||||
<span class="menu-item-label text-left">{{ $section['label'] }}</span>
|
||||
</a>
|
||||
@if ($menuItem['active'])
|
||||
<button type="button" class="menu-subitem"
|
||||
:class="activeSection === '{{ $section['id'] }}' && 'menu-subitem-active'"
|
||||
x-on:click="menuOpen = false; activeSection = '{{ $section['id'] }}'; history.replaceState(null, '', '#{{ $section['id'] }}'); window.scrollToSettingsSection?.('{{ $section['id'] }}')">
|
||||
<span class="menu-item-label text-left">{{ $section['label'] }}</span>
|
||||
</button>
|
||||
@else
|
||||
<a class="menu-subitem"
|
||||
href="{{ route($menuItem['route'], $applicationRouteParameters) }}#{{ $section['id'] }}"
|
||||
{{ wireNavigate() }} x-on:click="menuOpen = false">
|
||||
<span class="menu-item-label text-left">{{ $section['label'] }}</span>
|
||||
</a>
|
||||
@endif
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ class="fixed inset-0 z-99 overflow-y-auto">
|
|||
x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0"
|
||||
class="absolute inset-0 w-full h-full bg-black/50 backdrop-blur-[2px]"></div>
|
||||
<div @if ($closeOutside) @click.self="modalOpen=false" @endif class="relative flex min-h-full items-start justify-center p-2 sm:items-center sm:p-4">
|
||||
<div id="{{ $modalId }}" x-show="modalOpen" x-trap.inert.noscroll="modalOpen"
|
||||
<div x-show="modalOpen" x-trap.inert.noscroll="modalOpen"
|
||||
x-transition:enter="transition ease-out duration-200"
|
||||
x-transition:enter-start="opacity-0 scale-95"
|
||||
x-transition:enter-end="opacity-100 scale-100"
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
},
|
||||
targetCollapsed() {
|
||||
this.hasSecondBar = !!document.querySelector('.application-settings-navigation');
|
||||
return this.autoCollapse ? this.hasSecondBar : this.userCollapsed;
|
||||
return this.userCollapsed || (this.autoCollapse && this.hasSecondBar);
|
||||
},
|
||||
applyCollapsed(animate) {
|
||||
const target = this.targetCollapsed();
|
||||
|
|
@ -41,10 +41,8 @@
|
|||
},
|
||||
toggleSidebar() {
|
||||
this.collapsed = !this.collapsed;
|
||||
if (!this.autoCollapse) {
|
||||
this.userCollapsed = this.collapsed;
|
||||
localStorage.setItem('sidebarCollapsed', this.collapsed);
|
||||
}
|
||||
this.userCollapsed = this.collapsed;
|
||||
localStorage.setItem('sidebarCollapsed', this.userCollapsed);
|
||||
},
|
||||
toggleAutoCollapse() {
|
||||
this.autoCollapse = !this.autoCollapse;
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ class="rounded bg-neutral-100 px-1.5 py-0.5 font-mono text-xs text-neutral-700 d
|
|||
<a href="{{ route('server.show', ['server_uuid' => data_get($resource, 'destination.server.uuid')]) }}"
|
||||
{{ wireNavigate() }} class="button">Open server</a>
|
||||
<x-application.restart-limit-warning :application="$resource" />
|
||||
<x-status-summary :status="$resource->status" />
|
||||
<x-status-summary :status="$resource->status" align="right" />
|
||||
@if ($hasAdditionalDestinations)
|
||||
<x-forms.button canGate="deploy" :canResource="$resource"
|
||||
wire:click="redeploy('{{ data_get($resource, 'destination.id') }}','{{ data_get($resource, 'destination.server.id') }}')">
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
$applicationSection = str($view)->before('@else')->value();
|
||||
|
||||
expect($applicationSection)
|
||||
->toContain('<x-status-summary :status="$resource->status" />')
|
||||
->toContain('<x-status-summary :status="$resource->status" align="right" />')
|
||||
->not->toContain('<x-status :resource="$resource"');
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,13 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
|
||||
test('input modal renders without requiring a modal id', function () {
|
||||
$html = Blade::render('<x-modal-input>Modal content</x-modal-input>');
|
||||
|
||||
expect($html)->toContain('Modal content');
|
||||
});
|
||||
|
||||
test('confirmation modal closes before dispatching an event that can open another modal', function () {
|
||||
$modal = file_get_contents(resource_path('views/components/modal-confirmation.blade.php'));
|
||||
|
||||
|
|
|
|||
|
|
@ -45,6 +45,18 @@
|
|||
->toContain('stableFrames');
|
||||
});
|
||||
|
||||
test('application configuration subitems scroll without navigating the active page', function () {
|
||||
$sidebar = file_get_contents(resource_path('views/components/application/configuration-sidebar.blade.php'));
|
||||
|
||||
expect($sidebar)
|
||||
->toContain("@if (\$menuItem['active'])")
|
||||
->toContain('<button type="button" class="menu-subitem"')
|
||||
->toContain("window.scrollToSettingsSection?.('{{ \$section['id'] }}')")
|
||||
->toContain('@else')
|
||||
->toContain("href=\"{{ route(\$menuItem['route'], \$applicationRouteParameters) }}#{{ \$section['id'] }}\"")
|
||||
->not->toContain("if (document.getElementById('{{ \$section['id'] }}'))");
|
||||
});
|
||||
|
||||
test('postgresql general navigation lists each in-page settings section', function () {
|
||||
$sidebar = file_get_contents(resource_path('views/components/database/configuration-sidebar.blade.php'));
|
||||
$general = file_get_contents(resource_path('views/livewire/project/database/postgresql/general.blade.php'));
|
||||
|
|
|
|||
|
|
@ -27,7 +27,10 @@
|
|||
$layout = file_get_contents(resource_path('views/layouts/app.blade.php'));
|
||||
|
||||
expect($layout)
|
||||
->toContain("collapsed: localStorage.getItem('sidebarCollapsed') === 'true'")
|
||||
->toContain("userCollapsed: localStorage.getItem('sidebarCollapsed') === 'true'")
|
||||
->toContain('return this.userCollapsed || (this.autoCollapse && this.hasSecondBar);')
|
||||
->toContain('this.userCollapsed = this.collapsed;')
|
||||
->toContain("localStorage.setItem('sidebarCollapsed', this.userCollapsed);")
|
||||
->toContain('sidebarReady: false')
|
||||
->toContain(":class=\"[collapsed ? 'lg:w-16' : 'lg:w-56', sidebarReady ? 'transition-[width] duration-200' : '']\"")
|
||||
->toContain(":class=\"[collapsed ? 'lg:ml-16' : 'lg:ml-56', sidebarReady ? 'transition-[margin] duration-200' : '']\"");
|
||||
|
|
|
|||
Loading…
Reference in a new issue