Skip to content

Commit fd45f7d

Browse files
simonhampclaude
andcommitted
Replace desktop nav with unified mobile menu on all screen sizes
Remove the inline desktop navigation links and use the mobile menu popover everywhere. On lg+ screens, the theme toggle, search field, and Bifrost button remain visible in the navbar. The popover renders as a constrained dropdown anchored to the menu button on lg+ and full-screen on smaller viewports, with scroll-aware positioning to stay below the sticky navbar. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent f0c23b9 commit fd45f7d

13 files changed

Lines changed: 214 additions & 319 deletions

File tree

app/Filament/Resources/SupportTicketResource/Pages/ViewSupportTicket.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use App\Filament\Resources\SupportTicketResource\Widgets\TicketRepliesWidget;
77
use App\SupportTicket\Status;
88
use Filament\Actions;
9+
use Filament\Forms\Components\Select;
910
use Filament\Resources\Pages\ViewRecord;
1011

1112
class ViewSupportTicket extends ViewRecord
@@ -19,7 +20,7 @@ protected function getHeaderActions(): array
1920
->label('Update Status')
2021
->icon('heroicon-o-arrow-path')
2122
->form([
22-
\Filament\Forms\Components\Select::make('status')
23+
Select::make('status')
2324
->label('Status')
2425
->options(collect(Status::cases())->mapWithKeys(fn (Status $s) => [$s->value => ucwords(str_replace('_', ' ', $s->value))]))
2526
->required(),

app/Livewire/Customer/Support/Create.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@
1717
#[Title('Submit a Request')]
1818
class Create extends Component
1919
{
20+
public function boot(): void
21+
{
22+
abort_unless(auth()->user()->hasUltraAccess(), 403);
23+
}
24+
2025
#[Locked]
2126
public int $currentStep = 1;
2227

app/Livewire/Customer/Support/Index.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ class Index extends Component
1717
{
1818
use WithPagination;
1919

20+
public function boot(): void
21+
{
22+
abort_unless(auth()->user()->hasUltraAccess(), 403);
23+
}
24+
2025
#[Computed]
2126
public function supportTickets(): LengthAwarePaginator
2227
{

app/Livewire/Customer/Support/Show.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class Show extends Component
2121

2222
public function mount(SupportTicket $supportTicket): void
2323
{
24+
abort_unless(auth()->user()->hasUltraAccess(), 403);
2425
$this->authorize('view', $supportTicket);
2526

2627
$supportTicket->load(['user', 'replies.user']);

resources/views/components/layout.blade.php

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -102,13 +102,7 @@
102102
return ! this.scrolled && this.width >= 1024
103103
},
104104
}"
105-
x-resize="
106-
width = $width
107-
if (window.matchMedia('(min-width: 80rem)').matches) {
108-
showMobileMenu = false
109-
showDocsMenu = false
110-
}
111-
"
105+
x-resize="width = $width"
112106
x-init="
113107
window.addEventListener('scroll', () => {
114108
scrolled = window.scrollY > 1

resources/views/components/layouts/dashboard.blade.php

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,11 @@ class="min-h-screen bg-white font-poppins antialiased dark:bg-zinc-900 dark:text
111111
Purchase History
112112
</flux:sidebar.item>
113113

114-
<flux:sidebar.item icon="chat-bubble-left-right" href="{{ route('customer.support.tickets') }}" :current="request()->routeIs('customer.support.*')">
115-
Support Tickets
116-
</flux:sidebar.item>
114+
@if(auth()->user()->hasUltraAccess())
115+
<flux:sidebar.item icon="chat-bubble-left-right" href="{{ route('customer.support.tickets') }}" :current="request()->routeIs('customer.support.*')">
116+
Support Tickets
117+
</flux:sidebar.item>
118+
@endif
117119

118120
<flux:sidebar.group expandable :expanded="false" heading="Community" class="mt-4 grid">
119121
<flux:sidebar.item href="{{ route('customer.showcase.index') }}" :current="request()->routeIs('customer.showcase.*')">

resources/views/components/navbar/mobile-menu.blade.php

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,22 @@
11
<div
22
x-init="
33
() => {
4+
const updatePopoverTop = () => {
5+
const nav = $refs.menuButton.closest('nav');
6+
if (nav && $refs.mobilePopover.matches(':popover-open')) {
7+
$refs.mobilePopover.style.top = (nav.getBoundingClientRect().bottom + 8) + 'px';
8+
}
9+
};
10+
411
// Sync Popover ➜ Alpine
512
$refs.mobilePopover.addEventListener('toggle', (e) => {
613
showMobileMenu = $refs.mobilePopover.matches(':popover-open')
714
8-
// Scroll past the banner when menu opens
915
if (e.newState === 'open') {
10-
const banner = document.querySelector('[data-site-banner]')
11-
if (banner) {
12-
const bannerBottom = banner.offsetTop + banner.offsetHeight
13-
if (window.scrollY < bannerBottom) {
14-
window.scrollTo({ top: bannerBottom, behavior: 'smooth' })
15-
}
16-
}
16+
updatePopoverTop()
17+
window.addEventListener('scroll', updatePopoverTop, { passive: true })
18+
} else {
19+
window.removeEventListener('scroll', updatePopoverTop)
1720
}
1821
})
1922
@@ -27,10 +30,11 @@
2730
})
2831
}
2932
"
30-
class="relative z-40 xl:hidden"
33+
class="relative z-40"
3134
>
3235
<button
3336
type="button"
37+
x-ref="menuButton"
3438
popovertarget="mobile-menu-popover"
3539
popovertargetaction="toggle"
3640
class="-m-1.5 grid size-9 place-items-center overflow-hidden focus:ring-0 focus:outline-none"
@@ -79,7 +83,8 @@ class="-ml-2.5 h-5 w-0.5 -rotate-45 rounded-full bg-current transition duration-
7983
role="dialog"
8084
aria-modal="true"
8185
aria-label="Site menu"
82-
class="fixed top-20 right-3 bottom-3.5 left-3 w-auto origin-top -translate-y-2 scale-y-90 overflow-y-scroll overscroll-contain rounded-2xl bg-gray-200/50 opacity-0 ring-1 ring-gray-200/80 backdrop-blur-2xl transition transition-discrete duration-300 open:translate-y-0 open:scale-y-100 open:opacity-100 min-[500px]:right-3.5 min-[500px]:left-3.5 dark:bg-black/50 dark:text-white dark:ring-gray-700/70 starting:open:-translate-y-2 starting:open:scale-y-0 starting:open:opacity-0"
86+
x-bind:style="width >= 1024 ? 'right:' + (window.innerWidth - $refs.menuButton.getBoundingClientRect().right - 6) + 'px' : ''"
87+
class="fixed m-0 inset-[unset] inset-x-3 bottom-3.5 w-auto -translate-y-3 overflow-y-scroll overscroll-contain rounded-2xl bg-gray-200/50 opacity-0 ring-1 ring-gray-200/80 backdrop-blur-2xl transition-[opacity,transform] transition-discrete duration-300 open:translate-y-0 open:opacity-100 min-[500px]:inset-x-3.5 lg:bottom-auto lg:left-auto lg:w-md dark:bg-black/50 dark:text-white dark:ring-gray-700/70 starting:open:-translate-y-3 starting:open:opacity-0"
8388
>
8489
<div class="@container flex flex-col overflow-hidden px-6 pt-4 pb-6">
8590
<nav

resources/views/components/navigation-bar.blade.php

Lines changed: 15 additions & 211 deletions
Original file line numberDiff line numberDiff line change
@@ -70,225 +70,29 @@ class="-mt-px size-3.5"
7070

7171
{{-- Right side --}}
7272
<div class="flex items-center gap-2.5">
73-
{{-- Mobile menu --}}
74-
<x-navbar.mobile-menu />
73+
{{-- Theme toggle (visible on large screens) --}}
74+
<div class="hidden lg:block">
75+
<x-navbar.theme-toggle />
76+
</div>
7577

76-
{{-- Desktop menu --}}
78+
{{-- Doc search (visible on large screens) --}}
7779
<div
78-
class="hidden items-center gap-2.5 text-sm xl:flex"
79-
aria-label="Primary navigation"
80+
class="hidden -mr-0.5 transition-all duration-200 ease-in-out will-change-transform lg:block"
8081
>
81-
{{-- Link --}}
82-
<a
83-
href="{{ route('blog') }}"
84-
@class([
85-
'transition duration-200',
86-
'font-medium' => request()->routeIs('blog*'),
87-
'opacity-60 hover:opacity-100' => ! request()->routeIs('blog*'),
88-
])
89-
aria-current="{{ request()->routeIs('blog*') ? 'page' : 'false' }}"
90-
>
91-
Blog
92-
</a>
93-
94-
{{-- Decorative circle --}}
95-
<div
96-
class="size-[3px] rotate-45 rounded-xs bg-gray-400 transition duration-200 dark:opacity-60"
97-
aria-hidden="true"
98-
></div>
99-
100-
{{-- Link --}}
101-
{{-- <a
102-
href="https://shop.nativephp.com/"
103-
class="opacity-60 transition duration-200 hover:opacity-100"
104-
>
105-
Swag
106-
</a> --}}
107-
108-
{{-- Decorative circle --}}
109-
{{-- <div
110-
class="size-[3px] rotate-45 rounded-xs bg-gray-400 transition duration-200 dark:opacity-60"
111-
aria-hidden="true"
112-
></div> --}}
113-
114-
{{-- Link --}}
115-
<a
116-
href="/partners"
117-
@class([
118-
'transition duration-200',
119-
'font-medium' => request()->routeIs('partners'),
120-
'opacity-60 hover:opacity-100' => ! request()->routeIs('partners'),
121-
])
122-
aria-current="{{ request()->routeIs('partners') ? 'page' : 'false' }}"
123-
>
124-
Partners
125-
</a>
126-
127-
{{-- Decorative circle --}}
128-
<div
129-
class="size-[3px] rotate-45 rounded-xs bg-gray-400 transition duration-200 dark:opacity-60"
130-
aria-hidden="true"
131-
></div>
132-
133-
{{-- Link --}}
134-
<a
135-
href="/sponsor"
136-
@class([
137-
'transition duration-200',
138-
'font-medium' => request()->routeIs('sponsoring'),
139-
'opacity-60 hover:opacity-100' => ! request()->routeIs('sponsoring'),
140-
])
141-
aria-current="{{ request()->routeIs('sponsoring') ? 'page' : 'false' }}"
142-
>
143-
Sponsor
144-
</a>
145-
146-
{{-- Decorative circle --}}
147-
<div
148-
class="size-[3px] rotate-45 rounded-xs bg-gray-400 transition duration-200 dark:opacity-60"
149-
aria-hidden="true"
150-
></div>
151-
152-
{{-- Link --}}
153-
<a
154-
href="{{ route('build-my-app') }}"
155-
@class([
156-
'transition duration-200',
157-
'font-medium' => request()->routeIs('build-my-app'),
158-
'opacity-60 hover:opacity-100' => ! request()->routeIs('build-my-app'),
159-
])
160-
aria-current="{{ request()->routeIs('build-my-app') ? 'page' : 'false' }}"
161-
>
162-
Develop
163-
</a>
164-
165-
{{-- Decorative circle --}}
16682
<div
167-
class="size-[3px] rotate-45 rounded-xs bg-gray-400 transition duration-200 dark:opacity-60"
168-
aria-hidden="true"
83+
id="docsearch-desktop"
84+
x-on:click="if (window.innerWidth < 640) window.scrollTo({ top: 0, behavior: 'instant' })"
85+
aria-label="Search documentation"
16986
></div>
87+
</div>
17088

171-
{{-- Link --}}
172-
<a
173-
href="{{ route('pricing') }}"
174-
@class([
175-
'transition duration-200',
176-
'font-medium' => request()->routeIs('pricing'),
177-
'opacity-60 hover:opacity-100' => ! request()->routeIs('pricing'),
178-
])
179-
aria-current="{{ request()->routeIs('pricing') ? 'page' : 'false' }}"
180-
>
181-
<span class="inline-flex items-center gap-1.5">
182-
Ultra
183-
<span class="rounded-full bg-emerald-500 px-1.5 py-px text-[10px] font-bold leading-tight text-white">New</span>
184-
</span>
185-
</a>
186-
187-
{{-- Decorative circle --}}
188-
<div
189-
class="size-[3px] rotate-45 rounded-xs bg-gray-400 transition duration-200 dark:opacity-60"
190-
aria-hidden="true"
191-
></div>
192-
193-
{{-- Link --}}
194-
<a
195-
href="{{ route('course') }}"
196-
@class([
197-
'transition duration-200',
198-
'font-medium' => request()->routeIs('course'),
199-
'opacity-60 hover:opacity-100' => ! request()->routeIs('course'),
200-
])
201-
aria-current="{{ request()->routeIs('course') ? 'page' : 'false' }}"
202-
>
203-
<span class="inline-flex items-center gap-1.5">
204-
Learn
205-
<span class="rounded-full bg-emerald-500 px-1.5 py-px text-[10px] font-bold leading-tight text-white">New</span>
206-
</span>
207-
</a>
208-
209-
{{-- Decorative circle --}}
210-
<div
211-
class="size-[3px] rotate-45 rounded-xs bg-gray-400 transition duration-200 dark:opacity-60"
212-
aria-hidden="true"
213-
></div>
214-
215-
{{-- Link --}}
216-
<a
217-
href="{{ route('support.index') }}"
218-
@class([
219-
'transition duration-200',
220-
'font-medium' => request()->routeIs('support.*'),
221-
'opacity-60 hover:opacity-100' => ! request()->routeIs('support.*'),
222-
])
223-
aria-current="{{ request()->routeIs('support.*') ? 'page' : 'false' }}"
224-
>
225-
<span class="inline-flex items-center gap-1.5">
226-
Support
227-
<span class="rounded-full bg-emerald-500 px-1.5 py-px text-[10px] font-bold leading-tight text-white">New</span>
228-
</span>
229-
</a>
230-
231-
{{-- Login/Logout --}}
232-
@feature(App\Features\ShowAuthButtons::class)
233-
{{-- Decorative circle --}}
234-
<div
235-
class="size-[3px] rotate-45 rounded-xs bg-gray-400 transition duration-200 dark:opacity-60"
236-
aria-hidden="true"
237-
></div>
238-
239-
@auth
240-
<a
241-
href="{{ route('dashboard') }}"
242-
class="opacity-60 transition duration-200 hover:opacity-100"
243-
title="Logged in as {{ auth()->user()->email }}"
244-
>
245-
Dashboard
246-
</a>
247-
@else
248-
<a
249-
href="{{ route('customer.login') }}"
250-
class="opacity-60 transition duration-200 hover:opacity-100"
251-
>
252-
Dashboard
253-
</a>
254-
@endauth
255-
@endfeature
256-
257-
{{-- Cart Icon --}}
258-
@feature(App\Features\ShowPlugins::class)
259-
@if ($cartCount > 0)
260-
<a
261-
href="{{ route('cart.show') }}"
262-
class="relative opacity-60 transition duration-200 hover:opacity-100"
263-
title="View cart"
264-
>
265-
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-5">
266-
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 3h1.386c.51 0 .955.343 1.087.835l.383 1.437M7.5 14.25a3 3 0 0 0-3 3h15.75m-12.75-3h11.218c1.121-2.3 2.1-4.684 2.924-7.138a60.114 60.114 0 0 0-16.536-1.84M7.5 14.25 5.106 5.272M6 20.25a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0Zm12.75 0a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0Z" />
267-
</svg>
268-
<span class="absolute -top-1.5 -right-1.5 flex size-4 items-center justify-center rounded-full bg-indigo-600 text-[10px] font-bold text-white">
269-
{{ $cartCount > 9 ? '9+' : $cartCount }}
270-
</span>
271-
<span class="sr-only">Cart ({{ $cartCount }} items)</span>
272-
</a>
273-
@endif
274-
@endfeature
275-
276-
{{-- Theme toggle --}}
277-
<x-navbar.theme-toggle />
278-
279-
{{-- Doc search --}}
280-
<div
281-
class="-mr-0.5 transition-all duration-200 ease-in-out will-change-transform"
282-
>
283-
<div
284-
id="docsearch-desktop"
285-
x-on:click="if (window.innerWidth < 640) window.scrollTo({ top: 0, behavior: 'instant' })"
286-
aria-label="Search documentation"
287-
></div>
288-
</div>
289-
89+
{{-- Bifrost button (visible on large screens) --}}
90+
<div class="hidden lg:block">
29091
<x-bifrost-button small />
29192
</div>
93+
94+
{{-- Menu --}}
95+
<x-navbar.mobile-menu />
29296
</div>
29397
</div>
29498
</nav>

0 commit comments

Comments
 (0)