Макеты (Layouts)
Макеты определяют общую структуру HTML страницы: где находится header, footer, как подключаются стили и скрипты.
Структура макета app.blade.php
Главный макет находится в views/layouts/app.blade.php. При наследовании от standard он уже есть, но его можно переопределить.
Упрощенная структура
<!DOCTYPE html>
<html lang="..." data-theme="dark|light">
<head>
<!-- Meta теги, title, description -->
@stack('head')
@stack('styles')
<!-- Подключение SCSS темы -->
@at(tt('assets/sass/app.scss'))
<!-- HTMX и скрипты -->
</head>
<body>
<!-- Header -->
@include('flute::layouts.header')
<!-- Основной контент -->
<main id="main">
@stack('content')
</main>
<!-- Footer -->
@include('flute::layouts.footer')
<!-- Модальные окна -->
<div id="modals">
@stack('modals')
</div>
<!-- Скрипты -->
@at(tt('assets/scripts/app.js'))
@stack('scripts')
</body>
</html>Переопределение Header
Создайте views/layouts/header.blade.php:
<header class="my-header">
<div class="container">
<div class="header-content">
{{-- Логотип --}}
<a href="{{ url('/') }}" class="logo">
<img src="{{ asset(config('app.logo')) }}" alt="{{ config('app.name') }}">
</a>
{{-- Навигация --}}
<nav class="nav">
@foreach (navbar()->all() as $item)
@if (count($item['children']) === 0)
{{-- Обычная ссылка --}}
<a href="{{ url($item['url']) }}"
class="nav-link {{ active($item['url']) }}"
@if ($item['new_tab']) target="_blank" @endif>
@if ($item['icon'])
<x-icon :path="$item['icon']" />
@endif
{{ __($item['title']) }}
</a>
@else
{{-- Dropdown --}}
<div class="nav-dropdown">
<button class="nav-link"
data-dropdown-open="nav-{{ $loop->index }}"
data-dropdown-hover="true">
@if ($item['icon'])
<x-icon :path="$item['icon']" />
@endif
{{ __($item['title']) }}
<x-icon path="ph.regular.caret-down" />
</button>
<div data-dropdown="nav-{{ $loop->index }}" class="dropdown">
@foreach ($item['children'] as $child)
<a href="{{ url($child['url']) }}" class="dropdown__item">
@if ($child['icon'])
<x-icon :path="$child['icon']" />
@endif
{{ __($child['title']) }}
</a>
@endforeach
</div>
</div>
@endif
@endforeach
</nav>
{{-- Действия --}}
<div class="header-actions">
{{-- Переключатель темы --}}
<x-header.theme-switcher />
@auth
{{-- Уведомления --}}
<x-header.notifications />
{{-- Профиль --}}
<x-header.profile />
@else
{{-- Кнопка входа --}}
<x-button href="{{ url('login') }}" size="small">
@t('def.login')
</x-button>
<x-button href="{{ url('register') }}" size="small" type="accent">
@t('def.register')
</x-button>
@endauth
</div>
{{-- Мобильное меню --}}
<button class="mobile-menu-btn" data-trigger-right-sidebar>
<x-icon path="ph.bold.list" />
</button>
</div>
</div>
</header>Стили для header
// assets/sass/layouts/_header.scss
.my-header {
position: sticky;
top: 0;
z-index: 100;
background: var(--blurred-background);
backdrop-filter: blur(20px);
border-bottom: 1px solid var(--transp-1);
}
.header-content {
display: flex;
align-items: center;
justify-content: space-between;
height: 64px;
gap: var(--space-lg);
}
.logo img {
height: 32px;
width: auto;
}
.nav {
display: flex;
align-items: center;
gap: var(--space-xs);
@include media(mobile) {
display: none;
}
}
.nav-link {
display: flex;
align-items: center;
gap: var(--space-xs);
padding: var(--space-xs) var(--space-sm);
border-radius: var(--border05);
font-size: var(--p);
font-weight: 500;
color: var(--text-muted);
@include transition(all);
&:hover, &.active {
color: var(--text);
background: var(--transp-1);
}
}
.header-actions {
display: flex;
align-items: center;
gap: var(--space-sm);
}
.mobile-menu-btn {
display: none;
@include media(mobile) {
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
}
}Переопределение Footer
Создайте views/layouts/footer.blade.php:
<footer class="my-footer">
<div class="container">
<div class="footer-content">
{{-- Логотип и описание --}}
<div class="footer-brand">
<img src="{{ asset(config('app.logo')) }}" alt="{{ config('app.name') }}">
<p>{{ config('app.description') }}</p>
{{-- Соцсети --}}
<x-footer.socials />
</div>
{{-- Ссылки из админки --}}
<div class="footer-links">
<x-footer.links />
</div>
</div>
<div class="footer-bottom">
<x-footer.copyright />
</div>
</div>
</footer>Стеки (@stack / @push)
Стеки позволяют добавлять контент в определенные места макета.
Доступные стеки
| Стек | Где выводится | Для чего |
|---|---|---|
head | В <head> | Meta теги |
styles | В <head> | CSS файлы |
scripts | Перед </body> | JavaScript |
modals | В #modals | Модальные окна |
content | В <main> | Основной контент |
before-content | Перед <main> | Баннеры |
content-after | После <main> | Дополнительные блоки |
Примеры использования
{{-- Добавить стили --}}
@push('styles')
<link rel="stylesheet" href="/my-styles.css">
@at('Modules/Shop/Resources/assets/scss/shop.scss')
@endpush
{{-- Добавить скрипты --}}
@push('scripts')
<script src="/my-script.js"></script>
@at('Modules/Shop/Resources/assets/js/shop.js')
<script>
document.addEventListener('DOMContentLoaded', () => {
console.log('Page loaded');
});
</script>
@endpush
{{-- Добавить модалку --}}
@push('modals')
<x-modal id="my-modal" title="Заголовок">
...
</x-modal>
@endpush
{{-- Добавить meta теги --}}
@push('head')
<meta property="og:type" content="article">
<meta name="robots" content="noindex">
@endpushПолезные хелперы в шаблонах
Пользователь
{{-- Проверка авторизации --}}
@auth
<p>Привет, {{ user()->name }}!</p>
@else
<p>Вы не авторизованы</p>
@endauth
{{-- Короткая запись --}}
@guest
<a href="{{ url('login') }}">Войти</a>
@endguestПрава доступа
@can('admin')
<a href="{{ url('admin') }}">Админ-панель</a>
@endcan
@cannot('admin')
<p>Нет доступа</p>
@endcannotURL и ассеты
{{-- URL --}}
{{ url('/profile') }}
{{ url()->current() }}
{{-- Ассеты --}}
{{ asset('images/logo.png') }}
@asset('assets/js/app.js')
{{-- Путь к теме --}}
@at(tt('assets/sass/app.scss'))Переводы
@t('def.login')
@t('messages.welcome', ['name' => $user->name])
{{ __('messages.hello') }}Конфигурация
{{ config('app.name') }}
{{ config('app.description') }}
{{ config('app.logo') }}Navbar и Footer
{{-- Все пункты меню --}}
@foreach (navbar()->all() as $item)
{{ $item['title'] }}
{{ $item['url'] }}
{{ $item['icon'] }}
{{ $item['children'] }}
@endforeach
{{-- Footer колонки --}}
@foreach (footer()->all() as $column)
...
@endforeachBlade директивы
Условия
@if ($condition)
...
@elseif ($other)
...
@else
...
@endif
@unless ($condition)
{{-- Если условие false --}}
@endunlessЦиклы
@foreach ($items as $item)
{{ $loop->index }} {{-- 0, 1, 2... --}}
{{ $loop->iteration }} {{-- 1, 2, 3... --}}
{{ $loop->first }} {{-- true если первый --}}
{{ $loop->last }} {{-- true если последний --}}
@endforeach
@forelse ($items as $item)
{{ $item }}
@empty
<p>Нет элементов</p>
@endforelseВключение шаблонов
@include('partials.sidebar')
@include('partials.card', ['title' => 'Hello'])
@includeWhen($condition, 'partials.sidebar')
@includeUnless($condition, 'partials.sidebar')PHP код
@php
$total = $items->sum('price');
@endphp
{{ $total }}HTMX интеграция
Макет настроен для работы с HTMX — динамические обновления без перезагрузки страницы.
Как это работает
<body>имеет атрибуты HTMX расширений<main id="main">— контейнер для swap контента- Навигация использует
hx-boostдля SPA-like переходов
Проверка HTMX запроса
@php
$isPartialRequest = request()->htmx()->isHtmxRequest();
@endphp
@if (!$isPartialRequest)
{{-- Полный HTML (первая загрузка) --}}
@else
{{-- Только контент (HTMX swap) --}}
@endifПри HTMX запросе header/footer не перерисовываются — обновляется только <main>.