承接 povly/moonshine-flexible-layouts 相关项目开发

从需求分析到上线部署,全程专人跟进,保证项目质量与交付效率

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

povly/moonshine-flexible-layouts

Composer 安装命令:

composer require povly/moonshine-flexible-layouts

包简介

Flexible content blocks with unlimited nesting for MoonShine

README 文档

README

Tabs Block picker modal

Flexible content blocks field for MoonShine 4. Build page builder-style layouts with unlimited nesting, drag-to-reorder, and AJAX-powered block management.

Features

  • Tab-based UI — blocks displayed as reorderable tabs with optional icons
  • Unlimited nesting — Flexible Layouts inside block fields just work
  • Drag to reorder — powered by SortableJS via MoonShine's native iterable API
  • AJAX add/remove — blocks are fetched on-demand from the server, no page reload
  • Limit per block type — restrict how many instances of each block can be added
  • Native reindex — reuses MoonShine.iterable.reindex() for correct form field naming at any depth
  • Block picker modal — Gutenberg-style modal with search and category grouping
  • Layout components — use MoonShine Flex, Column, and other layout components inside blocks for multi-column field layouts
  • Localization — ships with English and Russian translations, extensible to any language

Requirements

  • PHP 8.2+
  • Laravel 12+
  • MoonShine 4+

Installation

composer require povly/moonshine-flexible-layouts

Publish assets:

php artisan vendor:publish --tag=flexible-layouts

Quick Start

use Povly\FlexibleLayouts\Fields\FlexibleLayouts;

FlexibleLayouts::make('Content', 'content')
    ->block('hero', 'Hero', [
        Text::make('Title', 'title'),
        Image::make('Background', 'image'),
    ])
    ->block('text', 'Text Block', [
        Textarea::make('Body', 'body'),
    ])
    ->block('gallery', 'Gallery', [
        Json::make('Images', 'images'),
    ]);

Usage

Block Registration

The block() method accepts:

Parameter Type Description
$name string Snake_case key stored in JSON as _type
$title string Human-readable label shown in tabs and picker
$fields iterable MoonShine fields (can include nested FlexibleLayouts)
$limit ?int Max instances of this block type (default: unlimited)
$category ?string Grouping label in the picker modal
$description ?string Short description shown in the picker card
$icon ?string MoonShine icon name, emoji, or SVG string

Basic usage:

->block('cta', 'Call to Action', [
    Text::make('Button Text', 'label'),
    Text::make('Link', 'url'),
], limit: 1)

With category, description, and icon (use named args):

// MoonShine icon names (301+ Heroicons built-in)
->block('hero', 'Hero', [
    Text::make('Title', 'title'),
    Image::make('Background', 'image'),
], category: 'Header', description: 'Large banner with background', icon: 'photo')

->block('gallery', 'Gallery', [
    Json::make('Images', 'images'),
], category: 'Media', description: 'Image grid gallery', icon: 'rectangle-stack')

->block('wysiwyg', 'Text Editor', [
    Textarea::make('Body', 'body'),
], category: 'Content', description: 'Rich text content', icon: 'document-text')

// Emoji also works
->block('cta', 'Call to Action', [
    Text::make('Text', 'label'),
    Text::make('Link', 'url'),
], limit: 1, icon: '🔗')

Icons

The icon parameter accepts three types:

Type Example Renders as
MoonShine icon name 'photo' SVG from moonshine::icons.photo
Emoji '📷' Raw text
Raw SVG '<svg>...</svg>' Pass-through HTML

MoonShine includes 301 Heroicons (stroke-based, 24×24). Browse them in vendor/moonshine/moonshine/src/UI/resources/views/icons/. Common examples: users, photo, document-text, rectangle-stack, bars-3, cog-6-tooth, star, bolt, globe-alt, bookmark.

Icons appear in both the tab label and the picker card.

Blocks without a category are shown in an ungrouped section. When all blocks lack categories, the category pills row is hidden automatically.

Block Picker Modal

Clicking Add block opens a Gutenberg-style modal with:

  • Search — type to filter by title, description, or block name
  • Category tabs — click to filter by category (only shown when 2+ categories exist)
  • Grid of cards — icon + title + description per block; click to add

Press Esc or click outside the modal to close.

Nested Flexible Layouts

You can put a FlexibleLayouts field inside any block. Nested layouts support the same features (drag, add, remove, reindex):

->block('section', 'Section', [
    Text::make('Title', 'title'),
    FlexibleLayouts::make('Blocks', 'blocks')
        ->block('button', 'Button', [
            Text::make('Text', 'text'),
            Text::make('Link', 'link'),
        ])
        ->block('form', 'Form', [
            Select::make('Type', 'form_type')->options([
                'hotel' => 'Hotel',
                'tickets' => 'Tickets',
            ]),
            Text::make('Redirect URL', 'url'),
        ]),
])

Disabling Features

->disableAdd()     // hide the "Add block" button
->disableRemove()  // hide the remove button on blocks
->disableSort()    // disable drag-to-reorder

Custom Buttons

->addButton(ActionButton::make('Add')->primary())
->removeButton(ActionButton::make('Delete')->icon('trash')->error())

Custom Labels per Field

By default, all Flexible Layouts fields share the same UI labels from messages.php. Use ->transKey() to give a specific field its own set of labels — useful for nested layouts with different content types:

// Top-level — default labels ("Add block", "Search blocks...")
FlexibleLayouts::make('Blocks', 'blocks')
    ->block('hero', 'Hero', [...])

// Nested — custom labels via separate translation file
->block('section', 'Section', [
    Text::make('Title', 'title'),

    FlexibleLayouts::make('Refs', 'refs')
        ->transKey('refs')   // uses flexible-layouts::refs.* translations
        ->block('reference', 'Справочник', [
            Text::make('Title', 'title'),
        ]),
])

Create lang/vendor/flexible-layouts/ru/refs.php:

return [
    'add_block'       => 'Добавить справочник',
    'search_blocks'   => 'Поиск справочников...',
    'no_blocks_found' => 'Справочники не найдены',
    'all_categories'  => 'Все',
];

Labels resolve in order: flexible-layouts::refs.{key} -> if missing -> flexible-layouts::messages.{key}. An example file ships in lang/en/refs.php and lang/ru/refs.php.

Multi-column Layouts

Use MoonShine's native Flex and Column components inside blocks for side-by-side fields:

->block('hero', 'Hero', [
    Flex::make([
        Column::make([
            Text::make('Title', 'title'),
        ])->columnSpan(6),

        Column::make([
            Text::make('Subtitle', 'subtitle'),
        ])->columnSpan(6),
    ]),
])

columnSpan(6) = half width (out of 12-column grid). Use columnSpan(4) for three columns, columnSpan(3) for four, etc.

Configuration

Publish the config file:

php artisan vendor:publish --tag=flexible-layouts-config
// config/flexible-layouts.php
return [
    'route_prefix' => 'flexible-layouts',
    'middleware' => ['web'],
];

Translations

The package ships with English and Russian translations. The UI adapts to the app locale automatically.

Available keys:

Key EN RU
add_block Add block Добавить блок
search_blocks Search blocks... Поиск блоков...
no_blocks_found No blocks found Блоки не найдены
all_categories All Все

Publish translations to customize or add new languages:

php artisan vendor:publish --tag=flexible-layouts-lang

This creates lang/vendor/flexible-layouts/ with en/ and ru/ directories. To add a language, copy any file to a new locale folder:

# Example: add German
cp lang/vendor/flexible-layouts/en/messages.php lang/vendor/flexible-layouts/de/messages.php
// lang/vendor/flexible-layouts/de/messages.php
return [
    'add_block' => 'Block hinzufügen',
    'search_blocks' => 'Blöcke suchen...',
    'no_blocks_found' => 'Keine Blöcke gefunden',
    'all_categories' => 'Alle',
];

Data Format

The field stores data as a flat JSON array. Each entry has a _type key and the block's field values:

[
  {
    "_type": "hero",
    "title": "Welcome",
    "image": "hero-bg.jpg"
  },
  {
    "_type": "section",
    "title": "About Us",
    "blocks": [
      {
        "_type": "button",
        "text": "Learn More",
        "link": "/about"
      }
    ]
  }
]

Cast

Register the cast on your model:

protected function casts(): array
{
    return [
        'content' => \Povly\FlexibleLayouts\Casts\FlexibleCast::class,
    ];
}

Or use $casts property if preferred.

Development

# Install JS dependencies
bun install

# Build assets
bun run build

# Watch mode
bun run dev

Assets are built to dist/ and published to public/vendor/flexible-layouts/.

统计信息

  • 总下载量: 0
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 2
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-06-25

承接程序开发

PHP开发

VUE

Vue开发

前端开发

小程序开发

公众号开发

系统定制

数据库设计

云部署

网站建设

安全加固