Skip to content

Instantly share code, notes, and snippets.

@patrickcurl
Created August 31, 2022 04:49
Show Gist options
  • Save patrickcurl/5d975e749f44db0bc428d9ea927de772 to your computer and use it in GitHub Desktop.
Save patrickcurl/5d975e749f44db0bc428d9ea927de772 to your computer and use it in GitHub Desktop.
Laravel Filament Sticky Tabs aka Persistent or Cached tabs.
<?php
public static function form(Form $form) : Form
{
return $form->schema(Tabs::make('Organizations')
->tabs([
Tab::make('Tab 1')
->schema([...]),
Tab::make('Tab 2')
->schema([...]),
Tab::make('Tab 3')
->schema([...]),
])->reactive()
);
}
// For ease, here's what's changed over the 'base' tabs component, I added:
// wire:click="dispatchFormEvent('tabs::setActive', '{{ $tab->getId() }}')"
// circa line 36.
// Put in: resources/views/vendor/filament/components/tabs.blade.php
<div x-data="{
tab: null,
init: function() {
this.tab = this.getTabs()[@js($getActiveTab()) - 1]
},
getTabs: function() {
return JSON.parse(this.$refs.tabsData.value)
},
}"
x-on:expand-concealing-component.window="
if (getTabs().includes($event.detail.id)) {
tab = $event.detail.id
$el.scrollIntoView({ behavior: 'smooth', block: 'start' })
}
"
x-cloak {!! $getId() ? "id=\"{$getId()}\"" : null !!}
{{ $attributes->merge($getExtraAttributes())->class([
'filament-forms-tabs-component rounded-xl shadow-sm border border-gray-300 bg-white',
'dark:bg-gray-800 dark:border-gray-700' => config('forms.dark_mode'),
]) }}
{{ $getExtraAlpineAttributeBag() }}>
<input type="hidden"
value='{{ collect($getChildComponentContainer()->getComponents())->filter(static fn(\Filament\Forms\Components\Tabs\Tab $tab): bool => !$tab->isHidden())->map(static fn(\Filament\Forms\Components\Tabs\Tab $tab) => $tab->getId())->values()->toJson() }}'
x-ref="tabsData" />
<div {!! $getLabel() ? 'aria-label="' . $getLabel() . '"' : null !!} role="tablist" @class([
'rounded-t-xl flex overflow-y-auto bg-gray-100',
'dark:bg-gray-700' => config('forms.dark_mode'),
])>
@foreach ($getChildComponentContainer()->getComponents() as $tab)
<button wire:click="dispatchFormEvent('tabs::setActive', '{{ $tab->getId() }}')" type="button"
aria-controls="{{ $tab->getId() }}" x-bind:aria-selected="tab === '{{ $tab->getId() }}'"
x-on:click="tab = '{{ $tab->getId() }}'" role="tab"
x-bind:tabindex="tab === '{{ $tab->getId() }}' ? 0 : -1"
class="flex items-center gap-2 p-3 text-sm font-medium filament-forms-tabs-component-button shrink-0"
x-bind:class="{
'text-gray-500 @if (config('forms.dark_mode')) dark:text-gray-400 @endif': tab !==
'{{ $tab->getId() }}',
'bg-white text-primary-600 @if (config('forms.dark_mode')) dark:bg-gray-800 @endif': tab ===
'{{ $tab->getId() }}',
}">
@if ($icon = $tab->getIcon())
<x-dynamic-component :component="$icon" class="w-5 h-5" />
@endif
<span>{{ $tab->getLabel() }}</span>
@if ($badge = $tab->getBadge())
<span
class="inline-flex items-center justify-center ml-auto rtl:ml-0 rtl:mr-auto min-h-4 px-2 py-0.5 text-xs font-medium tracking-tight rounded-xl whitespace-normal"
x-bind:class="{
'bg-gray-200 @if (config('forms.dark_mode')) dark:bg-gray-600 @endif': tab !==
'{{ $tab->getId() }}',
'bg-primary-500/10 font-medium': tab === '{{ $tab->getId() }}',
}">
{{ $badge }}
</span>
@endif
</button>
@endforeach
</div>
@foreach ($getChildComponentContainer()->getComponents() as $tab)
{{ $tab }}
@endforeach
</div>
<?php
declare(strict_types=1);
namespace App\Filament\Components;
use Filament\Forms\Components;
use Illuminate\Support\Str;
class Tabs extends Components\Tabs
{
protected string $view = 'filament::components.tabs';
protected function setUp() : void
{
parent::setUp();
// activeTab will throw a fit if it doesn't get an int/Closure,
// so let's ensure we have one, or go with default which is 1.
$this->activeTab = (int) \cache()->get($this->getCacheKey()) ?? 1;
$this->registerListeners([
// Register a listener to trigger when tabs are clicked
'tabs::setActive' => [
function (self $component, string $newTab) : void {
// Let's make a list of all the tab names, usually something like _name,
$tabs = \collect(
$component->getChildComponentContainer()
->getComponents()
)
->map(fn ($tab) => $tab->getId())->all();
// Let's grab the one that matches $newTab which is what was clicked.
// Again this must be an integer. We also add 1 because it's an array index
// and tabs start at 1.
$tab = \array_search($newTab, $tabs, true);
if($tab && is_numeric($tab)){
$newTabId = $tab+1;
}
$newTabId = is_numeric($tab) ? ($tab+1) : 1; // default value;
\cache()->set(
$this->getCacheKey(),
$newTabId,
\now()->addMinutes(60)
);
},
],
]);
}
public function getCacheKey()
{
$resourceName = Str::snake($this->getLabel()) ?? 'unknown_resource';
return "{$resourceName}.activeTab";
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment