MoonShine 2.6 "Jet Punch"

MoonShine 2.6 "Jet Punch"

Danil Shutsky
Danil Shutsky
07.01.2024 в 14:13

На этой неделе команда MoonShine выпустила первый в 2024 году релиз v.2.6.0 с кодовым именем "Jet Punch"! Давайте взглянем на самое интересное в этом обновлении!

Реактивность полей (beta)

Фича дает возможность реактивно изменять поля. Не только их значения, но и содержимое. Пока работает в бета-режиме.

Давайте рассмотрим пример формирования slug-поля на основе заголовка. Slug будет генерироваться в процессе ввода текста:

FormBuilder::make()
  ->name('my-form')
  ->fields([
      Text::make('Title')
          ->reactive(function(Fields $fields, ?string $value): Fields {
              return tap($fields, static fn ($fields) => $fields
                  ->findByColumn('slug')
                  ?->setValue(str($value ?? '')->slug()->value())
              );
          }),
  
      Text::make('Slug')->reactive(),
  ])

Также для slug-поля появился метод для быстрого формирования логики из примера выше:

FormBuilder::make()
  ->name('reactive')
  ->fields([
      Text::make('Title')->reactive(),
      Slug::make('Slug')->from('title')->live(),
  ])

В данном случае slug всего лишь пример использования этого мощного функционала.

Подробности в PR

CardsBuilder

Пример использования для вывода элементов в виде карточек:

CardsBuilder::make(items: Article::query()->with('author')->latest()->paginate())
  ->name('cards')
  ->async()
  ->fields([
      BelongsTo::make('Author', resource: new MoonShineUserResource())->badge(),
      Date::make('Created at')->format('d.m.Y')
  ])
  ->cast(ModelCast::make(Article::class))
  ->title('title')
  ->thumbnail(fn($data) => $data->author?->avatar ? Storage::url($data->author->avatar) : '')
  ->subtitle(fn() => 'Subtitle')
  ->url(fn($data) => (new ArticleResource())->formPageUrl($data))
  ->overlay()
  ->header(fn() => Badge::make('New', 'green'))
  ->content('Custom content')
  ->buttons([
      ActionButton::make('Go to', '#'),
      ActionButton::make('Go to 2', '#'),
  ])
  ->columnSpan(4)

Также можно изменить компонент самой карточки:

->customComponent(function (Article $article, int $index, CardsBuilder $builder) {
    return Badge::make($index + 1 . "." . $article->title, 'green');
})

Ну и событие изменения cards-updated-

Подробности в PR

Методы onChange

С этого момента метод updateOnPreview является сахаром метода onChangeUrl.

А новые методы onChangeUrl или onChangeMethod присутствуют для всех полей, кроме полей отношений HasOne и HasMany.

C помощью методов onChangeMethod и onChangeUrl вы сможете добавить логику при изменении значений:

Switcher::make('Name')->onChangeMethod('someMethod')

// ...

public function someMethod(MoonShineRequest $request): void
{
    // Logic
}

Большой пример, где мы меняем сортировку для компонента CardsBuilder:

Select::make('Sorts')->options([
    'created_at' => 'Date',
    'id' => 'ID',
])
    ->onChangeMethod('reSort', events: ['cards-updated-cards'])
    ->setValue(session('sort_column') ?: 'created_at'),


CardsBuilder::make(
    items: Article::query()->with('author')
        ->when(
            session('sort_column'),
            fn($q) => $q->orderBy(session('sort_column'), session('sort_direction', 'asc')),
            fn($q) => $q->latest()
        )
        ->paginate()
)
    ->name('cards')
    ->async()
    ->cast(ModelCast::make(Article::class))
    ->title('title')
    ->url(fn($data) => (new ArticleResource())->formPageUrl($data))
    ->overlay()
    ->columnSpan(4)
,

// ..

public function reSort(MoonShineRequest $request): void
{
  session()->put('sort_column', $request->get('value'));
  session()->put('sort_direction', 'ASC');
}

Подробности в PR

Методы для быстрого изменения основных компонентов страниц

Теперь можно быстро изменить только основной компонент страницы. Например, изменить на индексной странице отображение через компонент TableBuilder на CardsBuilder, а также изменить события изменения:

class MoonShineUserIndexPage extends IndexPage
{
    public function listComponentName(): string
    {
        return 'index-cards';
    }

    public function listEventName(): string
    {
        return 'cards-updated';
    }

    protected function itemsComponent(iterable $items, Fields $fields): MoonShineRenderable
    {
        return CardsBuilder::make($items, $fields)
            ->cast($this->getResource()->getModelCast())
            ->name($this->listComponentName())
            ->async()
            ->overlay()
            ->title('email')
            ->subtitle('name')
            ->url(fn ($user) => $this->getResource()->formPageUrl($user))
            ->thumbnail(fn ($user) => asset($user->avatar))
            ->buttons($this->getResource()->getIndexItemButtons());
    }
}

Тоже самое и для FormPage:

protected function detailComponent(?Model $item, Fields $fields): MoonShineRenderable

А также DetailPage:

protected function formComponent(
    string $action,
    ?Model $item,
    Fields $fields,
    bool $isAsync = false,
): MoonShineRenderable

Подробности в PR

Новые директивы и класс помощник AlpineJs

Blade-директивы для быстрого объявления событий:

@defineEvent('table-updated', 'index', 'asyncRequest')  // @table-updated-index.window="asyncRequest"
@defineEventWhen(true, 'table-updated', 'index', 'asyncRequest')

AlpineJs класс-помощник, для формирования событий и многое другое:

AlpineJs::event(JsEvent::TABLE_UPDATED, 'index', 'asyncRequest');   // table-updated-index
AlpineJs::eventBlade(JsEvent::TABLE_UPDATED, 'index', 'asyncRequest');   // @table-updated-index.window="asyncRequest"

Подробности в PR

Новые компоненты

Card, Badge, Boolean, Breadcrumbs, Color, Dropdown, Files, Thumbnails, Icon, ProgressBar, Spinner, Title, Url.

Подробности в PR

Компоненты Modals

В предыдущих версиях MoonShine была небольшая проблема с Modals, которая заключалась в том, что содержимое является строкой, оно изолировано и мы не имели доступ к компонентам внутри. Тем самым при передаче формы могли возникнуть проблемы с asyncSearch. Благодаря этому PR появилась возможность передавать компоненты, а модалки реализует интерфейс HasFields:

$modal = Modal::make(
    'Image Modal',
    components: PageComponents::make([$image])
)->name('image-modal');

Подробности в PR

Исправлены баги 🐞

  • Проблемы с layout-wrapper
  • Enum с null
  • Octane issue
  • Default-значение для asyncSearch
  • и многое другое

https://github.com/moonshine-software/moonshine/compare/2.5.0...2.6.0