mountainclans/livewire-section-builder

This is my package livewire-section-builder
50
Install
composer require mountainclans/livewire-section-builder
Latest Version:1.6.3
PHP:^8.2
License:MIT
Last Updated:Jul 7, 2026
Links: GitHub  ·  Packagist
Maintainer: VladimirBazhenov

Livewire Section Builder

Установка

Установите пакет при помощи composer:

composer require mountainclans/livewire-section-builder

Важно! Пакет зависит от других компонентов, в частности, необходимо сразу после установки этого пакета также сконфигурировать для использования пакет mountainclans/livewire-select (инструкции находятся на его странице).

Опубликуйте и примените миграции:

php artisan vendor:publish --tag="livewire-section-builder-migrations"
php artisan migrate

Опубликуйте конфигурационный файл:

php artisan vendor:publish --tag="livewire-section-builder-config"

Конфигурационный массив имеет следующую вложенность:

retyrn [
    'sections' => [
        [
            'key' => 'top_banner',
            'title' => 'Top banner',
            'model' => EloquentSectionModelClass::class,
            'editor' => LivewireEditorComponent::class,
            'frontend' => LivewireViewComponent::class,
        ],
        [
            'key' => 'advantages',
            'title' => 'Advantages',
            'model' => EloquentSectionModelClass2::class,
            'editor' => LivewireEditorComponent2::class,
            'frontend' => LivewireViewComponent2::class,
        ],
    ],
    'templates' => [
        'main_page' => [
            'top_banner',
            'advantages',
        ],
    ],
]

Добавьте в tailwind.config.js в секцию content:

'./vendor/mountainclans/livewire-section-builder/resources/views/**/*.blade.php'

Опционально, Вы можете опубликовать views для их переопределения:

php artisan vendor:publish --tag="livewire-section-builder-views"

Использование

Перед использованием компонентов пакета, пожалуйста, сконфигурируйте используемые классы в конфигурационном файле.

Модель секции

Модель секции должна расширять класс

Структура хранения модели состоит из следующих полей:

  • id
  • template
  • page_id
  • type
  • order_column
  • fields
  • created_at
  • updated_at

Не рекомендуется менять миграцию, создающую таблицу для секций. Все необходимые поля секции можно сохранить в атрибуте модели fields. Данный атрибут представляет собой schemaless attribute, реализация от Spatie.

Тем не менее, Вы вправе расширять модель секции как Вам необходимо, к примеру, использовать трейты HasTranslations, InteractsWithMedia и другие.

Для того, чтобы одна и та же секция могла быть доступна для разных шаблонов, пожалуйста, используйте уникальные ключи в качестве названий секций.

Админка

В нужном месте страницы используйте компонент редактора:

@if ($pageId && !empty($destination))
    <livewire:admin-section-builder :template="BuilderPage::DESTINATION_HOME_PAGE" 
                                    :page-id="$pageId"
    />
@endif

Компоненты editor сами занимаются валидацией и сохранением своего состояния, в них может быть любая логика. Удалением, сортировкой, скрытием и показом контента этих компонентов занимается компонент admin-section-builder.

Настоятельно рекомендуется, чтобы компонент взаимодействовал только с соответствующей моделью, указанной в конфиге как model.

Рекомендуется создавать как минимум один публичный метод - saveSection, но в целом реализация никак не регламентирована.

При сохранении компонента рекомендуется выбрасывать событие:

$this->dispatch(AdminSectionBuilder::EVENT_SECTION_UPDATED);

События при редактировании в админке

Вы можете расширить класс компонента AdminSectionBuilder и использовать собственный компонент в админке. В частности, Вы можете подписаться на одно из следующих событий:

  • AdminSectionBuilder::EVENT_SECTION_ADDED
  • AdminSectionBuilder::EVENT_SECTION_UPDATED
  • AdminSectionBuilder::EVENT_SECTION_SORTED
  • AdminSectionBuilder::EVENT_SECTION_DELETED

И реализовать, к примеру, отправку уведомлений пользователю.

Сворачивание секций и репитеров

Карточки секций и элементов репитеров сворачиваются шевроном; состояние живёт в localStorage браузера (ключи sb-collapsed:section:{id} / sb-collapsed:repeater:{id}) и переживает перезагрузку страницы. Это чисто UI-состояние конкретного браузера — в БД ничего не пишется.

View-компонент

<livewire:frontend-section-viewer :page-id="$page->id"
                                  :template="BuilderPage::DESTINATION_HOME_PAGE"
/>

Данный компонент выведет все секции с нужным контентом и в нужном порядке.

Повторители

Иногда при создании секции необходимо повторить в ней однотипные данные несколько раз. Сделать это призвана комбинация трейта WithRepeaters и использование blade-компонента admin.repeater-editor.

Создайте модель-наследника BuilderSectionRepeater из пакета. Переопределите метод allowedTypes. Используйте данную модель как базовую, от которой будут наследоваться все повторители секций.

use MountainClans\LivewireSectionBuilder\Models\BuilderSectionRepeater as BaseBuilderSectionRepeater;

class BuilderSectionRepeater extends BaseBuilderSectionRepeater
{
    public static function allowedTypes(): array
    {
        return [
            PropertiesSliderSection::REPEATER_TYPE => PropertiesTabRepeater::class,
        ];
    }
}

Унаследуйтесь от этой модели для создания уникальных классов-повторителей. Добавьте в них необходимые трейты (к примеру, HasTranslations, FilledTranslatableFields).

Если модель использует HasTranslations, укажите список переводимых полей.

class AdvantagesOneRepeater extends BuilderSectionRepeater
{
    protected function getInstanceType(): string
    {
        return AdvantagesOneSection::REPEATER_TYPE;
    }

    public array $translatable = [
        'fields->title',
        'fields->description',
    ];
}

Настройка секции, использующей повторитель

  • добавьте в класс секции, использующей repeater, константу REPEATER_TYPE;
  • добавьте метод, реализующий связь repeaters (HasMany):
class AdvantagesOneSection extends BuilderSection
{
    public const REPEATER_TYPE = 'advantages_1_repeater';

    public function repeaters(): HasMany
    {
        return $this->hasMany(AdvantagesOneRepeater::class, 'section_id');
    }
}

Редактор

В редакторе секции используйте трейт WithRepeaters.

Обязательно переопределите методы редактора секции getRepeaterModel и getRepeaterFields, как показано в примере.

Опционально вы можете использовать метод getRepeaterDefaults для того, чтобы задать начальные значения полей.

class AdvantagesOneSectionEditor extends Component
{
    // другие трейты
    use WithRepeaters;

    // поля компонента

    public AdvantagesOneSection $section;

    public function mount(AdvantagesOneSection $section): void
    {
        // инициализационная логика

        $this->setRepeaters();
    }

    public function saveSection(): void
    {
        // логика сохранения основного контента
        $this->saveRepeaters();
       
        // другая логика
        $this->dispatch(AdminSectionBuilder::EVENT_SECTION_UPDATED);
    }

    protected function getRepeaterModel(): string
    {
        return AdvantagesOneRepeater::class;
    }

    protected function getRepeaterFields(): array
    {
        return ['title', 'description'];
    }
    
    // другая логика компонента
}

Во view используйте компонент-обёртку для создания редактора контента повторителей. Пример ниже:

{{-- Repeaters --}}
    <div class="w-full border-gray-300 dark:border-purple-800 border-2 rounded-lg p-4 mb-6">
        <h2 class="dark:text-white font-semibold mb-4">{{ __('Advantages') }}</h2>

        @empty($repeaters)
            <div class="p-4 text-sm text-gray-800 rounded-lg bg-gray-50 dark:bg-gray-700 dark:text-gray-300"
                 role="alert">
                {{ __('Not exists yet') }}
            </div>
        @else
            @foreach($repeaters as $index => $repeater)
                <x-admin.repeater-editor :repeater="$repeater" :index="$index">
                    <x-ui.translatable>
                        <div class="grid gap-4 grid-cols-1 lg:grid-cols-5 lg:gap-6">
                            <div class="col-span-1">
                                <x-ui.input wire:model="repeaters.{{ $index }}.title"
                                            translatable
                                            :placeholder="__('Title')"
                                            :label="__('Title')"
                                />
                            </div>
                            <div class="col-span-4">
                                <x-ui.tiptap wire:model="repeaters.{{ $index }}.description"
                                             translatable
                                             height="70"
                                             placeholder="{{ __('Description') }}"
                                             label="{{ __('Description') }}"
                                />
                            </div>
                        </div>
                    </x-ui.translatable>
                </x-admin.repeater-editor>
            @endforeach
        @endempty
    </div>

    <div class="flex justify-between items-center">
        <button wire:click="addRepeater"
                type="button"
                class="cursor-pointer focus:outline-none text-white bg-purple-700 hover:bg-purple-800 focus:ring-4 focus:ring-purple-300 font-medium rounded-lg text-sm px-5 py-2.5 mb-2 dark:bg-purple-600 dark:hover:bg-purple-700 dark:focus:ring-purple-900"
        >{{ __('Add advantage') }}</button>

        <x-ui.submit-button>{{ __('Save section') }}</x-ui.submit-button>
    </div>

Headless-режим (API)

По умолчанию пакет работает в Livewire-first режиме и ведёт себя как раньше. Для headless-проектов (контент отдаётся фронтенду через JSON API) предусмотрен опциональный слой:

// config/livewire-section-builder.php
'headless' => true,           // 'frontend' в реестре опционален; вместо него обязателен 'schema'
                              // (класс API-схемы секции) — реестр как единое место всех схем контракта;
                              // резолв: $section->apiSchema(); вся карта «тип => схема»: BuilderSection::apiSchemas()
'validate_registry' => true,  // opt-in проверка реестра при загрузке приложения
'media_serializer' => App\Support\StrapiLikeMediaSerializer::class, // формат медиа задаёт приложение

Сериализация секции:

$section->toApiArray('ru');
// schemaless-поля секции: переводимые ('fields->title' в $translatable) — в запрошенной
// локали с фолбэком на app.fallback_locale, остальные — как есть;
// + 'repeaters' => [{id, ...поля}] по order_column (базовая связь repeaters() есть у любой секции).

Метод можно переопределить в конкретной секции — например, добавить медиа-коллекции через хелпер serializeMediaCollection('collection_name'), который использует настроенный media_serializer (класс-инвокабл, реализующий Contracts\SerializesMedia; дефолт — {url, alt}).

Секции без ключа frontend Livewire-компонент frontend-section-viewer молча пропускает, так что смешанные проекты (часть секций — Livewire, часть — API) тоже возможны.

Классы медиа-моделей, используемые WithRepeaterImages, настраиваются ключами media_model / temp_media_model (по умолчанию App\Models\Media / App\Models\TempMedia — как раньше).

Авторы

Лицензия

The MIT License (MIT). Please see License File for more information.