├── resources └── views │ ├── .gitkeep │ └── infolists │ └── components │ └── simple-list-entry.blade.php ├── CHANGELOG.md ├── src ├── Testing │ └── TestsFilamentSimpleListEntry.php ├── Infolists │ ├── Traits │ │ ├── HasItemUrl.php │ │ ├── HasItems.php │ │ ├── HasItemActions.php │ │ ├── HasListStyle.php │ │ ├── HasItemIcon.php │ │ ├── HasItemLabel.php │ │ ├── HasItemExtraAttributes.php │ │ └── HasEmptyState.php │ ├── Enums │ │ └── SimpleListListStyle.php │ └── Components │ │ └── SimpleListEntry.php ├── FilamentSimpleListEntryPlugin.php └── FilamentSimpleListEntryServiceProvider.php ├── LICENSE.md ├── composer.json ├── README.md └── configure.php /resources/views/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `:package_name` will be documented in this file. 4 | 5 | ## 1.0.0 - 202X-XX-XX 6 | 7 | - initial release 8 | -------------------------------------------------------------------------------- /src/Testing/TestsFilamentSimpleListEntry.php: -------------------------------------------------------------------------------- 1 | itemUrl = $itemUrl; 14 | 15 | return $this; 16 | } 17 | 18 | public function getItemUrl($record): ?string 19 | { 20 | return $this->evaluate($this->itemUrl, [ 21 | 'record' => $record, 22 | ]); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Infolists/Traits/HasItems.php: -------------------------------------------------------------------------------- 1 | items = $items; 15 | 16 | return $this; 17 | } 18 | 19 | public function getItems(): Collection 20 | { 21 | return collect($this->evaluate($this->items) ?: $this->getState()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Infolists/Enums/SimpleListListStyle.php: -------------------------------------------------------------------------------- 1 | name) { 15 | return $status->value; 16 | } elseif (is_object($name) && $name === $status) { 17 | return $status->value; 18 | } 19 | } 20 | 21 | throw new \ValueError("$name is not a valid backing value for enum " . self::class); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Infolists/Traits/HasItemActions.php: -------------------------------------------------------------------------------- 1 | itemActions = $itemActions; 15 | 16 | return $this; 17 | } 18 | 19 | public function getItemActions($record): mixed 20 | { 21 | return $this->evaluate($this->itemActions, [ 22 | 'record' => $record, 23 | 'state' => $this->getState(), 24 | ]); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/FilamentSimpleListEntryPlugin.php: -------------------------------------------------------------------------------- 1 | getId()); 34 | 35 | return $plugin; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Infolists/Components/SimpleListEntry.php: -------------------------------------------------------------------------------- 1 | listStyle = $listStyle; 17 | 18 | return $this; 19 | } 20 | 21 | public function getListStyle(): string 22 | { 23 | return SimpleListListStyle::fromName($this->evaluate($this->listStyle)); 24 | } 25 | 26 | public function inline($inline): self 27 | { 28 | $this->listStyle('inline'); 29 | $this->inline = $inline; 30 | 31 | return $this; 32 | } 33 | 34 | public function getInline(): bool 35 | { 36 | return (bool) $this->evaluate($this->inline); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Infolists/Traits/HasItemIcon.php: -------------------------------------------------------------------------------- 1 | itemIcon = $itemIcon; 16 | 17 | return $this; 18 | } 19 | 20 | public function getItemIcon($record): mixed 21 | { 22 | return $this->evaluate($this->itemIcon, [ 23 | 'record' => $record, 24 | 'state' => $this->getState(), 25 | ]); 26 | } 27 | 28 | public function itemIconColor(Closure | string | null $itemIconColor): self 29 | { 30 | $this->itemIconColor = $itemIconColor; 31 | 32 | return $this; 33 | } 34 | 35 | public function getItemIconColor($record): mixed 36 | { 37 | return $this->evaluate($this->itemIconColor, [ 38 | 'record' => $record, 39 | 'state' => $this->getState(), 40 | ]); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) :vendor_name 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Infolists/Traits/HasItemLabel.php: -------------------------------------------------------------------------------- 1 | itemLabel = $itemLabel; 16 | 17 | return $this; 18 | } 19 | 20 | public function getItemLabel($record): mixed 21 | { 22 | return $this->evaluate($this->itemLabel ?: $record, [ 23 | 'record' => $record, 24 | 'state' => $this->getState(), 25 | ]); 26 | } 27 | 28 | public function itemDescription(Closure | string | null $itemDescription): self 29 | { 30 | $this->itemDescription = $itemDescription; 31 | 32 | return $this; 33 | } 34 | 35 | public function getItemDescription($record): mixed 36 | { 37 | return $this->evaluate($this->itemDescription, [ 38 | 'record' => $record, 39 | 'state' => $this->getState(), 40 | ]); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Infolists/Traits/HasItemExtraAttributes.php: -------------------------------------------------------------------------------- 1 | | Closure> 12 | */ 13 | protected array $itemExtraAttributes = []; 14 | 15 | /** 16 | * @param array | Closure $attributes 17 | */ 18 | public function itemExtraAttributes(array | Closure $attributes, bool $merge = false): static 19 | { 20 | if ($merge) { 21 | $this->itemExtraAttributes[] = $attributes; 22 | } else { 23 | $this->itemExtraAttributes = [$attributes]; 24 | } 25 | 26 | return $this; 27 | } 28 | 29 | /** 30 | * @return array 31 | */ 32 | public function getItemExtraAttributes($record): array 33 | { 34 | $temporaryAttributeBag = new ComponentAttributeBag(); 35 | 36 | foreach ($this->itemExtraAttributes as $itemExtraAttributes) { 37 | $temporaryAttributeBag = $temporaryAttributeBag->merge($this->evaluate($itemExtraAttributes, [ 38 | 'record' => $record, 39 | ])); 40 | } 41 | 42 | return $temporaryAttributeBag->getAttributes(); 43 | } 44 | 45 | public function getItemExtraAttributeBag($record): ComponentAttributeBag 46 | { 47 | return new ComponentAttributeBag($this->getItemExtraAttributes($record)); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Infolists/Traits/HasEmptyState.php: -------------------------------------------------------------------------------- 1 | emptyStateEnabled = $emptyStateEnabled; 20 | 21 | return $this; 22 | } 23 | 24 | public function getEmptyStateEnabled(): mixed 25 | { 26 | return $this->evaluate($this->emptyStateEnabled); 27 | } 28 | 29 | public function emptyStateHeading(Closure | string | null $emptyStateHeading): self 30 | { 31 | $this->emptyStateHeading = $emptyStateHeading; 32 | 33 | return $this; 34 | } 35 | 36 | public function getEmptyStateHeading(): mixed 37 | { 38 | return $this->evaluate($this->emptyStateHeading); 39 | } 40 | 41 | public function emptyStateDescription(Closure | string | null $emptyStateDescription): self 42 | { 43 | $this->emptyStateDescription = $emptyStateDescription; 44 | 45 | return $this; 46 | } 47 | 48 | public function getEmptyStateDescription(): mixed 49 | { 50 | return $this->evaluate($this->emptyStateDescription); 51 | } 52 | 53 | public function emptyStateIcon(Closure | string | null $emptyStateIcon): self 54 | { 55 | $this->emptyStateIcon = $emptyStateIcon; 56 | 57 | return $this; 58 | } 59 | 60 | public function getEmptyStateIcon(): mixed 61 | { 62 | return $this->evaluate($this->emptyStateIcon); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "thiktak/filament-simple-list-entry", 3 | "type": "project", 4 | "description": "Simple List Entry for Infolist", 5 | "keywords": [ 6 | "laravel", 7 | "filament", 8 | "filament-plugin" 9 | ], 10 | "homepage": "https://github.com/Thiktak/filament-simple-list-entry", 11 | "support": { 12 | "issues": "https://github.com/Thiktak/filament-simple-list-entry/issues", 13 | "source": "https://github.com/Thiktak/filament-simple-list-entry" 14 | }, 15 | "license": "MIT", 16 | "authors": [ 17 | { 18 | "name": "Georges OLIVARES", 19 | "email": "georges.olivares@gmail.com", 20 | "role": "Developer" 21 | } 22 | ], 23 | "require": { 24 | "php": "^8.1", 25 | "filament/filament": "^3.0", 26 | "spatie/laravel-package-tools": "^1.15.0", 27 | "illuminate/contracts": "^10.0" 28 | }, 29 | "require-dev": { 30 | "laravel/pint": "^1.0", 31 | "nunomaduro/collision": "^7.9", 32 | "nunomaduro/larastan": "^2.0.1", 33 | "orchestra/testbench": "^8.0", 34 | "pestphp/pest": "^2.0", 35 | "pestphp/pest-plugin-arch": "^2.0", 36 | "pestphp/pest-plugin-laravel": "^2.0", 37 | "phpstan/extension-installer": "^1.1", 38 | "phpstan/phpstan-deprecation-rules": "^1.0", 39 | "phpstan/phpstan-phpunit": "^1.0", 40 | "spatie/laravel-ray": "^1.26" 41 | }, 42 | "autoload": { 43 | "psr-4": { 44 | "Thiktak\\FilamentSimpleListEntry\\": "src/" 45 | } 46 | }, 47 | "autoload-dev": { 48 | "psr-4": { 49 | "Thiktak\\FilamentSimpleListEntry\\Tests\\": "tests/" 50 | } 51 | }, 52 | "scripts": { 53 | "post-autoload-dump": "@php ./vendor/bin/testbench package:discover --ansi", 54 | "analyse": "vendor/bin/phpstan analyse", 55 | "test": "vendor/bin/pest", 56 | "test-coverage": "vendor/bin/pest --coverage", 57 | "format": "vendor/bin/pint" 58 | }, 59 | "config": { 60 | "sort-packages": true, 61 | "allow-plugins": { 62 | "pestphp/pest-plugin": true, 63 | "phpstan/extension-installer": true 64 | } 65 | }, 66 | "extra": { 67 | "laravel": { 68 | "providers": [ 69 | "Thiktak\\FilamentSimpleListEntry\\FilamentSimpleListEntryServiceProvider" 70 | ] 71 | } 72 | }, 73 | "minimum-stability": "dev", 74 | "prefer-stable": true 75 | } -------------------------------------------------------------------------------- /src/FilamentSimpleListEntryServiceProvider.php: -------------------------------------------------------------------------------- 1 | name(static::$name); 33 | //->hasCommands($this->getCommands()) 34 | /*->hasInstallCommand(function (InstallCommand $command) { 35 | $command 36 | ->publishConfigFile() 37 | ->publishMigrations() 38 | ->askToRunMigrations() 39 | ->askToStarRepoOnGitHub('Thiktak/filament-simple-list-entry'); 40 | });*/ 41 | 42 | $configFileName = $package->shortName(); 43 | 44 | /*if (file_exists($package->basePath("/../config/{$configFileName}.php"))) { 45 | $package->hasConfigFile(); 46 | } 47 | 48 | if (file_exists($package->basePath('/../database/migrations'))) { 49 | $package->hasMigrations($this->getMigrations()); 50 | } 51 | 52 | if (file_exists($package->basePath('/../resources/lang'))) { 53 | $package->hasTranslations(); 54 | }*/ 55 | 56 | if (file_exists($package->basePath('/../resources/views'))) { 57 | $package->hasViews(static::$viewNamespace); 58 | } 59 | } 60 | 61 | public function packageRegistered(): void 62 | { 63 | } 64 | 65 | public function packageBooted(): void 66 | { 67 | // Asset Registration 68 | FilamentAsset::register( 69 | $this->getAssets(), 70 | $this->getAssetPackageName() 71 | ); 72 | 73 | FilamentAsset::registerScriptData( 74 | $this->getScriptData(), 75 | $this->getAssetPackageName() 76 | ); 77 | 78 | // Icon Registration 79 | FilamentIcon::register($this->getIcons()); 80 | 81 | // Handle Stubs 82 | if (app()->runningInConsole()) { 83 | /*foreach (app(Filesystem::class)->files(__DIR__ . '/../stubs/') as $file) { 84 | $this->publishes([ 85 | $file->getRealPath() => base_path("stubs/skeleton/{$file->getFilename()}"), 86 | ], 'skeleton-stubs'); 87 | }*/ 88 | } 89 | 90 | // Testing 91 | //Testable::mixin(new TestsSkeleton()); 92 | } 93 | 94 | protected function getAssetPackageName(): ?string 95 | { 96 | return 'thiktak/filament-simple-list-entry'; 97 | } 98 | 99 | /** 100 | * @return array 101 | */ 102 | protected function getAssets(): array 103 | { 104 | return [ 105 | // AlpineComponent::make('skeleton', __DIR__ . '/../resources/dist/components/skeleton.js'), 106 | //Css::make('skeleton-styles', __DIR__ . '/../resources/dist/skeleton.css'), 107 | //Js::make('skeleton-scripts', __DIR__ . '/../resources/dist/skeleton.js'), 108 | ]; 109 | } 110 | 111 | /** 112 | * @return array 113 | */ 114 | protected function getCommands(): array 115 | { 116 | return [ 117 | //SkeletonCommand::class, 118 | ]; 119 | } 120 | 121 | /** 122 | * @return array 123 | */ 124 | protected function getIcons(): array 125 | { 126 | return []; 127 | } 128 | 129 | /** 130 | * @return array 131 | */ 132 | protected function getRoutes(): array 133 | { 134 | return []; 135 | } 136 | 137 | /** 138 | * @return array 139 | */ 140 | protected function getScriptData(): array 141 | { 142 | return []; 143 | } 144 | 145 | /** 146 | * @return array 147 | */ 148 | protected function getMigrations(): array 149 | { 150 | return [ 151 | //'create_skeleton_table', 152 | ]; 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /resources/views/infolists/components/simple-list-entry.blade.php: -------------------------------------------------------------------------------- 1 | 2 | @php 3 | $items = $getItems(); 4 | $inline = $getInline(); 5 | $badge = null; 6 | $hasBadge = null; 7 | 8 | $isStyleInline = $getListStyle() == 'inline'; 9 | $isStyleList = $getListStyle() == 'list'; 10 | 11 | if ($isStyleList) { 12 | $hasBadge = false; 13 | $separator = null; 14 | 15 | $l1Class = 'divide-y divide-gray-200 overflow-hidden rounded-xl bg-white shadow-sm ring-1 ring-gray-950/5 dark:divide-white/10 dark:bg-gray-900 dark:ring-white/10'; 16 | $l2Class = 'flex items-center w-full p-3 gap-x-3 text-gray-700 outline-none transition duration-75 hover:bg-gray-100 focus:bg-gray-100 dark:text-gray-200 dark:hover:bg-white/5 dark:focus:bg-white/5'; 17 | } else { 18 | $badge = $getBadge(); 19 | $hasBadge = !empty($badge); 20 | $separator = $getSeparator(); 21 | 22 | $l1Class = 'flex flex-wrap items-center ' . ($hasBadge ? 'gap-1.5' : ''); 23 | $l2Class = 'flex items-center ' . ($hasBadge ? '' : 'gap-0.5'); 24 | $inline = false; 25 | } 26 | 27 | @endphp 28 |
29 | @forelse ($items as $itemKey => $itemElement) 30 | @php 31 | $tagHref = $getItemUrl($itemElement); 32 | $tag = $tagHref ? 'a' : 'div'; 33 | 34 | $tagAttributes = $attributes->merge(['class' => $l2Class])->merge($getItemExtraAttributes($itemElement), escape: false); 35 | @endphp 36 |
37 | 38 | @if ($hasBadge) 39 | <{{ $tag }} href="{{ $tagHref }}"> 40 | 41 | {{ $getItemLabel($itemElement) }} 42 | 43 | 44 | @else 45 | <{{ $tag }} href="{{ $tagHref }}"> 46 | @if ($icon = $getItemIcon($itemElement)) 47 | 49 | @endif 50 | 51 | <{{ $tag }} href="{{ $tagHref }}" class="inline-block truncate"> 52 | {{ $getItemLabel($itemElement) }} 53 |

54 | {{ $getItemDescription($itemElement) }} 55 |

56 | 57 | @endif 58 | 59 | @php 60 | $itemActions = $getItemActions($itemElement); 61 | @endphp 62 | @if ($itemActions) 63 |
64 | @if (is_array($itemActions)) 65 | @foreach ($itemActions as $action) 66 | {{ $action }} 67 | @endforeach 68 | @else 69 | {{ $itemActions }} 70 | @endif 71 |
72 | @endif 73 |
74 | @if (!$loop->last && !$hasBadge) 75 | {{ $separator }} 76 | @endif 77 | @empty 78 | @if ($getEmptyStateEnabled()) 79 | @if ($isStyleList) 80 | 82 |
83 |
85 | 87 |
88 |
89 |

91 | {{ $getEmptyStateHeading() }} 92 |

93 |

94 | {{ $getEmptyStateDescription() }} 95 |

96 | {{-- Content --}} 97 |
98 | @else 99 |

100 | {{ $getEmptyStateHeading() }} 101 |

102 | @endif 103 | @endif 104 | @endforelse 105 |
106 |
107 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Preview 2 | 3 | 4 | # Simple List Entry (filament InfoList plugin) 5 | 6 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/Thiktak/filament-simple-list-entry.svg?style=flat-square)](https://packagist.org/packages/Thiktak/filament-simple-list-entry) 7 | [![GitHub Tests Action Status](https://img.shields.io/github/actions/workflow/status/Thiktak/filament-simple-list-entry/run-tests.yml?branch=main&label=tests&style=flat-square)](https://github.com/Thiktak/filament-simple-list-entry/actions?query=workflow%3Arun-tests+branch%3Amain) 8 | [![GitHub Code Style Action Status](https://img.shields.io/github/actions/workflow/status/Thiktak/filament-simple-list-entry/fix-php-code-style-issues.yml?branch=main&label=code%20style&style=flat-square)](https://github.com/Thiktak/filament-simple-list-entry/actions?query=workflow%3A"Fix+PHP+code+style+issues"+branch%3Amain) 9 | [![Total Downloads](https://img.shields.io/packagist/dt/Thiktak/filament-simple-list-entry.svg?style=flat-square)](https://packagist.org/packages/Thiktak/filament-simple-list-entry) 10 | 11 | 12 | Plugin for FilamentPHP v3. 13 | 14 | 15 | ## Installation 16 | 17 | You can install the package via composer: 18 | 19 | ```bash 20 | composer require Thiktak/filament-simple-list-entry 21 | ``` 22 | 23 | ## Usage 24 | 25 | Use it in your Infolist section. 26 | 27 | ```php 28 | // use Thiktak\FilamentSimpleListEntry\Infolists\Components\SimpleListEntry; 29 | 30 | public static function infolist(Infolist $infolist): Infolist 31 | { 32 | return $infolist 33 | ->schema([ 34 | SimpleListEntry::make('users') 35 | ->label('Default with Icon') 36 | ->itemIcon('heroicon-o-check'), 37 | ]); 38 | } 39 | ``` 40 | 41 | All methods: 42 | 43 | * Generic: 44 | * ```->label('Define top section label')``` 45 | * ```->inline(true)``` change the listStyle to inline. Activate separator. 46 | * ```->badge(true)``` activate the badge for each line. Desactivate itemActions, itemDescription 47 | * ```->separator(',')``` change the separator, by default ```, ``` (coma space) 48 | * ```->getStateUsing(['a', 'b', 'c'])``` specify manually the data to be used oterwize use the relationship 49 | * ```->emptyStateEnabled(true)``` activate or not the Empty State. Default true 50 | * ```->emptyStateHeading('No data')``` change the Heading of the Empty State 51 | * ```->emptyStateDescription('There is nothing')``` change the Description of the Empty State 52 | * ```->emptyStateIcon('heroicon-o-x-mark')``` change the Icon of the Empty State 53 | 54 | * Record specific (all are Closure compatible): 55 | * ```->itemLabel(fn ($record) => $record->item)``` specify the label. By default, will try to stringify the record 56 | * ```->itemDescription(fn ($record) => sprintf('Percentage: %s%%', $record['score'] * 100))``` add description under the label 57 | * ```->itemIcon(fn($record) => 'heroicon-o-check')``` define an icon. 58 | * ```->itemIconColor(fn($record) => 'warning')``` define a color for the icon. 59 | * ```->itemUrl(fn($record) => '#')``` define a link if the user click on the icon, label or description. 60 | * ```->itemActions(fn($record) => ...)``` define Actions and ActionGroups at the right of the line. See Filament Actions documentation. 61 | 62 | ## List of examples 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 75 | 84 | 85 | 86 | 87 | 92 | 104 | 105 | 106 | 107 | 112 | 122 | 123 | 124 | 125 | 130 | 140 | 141 | 142 | 143 | 148 | 165 | 166 | 167 | 168 | 173 | 210 | 211 |
Example Code
71 | Default with Icon 72 | 73 | ![image](https://github.com/Thiktak/filament-simple-list-entry/assets/1201486/7e0ed37a-d4a2-495b-9256-97ed259c6db9) 74 | 76 | 77 | ```php 78 | SimpleListEntry::make('') 79 | ->label('Default with Icon') 80 | ->getStateUsing(['a', 'b', 'c']) 81 | ->itemIcon('heroicon-o-check'), 82 | ``` 83 |
88 | Inline badges list with icons & links 89 | 90 | ![image](https://github.com/Thiktak/filament-simple-list-entry/assets/1201486/d3d065a6-a6d1-4ae9-a14c-16ec4caa8897) 91 | 93 | 94 | ```php 95 | SimpleListEntry::make('') 96 | ->inline(true) 97 | ->label('Inline badge with icon & link') 98 | ->getStateUsing(['a', 'b', 'c']) 99 | ->itemIcon('heroicon-o-check') 100 | ->itemUrl(fn ($record) => '#' . $record) 101 | ->badge(true), 102 | ``` 103 |
108 | Inline list with custom separator 109 | 110 | ![image](https://github.com/Thiktak/filament-simple-list-entry/assets/1201486/f5ed573c-0e89-4314-964c-a9c50117a1af) 111 | 113 | 114 | ```php 115 | SimpleListEntry::make('') 116 | ->listStyle('inline') 117 | ->label('inline simple +') 118 | ->getStateUsing(['a', 'b', 'c']) 119 | ->separator(' + '), 120 | ``` 121 |
126 | Inline list with Icon 127 | 128 | ![image](https://github.com/Thiktak/filament-simple-list-entry/assets/1201486/abd4f68d-8704-4d3f-a8c9-90f8eeeaca11) 129 | 131 | 132 | ```php 133 | SimpleListEntry::make('') 134 | ->listStyle('inline') 135 | ->label('inline with Icon') 136 | ->getStateUsing(['a', 'b', 'c']) 137 | ->itemIcon('heroicon-o-check'), 138 | ``` 139 |
144 | Complexe list with actions 145 | 146 | ![image](https://github.com/Thiktak/filament-simple-list-entry/assets/1201486/84737aa3-4a3f-440e-92a7-829ae7cfce3c) 147 | 149 | 150 | ```php 151 | SimpleListEntry::make('scoresTop5') 152 | ->listStyle('list') 153 | ->itemLabel(fn ($record) => $record->item) 154 | ->itemUrl(fn ($record) => '#Url-' . $record->id) 155 | ->itemActions( 156 | fn ($record) => ActionGroup::make([ 157 | Action::make('view'), 158 | Action::make('edit'), 159 | Action::make('delete'), 160 | ]) 161 | ->size(ActionSize::Small) 162 | ), 163 | ``` 164 |
169 | Complexe list with custom data, and all options 170 | 171 | ![image](https://github.com/Thiktak/filament-simple-list-entry/assets/1201486/43716a49-84ea-4d80-bfcd-6d5b0bfa9624) 172 | 174 | 175 | ```php 176 | SimpleListEntry::make('checklist') 177 | ->listStyle('list') 178 | ->getStateUsing([ 179 | ['name' => 'Complete profile #1', 'score' => 1], 180 | ['name' => 'Complete profile #2', 'score' => .75] 181 | ]) 182 | ->itemIcon(fn ($record) => match (true) { 183 | $record['score'] >= 1 => 'heroicon-o-check', 184 | default => 'heroicon-o-exclamation-triangle' 185 | }) 186 | ->itemIconColor(fn ($record) => match (true) { 187 | $record['score'] >= 1 => 'success', 188 | default => 'danger' 189 | }) 190 | ->itemActions( 191 | fn ($record) => [ 192 | ViewAction::make('view1') 193 | ->url('#View1-' . $record['name']), 194 | ActionGroup::make([ 195 | Action::make('view2') 196 | ->url('#View2-' . $record['name']), 197 | Action::make('edit'), 198 | Action::make('delete'), 199 | ]) 200 | ->size(ActionSize::Small) 201 | ] 202 | ) 203 | ->itemUrl(fn ($record) => '#Url-' . $record['name']) 204 | ->itemLabel(fn ($record) => $record['name']) 205 | ->itemDescription(function ($record) { 206 | return sprintf('Percentage: %s%%', $record['score'] * 100) 207 | }), 208 | ``` 209 |
212 | 213 | ## Dark mode support 214 | 215 | This plugin is compatible with Light mode and Dark mode. 216 | 217 | ![image](https://github.com/Thiktak/filament-simple-list-entry/assets/1201486/081f067c-dba6-4f58-a080-334de72b5041) 218 | ![image](https://github.com/Thiktak/filament-simple-list-entry/assets/1201486/53171918-7c13-4c14-99af-63cc42ff9a95) 219 | 220 | 221 | 222 | ## Testing 223 | 224 | ```bash 225 | composer test 226 | ``` 227 | 228 | ## Changelog 229 | 230 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 231 | 232 | ## Contributing 233 | 234 | Please see [CONTRIBUTING](.github/CONTRIBUTING.md) for details. 235 | 236 | (Do not hesitate to contribute !) 237 | 238 | ## Security Vulnerabilities 239 | 240 | Please review [our security policy](../../security/policy) on how to report security vulnerabilities. 241 | 242 | ## Credits 243 | 244 | - [Thiktak](https://github.com/:Thiktak) 245 | - [All Contributors](../../contributors) 246 | 247 | ## License 248 | 249 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 250 | -------------------------------------------------------------------------------- /configure.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | " : '') . "\e[0m"); 49 | writeln("Namespace : \e[0;36m$vendorNamespace\\$className\e[0m"); 50 | writeln("Class name : \e[0;36m$className\e[0m"); 51 | writeln('---'); 52 | writeln("\e[1;37mPackages & Utilities\e[0m"); 53 | writeln('Larastan/PhpStan : ' . ($usePhpStan ? "\e[0;32mYes" : "\e[0;31mNo") . "\e[0m"); 54 | writeln('Pint : ' . ($usePint ? "\e[0;32mYes" : "\e[0;31mNo") . "\e[0m"); 55 | writeln('Use Dependabot : ' . ($useDependabot ? "\e[0;32mYes" : "\e[0;31mNo") . "\e[0m"); 56 | writeln('Use Ray : ' . ($useLaravelRay ? "\e[0;32mYes" : "\e[0;31mNo") . "\e[0m"); 57 | writeln('Auto-Changelog : ' . ($useUpdateChangelogWorkflow ? "\e[0;32mYes" : "\e[0;31mNo") . "\e[0m"); 58 | if ($formsOnly) { 59 | writeln("Filament/Forms : \e[0;32mYes\e[0m"); 60 | } elseif ($tablesOnly) { 61 | writeln("Filament/Tables : \e[0;32mYes\e[0m"); 62 | } else { 63 | writeln("Filament/Filament : \e[0;32mYes\e[0m"); 64 | } 65 | writeln('------'); 66 | writeln("\r"); 67 | writeln('This script will replace the above values in all relevant files in the project directory.'); 68 | writeln("\r"); 69 | 70 | if (! confirm('Modify files?', true)) { 71 | exit(1); 72 | } 73 | 74 | if ($formsOnly) { 75 | safeUnlink(__DIR__ . '/src/SkeletonTheme.php'); 76 | safeUnlink(__DIR__ . '/src/SkeletonPlugin.php'); 77 | 78 | removeComposerDeps([ 79 | 'filament/filament', 80 | 'filament/tables', 81 | ], 'require'); 82 | } elseif ($tablesOnly) { 83 | safeUnlink(__DIR__ . '/src/SkeletonTheme.php'); 84 | safeUnlink(__DIR__ . '/src/SkeletonPlugin.php'); 85 | 86 | removeComposerDeps([ 87 | 'filament/filament', 88 | 'filament/forms', 89 | ], 'require'); 90 | } else { 91 | if ($isTheme) { 92 | safeUnlink(__DIR__ . '/src/SkeletonServiceProvider.php'); 93 | safeUnlink(__DIR__ . '/src/SkeletonPlugin.php'); 94 | safeUnlink(__DIR__ . '/src/Skeleton.php'); 95 | removeDirectory(__DIR__ . '/bin'); 96 | removeDirectory(__DIR__ . '/config'); 97 | removeDirectory(__DIR__ . '/database'); 98 | removeDirectory(__DIR__ . '/stubs'); 99 | removeDirectory(__DIR__ . '/resources/js'); 100 | removeDirectory(__DIR__ . '/resources/lang'); 101 | removeDirectory(__DIR__ . '/resources/views'); 102 | removeDirectory(__DIR__ . '/src/Commands'); 103 | removeDirectory(__DIR__ . '/src/Facades'); 104 | removeDirectory(__DIR__ . '/src/Testing'); 105 | 106 | setupPackageJsonForTheme(); 107 | 108 | } else { 109 | safeUnlink(__DIR__ . '/src/SkeletonTheme.php'); 110 | } 111 | 112 | removeComposerDeps([ 113 | 'filament/forms', 114 | 'filament/tables', 115 | ], 'require'); 116 | } 117 | 118 | $files = (str_starts_with(strtoupper(PHP_OS), 'WIN') ? replaceForWindows() : replaceForAllOtherOSes()); 119 | 120 | foreach ($files as $file) { 121 | replaceInFile($file, [ 122 | ':author_name' => $authorName, 123 | ':author_username' => $authorUsername, 124 | 'author@domain.com' => $authorEmail, 125 | ':vendor_name' => $vendorName, 126 | ':vendor_slug' => $vendorSlug, 127 | 'VendorName' => $vendorNamespace, 128 | ':package_name' => $packageName, 129 | ':package_slug' => $packageSlug, 130 | ':package_slug_without_prefix' => $packageSlugWithoutPrefix, 131 | 'Skeleton' => $className, 132 | 'skeleton' => $packageSlug, 133 | 'migration_table_name' => titleSnake($packageSlug), 134 | 'variable' => $variableName, 135 | ':package_description' => $description, 136 | ]); 137 | 138 | match (true) { 139 | str_contains($file, determineSeparator('src/Skeleton.php')) => rename($file, determineSeparator('./src/' . $className . '.php')), 140 | str_contains($file, determineSeparator('src/SkeletonServiceProvider.php')) => rename($file, determineSeparator('./src/' . $className . 'ServiceProvider.php')), 141 | str_contains($file, determineSeparator('src/SkeletonTheme.php')) => rename($file, determineSeparator('./src/' . $className . 'Theme.php')), 142 | str_contains($file, determineSeparator('src/SkeletonPlugin.php')) => rename($file, determineSeparator('./src/' . $className . 'Plugin.php')), 143 | str_contains($file, determineSeparator('src/Facades/Skeleton.php')) => rename($file, determineSeparator('./src/Facades/' . $className . '.php')), 144 | str_contains($file, determineSeparator('src/Commands/SkeletonCommand.php')) => rename($file, determineSeparator('./src/Commands/' . $className . 'Command.php')), 145 | str_contains($file, determineSeparator('src/Testing/TestsSkeleton.php')) => rename($file, determineSeparator('./src/Testing/Tests' . $className . '.php')), 146 | str_contains($file, determineSeparator('database/migrations/create_skeleton_table.php.stub')) => rename($file, determineSeparator('./database/migrations/create_' . titleSnake($packageSlugWithoutPrefix) . '_table.php.stub')), 147 | str_contains($file, determineSeparator('config/skeleton.php')) => rename($file, determineSeparator('./config/' . $packageSlugWithoutPrefix . '.php')), 148 | str_contains($file, determineSeparator('resources/lang/en/skeleton.php')) => rename($file, determineSeparator('./resources/lang/en/' . $packageSlugWithoutPrefix . '.php')), 149 | str_contains($file, 'README.md') => removeTag($file, 'delete'), 150 | default => [], 151 | }; 152 | } 153 | 154 | if (! $useDependabot) { 155 | safeUnlink(__DIR__ . '/.github/dependabot.yml'); 156 | safeUnlink(__DIR__ . '/.github/workflows/dependabot-auto-merge.yml'); 157 | } 158 | 159 | if (! $useLaravelRay) { 160 | removeComposerDeps(['spatie/laravel-ray'], 'require-dev'); 161 | } 162 | 163 | if (! $usePhpStan) { 164 | safeUnlink(__DIR__ . '/phpstan.neon.dist'); 165 | safeUnlink(__DIR__ . '/phpstan-baseline.neon'); 166 | safeUnlink(__DIR__ . '/.github/workflows/phpstan.yml'); 167 | 168 | removeComposerDeps([ 169 | 'phpstan/extension-installer', 170 | 'phpstan/phpstan-deprecation-rules', 171 | 'phpstan/phpstan-phpunit', 172 | 'nunomaduro/larastan', 173 | ], 'require-dev'); 174 | 175 | removeComposerDeps(['analyse'], 'scripts'); 176 | } 177 | 178 | if (! $usePint) { 179 | safeUnlink(__DIR__ . '/.github/workflows/fix-php-code-style-issues.yml'); 180 | safeUnlink(__DIR__ . '/pint.json'); 181 | 182 | removeComposerDeps([ 183 | 'laravel/pint', 184 | ], 'require-dev'); 185 | 186 | removeComposerDeps(['format'], 'scripts'); 187 | } 188 | 189 | if (! $useUpdateChangelogWorkflow) { 190 | safeUnlink(__DIR__ . '/.github/workflows/update-changelog.yml'); 191 | } 192 | 193 | confirm('Execute `composer install`?') && run('composer install'); 194 | 195 | if (confirm('Let this script delete itself?', true)) { 196 | unlink(__FILE__); 197 | } 198 | 199 | function ask(string $question, string $default = ''): string 200 | { 201 | $def = $default ? "\e[0;33m ($default)" : ''; 202 | $answer = readline("\e[0;32m" . $question . $def . ": \e[0m"); 203 | 204 | if (! $answer) { 205 | return $default; 206 | } 207 | 208 | return $answer; 209 | } 210 | 211 | function confirm(string $question, bool $default = false): bool 212 | { 213 | $answer = ask($question, ($default ? 'Y/n' : 'y/N')); 214 | 215 | if (strtolower($answer) === 'y/n') { 216 | return $default; 217 | } 218 | 219 | return strtolower($answer) === 'y'; 220 | } 221 | 222 | function writeln(string $line): void 223 | { 224 | echo $line . PHP_EOL; 225 | } 226 | 227 | function run(string $command): string 228 | { 229 | return trim((string) shell_exec($command)); 230 | } 231 | 232 | function slugify(string $subject): string 233 | { 234 | return strtolower(trim(preg_replace('/[^A-Za-z0-9-]+/', '-', $subject), '-')); 235 | } 236 | 237 | function titleCase(string $subject): string 238 | { 239 | return str_replace(' ', '', ucwords(str_replace(['-', '_'], ' ', $subject))); 240 | } 241 | 242 | function titleSnake(string $subject, string $replace = '_'): string 243 | { 244 | return str_replace(['-', '_'], $replace, $subject); 245 | } 246 | 247 | function replaceInFile(string $file, array $replacements): void 248 | { 249 | $contents = file_get_contents($file); 250 | 251 | file_put_contents( 252 | $file, 253 | str_replace( 254 | array_keys($replacements), 255 | array_values($replacements), 256 | $contents 257 | ) 258 | ); 259 | } 260 | 261 | function removePrefix(string $prefix, string $content): string 262 | { 263 | if (str_starts_with($content, $prefix)) { 264 | return substr($content, strlen($prefix)); 265 | } 266 | 267 | return $content; 268 | } 269 | 270 | function removeComposerDeps(array $names, string $location): void 271 | { 272 | $data = json_decode(file_get_contents(__DIR__ . '/composer.json'), true); 273 | 274 | foreach ($data[$location] as $name => $version) { 275 | if (in_array($name, $names, true)) { 276 | unset($data[$location][$name]); 277 | } 278 | } 279 | 280 | file_put_contents(__DIR__ . '/composer.json', json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); 281 | } 282 | 283 | function removeNpmDeps(array $names, string $location): void 284 | { 285 | $data = json_decode(file_get_contents(__DIR__ . '/package.json'), true); 286 | 287 | foreach ($data[$location] as $name => $version) { 288 | if (in_array($name, $names, true)) { 289 | unset($data[$location][$name]); 290 | } 291 | } 292 | 293 | file_put_contents(__DIR__ . '/package.json', json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | 294 | JSON_UNESCAPED_UNICODE)); 295 | } 296 | 297 | function removeTag(string $file, string $tag): void 298 | { 299 | $contents = file_get_contents($file); 300 | 301 | file_put_contents( 302 | $file, 303 | preg_replace('/.*/s', '', $contents) ?: $contents 304 | ); 305 | } 306 | 307 | function setupPackageJsonForTheme(): void 308 | { 309 | removeNpmDeps([ 310 | 'purge', 311 | 'dev', 312 | 'dev:scripts', 313 | 'build', 314 | 'build:scripts', 315 | ], 'scripts'); 316 | 317 | removeNpmDeps([ 318 | '@awcodes/filament-plugin-purge', 319 | 'esbuild', 320 | 'npm-run-all', 321 | 'prettier', 322 | 'prettier-plugin-tailwindcss', 323 | ], 'devDependencies'); 324 | 325 | replaceInFile(__DIR__ . '/package.json', [ 326 | 'dev:styles' => 'dev', 327 | 'build:styles' => 'build', 328 | ]); 329 | } 330 | 331 | function safeUnlink(string $filename): void 332 | { 333 | if (file_exists($filename) && is_file($filename)) { 334 | unlink($filename); 335 | } 336 | } 337 | 338 | function determineSeparator(string $path): string 339 | { 340 | return str_replace('/', DIRECTORY_SEPARATOR, $path); 341 | } 342 | 343 | function replaceForWindows(): array 344 | { 345 | return preg_split('/\\r\\n|\\r|\\n/', run('dir /S /B * | findstr /v /i .git\ | findstr /v /i vendor | findstr /v /i ' . basename(__FILE__) . ' | findstr /r /i /M /F:/ ":author :vendor :package VendorName skeleton migration_table_name vendor_name vendor_slug author@domain.com"')); 346 | } 347 | 348 | function replaceForAllOtherOSes(): array 349 | { 350 | return explode(PHP_EOL, run('grep -E -r -l -i ":author|:vendor|:package|VendorName|skeleton|migration_table_name|vendor_name|vendor_slug|author@domain.com" --exclude-dir=vendor ./* ./.github/* | grep -v ' . basename(__FILE__))); 351 | } 352 | 353 | function removeDirectory($dir): void 354 | { 355 | if (is_dir($dir)) { 356 | $objects = scandir($dir); 357 | foreach ($objects as $object) { 358 | if ($object != '.' && $object != '..') { 359 | if (filetype($dir . '/' . $object) == 'dir') { 360 | removeDirectory($dir . '/' . $object); 361 | } else { 362 | unlink($dir . '/' . $object); 363 | } 364 | } 365 | } 366 | rmdir($dir); 367 | } 368 | } 369 | --------------------------------------------------------------------------------