feat: add resources and view components

This commit is contained in:
2026-05-21 16:05:19 +07:00
parent 28a06315b8
commit b2d60e680d
249 changed files with 37379 additions and 0 deletions
@@ -0,0 +1,153 @@
@php
$debounce = filament()->getGlobalSearchDebounce();
$keyBindings = filament()->getGlobalSearchKeyBindings();
$suffix = filament()->getGlobalSearchFieldSuffix();
@endphp
<div class="fi-global-search-ctn">
{{ \Filament\Support\Facades\FilamentView::renderHook(\Filament\View\PanelsRenderHook::GLOBAL_SEARCH_START) }}
<div
x-on:focus-first-global-search-result.stop="$el.querySelector('.fi-global-search-result-link')?.focus()"
class="fi-global-search"
>
<div x-id="['input']" class="fi-global-search-field">
<label x-bind:for="$id('input')" class="fi-sr-only">
{{ __('filament-panels::global-search.field.label') }}
</label>
<x-filament::input.wrapper
:prefix-icon="\Filament\Support\Icons\Heroicon::MagnifyingGlass"
:prefix-icon-alias="\Filament\View\PanelsIconAlias::GLOBAL_SEARCH_FIELD"
inline-prefix
:suffix="$suffix"
inline-suffix
wire:target="search"
>
<input
autocomplete="off"
maxlength="1000"
placeholder="{{ __('filament-panels::global-search.field.placeholder') }}"
type="search"
wire:key="global-search.field.input"
x-bind:id="$id('input')"
x-on:keydown.down.prevent.stop="$dispatch('focus-first-global-search-result')"
wire:model.live.debounce.{{ $debounce }}="search"
x-mousetrap.global.{{ collect($keyBindings)->map(fn (string $keyBinding): string => str_replace('+', '-', $keyBinding))->implode('.') }}="document.getElementById($id('input'))?.focus()"
class="fi-input fi-input-has-inline-prefix"
/>
</x-filament::input.wrapper>
</div>
@if ($results !== null)
<div
x-data="{
isOpen: false,
open(event) {
this.isOpen = true
},
close(event) {
this.isOpen = false
},
}"
x-init="$nextTick(() => open())"
x-on:click.away="close()"
x-on:keydown.escape.window="close()"
x-on:keydown.up.prevent="$focus.wrap().previous()"
x-on:keydown.down.prevent="$focus.wrap().next()"
x-on:open-global-search-results.window="$nextTick(() => open())"
x-show="isOpen"
x-transition:enter-start="fi-transition-enter-start"
x-transition:leave-end="fi-transition-leave-end"
class="fi-global-search-results-ctn"
>
@if ($results->getCategories()->isEmpty())
<p class="fi-global-search-no-results-message">
{{ __('filament-panels::global-search.no_results_message') }}
</p>
@else
<ul class="fi-global-search-results">
@foreach ($results->getCategories() as $group => $groupedResults)
<li class="fi-global-search-result-group">
<h3
class="fi-global-search-result-group-header"
>
{{ $group }}
</h3>
<ul
class="fi-global-search-result-group-results"
>
@foreach ($groupedResults as $result)
@php
$resultVisibleActions = $result->getVisibleActions();
@endphp
<li
@class([
'fi-global-search-result',
'fi-global-search-result-has-actions' => $resultVisibleActions,
])
>
<a
{{ \Filament\Support\generate_href_html($result->url) }}
x-on:click="close()"
class="fi-global-search-result-link"
>
<h4
class="fi-global-search-result-heading"
>
{{ $result->title }}
</h4>
@if ($result->details)
<dl
class="fi-global-search-result-details"
>
@foreach ($result->details as $label => $value)
<div
class="fi-global-search-result-detail"
>
@if ($isAssoc ??= \Illuminate\Support\Arr::isAssoc($result->details))
<dt
class="fi-global-search-result-detail-label"
>
{{ $label }}:
</dt>
@endif
<dd
class="fi-global-search-result-detail-value"
>
{{ $value }}
</dd>
</div>
@endforeach
</dl>
@endif
</a>
@if ($resultVisibleActions)
<div
class="fi-global-search-result-actions"
>
@foreach ($resultVisibleActions as $action)
{{ $action }}
@endforeach
</div>
@endif
</li>
@endforeach
</ul>
</li>
@endforeach
</ul>
@endif
</div>
@endif
</div>
{{ \Filament\Support\Facades\FilamentView::renderHook(\Filament\View\PanelsRenderHook::GLOBAL_SEARCH_END) }}
</div>
@@ -0,0 +1,203 @@
<div>
@php
$navigation = filament()->getNavigation();
$isRtl = __('filament-panels::layout.direction') === 'rtl';
$isSidebarCollapsibleOnDesktop = filament()->isSidebarCollapsibleOnDesktop();
$isSidebarFullyCollapsibleOnDesktop = filament()->isSidebarFullyCollapsibleOnDesktop();
$hasNavigation = filament()->hasNavigation();
$hasTopbar = filament()->hasTopbar();
@endphp
{{-- format-ignore-start --}}
<aside
x-data="{}"
@if ($isSidebarCollapsibleOnDesktop || $isSidebarFullyCollapsibleOnDesktop)
x-cloak
@else
x-cloak="-lg"
@endif
x-bind:class="{ 'fi-sidebar-open': $store.sidebar.isOpen }"
class="fi-sidebar fi-main-sidebar"
>
{{ \Filament\Support\Facades\FilamentView::renderHook(\Filament\View\PanelsRenderHook::SIDEBAR_START) }}
<div class="fi-sidebar-header-ctn">
<header
class="fi-sidebar-header"
>
@if ((! $hasTopbar) && $isSidebarCollapsibleOnDesktop)
<x-filament::icon-button
color="gray"
:icon="$isRtl ? \Filament\Support\Icons\Heroicon::OutlinedChevronLeft : \Filament\Support\Icons\Heroicon::OutlinedChevronRight"
{{-- @deprecated Use `PanelsIconAlias::SIDEBAR_EXPAND_BUTTON_RTL` instead of `PanelsIconAlias::SIDEBAR_EXPAND_BUTTON` for RTL. --}}
:icon-alias="
$isRtl
? [
\Filament\View\PanelsIconAlias::SIDEBAR_EXPAND_BUTTON_RTL,
\Filament\View\PanelsIconAlias::SIDEBAR_EXPAND_BUTTON,
]
: \Filament\View\PanelsIconAlias::SIDEBAR_EXPAND_BUTTON
"
icon-size="lg"
:label="__('filament-panels::layout.actions.sidebar.expand.label')"
x-cloak
x-data="{}"
x-on:click="$store.sidebar.open()"
x-show="! $store.sidebar.isOpen"
class="fi-sidebar-open-collapse-sidebar-btn"
/>
@endif
@if ((! $hasTopbar) && ($isSidebarCollapsibleOnDesktop || $isSidebarFullyCollapsibleOnDesktop))
<x-filament::icon-button
color="gray"
:icon="$isRtl ? \Filament\Support\Icons\Heroicon::OutlinedChevronRight : \Filament\Support\Icons\Heroicon::OutlinedChevronLeft"
{{-- @deprecated Use `PanelsIconAlias::SIDEBAR_COLLAPSE_BUTTON_RTL` instead of `PanelsIconAlias::SIDEBAR_COLLAPSE_BUTTON` for RTL. --}}
:icon-alias="
$isRtl
? [
\Filament\View\PanelsIconAlias::SIDEBAR_COLLAPSE_BUTTON_RTL,
\Filament\View\PanelsIconAlias::SIDEBAR_COLLAPSE_BUTTON,
]
: \Filament\View\PanelsIconAlias::SIDEBAR_COLLAPSE_BUTTON
"
icon-size="lg"
:label="__('filament-panels::layout.actions.sidebar.collapse.label')"
x-cloak
x-data="{}"
x-on:click="$store.sidebar.close()"
x-show="$store.sidebar.isOpen"
class="fi-sidebar-close-collapse-sidebar-btn"
/>
@endif
{{ \Filament\Support\Facades\FilamentView::renderHook(\Filament\View\PanelsRenderHook::SIDEBAR_LOGO_BEFORE) }}
<div
@if ($isSidebarCollapsibleOnDesktop || $isSidebarFullyCollapsibleOnDesktop)
x-show="$store.sidebar.isOpen"
@endif
class="fi-sidebar-header-logo-ctn"
>
@if ($homeUrl = filament()->getHomeUrl())
<a {{ \Filament\Support\generate_href_html($homeUrl) }}>
<x-filament-panels::logo />
</a>
@else
<x-filament-panels::logo />
@endif
</div>
{{ \Filament\Support\Facades\FilamentView::renderHook(\Filament\View\PanelsRenderHook::SIDEBAR_LOGO_AFTER) }}
</header>
</div>
@if (filament()->hasTenancy() && filament()->hasTenantMenu())
<x-filament-panels::tenant-menu />
@endif
@if (filament()->isGlobalSearchEnabled() && filament()->getGlobalSearchPosition() === \Filament\Enums\GlobalSearchPosition::Sidebar)
<div
@if ($isSidebarCollapsibleOnDesktop || $isSidebarFullyCollapsibleOnDesktop)
x-show="$store.sidebar.isOpen"
@endif
>
@livewire(Filament\Livewire\GlobalSearch::class)
</div>
@endif
<nav class="fi-sidebar-nav">
{{ \Filament\Support\Facades\FilamentView::renderHook(\Filament\View\PanelsRenderHook::SIDEBAR_NAV_START) }}
<ul class="fi-sidebar-nav-groups">
@foreach ($navigation as $group)
@php
$isGroupActive = $group->isActive();
$isGroupCollapsible = $group->isCollapsible();
$groupIcon = $group->getIcon();
$groupItems = $group->getItems();
$groupLabel = $group->getLabel();
$groupExtraSidebarAttributeBag = $group->getExtraSidebarAttributeBag();
@endphp
<x-filament-panels::sidebar.group
:active="$isGroupActive"
:collapsible="$isGroupCollapsible"
:icon="$groupIcon"
:items="$groupItems"
:label="$groupLabel"
:attributes="\Filament\Support\prepare_inherited_attributes($groupExtraSidebarAttributeBag)"
/>
@endforeach
</ul>
<script>
var collapsedGroups = JSON.parse(
localStorage.getItem('collapsedGroups'),
)
if (collapsedGroups === null || collapsedGroups === 'null') {
localStorage.setItem(
'collapsedGroups',
JSON.stringify(@js(
collect($navigation)
->filter(fn (\Filament\Navigation\NavigationGroup $group): bool => $group->isCollapsed())
->map(fn (\Filament\Navigation\NavigationGroup $group): string => $group->getLabel())
->values()
->all()
)),
)
}
collapsedGroups = JSON.parse(
localStorage.getItem('collapsedGroups'),
)
document
.querySelectorAll('.fi-sidebar-group')
.forEach((group) => {
if (
!collapsedGroups.includes(group.dataset.groupLabel)
) {
return
}
// Alpine.js loads too slow, so attempt to hide a
// collapsed sidebar group earlier.
group.querySelector(
'.fi-sidebar-group-items',
).style.display = 'none'
group.classList.add('fi-collapsed')
})
</script>
{{ \Filament\Support\Facades\FilamentView::renderHook(\Filament\View\PanelsRenderHook::SIDEBAR_NAV_END) }}
</nav>
@php
$isAuthenticated = filament()->auth()->check();
$hasDatabaseNotificationsInSidebar = filament()->hasDatabaseNotifications() && filament()->getDatabaseNotificationsPosition() === \Filament\Enums\DatabaseNotificationsPosition::Sidebar;
$hasUserMenuInSidebar = filament()->hasUserMenu() && filament()->getUserMenuPosition() === \Filament\Enums\UserMenuPosition::Sidebar;
$shouldRenderFooter = $isAuthenticated && ($hasDatabaseNotificationsInSidebar || $hasUserMenuInSidebar);
@endphp
@if ($shouldRenderFooter)
<div class="fi-sidebar-footer">
@if ($hasDatabaseNotificationsInSidebar)
@livewire(filament()->getDatabaseNotificationsLivewireComponent(), [
'lazy' => filament()->hasLazyLoadedDatabaseNotifications(),
])
@endif
@if ($hasUserMenuInSidebar)
<x-filament-panels::user-menu />
@endif
</div>
@endif
{{ \Filament\Support\Facades\FilamentView::renderHook(\Filament\View\PanelsRenderHook::SIDEBAR_FOOTER) }}
</aside>
{{-- format-ignore-end --}}
<x-filament-actions::modals />
</div>
@@ -0,0 +1,9 @@
<div>
<div class="fi-simple-user-menu-ctn">
<x-filament-panels::user-menu
:position="\Filament\Enums\UserMenuPosition::Topbar"
/>
</div>
<x-filament-actions::modals />
</div>
@@ -0,0 +1,269 @@
<div class="fi-topbar-ctn">
@php
$isRtl = __('filament-panels::layout.direction') === 'rtl';
$isSidebarCollapsibleOnDesktop = filament()->isSidebarCollapsibleOnDesktop();
$isSidebarFullyCollapsibleOnDesktop = filament()->isSidebarFullyCollapsibleOnDesktop();
$hasTopNavigation = filament()->hasTopNavigation();
$hasNavigation = filament()->hasNavigation();
$hasTenancy = filament()->hasTenancy();
@endphp
<nav class="fi-topbar">
{{ \Filament\Support\Facades\FilamentView::renderHook(\Filament\View\PanelsRenderHook::TOPBAR_START) }}
@if ($hasNavigation)
<x-filament::icon-button
color="gray"
:icon="\Filament\Support\Icons\Heroicon::OutlinedBars3"
:icon-alias="\Filament\View\PanelsIconAlias::TOPBAR_OPEN_SIDEBAR_BUTTON"
icon-size="lg"
:label="__('filament-panels::layout.actions.sidebar.expand.label')"
x-cloak
x-data="{}"
x-on:click="$store.sidebar.open()"
x-show="! $store.sidebar.isOpen"
class="fi-topbar-open-sidebar-btn"
/>
<x-filament::icon-button
color="gray"
:icon="\Filament\Support\Icons\Heroicon::OutlinedXMark"
:icon-alias="\Filament\View\PanelsIconAlias::TOPBAR_CLOSE_SIDEBAR_BUTTON"
icon-size="lg"
:label="__('filament-panels::layout.actions.sidebar.collapse.label')"
x-cloak
x-data="{}"
x-on:click="$store.sidebar.close()"
x-show="$store.sidebar.isOpen"
class="fi-topbar-close-sidebar-btn"
/>
@endif
<div class="fi-topbar-start">
@if ($isSidebarCollapsibleOnDesktop || $isSidebarFullyCollapsibleOnDesktop)
<div
x-show="$store.sidebar.isOpen || @js($isSidebarCollapsibleOnDesktop)"
class="fi-topbar-collapse-sidebar-btn-ctn"
>
@if ($isSidebarCollapsibleOnDesktop)
<x-filament::icon-button
color="gray"
:icon="$isRtl ? \Filament\Support\Icons\Heroicon::OutlinedChevronLeft : \Filament\Support\Icons\Heroicon::OutlinedChevronRight"
{{-- @deprecated Use `PanelsIconAlias::SIDEBAR_EXPAND_BUTTON_RTL` instead of `PanelsIconAlias::SIDEBAR_EXPAND_BUTTON` for RTL. --}}
:icon-alias="
$isRtl
? [
\Filament\View\PanelsIconAlias::SIDEBAR_EXPAND_BUTTON_RTL,
\Filament\View\PanelsIconAlias::SIDEBAR_EXPAND_BUTTON,
]
: \Filament\View\PanelsIconAlias::SIDEBAR_EXPAND_BUTTON
"
icon-size="lg"
:label="__('filament-panels::layout.actions.sidebar.expand.label')"
x-cloak
x-data="{}"
x-on:click="$store.sidebar.open()"
x-show="! $store.sidebar.isOpen"
class="fi-topbar-open-collapse-sidebar-btn"
/>
@endif
@if ($isSidebarCollapsibleOnDesktop || $isSidebarFullyCollapsibleOnDesktop)
<x-filament::icon-button
color="gray"
:icon="$isRtl ? \Filament\Support\Icons\Heroicon::OutlinedChevronRight : \Filament\Support\Icons\Heroicon::OutlinedChevronLeft"
{{-- @deprecated Use `PanelsIconAlias::SIDEBAR_COLLAPSE_BUTTON_RTL` instead of `PanelsIconAlias::SIDEBAR_COLLAPSE_BUTTON` for RTL. --}}
:icon-alias="
$isRtl
? [
\Filament\View\PanelsIconAlias::SIDEBAR_COLLAPSE_BUTTON_RTL,
\Filament\View\PanelsIconAlias::SIDEBAR_COLLAPSE_BUTTON,
]
: \Filament\View\PanelsIconAlias::SIDEBAR_COLLAPSE_BUTTON
"
icon-size="lg"
:label="__('filament-panels::layout.actions.sidebar.collapse.label')"
x-cloak
x-data="{}"
x-on:click="$store.sidebar.close()"
x-show="$store.sidebar.isOpen"
class="fi-topbar-close-collapse-sidebar-btn"
/>
@endif
</div>
@endif
{{ \Filament\Support\Facades\FilamentView::renderHook(\Filament\View\PanelsRenderHook::TOPBAR_LOGO_BEFORE) }}
@if ($homeUrl = filament()->getHomeUrl())
<a {{ \Filament\Support\generate_href_html($homeUrl) }}>
<x-filament-panels::logo />
</a>
@else
<x-filament-panels::logo />
@endif
{{ \Filament\Support\Facades\FilamentView::renderHook(\Filament\View\PanelsRenderHook::TOPBAR_LOGO_AFTER) }}
</div>
@if ($hasTopNavigation || (! $hasNavigation))
@if ($hasTenancy && filament()->hasTenantMenu())
<x-filament-panels::tenant-menu teleport />
@endif
@if ($hasNavigation)
@php
$navigation = filament()->getNavigation();
@endphp
<ul class="fi-topbar-nav-groups">
@foreach ($navigation as $group)
@php
$groupLabel = $group->getLabel();
$groupExtraTopbarAttributeBag = $group->getExtraTopbarAttributeBag();
$isGroupActive = $group->isActive();
$groupIcon = $group->getIcon();
@endphp
@if ($groupLabel)
<x-filament::dropdown
placement="bottom-start"
teleport
:attributes="\Filament\Support\prepare_inherited_attributes($groupExtraTopbarAttributeBag)"
>
<x-slot name="trigger">
<x-filament-panels::topbar.item
:active="$isGroupActive"
:icon="$groupIcon"
>
{{ $groupLabel }}
</x-filament-panels::topbar.item>
</x-slot>
@php
$lists = [];
foreach ($group->getItems() as $item) {
if ($childItems = $item->getChildItems()) {
$lists[] = [
$item,
...$childItems,
];
$lists[] = [];
continue;
}
if (empty($lists)) {
$lists[] = [$item];
continue;
}
$lists[count($lists) - 1][] = $item;
}
if (empty($lists[count($lists) - 1])) {
array_pop($lists);
}
@endphp
@foreach ($lists as $list)
<x-filament::dropdown.list>
@foreach ($list as $item)
@php
$isItemActive = $item->isActive();
$itemBadge = $item->getBadge();
$itemBadgeColor = $item->getBadgeColor();
$itemBadgeTooltip = $item->getBadgeTooltip();
$itemUrl = $item->getUrl();
$itemIcon = $isItemActive ? ($item->getActiveIcon() ?? $item->getIcon()) : $item->getIcon();
$shouldItemOpenUrlInNewTab = $item->shouldOpenUrlInNewTab();
$itemExtraAttributes = $item->getExtraAttributeBag();
@endphp
<x-filament::dropdown.list.item
:badge="$itemBadge"
:badge-color="$itemBadgeColor"
:badge-tooltip="$itemBadgeTooltip"
:color="$isItemActive ? 'primary' : 'gray'"
:href="$itemUrl"
:icon="$itemIcon"
tag="a"
:target="$shouldItemOpenUrlInNewTab ? '_blank' : null"
:attributes="\Filament\Support\prepare_inherited_attributes($itemExtraAttributes)"
>
{{ $item->getLabel() }}
</x-filament::dropdown.list.item>
@endforeach
</x-filament::dropdown.list>
@endforeach
</x-filament::dropdown>
@else
@foreach ($group->getItems() as $item)
@php
$isItemActive = $item->isActive();
$itemActiveIcon = $item->getActiveIcon();
$itemBadge = $item->getBadge();
$itemBadgeColor = $item->getBadgeColor();
$itemBadgeTooltip = $item->getBadgeTooltip();
$itemIcon = $item->getIcon();
$shouldItemOpenUrlInNewTab = $item->shouldOpenUrlInNewTab();
$itemUrl = $item->getUrl();
$itemExtraAttributes = $item->getExtraAttributeBag();
@endphp
<x-filament-panels::topbar.item
:active="$isItemActive"
:active-icon="$itemActiveIcon"
:badge="$itemBadge"
:badge-color="$itemBadgeColor"
:badge-tooltip="$itemBadgeTooltip"
:icon="$itemIcon"
:should-open-url-in-new-tab="$shouldItemOpenUrlInNewTab"
:url="$itemUrl"
:attributes="\Filament\Support\prepare_inherited_attributes($itemExtraAttributes)"
>
{{ $item->getLabel() }}
</x-filament-panels::topbar.item>
@endforeach
@endif
@endforeach
</ul>
@endif
@endif
<div
@if ($hasTenancy)
x-persist="topbar.end.panel-{{ filament()->getId() }}.tenant-{{ filament()->getTenant()?->getKey() }}"
@else
x-persist="topbar.end.panel-{{ filament()->getId() }}"
@endif
class="fi-topbar-end"
>
{{ \Filament\Support\Facades\FilamentView::renderHook(\Filament\View\PanelsRenderHook::GLOBAL_SEARCH_BEFORE) }}
@if (filament()->isGlobalSearchEnabled() && filament()->getGlobalSearchPosition() === \Filament\Enums\GlobalSearchPosition::Topbar)
@livewire(Filament\Livewire\GlobalSearch::class)
@endif
{{ \Filament\Support\Facades\FilamentView::renderHook(\Filament\View\PanelsRenderHook::GLOBAL_SEARCH_AFTER) }}
@if (filament()->auth()->check())
@if (filament()->hasDatabaseNotifications() && filament()->getDatabaseNotificationsPosition() === \Filament\Enums\DatabaseNotificationsPosition::Topbar)
@livewire(filament()->getDatabaseNotificationsLivewireComponent(), [
'lazy' => filament()->hasLazyLoadedDatabaseNotifications(),
])
@endif
@if (filament()->hasUserMenu() && filament()->getUserMenuPosition() === \Filament\Enums\UserMenuPosition::Topbar)
<x-filament-panels::user-menu />
@endif
@endif
</div>
{{ \Filament\Support\Facades\FilamentView::renderHook(\Filament\View\PanelsRenderHook::TOPBAR_END) }}
</nav>
<x-filament-actions::modals />
</div>