Layout в MoonShine представляет собой набор компонентов, формирующих структуру страницы административной панели.
Каждый элемент страницы, включая HTML теги, является компонентом MoonShine.
Это обеспечивает высокую степень гибкости и возможность кастомизации.
При установке MoonShine, публикуется шаблон по умолчанию app/MoonShine/Layouts/AppLayout.php и регистрируется в конфигурационном файле.
Вы можете:
- Модифицировать существующий шаблон,
- Создать новый шаблон,
- Применять разные шаблоны для различных страниц.
Полный список компонентов ищите в разделе Компоненты.
Как можно заметить, компонентов огромное количество, и для удобства мы объединили их в группы, чтобы вы могли удобно переопределять только те группы, которые требуются.
use MoonShine\Laravel\Layouts\AppLayout;
final class MoonShineLayout extends AppLayout
{
protected function getFooterMenu(): array
{
return [
'https://example.com' => 'Custom link',
];
}
protected function getFooterCopyright(): string
{
return 'MoonShine';
}
public function build(): Layout
{
return parent::build();
}
}
namespaces
use MoonShine\Laravel\Layouts\AppLayout;
final class MoonShineLayout extends AppLayout
{
// ...
protected function getFooterMenu(): array
{
return [
'https://example.com' => 'Custom link',
];
}
protected function getFooterCopyright(): string
{
return 'MoonShine';
}
public function build(): Layout
{
return parent::build();
}
}
use MoonShine\Laravel\Layouts\AppLayout;
final class MoonShineLayout extends AppLayout
{
// ...
protected function getFooterMenu(): array
{
return [
'https://example.com' => 'Custom link',
];
}
protected function getFooterCopyright(): string
{
return 'MoonShine';
}
public function build(): Layout
{
return parent::build();
}
}
namespaces
use MoonShine\Laravel\Layouts\AppLayout;
final class MoonShineLayout extends AppLayout
{
// ...
protected function getFooterMenu(): array
{
return [
'https://example.com' => 'Custom link',
];
}
protected function getFooterCopyright(): string
{
return 'MoonShine';
}
public function build(): Layout
{
return parent::build();
}
}
use MoonShine\Laravel\Layouts\AppLayout;
final class MoonShineLayout extends AppLayout
{
// ...
protected function getFooterMenu(): array
{
return [
'https://example.com' => 'Custom link',
];
}
protected function getFooterCopyright(): string
{
return 'MoonShine';
}
public function build(): Layout
{
return parent::build();
}
}
В примере выше, с помощью методов getFooterMenu() и getFooterCopyright(), мы переопределили вывод меню в футере и copyright.
Доступные быстрые методы:
Переопределить компонент Head
protected function getHeadComponent(bool $withAssetsFragment = true): Head
{
return Head::make([
]);
}
protected function getHeadComponent(bool $withAssetsFragment = true): Head
{
return Head::make([
// ...
]);
}
protected function getHeadComponent(bool $withAssetsFragment = true): Head
{
return Head::make([
// ...
]);
}
protected function getHeadComponent(bool $withAssetsFragment = true): Head
{
return Head::make([
// ...
]);
}
protected function getHeadComponent(bool $withAssetsFragment = true): Head
{
return Head::make([
// ...
]);
}
Переопределить компонент Logo
protected function getLogoComponent(): Logo
{
return Logo::make(
$this->getHomeUrl(),
$this->getLogo(),
$this->getLogo(small: true),
);
}
protected function getLogoComponent(): Logo
{
return Logo::make(
$this->getHomeUrl(),
$this->getLogo(),
$this->getLogo(small: true),
);
}
protected function getLogoComponent(): Logo
{
return Logo::make(
$this->getHomeUrl(),
$this->getLogo(),
$this->getLogo(small: true),
);
}
protected function getLogoComponent(): Logo
{
return Logo::make(
$this->getHomeUrl(),
$this->getLogo(),
$this->getLogo(small: true),
);
}
protected function getLogoComponent(): Logo
{
return Logo::make(
$this->getHomeUrl(),
$this->getLogo(),
$this->getLogo(small: true),
);
}
Переопределить компонент Sidebar
protected function getSidebarComponent(): Sidebar
{
return Sidebar::make([
]);
}
protected function getSidebarComponent(): Sidebar
{
return Sidebar::make([
// ...
]);
}
protected function getSidebarComponent(): Sidebar
{
return Sidebar::make([
// ...
]);
}
protected function getSidebarComponent(): Sidebar
{
return Sidebar::make([
// ...
]);
}
protected function getSidebarComponent(): Sidebar
{
return Sidebar::make([
// ...
]);
}
Переопределить компонент Header
protected function getHeaderComponent(): Header
{
Header::make([
]);
}
protected function getHeaderComponent(): Header
{
Header::make([
// ...
]);
}
protected function getHeaderComponent(): Header
{
Header::make([
// ...
]);
}
protected function getHeaderComponent(): Header
{
Header::make([
// ...
]);
}
protected function getHeaderComponent(): Header
{
Header::make([
// ...
]);
}
Переопределить или интегрировать компонент TopBar
protected function getTopBarComponent(): Topbar
{
Topbar::make([
]);
}
protected function getTopBarComponent(): Topbar
{
Topbar::make([
// ...
]);
}
protected function getTopBarComponent(): Topbar
{
Topbar::make([
// ...
]);
}
protected function getTopBarComponent(): Topbar
{
Topbar::make([
// ...
]);
}
protected function getTopBarComponent(): Topbar
{
Topbar::make([
// ...
]);
}
Переопределить компонент Footer
protected function getFooterComponent(): Footer
{
Footer::make([
]);
}
protected function getFooterComponent(): Footer
{
Footer::make([
// ...
]);
}
protected function getFooterComponent(): Footer
{
Footer::make([
// ...
]);
}
protected function getFooterComponent(): Footer
{
Footer::make([
// ...
]);
}
protected function getFooterComponent(): Footer
{
Footer::make([
// ...
]);
}
Переопределить компонент Profile
protected function getProfileComponent(): Profile
{
return Profile::make();
}
protected function getProfileComponent(): Profile
{
return Profile::make();
}
protected function getProfileComponent(): Profile
{
return Profile::make();
}
protected function getProfileComponent(): Profile
{
return Profile::make();
}
protected function getProfileComponent(): Profile
{
return Profile::make();
}
Переопределить содержимое компонента Content
protected function getContentComponents(): array
{
}
protected function getContentComponents(): array
{
// ...
}
protected function getContentComponents(): array
{
// ...
}
protected function getContentComponents(): array
{
// ...
}
protected function getContentComponents(): array
{
// ...
}
Content::make(
$this->getContentComponents()
)
Content::make(
$this->getContentComponents()
)
Content::make(
$this->getContentComponents()
)
Content::make(
$this->getContentComponents()
)
Content::make(
$this->getContentComponents()
)
Путь до логотипа
protected function getLogo(bool $small = false): string
{
}
protected function getLogo(bool $small = false): string
{
// ...
}
protected function getLogo(bool $small = false): string
{
// ...
}
protected function getLogo(bool $small = false): string
{
// ...
}
protected function getLogo(bool $small = false): string
{
// ...
}
URL главной страницы
protected function getHomeUrl(): string
{
}
protected function getHomeUrl(): string
{
// ...
}
protected function getHomeUrl(): string
{
// ...
}
protected function getHomeUrl(): string
{
// ...
}
protected function getHomeUrl(): string
{
// ...
}
Упрощенное отображение контента
Вы можете убрать обводку и фон у контентной части страницы, установив свойство $contentSimpled = true в вашем Layout:
protected bool $contentSimpled = true;
protected bool $contentSimpled = true; // default false
protected bool $contentSimpled = true; // default false
protected bool $contentSimpled = true; // default false
protected bool $contentSimpled = true; // default false
Отображение контента по центру
По умолчанию контент страницы занимает всю ширину экрана. Чтобы разместить его в центрированном контейнере фиксированной ширины, установите свойство $contentCentered = true в вашем Layout.
protected bool $contentCentered = true;
protected bool $contentCentered = true; // default false
protected bool $contentCentered = true; // default false
protected bool $contentCentered = true; // default false
protected bool $contentCentered = true; // default false
С помощью "слотов" вы можете быстро добавить компоненты в Sidebar или Topbar.
protected function sidebarSlot(): array
{
return [
Search::make()->enabled(),
];
}
protected function sidebarTopSlot(): array
{
return [
Notifications::make(),
];
}
protected function topBarSlot(): array
{
return [
];
}
protected function sidebarSlot(): array
{
return [
Search::make()->enabled(),
// ...
];
}
protected function sidebarTopSlot(): array
{
return [
Notifications::make(),
// ...
];
}
protected function topBarSlot(): array
{
return [
// ...
];
}
protected function sidebarSlot(): array
{
return [
Search::make()->enabled(),
// ...
];
}
protected function sidebarTopSlot(): array
{
return [
Notifications::make(),
// ...
];
}
protected function topBarSlot(): array
{
return [
// ...
];
}
protected function sidebarSlot(): array
{
return [
Search::make()->enabled(),
// ...
];
}
protected function sidebarTopSlot(): array
{
return [
Notifications::make(),
// ...
];
}
protected function topBarSlot(): array
{
return [
// ...
];
}
protected function sidebarSlot(): array
{
return [
Search::make()->enabled(),
// ...
];
}
protected function sidebarTopSlot(): array
{
return [
Notifications::make(),
// ...
];
}
protected function topBarSlot(): array
{
return [
// ...
];
}
Вы также можете создать собственный шаблон со своим набором удобных методов для дальнейшего удобного взаимодействия.
В стандартном Layout компоненты Sidebar, Topbar и Mobilebar оформлены в темных цветах.
Но если добавить в них другие компоненты, они будут меняться в зависимости от выбранной темы, что приведёт к некорректному их отображению.
Чтобы избежать такого поведения, можно принудительно перевести их в тёмный режим добавив класс 'dark'.
$this->getSidebarComponent()->class('dark'),
$this->getTopBarComponent()->class('dark'),
MobileBar::make([
])->class('dark'),
$this->getSidebarComponent()->class('dark'),
$this->getTopBarComponent()->class('dark'),
MobileBar::make([
// ...
])->class('dark'),
$this->getSidebarComponent()->class('dark'),
$this->getTopBarComponent()->class('dark'),
MobileBar::make([
// ...
])->class('dark'),
$this->getSidebarComponent()->class('dark'),
$this->getTopBarComponent()->class('dark'),
MobileBar::make([
// ...
])->class('dark'),
$this->getSidebarComponent()->class('dark'),
$this->getTopBarComponent()->class('dark'),
MobileBar::make([
// ...
])->class('dark'),
Чтобы создать еще один шаблон, воспользуйтесь командой:
php artisan moonshine:layout
php artisan moonshine:layout
php artisan moonshine:layout
php artisan moonshine:layout
php artisan moonshine:layout
О всех поддерживаемых опциях можно узнать в разделе Команды.
По умолчанию страницы используют шаблон отображения AppLayout.
Но вы можете изменить его на собственный шаблон, просто заменив значение свойства $layout.
Подробнее про страницы читайте в разделе Страница.
use App\MoonShine\Layouts\MyLayout;
use MoonShine\Laravel\Pages\Page;
class CustomPage extends Page
{
protected ?string $layout = MyLayout::class;
}
namespaces
use App\MoonShine\Layouts\MyLayout;
use MoonShine\Laravel\Pages\Page;
class CustomPage extends Page
{
protected ?string $layout = MyLayout::class;
// ...
}
use App\MoonShine\Layouts\MyLayout;
use MoonShine\Laravel\Pages\Page;
class CustomPage extends Page
{
protected ?string $layout = MyLayout::class;
// ...
}
namespaces
use App\MoonShine\Layouts\MyLayout;
use MoonShine\Laravel\Pages\Page;
class CustomPage extends Page
{
protected ?string $layout = MyLayout::class;
// ...
}
use App\MoonShine\Layouts\MyLayout;
use MoonShine\Laravel\Pages\Page;
class CustomPage extends Page
{
protected ?string $layout = MyLayout::class;
// ...
}
Каждый шаблон может иметь свой набор стилей и скриптов, определяемых через метод assets().
use MoonShine\Laravel\Layouts\AppLayout;
use MoonShine\AssetManager\Css;
final class MyLayout extends AppLayout
{
protected function assets(): array
{
return [
...parent::assets(),
Css::make('/vendor/moonshine/assets/minimalistic.css')->defer(),
];
}
}
namespaces
use MoonShine\Laravel\Layouts\AppLayout;
use MoonShine\AssetManager\Css;
final class MyLayout extends AppLayout
{
// ...
protected function assets(): array
{
return [
...parent::assets(),
Css::make('/vendor/moonshine/assets/minimalistic.css')->defer(),
];
}
// ...
}
use MoonShine\Laravel\Layouts\AppLayout;
use MoonShine\AssetManager\Css;
final class MyLayout extends AppLayout
{
// ...
protected function assets(): array
{
return [
...parent::assets(),
Css::make('/vendor/moonshine/assets/minimalistic.css')->defer(),
];
}
// ...
}
namespaces
use MoonShine\Laravel\Layouts\AppLayout;
use MoonShine\AssetManager\Css;
final class MyLayout extends AppLayout
{
// ...
protected function assets(): array
{
return [
...parent::assets(),
Css::make('/vendor/moonshine/assets/minimalistic.css')->defer(),
];
}
// ...
}
use MoonShine\Laravel\Layouts\AppLayout;
use MoonShine\AssetManager\Css;
final class MyLayout extends AppLayout
{
// ...
protected function assets(): array
{
return [
...parent::assets(),
Css::make('/vendor/moonshine/assets/minimalistic.css')->defer(),
];
}
// ...
}
За более подробной информацией обратитесь в раздел Assets.
protected function assets(): array
{
return [
...parent::assets(),
InlineCss::make(<<<'Style'
:root {
--spacing: 0.15rem;
}
Style),
];
}
protected function assets(): array
{
return [
...parent::assets(),
InlineCss::make(<<<'Style'
:root {
--spacing: 0.15rem;
}
Style),
];
}
protected function assets(): array
{
return [
...parent::assets(),
InlineCss::make(<<<'Style'
:root {
--spacing: 0.15rem;
}
Style),
];
}
protected function assets(): array
{
return [
...parent::assets(),
InlineCss::make(<<<'Style'
:root {
--spacing: 0.15rem;
}
Style),
];
}
protected function assets(): array
{
return [
...parent::assets(),
InlineCss::make(<<<'Style'
:root {
--spacing: 0.15rem;
}
Style),
];
}
Вы можете заменить набор favicons в шаблоне через переопределение метода getFaviconComponent().
use MoonShine\Laravel\Layouts\AppLayout;
final class MyLayout extends AppLayout
{
protected function getFaviconComponent(): Favicon
{
return parent::getFaviconComponent()->customAssets([
'apple-touch' => 'favicon_path',
'32' => 'favicon_path',
'16' => 'favicon_path',
'safari-pinned-tab' => 'favicon_path',
'web-manifest' => 'favicon_path',
]);
}
}
namespaces
use MoonShine\Laravel\Layouts\AppLayout;
final class MyLayout extends AppLayout
{
// ...
protected function getFaviconComponent(): Favicon
{
return parent::getFaviconComponent()->customAssets([
'apple-touch' => 'favicon_path',
'32' => 'favicon_path',
'16' => 'favicon_path',
'safari-pinned-tab' => 'favicon_path',
'web-manifest' => 'favicon_path',
]);
}
}
use MoonShine\Laravel\Layouts\AppLayout;
final class MyLayout extends AppLayout
{
// ...
protected function getFaviconComponent(): Favicon
{
return parent::getFaviconComponent()->customAssets([
'apple-touch' => 'favicon_path',
'32' => 'favicon_path',
'16' => 'favicon_path',
'safari-pinned-tab' => 'favicon_path',
'web-manifest' => 'favicon_path',
]);
}
}
namespaces
use MoonShine\Laravel\Layouts\AppLayout;
final class MyLayout extends AppLayout
{
// ...
protected function getFaviconComponent(): Favicon
{
return parent::getFaviconComponent()->customAssets([
'apple-touch' => 'favicon_path',
'32' => 'favicon_path',
'16' => 'favicon_path',
'safari-pinned-tab' => 'favicon_path',
'web-manifest' => 'favicon_path',
]);
}
}
use MoonShine\Laravel\Layouts\AppLayout;
final class MyLayout extends AppLayout
{
// ...
protected function getFaviconComponent(): Favicon
{
return parent::getFaviconComponent()->customAssets([
'apple-touch' => 'favicon_path',
'32' => 'favicon_path',
'16' => 'favicon_path',
'safari-pinned-tab' => 'favicon_path',
'web-manifest' => 'favicon_path',
]);
}
}
Для каждого шаблона можно объявить список пунктов меню через метод menu(), которые автоматически будут переданы в компонент Menu.
use MoonShine\Laravel\Layouts\AppLayout;
use MoonShine\MenuManager\MenuItem;
final class MyLayout extends AppLayout
{
protected function menu(): array
{
return [
...parent::menu(),
MenuItem::make(ArticleResource::class),
];
}
}
namespaces
use MoonShine\Laravel\Layouts\AppLayout;
use MoonShine\MenuManager\MenuItem;
final class MyLayout extends AppLayout
{
// ...
protected function menu(): array
{
return [
...parent::menu(),
MenuItem::make(ArticleResource::class),
];
}
}
use MoonShine\Laravel\Layouts\AppLayout;
use MoonShine\MenuManager\MenuItem;
final class MyLayout extends AppLayout
{
// ...
protected function menu(): array
{
return [
...parent::menu(),
MenuItem::make(ArticleResource::class),
];
}
}
namespaces
use MoonShine\Laravel\Layouts\AppLayout;
use MoonShine\MenuManager\MenuItem;
final class MyLayout extends AppLayout
{
// ...
protected function menu(): array
{
return [
...parent::menu(),
MenuItem::make(ArticleResource::class),
];
}
}
use MoonShine\Laravel\Layouts\AppLayout;
use MoonShine\MenuManager\MenuItem;
final class MyLayout extends AppLayout
{
// ...
protected function menu(): array
{
return [
...parent::menu(),
MenuItem::make(ArticleResource::class),
];
}
}
За более подробной информацией обратитесь в раздел Меню.
Вы также можете не пользоваться методом menu(), а передать список вручную в компонент Menu.
По умолчанию MoonShine имеет компонент верхнего меню, который можно использовать вместо Sidebar или совместно с ним.
Чтобы заменить Sidebar на TopBar, переопределите метод build() и заменит в нём вызов метода getSidebarComponent() на getTopBarComponent().
Если вы хотите оставить и Sidebar и TopBar одновременно, то обязательно соблюдайте очередность, первым должен идти TopBar.
В Moonshine "из коробки" доступна поддержка двух тем оформления — светлой и тёмной.
По умолчанию используется тема, заданная в системе, либо светлая, если определить не удалось.
Если вы хотите, чтобы тёмная тема всегда была включена, переопределите метод isAlwaysDark() и верните true. Переключатель тем при этом отображаться не будет.
protected function isAlwaysDark(): bool
{
return true;
}
protected function isAlwaysDark(): bool
{
return true;
}
protected function isAlwaysDark(): bool
{
return true;
}
protected function isAlwaysDark(): bool
{
return true;
}
protected function isAlwaysDark(): bool
{
return true;
}
Чтобы убрать переключатель тем и оставить только светлую тему, переопределите метод hasThemes() и верните false.
protected function hasThemes(): bool
{
return false;
}
protected function hasThemes(): bool
{
return false;
}
protected function hasThemes(): bool
{
return false;
}
protected function hasThemes(): bool
{
return false;
}
protected function hasThemes(): bool
{
return false;
}
Каждый шаблон может иметь собственную цветовую схему.
Самый простой способ задать её — указать реализацию PaletteContract в свойстве $palette:
use App\MoonShine\Palettes\CorporatePalette;
use MoonShine\Laravel\Layouts\AppLayout;
final class MyLayout extends AppLayout
{
protected ?string $palette = CorporatePalette::class;
}
use App\MoonShine\Palettes\CorporatePalette;
use MoonShine\Laravel\Layouts\AppLayout;
final class MyLayout extends AppLayout
{
protected ?string $palette = CorporatePalette::class;
}
use App\MoonShine\Palettes\CorporatePalette;
use MoonShine\Laravel\Layouts\AppLayout;
final class MyLayout extends AppLayout
{
protected ?string $palette = CorporatePalette::class;
}
use App\MoonShine\Palettes\CorporatePalette;
use MoonShine\Laravel\Layouts\AppLayout;
final class MyLayout extends AppLayout
{
protected ?string $palette = CorporatePalette::class;
}
use App\MoonShine\Palettes\CorporatePalette;
use MoonShine\Laravel\Layouts\AppLayout;
final class MyLayout extends AppLayout
{
protected ?string $palette = CorporatePalette::class;
}
За более подробной информацией обратитесь в раздел Палитры.
Если оставить $palette равным null, будет использоваться значение из config('moonshine.palette').
Если требуется полное управление, переопределите метод colors():
use MoonShine\Laravel\Layouts\AppLayout;
use MoonShine\Contracts\ColorManager\ColorManagerContract;
final class MyLayout extends AppLayout
{
protected function colors(ColorManagerContract $colorManager): void
{
$colorManager
->primary('oklch(65% 0.18 264)')
->secondary('oklch(70% 0.14 230)')
->bulkAssign([
'theme' => [
'body' => '0 0 0',
50 => '0.99 0 0',
100 => '0.98 0 0',
900 => '0.90 0 0',
],
])
->successBg('oklch(63.9% 0.218 142.495)')
->warningBg('oklch(80.88% 0.170358 75.3501)')
->errorBg('oklch(58.9% 0.214 26.855)')
->infoBg('oklch(60.1% 0.219 257.63)');
$colorManager
->set('body', '0.2 0.0168 274.32', dark: true)
->theme([
'body' => '1 0 0',
'stroke' => '1 0 0 / 10%',
'default' => '0.24 0.0168 274.32',
900 => '0.39 0.025 274.32',
], dark: true)
->successBg('0.639 0.218 142.495', dark: true)
->warningBg('0.898 0.177 96.726', dark: true)
->errorBg('0.589 0.214 26.855', dark: true)
->infoBg('0.601 0.219 257.63', dark: true);
}
}
namespaces
use MoonShine\Laravel\Layouts\AppLayout;
use MoonShine\Contracts\ColorManager\ColorManagerContract;
final class MyLayout extends AppLayout
{
protected function colors(ColorManagerContract $colorManager): void
{
$colorManager
->primary('oklch(65% 0.18 264)')
->secondary('oklch(70% 0.14 230)')
->bulkAssign([
'theme' => [
'body' => '0 0 0',
50 => '0.99 0 0',
100 => '0.98 0 0',
900 => '0.90 0 0',
],
])
->successBg('oklch(63.9% 0.218 142.495)')
->warningBg('oklch(80.88% 0.170358 75.3501)')
->errorBg('oklch(58.9% 0.214 26.855)')
->infoBg('oklch(60.1% 0.219 257.63)');
$colorManager
->set('body', '0.2 0.0168 274.32', dark: true)
->theme([
'body' => '1 0 0',
'stroke' => '1 0 0 / 10%',
'default' => '0.24 0.0168 274.32',
900 => '0.39 0.025 274.32',
], dark: true)
->successBg('0.639 0.218 142.495', dark: true)
->warningBg('0.898 0.177 96.726', dark: true)
->errorBg('0.589 0.214 26.855', dark: true)
->infoBg('0.601 0.219 257.63', dark: true);
}
}
use MoonShine\Laravel\Layouts\AppLayout;
use MoonShine\Contracts\ColorManager\ColorManagerContract;
final class MyLayout extends AppLayout
{
protected function colors(ColorManagerContract $colorManager): void
{
$colorManager
->primary('oklch(65% 0.18 264)')
->secondary('oklch(70% 0.14 230)')
->bulkAssign([
'theme' => [
'body' => '0 0 0',
50 => '0.99 0 0',
100 => '0.98 0 0',
900 => '0.90 0 0',
],
])
->successBg('oklch(63.9% 0.218 142.495)')
->warningBg('oklch(80.88% 0.170358 75.3501)')
->errorBg('oklch(58.9% 0.214 26.855)')
->infoBg('oklch(60.1% 0.219 257.63)');
$colorManager
->set('body', '0.2 0.0168 274.32', dark: true)
->theme([
'body' => '1 0 0',
'stroke' => '1 0 0 / 10%',
'default' => '0.24 0.0168 274.32',
900 => '0.39 0.025 274.32',
], dark: true)
->successBg('0.639 0.218 142.495', dark: true)
->warningBg('0.898 0.177 96.726', dark: true)
->errorBg('0.589 0.214 26.855', dark: true)
->infoBg('0.601 0.219 257.63', dark: true);
}
}
namespaces
use MoonShine\Laravel\Layouts\AppLayout;
use MoonShine\Contracts\ColorManager\ColorManagerContract;
final class MyLayout extends AppLayout
{
protected function colors(ColorManagerContract $colorManager): void
{
$colorManager
->primary('oklch(65% 0.18 264)')
->secondary('oklch(70% 0.14 230)')
->bulkAssign([
'theme' => [
'body' => '0 0 0',
50 => '0.99 0 0',
100 => '0.98 0 0',
900 => '0.90 0 0',
],
])
->successBg('oklch(63.9% 0.218 142.495)')
->warningBg('oklch(80.88% 0.170358 75.3501)')
->errorBg('oklch(58.9% 0.214 26.855)')
->infoBg('oklch(60.1% 0.219 257.63)');
$colorManager
->set('body', '0.2 0.0168 274.32', dark: true)
->theme([
'body' => '1 0 0',
'stroke' => '1 0 0 / 10%',
'default' => '0.24 0.0168 274.32',
900 => '0.39 0.025 274.32',
], dark: true)
->successBg('0.639 0.218 142.495', dark: true)
->warningBg('0.898 0.177 96.726', dark: true)
->errorBg('0.589 0.214 26.855', dark: true)
->infoBg('0.601 0.219 257.63', dark: true);
}
}
use MoonShine\Laravel\Layouts\AppLayout;
use MoonShine\Contracts\ColorManager\ColorManagerContract;
final class MyLayout extends AppLayout
{
protected function colors(ColorManagerContract $colorManager): void
{
$colorManager
->primary('oklch(65% 0.18 264)')
->secondary('oklch(70% 0.14 230)')
->bulkAssign([
'theme' => [
'body' => '0 0 0',
50 => '0.99 0 0',
100 => '0.98 0 0',
900 => '0.90 0 0',
],
])
->successBg('oklch(63.9% 0.218 142.495)')
->warningBg('oklch(80.88% 0.170358 75.3501)')
->errorBg('oklch(58.9% 0.214 26.855)')
->infoBg('oklch(60.1% 0.219 257.63)');
$colorManager
->set('body', '0.2 0.0168 274.32', dark: true)
->theme([
'body' => '1 0 0',
'stroke' => '1 0 0 / 10%',
'default' => '0.24 0.0168 274.32',
900 => '0.39 0.025 274.32',
], dark: true)
->successBg('0.639 0.218 142.495', dark: true)
->warningBg('0.898 0.177 96.726', dark: true)
->errorBg('0.589 0.214 26.855', dark: true)
->infoBg('0.601 0.219 257.63', dark: true);
}
}
По умолчанию в базовом шаблоне отдельные его части находятся внутри компонентов Fragment. За счёт этого вы можете обновлять их без перезагрузки страницы с помощью событий JSEvents.
Доступные фрагменты:
sidebar-top - Верхняя часть бокового меню (логотип и переключатель темы оформления),
sidebar-content - Контентная часть бокового меню,
topbar-logo - Логотип в верхнем меню,
topbar-menu - Пункты верхнего меню,
topbar-actions - Блок действий в верхнем меню,
assets - Набор стилей и скриптов.
Пример обновления всех фрагментов шаблона из метода контроллера:
public function saveElement(CrudRequestContract $request): JsonResponse
{
return JsonResponse::make()->events([
AlpineJs::event(JsEvent::FRAGMENT_UPDATED, 'sidebar-top'),
AlpineJs::event(JsEvent::FRAGMENT_UPDATED, 'sidebar-content'),
AlpineJs::event(JsEvent::FRAGMENT_UPDATED, 'topbar-logo'),
AlpineJs::event(JsEvent::FRAGMENT_UPDATED, 'topbar-menu'),
AlpineJs::event(JsEvent::FRAGMENT_UPDATED, 'topbar-actions'),
AlpineJs::event(JsEvent::FRAGMENT_UPDATED, 'assets'),
]);
}
public function saveElement(CrudRequestContract $request): JsonResponse
{
//...
return JsonResponse::make()->events([
AlpineJs::event(JsEvent::FRAGMENT_UPDATED, 'sidebar-top'),
AlpineJs::event(JsEvent::FRAGMENT_UPDATED, 'sidebar-content'),
AlpineJs::event(JsEvent::FRAGMENT_UPDATED, 'topbar-logo'),
AlpineJs::event(JsEvent::FRAGMENT_UPDATED, 'topbar-menu'),
AlpineJs::event(JsEvent::FRAGMENT_UPDATED, 'topbar-actions'),
AlpineJs::event(JsEvent::FRAGMENT_UPDATED, 'assets'),
]);
}
public function saveElement(CrudRequestContract $request): JsonResponse
{
//...
return JsonResponse::make()->events([
AlpineJs::event(JsEvent::FRAGMENT_UPDATED, 'sidebar-top'),
AlpineJs::event(JsEvent::FRAGMENT_UPDATED, 'sidebar-content'),
AlpineJs::event(JsEvent::FRAGMENT_UPDATED, 'topbar-logo'),
AlpineJs::event(JsEvent::FRAGMENT_UPDATED, 'topbar-menu'),
AlpineJs::event(JsEvent::FRAGMENT_UPDATED, 'topbar-actions'),
AlpineJs::event(JsEvent::FRAGMENT_UPDATED, 'assets'),
]);
}
public function saveElement(CrudRequestContract $request): JsonResponse
{
//...
return JsonResponse::make()->events([
AlpineJs::event(JsEvent::FRAGMENT_UPDATED, 'sidebar-top'),
AlpineJs::event(JsEvent::FRAGMENT_UPDATED, 'sidebar-content'),
AlpineJs::event(JsEvent::FRAGMENT_UPDATED, 'topbar-logo'),
AlpineJs::event(JsEvent::FRAGMENT_UPDATED, 'topbar-menu'),
AlpineJs::event(JsEvent::FRAGMENT_UPDATED, 'topbar-actions'),
AlpineJs::event(JsEvent::FRAGMENT_UPDATED, 'assets'),
]);
}
public function saveElement(CrudRequestContract $request): JsonResponse
{
//...
return JsonResponse::make()->events([
AlpineJs::event(JsEvent::FRAGMENT_UPDATED, 'sidebar-top'),
AlpineJs::event(JsEvent::FRAGMENT_UPDATED, 'sidebar-content'),
AlpineJs::event(JsEvent::FRAGMENT_UPDATED, 'topbar-logo'),
AlpineJs::event(JsEvent::FRAGMENT_UPDATED, 'topbar-menu'),
AlpineJs::event(JsEvent::FRAGMENT_UPDATED, 'topbar-actions'),
AlpineJs::event(JsEvent::FRAGMENT_UPDATED, 'assets'),
]);
}
MoonShine позволяет создавать шаблоны напрямую через Blade.
Пример базового шаблона:
<x-moonshine::layout>
<x-moonshine::layout.html :with-alpine-js="true" :with-themes="true">
<x-moonshine::layout.head>
<x-moonshine::layout.meta name="csrf-token" :content="csrf_token()"/>
<x-moonshine::layout.favicon />
<x-moonshine::layout.assets>
@vite([
'resources/css/main.css',
'resources/js/app.js',
], 'vendor/moonshine')
</x-moonshine::layout.assets>
</x-moonshine::layout.head>
<x-moonshine::layout.body>
<x-moonshine::layout.wrapper>
<x-moonshine::layout.sidebar :collapsed="true">
<x-moonshine::layout.div class="menu-header">
<x-moonshine::layout.div class="menu-logo">
<x-moonshine::layout.logo href="/" logo="/tableau.png" logo-small="/tableau.png" :minimized="true"/>
</x-moonshine::layout.div>
<x-moonshine::layout.div class="menu-actions">
<x-moonshine::layout.theme-switcher/>
</x-moonshine::layout.div>
<x-moonshine::layout.div class="menu-burger">
<x-moonshine::layout.burger/>
</x-moonshine::layout.div>
</x-moonshine::layout.div>
<x-moonshine::layout.div class="menu menu--vertical">
<x-moonshine::layout.menu :elements="[['label' => 'Dashboard', 'url' => '/'], ['label' => 'Section', 'url' => '/section']]"/>
</x-moonshine::layout.div>
</x-moonshine::layout.sidebar>
<x-moonshine::layout.div class="layout-main">
<x-moonshine::layout.div class="layout-page">
<x-moonshine::layout.header>
<x-moonshine::layout.div class="menu-burger">
<x-moonshine::layout.burger/>
</x-moonshine::layout.div>
<x-moonshine::breadcrumbs :items="['#' => 'Home']"/>
<x-moonshine::layout.search placeholder="Search" />
<x-moonshine::layout.locales :locales="collect()"/>
</x-moonshine::layout.header>
<x-moonshine::layout.content>
<article class="article">
Your content
</article>
</x-moonshine::layout.content>
</x-moonshine::layout.div>
</x-moonshine::layout.div>
</x-moonshine::layout.wrapper>
</x-moonshine::layout.body>
</x-moonshine::layout.html>
</x-moonshine::layout>
<x-moonshine::layout>
<x-moonshine::layout.html :with-alpine-js="true" :with-themes="true">
<x-moonshine::layout.head>
<x-moonshine::layout.meta name="csrf-token" :content="csrf_token()"/>
<x-moonshine::layout.favicon />
<x-moonshine::layout.assets>
@vite([
'resources/css/main.css',
'resources/js/app.js',
], 'vendor/moonshine')
</x-moonshine::layout.assets>
</x-moonshine::layout.head>
<x-moonshine::layout.body>
<x-moonshine::layout.wrapper>
<x-moonshine::layout.sidebar :collapsed="true">
<x-moonshine::layout.div class="menu-header">
<x-moonshine::layout.div class="menu-logo">
<x-moonshine::layout.logo href="/" logo="/tableau.png" logo-small="/tableau.png" :minimized="true"/>
</x-moonshine::layout.div>
<x-moonshine::layout.div class="menu-actions">
<x-moonshine::layout.theme-switcher/>
</x-moonshine::layout.div>
<x-moonshine::layout.div class="menu-burger">
<x-moonshine::layout.burger/>
</x-moonshine::layout.div>
</x-moonshine::layout.div>
<x-moonshine::layout.div class="menu menu--vertical">
<x-moonshine::layout.menu :elements="[['label' => 'Dashboard', 'url' => '/'], ['label' => 'Section', 'url' => '/section']]"/>
</x-moonshine::layout.div>
</x-moonshine::layout.sidebar>
<x-moonshine::layout.div class="layout-main">
<x-moonshine::layout.div class="layout-page">
<x-moonshine::layout.header>
<x-moonshine::layout.div class="menu-burger">
<x-moonshine::layout.burger/>
</x-moonshine::layout.div>
<x-moonshine::breadcrumbs :items="['#' => 'Home']"/>
<x-moonshine::layout.search placeholder="Search" />
<x-moonshine::layout.locales :locales="collect()"/>
</x-moonshine::layout.header>
<x-moonshine::layout.content>
<article class="article">
Your content
</article>
</x-moonshine::layout.content>
</x-moonshine::layout.div>
</x-moonshine::layout.div>
</x-moonshine::layout.wrapper>
</x-moonshine::layout.body>
</x-moonshine::layout.html>
</x-moonshine::layout>
<x-moonshine::layout>
<x-moonshine::layout.html :with-alpine-js="true" :with-themes="true">
<x-moonshine::layout.head>
<x-moonshine::layout.meta name="csrf-token" :content="csrf_token()"/>
<x-moonshine::layout.favicon />
<x-moonshine::layout.assets>
@vite([
'resources/css/main.css',
'resources/js/app.js',
], 'vendor/moonshine')
</x-moonshine::layout.assets>
</x-moonshine::layout.head>
<x-moonshine::layout.body>
<x-moonshine::layout.wrapper>
<x-moonshine::layout.sidebar :collapsed="true">
<x-moonshine::layout.div class="menu-header">
<x-moonshine::layout.div class="menu-logo">
<x-moonshine::layout.logo href="/" logo="/tableau.png" logo-small="/tableau.png" :minimized="true"/>
</x-moonshine::layout.div>
<x-moonshine::layout.div class="menu-actions">
<x-moonshine::layout.theme-switcher/>
</x-moonshine::layout.div>
<x-moonshine::layout.div class="menu-burger">
<x-moonshine::layout.burger/>
</x-moonshine::layout.div>
</x-moonshine::layout.div>
<x-moonshine::layout.div class="menu menu--vertical">
<x-moonshine::layout.menu :elements="[['label' => 'Dashboard', 'url' => '/'], ['label' => 'Section', 'url' => '/section']]"/>
</x-moonshine::layout.div>
</x-moonshine::layout.sidebar>
<x-moonshine::layout.div class="layout-main">
<x-moonshine::layout.div class="layout-page">
<x-moonshine::layout.header>
<x-moonshine::layout.div class="menu-burger">
<x-moonshine::layout.burger/>
</x-moonshine::layout.div>
<x-moonshine::breadcrumbs :items="['#' => 'Home']"/>
<x-moonshine::layout.search placeholder="Search" />
<x-moonshine::layout.locales :locales="collect()"/>
</x-moonshine::layout.header>
<x-moonshine::layout.content>
<article class="article">
Your content
</article>
</x-moonshine::layout.content>
</x-moonshine::layout.div>
</x-moonshine::layout.div>
</x-moonshine::layout.wrapper>
</x-moonshine::layout.body>
</x-moonshine::layout.html>
</x-moonshine::layout>
<x-moonshine::layout>
<x-moonshine::layout.html :with-alpine-js="true" :with-themes="true">
<x-moonshine::layout.head>
<x-moonshine::layout.meta name="csrf-token" :content="csrf_token()"/>
<x-moonshine::layout.favicon />
<x-moonshine::layout.assets>
@vite([
'resources/css/main.css',
'resources/js/app.js',
], 'vendor/moonshine')
</x-moonshine::layout.assets>
</x-moonshine::layout.head>
<x-moonshine::layout.body>
<x-moonshine::layout.wrapper>
<x-moonshine::layout.sidebar :collapsed="true">
<x-moonshine::layout.div class="menu-header">
<x-moonshine::layout.div class="menu-logo">
<x-moonshine::layout.logo href="/" logo="/tableau.png" logo-small="/tableau.png" :minimized="true"/>
</x-moonshine::layout.div>
<x-moonshine::layout.div class="menu-actions">
<x-moonshine::layout.theme-switcher/>
</x-moonshine::layout.div>
<x-moonshine::layout.div class="menu-burger">
<x-moonshine::layout.burger/>
</x-moonshine::layout.div>
</x-moonshine::layout.div>
<x-moonshine::layout.div class="menu menu--vertical">
<x-moonshine::layout.menu :elements="[['label' => 'Dashboard', 'url' => '/'], ['label' => 'Section', 'url' => '/section']]"/>
</x-moonshine::layout.div>
</x-moonshine::layout.sidebar>
<x-moonshine::layout.div class="layout-main">
<x-moonshine::layout.div class="layout-page">
<x-moonshine::layout.header>
<x-moonshine::layout.div class="menu-burger">
<x-moonshine::layout.burger/>
</x-moonshine::layout.div>
<x-moonshine::breadcrumbs :items="['#' => 'Home']"/>
<x-moonshine::layout.search placeholder="Search" />
<x-moonshine::layout.locales :locales="collect()"/>
</x-moonshine::layout.header>
<x-moonshine::layout.content>
<article class="article">
Your content
</article>
</x-moonshine::layout.content>
</x-moonshine::layout.div>
</x-moonshine::layout.div>
</x-moonshine::layout.wrapper>
</x-moonshine::layout.body>
</x-moonshine::layout.html>
</x-moonshine::layout>
<x-moonshine::layout>
<x-moonshine::layout.html :with-alpine-js="true" :with-themes="true">
<x-moonshine::layout.head>
<x-moonshine::layout.meta name="csrf-token" :content="csrf_token()"/>
<x-moonshine::layout.favicon />
<x-moonshine::layout.assets>
@vite([
'resources/css/main.css',
'resources/js/app.js',
], 'vendor/moonshine')
</x-moonshine::layout.assets>
</x-moonshine::layout.head>
<x-moonshine::layout.body>
<x-moonshine::layout.wrapper>
<x-moonshine::layout.sidebar :collapsed="true">
<x-moonshine::layout.div class="menu-header">
<x-moonshine::layout.div class="menu-logo">
<x-moonshine::layout.logo href="/" logo="/tableau.png" logo-small="/tableau.png" :minimized="true"/>
</x-moonshine::layout.div>
<x-moonshine::layout.div class="menu-actions">
<x-moonshine::layout.theme-switcher/>
</x-moonshine::layout.div>
<x-moonshine::layout.div class="menu-burger">
<x-moonshine::layout.burger/>
</x-moonshine::layout.div>
</x-moonshine::layout.div>
<x-moonshine::layout.div class="menu menu--vertical">
<x-moonshine::layout.menu :elements="[['label' => 'Dashboard', 'url' => '/'], ['label' => 'Section', 'url' => '/section']]"/>
</x-moonshine::layout.div>
</x-moonshine::layout.sidebar>
<x-moonshine::layout.div class="layout-main">
<x-moonshine::layout.div class="layout-page">
<x-moonshine::layout.header>
<x-moonshine::layout.div class="menu-burger">
<x-moonshine::layout.burger/>
</x-moonshine::layout.div>
<x-moonshine::breadcrumbs :items="['#' => 'Home']"/>
<x-moonshine::layout.search placeholder="Search" />
<x-moonshine::layout.locales :locales="collect()"/>
</x-moonshine::layout.header>
<x-moonshine::layout.content>
<article class="article">
Your content
</article>
</x-moonshine::layout.content>
</x-moonshine::layout.div>
</x-moonshine::layout.div>
</x-moonshine::layout.wrapper>
</x-moonshine::layout.body>
</x-moonshine::layout.html>
</x-moonshine::layout>