Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 22 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,22 @@ User resource with create/edit/delete, theming, and the Media Manager. The
generated application code remains editable; package code stays in `vendor/`
and `node_modules/`.

This demo also registers a small Blog resource at `/admin/blogs`. Its create and
edit screens use the Forms package, its list screen uses the Tables package, and
its centralized `BlogRules` class keeps validation in application code. It is a
compact example of the workflow a real Laravel resource can grow into.
This demo also registers a PHP-authored showcase across three navigation groups:

- **Shop** — products, product categories, brands, customers, and orders (status
tabs, money columns,
repeatable line items, filters, badges, and order detail infolists).
- **Blog** — posts, authors, and categories (content forms, status views,
searchable tables, copyable slugs, and read-only detail pages).
- **HR & projects** — departments, employees, projects, tasks, timesheets,
leave requests, and expenses (checkbox skill lists, key/value metadata,
repeatable plans and expense lines, workflow tabs, and central validation).

Every resource uses the shared Inlay resource pages and renderer contracts. The
React files under `resources/js/pages/inlay/resource/` are intentionally generic
mount points; the schema, table, infolist, actions, authorization, validation,
navigation group, and seeded data are all defined in PHP. `ShowcaseRules` is an
application example, not a package rule set.

The public demo adds two standalone examples outside the panel:

Expand All @@ -42,8 +54,12 @@ The public demo adds two standalone examples outside the panel:
Those package examples, together with the GitHub source link, are grouped under
**Package demos** in the panel navigation and open in a new browser tab.

The landing page links to those examples, the complete panel, and the Inlay
source repository. CMS packages are intentionally excluded.
The dashboard widgets are also resolved from PHP and include stats, charts, and
recent blog/order tables. CMS packages are intentionally excluded from this
demo so the admin surface stays focused on the core Inlay experience. The
default Media Manager seed includes a text asset and two SVG dashboard assets,
so the picker and delivery routes have useful content immediately after
`migrate --seed`.

## Demo login

Expand Down
2 changes: 1 addition & 1 deletion app/Http/Middleware/HandleAppearance.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class HandleAppearance
*/
public function handle(Request $request, Closure $next): Response
{
View::share('appearance', $request->cookie('appearance') ?? 'system');
View::share('appearance', $request->cookie('appearance') ?? 'light');

return $next($request);
}
Expand Down
12 changes: 12 additions & 0 deletions app/Inlay/Resources/Blog/AuthorCreate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace App\Inlay\Resources\Blog;

use App\Inlay\Resources\Pages\GenericCreatePage;

final class AuthorCreate extends GenericCreatePage
{
protected static string $resource = AuthorResource::class;
}
12 changes: 12 additions & 0 deletions app/Inlay/Resources/Blog/AuthorEdit.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace App\Inlay\Resources\Blog;

use App\Inlay\Resources\Pages\GenericEditPage;

final class AuthorEdit extends GenericEditPage
{
protected static string $resource = AuthorResource::class;
}
14 changes: 14 additions & 0 deletions app/Inlay/Resources/Blog/AuthorList.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

declare(strict_types=1);

namespace App\Inlay\Resources\Blog;

use App\Inlay\Resources\Pages\GenericListPage;

final class AuthorList extends GenericListPage
{
protected static string $resource = AuthorResource::class;

protected int $perPage = 10;
}
104 changes: 104 additions & 0 deletions app/Inlay/Resources/Blog/AuthorResource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<?php

declare(strict_types=1);

namespace App\Inlay\Resources\Blog;

use App\Models\Blog\Author;
use App\Validation\ShowcaseRules;
use Illuminate\Database\Eloquent\Model;
use Inlay\Actions\Action;
use Inlay\Forms\Fields\Textarea;
use Inlay\Forms\Fields\TextInput;
use Inlay\Forms\Fields\Toggle;
use Inlay\Forms\Form;
use Inlay\Infolists\Entries\TextEntry;
use Inlay\Infolists\Infolist;
use Inlay\Resources\Resource;
use Inlay\Resources\ResourceOperation;
use Inlay\Schemas\Components\Grid;
use Inlay\Schemas\Components\Section;
use Inlay\Tables\Columns\IconColumn;
use Inlay\Tables\Columns\TextColumn;
use Inlay\Tables\Filters\BooleanFilter;
use Inlay\Tables\Table;

final class AuthorResource extends Resource
{
protected static string $model = Author::class;

protected static ?string $label = 'Author';

protected static ?string $pluralLabel = 'Authors';

protected static ?string $navigationIcon = 'pen-line';

protected static ?string $navigationGroup = 'Blog';

protected static int $navigationSort = 10;

public static function globallySearchableAttributes(): array
{
return ['name', 'email', 'bio'];
}

public static function recordTitleAttribute(): string
{
return 'name';
}

public static function table(Table $table): Table
{
return $table->searchPlaceholder('Search authors…')->filters([
BooleanFilter::make('active')->label('Active authors'),
])->columns([
TextColumn::make('name')->searchable()->sortable(),
TextColumn::make('email')->searchable()->copyable(),
IconColumn::make('active')->boolean()->trueIcon('check')->falseIcon('x'),
TextColumn::make('created_at')->label('Joined')->date('M j, Y')->sortable(),
])->actions([
Action::make('view')->label('View')->url('/admin/authors/{id}')->method('get'),
Action::make('edit')->url('/admin/authors/{id}/edit')->method('get'),
Action::make('delete')->color('danger')->url('/admin/authors/{id}')->method('delete')->requiresConfirmation(),
])->paginationPageOptions([10, 25, 50]);
}

public static function form(Form $form): Form
{
return $form->submitLabel('Save author')->schema([
Section::make('author')->label('Author profile')->schema([
Grid::make(2)->schema([
TextInput::make('name')->required()->autofocus(),
TextInput::make('email')->email()->required(),
Toggle::make('active')->label('Accepting new posts'),
]),
Textarea::make('bio')->rows(6)->maxLength(2000),
]),
]);
}

public static function infolist(Infolist $infolist): Infolist
{
return $infolist->columns(2)->schema([
TextEntry::make('name')->label('Author'),
TextEntry::make('email')->copyable(),
TextEntry::make('active')->badge()->color('success'),
TextEntry::make('bio')->columnSpanFull()->prose(),
]);
}

public static function validation(): string
{
return ShowcaseRules::class;
}

public static function getPages(): array
{
return ['index' => AuthorList::route('/'), 'create' => AuthorCreate::route('/create'), 'view' => AuthorView::route('/{record}'), 'edit' => AuthorEdit::route('/{record}/edit')];
}

protected static function canAccess(ResourceOperation $operation, ?Model $record, mixed $user): bool
{
return $user !== null;
}
}
12 changes: 12 additions & 0 deletions app/Inlay/Resources/Blog/AuthorView.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace App\Inlay\Resources\Blog;

use App\Inlay\Resources\Pages\GenericViewPage;

final class AuthorView extends GenericViewPage
{
protected static string $resource = AuthorResource::class;
}
12 changes: 12 additions & 0 deletions app/Inlay/Resources/Blog/CategoryCreate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace App\Inlay\Resources\Blog;

use App\Inlay\Resources\Pages\GenericCreatePage;

final class CategoryCreate extends GenericCreatePage
{
protected static string $resource = CategoryResource::class;
}
12 changes: 12 additions & 0 deletions app/Inlay/Resources/Blog/CategoryEdit.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace App\Inlay\Resources\Blog;

use App\Inlay\Resources\Pages\GenericEditPage;

final class CategoryEdit extends GenericEditPage
{
protected static string $resource = CategoryResource::class;
}
14 changes: 14 additions & 0 deletions app/Inlay/Resources/Blog/CategoryList.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

declare(strict_types=1);

namespace App\Inlay\Resources\Blog;

use App\Inlay\Resources\Pages\GenericListPage;

final class CategoryList extends GenericListPage
{
protected static string $resource = CategoryResource::class;

protected int $perPage = 10;
}
81 changes: 81 additions & 0 deletions app/Inlay/Resources/Blog/CategoryResource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php

declare(strict_types=1);

namespace App\Inlay\Resources\Blog;

use App\Models\Blog\Category;
use App\Validation\ShowcaseRules;
use Illuminate\Database\Eloquent\Model;
use Inlay\Actions\Action;
use Inlay\Forms\Fields\Textarea;
use Inlay\Forms\Fields\TextInput;
use Inlay\Forms\Form;
use Inlay\Infolists\Entries\TextEntry;
use Inlay\Infolists\Infolist;
use Inlay\Resources\Resource;
use Inlay\Resources\ResourceOperation;
use Inlay\Schemas\Components\Section;
use Inlay\Tables\Columns\TextColumn;
use Inlay\Tables\Table;

final class CategoryResource extends Resource
{
protected static string $model = Category::class;

protected static ?string $label = 'Category';

protected static ?string $pluralLabel = 'Categories';

protected static ?string $navigationIcon = 'tags';

protected static ?string $navigationGroup = 'Blog';

protected static int $navigationSort = 20;

public static function globallySearchableAttributes(): array
{
return ['name', 'slug', 'description'];
}

public static function recordTitleAttribute(): string
{
return 'name';
}

public static function table(Table $table): Table
{
return $table->searchPlaceholder('Search categories…')->columns([
TextColumn::make('name')->searchable()->sortable(), TextColumn::make('slug')->copyable()->sortable(), TextColumn::make('description')->limit(64)->wrap(),
])->actions([
Action::make('view')->label('View')->url('/admin/categories/{id}')->method('get'), Action::make('edit')->url('/admin/categories/{id}/edit')->method('get'), Action::make('delete')->color('danger')->url('/admin/categories/{id}')->method('delete')->requiresConfirmation(),
])->paginationPageOptions([10, 25, 50]);
}

public static function form(Form $form): Form
{
return $form->submitLabel('Save category')->schema([Section::make('category')->label('Category details')->schema([
TextInput::make('name')->required()->autofocus(), TextInput::make('slug')->required()->helperText('Lowercase URL slug, for example tutorials.'), Textarea::make('description')->rows(5),
])]);
}

public static function infolist(Infolist $infolist): Infolist
{
return $infolist->schema([TextEntry::make('name')->label('Category'), TextEntry::make('slug')->copyable(), TextEntry::make('description')->prose()]);
}

public static function validation(): string
{
return ShowcaseRules::class;
}

public static function getPages(): array
{
return ['index' => CategoryList::route('/'), 'create' => CategoryCreate::route('/create'), 'view' => CategoryView::route('/{record}'), 'edit' => CategoryEdit::route('/{record}/edit')];
}

protected static function canAccess(ResourceOperation $operation, ?Model $record, mixed $user): bool
{
return $user !== null;
}
}
12 changes: 12 additions & 0 deletions app/Inlay/Resources/Blog/CategoryView.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace App\Inlay\Resources\Blog;

use App\Inlay\Resources\Pages\GenericViewPage;

final class CategoryView extends GenericViewPage
{
protected static string $resource = CategoryResource::class;
}
Loading
Loading