Skip to Content

Макеты (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; } }

Создайте 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> @endcannot

URL и ассеты

{{-- 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') }}
{{-- Все пункты меню --}} @foreach (navbar()->all() as $item) {{ $item['title'] }} {{ $item['url'] }} {{ $item['icon'] }} {{ $item['children'] }} @endforeach {{-- Footer колонки --}} @foreach (footer()->all() as $column) ... @endforeach

Blade директивы

Условия

@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 — динамические обновления без перезагрузки страницы.

Как это работает

  1. <body> имеет атрибуты HTMX расширений
  2. <main id="main"> — контейнер для swap контента
  3. Навигация использует hx-boost для SPA-like переходов

Проверка HTMX запроса

@php $isPartialRequest = request()->htmx()->isHtmxRequest(); @endphp @if (!$isPartialRequest) {{-- Полный HTML (первая загрузка) --}} @else {{-- Только контент (HTMX swap) --}} @endif

При HTMX запросе header/footer не перерисовываются — обновляется только <main>.