Label dan Navigation
Setiap Resource menurunkan singular label, plural label, dan sidebar entry dari nama model. Halaman ini membahas deklarasi yang dapat mengubah ketiganya serta bagaimana sidebar entry dibangun. Gunakan referensi ini ketika nama Resource terlihat salah, berada pada group yang salah, atau memang tidak seharusnya muncul di sidebar.
Tanpa deklarasi tambahan
use App\Models\BlogPost;
use PandaPanel\Resources\Resource;
final class BlogPostResource extends Resource
{
protected static string $model = BlogPost::class;
// ...
}2
3
4
5
6
7
8
9
| Pertanyaan | Jawaban | Diturunkan dari |
|---|---|---|
BlogPostResource::label() | Blog Post | Str::headline(class_basename($model)) |
BlogPostResource::pluralLabel() | Blog Posts | Str::plural() dari label |
BlogPostResource::slug() | blog-posts | Bentuk plural kebab-case dari basename model |
| Label sidebar entry | Blog Posts | Plural label |
| Icon sidebar entry | Tidak ada | — |
| Sidebar group | Tidak dikelompokkan | — |
Labels
protected static ?string $label = 'Article';
protected static ?string $pluralLabel = 'Articles';2
3
Singular label digunakan pada title create page (New Article), create button, subheading view page, dan success notification (Article created.). Plural label digunakan sebagai heading list page, breadcrumb, dan sidebar entry ketika navigation label tidak dideklarasikan.
Empat static method membacanya:
public static function defaultLabel(): string; // the class's own, before any panel configures it
public static function defaultPluralLabel(): string;
public static function label(): string; // as the current panel configured it
public static function pluralLabel(): string;2
3
4
Pasangan default* menjawab value asli yang dideklarasikan class. Pasangan tanpa prefix menanyakan konfigurasi current Panel terlebih dahulu. Perbedaan ini penting ketika class Resource yang sama diregistrasikan pada dua Panel. Lihat Per-panel configuration.
Deklarasikan $pluralLabel ketika Str::plural() tidak menghasilkan bentuk yang tepat, terutama untuk irregular noun dan acronym.
Sidebar entry
use BackedEnum;
protected static ?string $navigationLabel = 'Articles';
protected static ?string $navigationIcon = 'newspaper';
protected static ?string $activeNavigationIcon = 'newspaper-solid';
protected static string|BackedEnum|null $navigationGroup = 'Content';
protected static int $navigationSort = 20;
protected static bool $shouldRegisterNavigation = true;2
3
4
5
6
7
8
9
10
11
12
13
| Property | Type | Default | Efek |
|---|---|---|---|
$navigationLabel | ?string | Plural label | Teks sidebar entry |
$navigationIcon | ?string | null | Icon registry key |
$activeNavigationIcon | ?string | $navigationIcon | Icon alternatif ketika entry sedang aktif |
$navigationGroup | string|BackedEnum|null | null | Group sidebar tempat entry berada |
$navigationSort | int | 0 | Urutan di dalam group; nilai lebih kecil lebih dulu |
$shouldRegisterNavigation | bool | true | Menentukan apakah entry dibuat sama sekali |
Icon adalah registry key, bukan component path. Registry merupakan build-time allowlist: nama yang tidak dikenal di-resolve menjadi nothing, bukan error. Setelah menambahkan icon baru, build ulang registry agar bundle memasukkannya:
php artisan panel:icons # rewrite the registry from the source
php artisan panel:icons --check # fail if it is out of date, for CI2
Dua accessor bersifat public karena navigation builder dan global search sama-sama membutuhkannya:
public static function navigationIcon(): ?string;
public static function activeNavigationIcon(): ?string; // falls back to navigationIcon()2
Groups
Group dapat dinamai menggunakan string atau backed enum. Begitu lebih dari satu class menggunakan group yang sama, enum biasanya lebih aman: typo pada string membuat group kedua yang secara visual terlihat mirip dan diam-diam memecah sidebar, sedangkan typo pada enum case tidak dapat lolos compile.
use App\Enums\NavigationGroup;
protected static string|BackedEnum|null $navigationGroup = NavigationGroup::Content;2
3
Urutan group ditentukan oleh Panel. Group yang tidak disebut Panel ditambahkan kemudian secara alfabetis:
$panel->navigationGroups([
'Content',
NavigationGroup::System,
'Access' => 'System', // nests Access under System
]);2
3
4
5
Lihat Navigation groups.
Tidak menampilkan Resource di sidebar
protected static bool $shouldRegisterNavigation = false;Resource tetap memiliki route dan URL; hanya sidebar entry yang dihilangkan. Ini pilihan yang tepat untuk Resource yang selalu dibuka dari page lain dan tidak membutuhkan entry global di sidebar.
Ada tiga kondisi lain yang membuat entry tidak dibuat tanpa perlu deklarasi tambahan:
- Nested resource. Seluruh page-nya hanya ada di bawah parent record, sedangkan sidebar tidak memiliki parent yang sedang aktif. Karena itu tidak ada konsep "all posts" global yang dapat ditautkan.
- Resource tanpa page
index. Membuat sidebar entry akan menghasilkan link ke route yang tidak pernah didaftarkan. Kegagalan saat rendering sidebar akan merusak seluruh page pada Panel, bukan hanya Resource tersebut. - Resource yang tidak boleh dilihat user. Navigation builder melakukan filter menggunakan
canViewAny()sebelum pekerjaan lain, sehingga unauthorized entry bahkan tidak pernah sampai ke tahap badge evaluation.
Menyembunyikan entry hanyalah convenience UI, bukan security control. Route dan Action tetap melakukan authorization secara independen. Lihat Authorization.
navigationItem()
use PandaPanel\Contracts\PanelContract;
use PandaPanel\Support\NavigationItem;
public static function navigationItem(PanelContract $panel): ?NavigationItem2
3
4
Method inilah yang dipanggil navigation builder. Return value menjadi null pada tiga kondisi sebelumnya. Jika entry valid, method menghasilkan NavigationItem dengan seluruh field yang fallback ke value milik class, sehingga Panel hanya perlu mengubah hal yang memang berbeda.
Default Resource navigation item tidak memiliki badge. Tidak ada property $navigationBadge; override method untuk menambahkannya:
use App\Models\Article;
use PandaPanel\Contracts\PanelContract;
use PandaPanel\Support\NavigationItem;
public static function navigationItem(PanelContract $panel): ?NavigationItem
{
$item = parent::navigationItem($panel);
if ($item === null) {
return null;
}
return NavigationItem::make(
label: $item->label,
href: $item->href,
icon: $item->icon,
badge: static fn (): int => Article::query()->whereNull('published_at')->count(),
sort: $item->sort,
group: $item->group,
activeIcon: $item->activeIcon,
);
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Badge dalam bentuk Closure dievaluasi di server hanya untuk item yang sudah lolos authorization, dan hanya scalar result yang dikirim ke Vue.
Clusters
use App\Panels\Admin\Clusters\ContentCluster;
protected static ?string $cluster = ContentCluster::class;2
3
Membership dideklarasikan oleh member Resource, bukan disimpan sebagai daftar pada Cluster. Dengan begitu setiap class membawa sendiri informasi posisinya di Panel dan tidak ada dua list terpisah yang dapat saling tidak sinkron. Resource yang berada dalam Cluster ditampilkan di bawah Cluster, bukan sejajar dengannya. Cluster menjadi satu sidebar entry yang dapat diexpand dan mengarah ke member pertama yang memang boleh dilihat user.
Path Resource mendapat prefix dari Cluster, sedangkan route name tidak berubah. panel.admin.resources.articles.index tetap menjadi route name yang sama, sehingga seluruh pemanggilan Resource::url() yang sudah ada tetap berfungsi. Lihat Clusters.
Memberi nama satu record
Breadcrumb, page heading, sub-navigation, dan search result menanyakan kepada Resource apa nama satu record:
protected static ?string $recordTitleAttribute = 'title';public static function recordTitle(Model $record): stringAttribute default adalah name. Jika value bukan scalar, method fallback ke primary key. Lihat Model binding.
Record sub-navigation
use PandaPanel\Enums\SubNavigationPosition;
protected static ?SubNavigationPosition $subNavigationPosition = SubNavigationPosition::Start;2
3
Top tampil seperti tabs. Start dan End tampil sebagai rail di sisi content. null, yang menjadi default, mengikuti posisi yang dikonfigurasi Panel; Resource hanya perlu menentukan value jika ingin berbeda dari Resource lain.
public static function subNavigationPosition(): ?SubNavigationPositionLink sub-navigation dibangun dari map pages() milik Resource: view dan edit page ketika keduanya dideklarasikan serta di-authorize untuk record tersebut, ditambah setiap ManageRelatedRecords page. Satu link saja bukan navigation, sehingga record yang hanya memiliki satu destination yang dapat diakses tidak menampilkan navigation bar. Lihat Sub-navigation.
Override per Panel
Seluruh navigation field dapat didefinisikan ulang untuk satu Panel tanpa mengubah class:
use PandaPanel\Resources\ResourceConfiguration;
$panel->resources([
ResourceConfiguration::for(ArticleResource::class)
->label('Story')
->pluralLabel('Stories')
->navigationLabel('Newsroom')
->navigationIcon('newspaper')
->navigationGroup('Editorial')
->navigationSort(5)
->registerNavigation(false),
]);2
3
4
5
6
7
8
9
10
11
12
Lihat Per-panel configuration.
Catatan penting
- Navigation label fallback ke plural label, bukan singular label. Mendeklarasikan
$labelsaja tetap membuat sidebar menggunakan bentuk plural. $navigationSorthanya mengatur urutan di dalam satu group, bukan seluruh sidebar. Urutan antar-group ditentukan konfigurasi Panel.- Nama icon yang tidak terdaftar tidak merender apa pun dan tidak melempar error. Registry sengaja menjadi allowlist. Jalankan
panel:iconssetelah menambahkan icon baru. label()danpluralLabel()mempertimbangkan current Panel. Jika dipanggil dari console command tanpa current Panel, keduanya mengembalikan default milik class.- Navigation dibangun ulang setiap request. Authorization result, badge, dan active state bergantung pada user dan URL sehingga tidak di-cache bersama manifest Panel.