First commit
This commit is contained in:
commit
7cf3558ba7
12933 changed files with 1180047 additions and 0 deletions
57
packages/flux-cms/starter-components/composer.json
Normal file
57
packages/flux-cms/starter-components/composer.json
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
{
|
||||
"name": "flux-cms/starter-components",
|
||||
"description": "Flux CMS Starter Components - Ready-to-use components for quick setup",
|
||||
"type": "library",
|
||||
"keywords": [
|
||||
"laravel",
|
||||
"cms",
|
||||
"livewire",
|
||||
"components",
|
||||
"starter",
|
||||
"templates"
|
||||
],
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Flux CMS Contributors",
|
||||
"email": "contributors@flux-cms.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^8.2",
|
||||
"laravel/framework": "^11.0|^12.0",
|
||||
"livewire/livewire": "^3.0",
|
||||
"flux-cms/core": "*",
|
||||
"flux-cms/components": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"orchestra/testbench": "^9.0",
|
||||
"pestphp/pest": "^3.8",
|
||||
"pestphp/pest-plugin-laravel": "^3.2"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"FluxCms\\StarterComponents\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"FluxCms\\StarterComponents\\Tests\\": "tests/"
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"FluxCms\\StarterComponents\\FluxCmsStarterComponentsServiceProvider"
|
||||
]
|
||||
}
|
||||
},
|
||||
"minimum-stability": "stable",
|
||||
"prefer-stable": true,
|
||||
"config": {
|
||||
"sort-packages": true,
|
||||
"allow-plugins": {
|
||||
"pestphp/pest-plugin": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,225 @@
|
|||
<?php
|
||||
|
||||
namespace FluxCms\StarterComponents\Components;
|
||||
|
||||
use Livewire\Component;
|
||||
use FluxCms\Core\FieldTypes\TextField;
|
||||
use FluxCms\Core\FieldTypes\WysiwygField;
|
||||
use FluxCms\Core\FieldTypes\MediaField;
|
||||
use FluxCms\Core\FieldTypes\BooleanField;
|
||||
|
||||
class HeroSection extends Component
|
||||
{
|
||||
public array $content = [];
|
||||
|
||||
public function mount(array $content = [])
|
||||
{
|
||||
$this->content = $content;
|
||||
}
|
||||
|
||||
public static function getCmsName(): string
|
||||
{
|
||||
return 'Hero Section';
|
||||
}
|
||||
|
||||
public static function getCmsDescription(): string
|
||||
{
|
||||
return 'Large header section with headline, text and optional background image with call-to-action button';
|
||||
}
|
||||
|
||||
public static function getCmsCategory(): string
|
||||
{
|
||||
return 'Layout';
|
||||
}
|
||||
|
||||
public static function getCmsIcon(): string
|
||||
{
|
||||
return 'hero';
|
||||
}
|
||||
|
||||
public static function getCmsTags(): array
|
||||
{
|
||||
return ['header', 'hero', 'banner', 'cta'];
|
||||
}
|
||||
|
||||
public static function getCmsPreview(): string
|
||||
{
|
||||
return '<div class="bg-gradient-to-r from-blue-600 to-purple-700 text-white p-8 text-center rounded-lg">
|
||||
<h1 class="text-3xl font-bold mb-4">Hero Headline</h1>
|
||||
<p class="text-lg mb-6">Compelling subtitle text that draws attention</p>
|
||||
<button class="bg-white text-blue-600 px-6 py-3 rounded-lg font-semibold">Call to Action</button>
|
||||
</div>';
|
||||
}
|
||||
|
||||
public static function getCmsFields(): array
|
||||
{
|
||||
return [
|
||||
TextField::make('headline', 'Headline')
|
||||
->translatable()
|
||||
->required()
|
||||
->maxLength(100)
|
||||
->helpText('Main headline for the hero section'),
|
||||
|
||||
TextField::make('subheadline', 'Subheadline')
|
||||
->translatable()
|
||||
->maxLength(200)
|
||||
->helpText('Supporting text below the main headline'),
|
||||
|
||||
WysiwygField::make('description', 'Description')
|
||||
->translatable()
|
||||
->basic()
|
||||
->helpText('Additional descriptive text'),
|
||||
|
||||
TextField::make('cta_text', 'Button Text')
|
||||
->translatable()
|
||||
->maxLength(50)
|
||||
->placeholder('Get Started'),
|
||||
|
||||
TextField::make('cta_link', 'Button Link')
|
||||
->url()
|
||||
->placeholder('https://example.com'),
|
||||
|
||||
BooleanField::make('cta_new_tab', 'Open Link in New Tab')
|
||||
->default(false),
|
||||
|
||||
MediaField::make('background_image', 'Background Image')
|
||||
->images()
|
||||
->helpText('Optional background image for the hero section'),
|
||||
|
||||
BooleanField::make('dark_overlay', 'Dark Overlay')
|
||||
->default(true)
|
||||
->helpText('Add dark overlay for better text readability'),
|
||||
|
||||
TextField::make('text_alignment', 'Text Alignment')
|
||||
->default('center')
|
||||
->helpText('Text alignment: left, center, right'),
|
||||
|
||||
TextField::make('min_height', 'Minimum Height')
|
||||
->default('500px')
|
||||
->helpText('Minimum height of the hero section (e.g., 500px, 50vh)'),
|
||||
];
|
||||
}
|
||||
|
||||
public static function validateContent(array $content): array
|
||||
{
|
||||
$errors = [];
|
||||
|
||||
// Custom validation: If CTA text is provided, link must also be provided
|
||||
$ctaText = $content['cta_text'] ?? [];
|
||||
$ctaLink = $content['cta_link'] ?? '';
|
||||
|
||||
foreach ($ctaText as $locale => $text) {
|
||||
if (!empty($text) && empty($ctaLink)) {
|
||||
$errors["cta_link"] = ['Button link is required when button text is provided'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('flux-cms-starter::components.hero-section');
|
||||
}
|
||||
|
||||
// Helper methods for accessing content
|
||||
protected function getHeadline(?string $locale = null): string
|
||||
{
|
||||
$locale = $locale ?? app()->getLocale();
|
||||
return $this->content['headline'][$locale] ?? '';
|
||||
}
|
||||
|
||||
protected function getSubheadline(?string $locale = null): string
|
||||
{
|
||||
$locale = $locale ?? app()->getLocale();
|
||||
return $this->content['subheadline'][$locale] ?? '';
|
||||
}
|
||||
|
||||
protected function getDescription(?string $locale = null): string
|
||||
{
|
||||
$locale = $locale ?? app()->getLocale();
|
||||
return $this->content['description'][$locale] ?? '';
|
||||
}
|
||||
|
||||
protected function getCtaText(?string $locale = null): string
|
||||
{
|
||||
$locale = $locale ?? app()->getLocale();
|
||||
return $this->content['cta_text'][$locale] ?? '';
|
||||
}
|
||||
|
||||
protected function getCtaLink(): string
|
||||
{
|
||||
return $this->content['cta_link'] ?? '';
|
||||
}
|
||||
|
||||
protected function getTextAlignment(): string
|
||||
{
|
||||
return $this->content['text_alignment'] ?? 'center';
|
||||
}
|
||||
|
||||
protected function getMinHeight(): string
|
||||
{
|
||||
return $this->content['min_height'] ?? '500px';
|
||||
}
|
||||
|
||||
protected function hasCta(): bool
|
||||
{
|
||||
return !empty($this->getCtaText()) && !empty($this->getCtaLink());
|
||||
}
|
||||
|
||||
protected function hasBackgroundImage(): bool
|
||||
{
|
||||
return !empty($this->content['background_image']);
|
||||
}
|
||||
|
||||
protected function getBackgroundImageUrl(): ?string
|
||||
{
|
||||
if (!$this->hasBackgroundImage()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$mediaId = $this->content['background_image'];
|
||||
$media = \Spatie\MediaLibrary\MediaCollections\Models\Media::find($mediaId);
|
||||
|
||||
return $media?->getUrl('large') ?? $media?->getUrl();
|
||||
}
|
||||
|
||||
protected function hasDarkOverlay(): bool
|
||||
{
|
||||
return $this->content['dark_overlay'] ?? true;
|
||||
}
|
||||
|
||||
protected function opensInNewTab(): bool
|
||||
{
|
||||
return $this->content['cta_new_tab'] ?? false;
|
||||
}
|
||||
|
||||
protected function getContainerClasses(): string
|
||||
{
|
||||
$classes = ['hero-section', 'relative', 'flex', 'items-center', 'justify-center'];
|
||||
|
||||
// Text alignment
|
||||
$alignment = $this->getTextAlignment();
|
||||
if ($alignment === 'left') {
|
||||
$classes[] = 'text-left';
|
||||
} elseif ($alignment === 'right') {
|
||||
$classes[] = 'text-right';
|
||||
} else {
|
||||
$classes[] = 'text-center';
|
||||
}
|
||||
|
||||
return implode(' ', $classes);
|
||||
}
|
||||
|
||||
protected function getTextClasses(): string
|
||||
{
|
||||
$classes = ['relative', 'z-10'];
|
||||
|
||||
if ($this->hasBackgroundImage()) {
|
||||
$classes[] = 'text-white';
|
||||
}
|
||||
|
||||
return implode(' ', $classes);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
namespace FluxCms\StarterComponents;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Livewire\Livewire;
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
use Illuminate\Support\Str;
|
||||
use ReflectionClass;
|
||||
|
||||
class FluxCmsStarterComponentsServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register services
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
// Register any bindings
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap services
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
$this->bootViews();
|
||||
$this->bootPublishing();
|
||||
$this->bootLivewireComponents();
|
||||
}
|
||||
|
||||
/**
|
||||
* Boot views
|
||||
*/
|
||||
protected function bootViews(): void
|
||||
{
|
||||
$this->loadViewsFrom(__DIR__ . '/../resources/views', 'flux-cms-starter');
|
||||
}
|
||||
|
||||
/**
|
||||
* Boot publishing
|
||||
*/
|
||||
protected function bootPublishing(): void
|
||||
{
|
||||
if ($this->app->runningInConsole()) {
|
||||
// Publish views
|
||||
$this->publishes([
|
||||
__DIR__ . '/../resources/views' => resource_path('views/vendor/flux-cms-starter'),
|
||||
], 'flux-cms-starter-views');
|
||||
|
||||
// Publish components
|
||||
$this->publishes([
|
||||
__DIR__ . '/Components' => app_path('Livewire/Components'),
|
||||
], 'flux-cms-starter-components');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register Livewire components
|
||||
*/
|
||||
protected function bootLivewireComponents(): void
|
||||
{
|
||||
$this->registerLivewireComponentsFrom(__DIR__ . '/Components', 'FluxCms\\StarterComponents\\Components', 'flux-cms-starter::');
|
||||
}
|
||||
|
||||
protected function registerLivewireComponentsFrom(string $path, string $namespace, string $aliasPrefix = ''): void
|
||||
{
|
||||
$filesystem = new Filesystem();
|
||||
if (!$filesystem->isDirectory($path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($filesystem->allFiles($path) as $file) {
|
||||
$class = $namespace . '\\' . str_replace(['/', '.php'], ['\\', ''], $file->getRelativePathname());
|
||||
|
||||
if (class_exists($class) && is_subclass_of($class, \Livewire\Component::class) && !(new ReflectionClass($class))->isAbstract()) {
|
||||
$alias = $aliasPrefix . Str::kebab(class_basename($class));
|
||||
Livewire::component($alias, $class);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue