Toggleable SPA mode

This commit is contained in:
Dan Harrin
2023-09-10 10:24:43 +01:00
parent 3703fb1fdf
commit f32e678a2c
24 changed files with 147 additions and 42 deletions
@@ -117,10 +117,7 @@
>
@if ($url)
<a
href="{{ $url }}"
@if ($shouldOpenUrlInNewTab)
target="_blank"
@endif
{{ \Filament\Support\generate_href_html($url, $shouldOpenUrlInNewTab) }}
class="block"
>
{{ $slot }}
@@ -9,7 +9,7 @@
{{ $attributes->class(['fi-global-search-result transition duration-75 focus-within:bg-gray-50 hover:bg-gray-50 dark:focus-within:bg-white/5 dark:hover:bg-white/5']) }}
>
<a
href="{{ $url }}"
{{ \Filament\Support\generate_href_html($url) }}
@class([
'block outline-none',
'pe-4 ps-4 pt-4' => $actions,
@@ -43,7 +43,7 @@
@endif
>
@if ($homeUrl = filament()->getHomeUrl())
<a href="{{ $homeUrl }}">
<a {{ \Filament\Support\generate_href_html($homeUrl) }}>
<x-filament-panels::logo />
</a>
@else
@@ -18,12 +18,7 @@
])
>
<a
href="{{ $url }}"
@if ($shouldOpenUrlInNewTab)
target="_blank"
@else
wire:navigate
@endif
{{ \Filament\Support\generate_href_html($url, $shouldOpenUrlInNewTab) }}
x-on:click="window.matchMedia(`(max-width: 1024px)`).matches && $store.sidebar.close()"
@if (filament()->isSidebarCollapsibleOnDesktop())
x-data="{ tooltip: false }"
@@ -88,7 +88,7 @@
<x-filament::dropdown.list.item
:color="$profileItem?->getColor()"
:href="$profileItemUrl ?? filament()->getTenantProfileUrl()"
:should-open-url-in-new-tab="$profileItem?->shouldOpenUrlInNewTab() ?? false"
:target="($profileItem?->shouldOpenUrlInNewTab() ?? false) ? '_blank' : null"
:icon="$profileItem?->getIcon() ?? 'heroicon-m-cog-6-tooth'"
tag="a"
>
@@ -100,7 +100,7 @@
<x-filament::dropdown.list.item
:color="$billingItem?->getColor() ?? 'gray'"
:href="$billingItemUrl ?? filament()->getTenantBillingUrl()"
:should-open-url-in-new-tab="$billingItem?->shouldOpenUrlInNewTab() ?? false"
:target="($billingItem?->shouldOpenUrlInNewTab() ?? false) ? '_blank' : null"
:icon="$billingItem?->getIcon() ?? 'heroicon-m-credit-card'"
tag="a"
>
@@ -116,7 +116,7 @@
<x-filament::dropdown.list.item
:color="$item->getColor()"
:href="$item->getUrl()"
:should-open-url-in-new-tab="$item->shouldOpenUrlInNewTab()"
:target="$item->shouldOpenUrlInNewTab() ? '_blank' : null"
:icon="$item->getIcon()"
tag="a"
>
@@ -145,7 +145,7 @@
<x-filament::dropdown.list.item
:color="$registrationItem?->getColor()"
:href="$registrationItemUrl ?? filament()->getTenantRegistrationUrl()"
:should-open-url-in-new-tab="$registrationItem?->shouldOpenUrlInNewTab() ?? false"
:target="($registrationItem?->shouldOpenUrlInNewTab() ?? false) ? '_blank' : null"
:icon="$registrationItem?->getIcon() ?? 'heroicon-m-plus'"
tag="a"
>
@@ -46,7 +46,7 @@
@if (filament()->hasTopNavigation())
<div class="me-6 hidden lg:flex">
@if ($homeUrl = filament()->getHomeUrl())
<a href="{{ $homeUrl }}" wire:navigate>
<a {{ \Filament\Support\generate_href_html($homeUrl) }}>
<x-filament-panels::logo />
</a>
@else
@@ -20,13 +20,7 @@
>
<{{ $tag }}
@if ($url)
href="{{ $url }}"
@if ($shouldOpenUrlInNewTab)
target="_blank"
@else
wire:navigate
@endif
{{ \Filament\Support\generate_href_html($url, $shouldOpenUrlInNewTab) }}
@else
type="button"
@endif
@@ -40,7 +40,7 @@
:color="$profileItem?->getColor()"
:icon="$profileItem?->getIcon() ?? 'heroicon-m-user-circle'"
:href="$profileItemUrl ?? filament()->getProfileUrl()"
:should-open-url-in-new-tab="$profileItem?->shouldOpenUrlInNewTab() ?? false"
:target="($profileItem?->shouldOpenUrlInNewTab() ?? false) ? '_blank' : null"
icon-alias="panels::user-menu.profile-item"
tag="a"
>
@@ -71,7 +71,7 @@
<x-filament::dropdown.list.item
:color="$item->getColor()"
:href="$item->getUrl()"
:should-open-url-in-new-tab="$item->shouldOpenUrlInNewTab()"
:target="$item->shouldOpenUrlInNewTab() ? '_blank' : null"
:icon="$item->getIcon()"
tag="a"
>
+4
View File
@@ -6,6 +6,7 @@ use Closure;
use Filament\Support\Components\Component;
use Filament\Support\Facades\FilamentColor;
use Filament\Support\Facades\FilamentIcon;
use Filament\Support\Facades\FilamentView;
class Panel extends Component
{
@@ -29,6 +30,7 @@ class Panel extends Component
use Panel\Concerns\HasRenderHooks;
use Panel\Concerns\HasRoutes;
use Panel\Concerns\HasSidebar;
use Panel\Concerns\HasSpaMode;
use Panel\Concerns\HasTenancy;
use Panel\Concerns\HasTheme;
use Panel\Concerns\HasTopNavigation;
@@ -59,6 +61,8 @@ class Panel extends Component
FilamentIcon::register($this->icons);
FilamentView::spa($this->hasSpaMode());
$this->registerRenderHooks();
$this->registerLivewireComponents();
@@ -0,0 +1,22 @@
<?php
namespace Filament\Panel\Concerns;
use Closure;
trait HasSpaMode
{
protected bool | Closure $hasSpaMode = false;
public function spa(bool | Closure $condition = true): static
{
$this->hasSpaMode = $condition;
return $this;
}
public function hasSpaMode(): bool
{
return (bool) $this->evaluate($this->hasSpaMode);
}
}
+1 -1
View File
@@ -8,7 +8,7 @@ use Illuminate\Auth\Access\Response;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Gate;
if (! function_exists('authorize')) {
if (! function_exists('Filament\authorize')) {
/**
* @throws AuthorizationException
*/
@@ -32,8 +32,7 @@
@endif
<a
href="{{ is_int($url) ? '#' : $url }}"
wire:navigate
{{ \Filament\Support\generate_href_html(is_int($url) ? '#' : $url) }}
class="text-sm font-medium text-gray-500 outline-none transition duration-75 hover:text-gray-700 focus:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 dark:focus:text-gray-200"
>
{{ $label }}
@@ -11,6 +11,7 @@
'disabled' => false,
'form' => null,
'grouped' => false,
'href' => null,
'icon' => null,
'iconAlias' => null,
'iconPosition' => IconPosition::Before,
@@ -21,6 +22,7 @@
'outlined' => false,
'size' => 'md',
'tag' => 'button',
'target' => null,
'tooltip' => null,
'type' => 'button',
])
@@ -280,6 +282,7 @@
</button>
@elseif ($tag === 'a')
<a
{{ \Filament\Support\generate_href_html($href, $target === '_blank') }}
@if ($keyBindings || $tooltip)
x-data="{}"
@endif
@@ -7,13 +7,14 @@
'badgeColor' => null,
'color' => 'gray',
'disabled' => false,
'href' => null,
'icon' => null,
'iconAlias' => null,
'iconSize' => IconSize::Medium,
'image' => null,
'keyBindings' => null,
'tag' => 'button',
'shouldOpenUrlInNewTab' => false,
'target' => null,
])
@php
@@ -119,11 +120,7 @@
</button>
@elseif ($tag === 'a')
<a
@if ($shouldOpenUrlInNewTab)
target="_blank"
@else
wire:navigate
@endif
{{ \Filament\Support\generate_href_html($href, $target === '_blank') }}
@if ($keyBindings)
x-data="{}"
x-mousetrap.global.{{ collect($keyBindings)->map(fn (string $keyBinding): string => str_replace('+', '-', $keyBinding))->implode('.') }}
@@ -9,6 +9,7 @@
'color' => 'primary',
'disabled' => false,
'form' => null,
'href' => null,
'icon' => null,
'iconAlias' => null,
'iconSize' => null,
@@ -16,6 +17,7 @@
'label' => null,
'size' => 'md',
'tag' => 'button',
'target' => null,
'tooltip' => null,
'type' => 'button',
])
@@ -123,6 +125,7 @@
</button>
@elseif ($tag === 'a')
<a
{{ \Filament\Support\generate_href_html($href, $target === '_blank') }}
@if ($keyBindings || $tooltip)
x-data="{}"
@endif
@@ -10,6 +10,7 @@
'color' => 'primary',
'disabled' => false,
'form' => null,
'href' => null,
'icon' => null,
'iconAlias' => null,
'iconPosition' => IconPosition::Before,
@@ -17,6 +18,7 @@
'keyBindings' => null,
'size' => 'md',
'tag' => 'a',
'target' => null,
'tooltip' => null,
'type' => 'button',
])
@@ -88,6 +90,7 @@
@if ($tag === 'a')
<a
{{ \Filament\Support\generate_href_html($href, $target === '_blank') }}
@if ($keyBindings || $tooltip)
x-data="{}"
@endif
@@ -7,10 +7,12 @@
'alpineActive' => null,
'badge' => null,
'badgeColor' => null,
'href' => null,
'icon' => null,
'iconColor' => 'gray',
'iconPosition' => IconPosition::Before,
'tag' => 'button',
'target' => null,
'type' => 'button',
])
@@ -35,6 +37,8 @@
<{{ $tag }}
@if ($tag === 'button')
type="{{ $type }}"
@elseif ($tag === 'a')
{{ \Filament\Support\generate_href_html($href, $target === '_blank') }}
@endif
@if ($hasAlpineActiveClasses)
x-bind:class="{
@@ -8,6 +8,7 @@ use Illuminate\Contracts\Support\Htmlable;
use Illuminate\Support\Facades\Facade;
/**
* @method static bool hasSpaMode()
* @method static Htmlable renderHook(string $name, string | array | null $scopes = null)
*
* @see ViewManager
@@ -28,4 +29,11 @@ class FilamentView extends Facade
$viewManager->registerRenderHook($name, $hook, $scopes);
});
}
public static function spa(bool $condition = true): void
{
static::resolved(function (ViewManager $viewManager) use ($condition) {
$viewManager->spa($condition);
});
}
}
+12
View File
@@ -14,6 +14,8 @@ class ViewManager
*/
protected array $renderHooks = [];
protected bool $hasSpaMode = false;
/**
* @param string | array<string> | null $scopes
*/
@@ -66,4 +68,14 @@ class ViewManager
return new HtmlString(implode('', $hooks));
}
public function spa(bool $condition = true): void
{
$this->hasSpaMode = $condition;
}
public function hasSpaMode(): bool
{
return $this->hasSpaMode;
}
}
+19
View File
@@ -2,7 +2,9 @@
namespace Filament\Support;
use Filament\Support\Facades\FilamentView;
use Illuminate\Contracts\Support\Htmlable;
use Illuminate\Support\HtmlString;
use Illuminate\Support\Str;
use Illuminate\Translation\MessageSelector;
use Illuminate\View\ComponentAttributeBag;
@@ -111,3 +113,20 @@ if (! function_exists('Filament\Support\is_slot_empty')) {
) === '';
}
}
if (! function_exists('Filament\Support\generate_href_html')) {
function generate_href_html(?string $url, bool $shouldOpenInNewTab = false): Htmlable
{
$html = "href=\"{$url}\"";
if (FilamentView::hasSpaMode() && str($url)->startsWith(request()->root())) {
$html .= ' wire:navigate';
}
if ($shouldOpenInNewTab) {
$html .= ' target="_blank"';
}
return new HtmlString($html);
}
}
@@ -43,8 +43,7 @@
>
@if (($url || ($recordUrl && $action === null)) && (! $isClickDisabled))
<a
href="{{ $url ?: $recordUrl }}"
@if ($shouldOpenUrlInNewTab) target="_blank" @endif
{{ \Filament\Support\generate_href_html($url ?: $recordUrl, $shouldOpenUrlInNewTab) }}
class="{{ $columnClasses }}"
>
{{ $slot }}
@@ -650,7 +650,7 @@
<div class="flex-1">
@if ($recordUrl)
<a
href="{{ $recordUrl }}"
{{ \Filament\Support\generate_href_html($recordUrl) }}
class="{{ $recordContentClasses }}"
>
<x-filament-tables::columns.layout
@@ -24,10 +24,7 @@
<{!! $tag !!}
@if ($url)
href="{{ $url }}"
@if ($shouldOpenUrlInNewTab())
target="_blank"
@endif
{{ \Filament\Support\generate_href_html($url, $shouldOpenUrlInNewTab()) }}
@endif
{{
$getExtraAttributeBag()
+49
View File
@@ -0,0 +1,49 @@
<?php
use Filament\Support\Facades\FilamentView;
use Filament\Tests\TestCase;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\HtmlString;
use function Filament\Support\generate_href_html;
uses(TestCase::class);
test('SPA mode can be toggled', function () {
expect(FilamentView::hasSpaMode())->toBeFalse();
FilamentView::spa();
expect(FilamentView::hasSpaMode())->toBeTrue();
FilamentView::spa(false);
expect(FilamentView::hasSpaMode())->toBeFalse();
});
test('`href` HTML can be generated with `wire:navigate` based on SPA mode', function () {
FilamentView::spa();
expect(generate_href_html('http://localhost/page'))
->toHtml()->toBe('href="http://localhost/page" wire:navigate');
FilamentView::spa(false);
expect(generate_href_html('http://localhost/page'))
->toHtml()->toBe('href="http://localhost/page"');
});
test('`wire:navigate` is not used in the `href` HTML if it doesn\'t match the request\'s domain', function () {
FilamentView::spa();
expect(generate_href_html('http://another-localhost/page'))
->toHtml()->toBe('href="http://another-localhost/page"');
FilamentView::spa(false);
expect(generate_href_html('http://another-localhost/page'))
->toHtml()->toBe('href="http://another-localhost/page"');
});
test('`target` HTML can be generated if the URL should open in a new tab', function () {
FilamentView::spa();
expect(generate_href_html('http://localhost/page', shouldOpenInNewTab: true))
->toHtml()->toBe('href="http://localhost/page" wire:navigate target="_blank"');
FilamentView::spa(false);
expect(generate_href_html('http://localhost/page', shouldOpenInNewTab: true))
->toHtml()->toBe('href="http://localhost/page" target="_blank"');
});