├── tests ├── Browser │ ├── source │ │ └── .gitignore │ ├── console │ │ └── .gitignore │ ├── screenshots │ │ └── .gitignore │ ├── resources │ │ └── views │ │ │ ├── options.blade.php │ │ │ └── select.blade.php │ └── ComboboxComponentTest.php ├── App │ ├── Models │ │ ├── Car.php │ │ ├── Extra.php │ │ └── Option.php │ └── Http │ │ └── Livewire │ │ ├── ComboboxSelects.php │ │ └── ComboboxOptions.php ├── Feature │ ├── ComboboxComponentTest.php │ └── FieldComponentTest.php ├── DuskElements.php ├── DuskCommand.php ├── TestCase.php └── HttpKernel.php ├── .github └── FUNDING.yml ├── .styleci.yml ├── src ├── Contracts │ ├── Field.php │ └── Combobox.php ├── LivewireComboboxServiceProvider.php └── Components │ ├── Fields │ └── Select.php │ ├── FieldGetters.php │ ├── FieldComponent.php │ └── ComboboxComponent.php ├── resources ├── views │ ├── loading.blade.php │ ├── combobox.blade.php │ └── type │ │ └── select.blade.php └── css │ └── combobox.min.css ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── phpunit.xml.dist ├── composer.json ├── CONTRIBUTING.md └── README.md /tests/Browser/source/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /tests/Browser/console/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /tests/Browser/screenshots/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [daguilarm] 4 | -------------------------------------------------------------------------------- /.styleci.yml: -------------------------------------------------------------------------------- 1 | risky: false 2 | version: 8 3 | preset: laravel 4 | finder: 5 | exclude: 6 | - "modules" 7 | - "node_modules" 8 | - "storage" 9 | - "vendor" 10 | name: "*.php" 11 | not-name: 12 | - "*.blade.php" 13 | - "_ide_helper.php" 14 | -------------------------------------------------------------------------------- /src/Contracts/Field.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | public function elements(): array; 15 | } 16 | -------------------------------------------------------------------------------- /tests/Browser/resources/views/options.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Testing 5 | @livewireStyles 6 | @LivewireComboboxCss 7 | 8 | 9 |
10 | @livewire('combobox-options') 11 |
12 | @livewireScripts 13 | 14 | 15 | -------------------------------------------------------------------------------- /tests/Browser/resources/views/select.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Testing 5 | @livewireStyles 6 | @LivewireComboboxCss 7 | 8 | 9 |
10 | @livewire('combobox-selects') 11 |
12 | @livewireScripts 13 | 14 | 15 | -------------------------------------------------------------------------------- /tests/App/Models/Car.php: -------------------------------------------------------------------------------- 1 | 1, 'name' => 'Renault'], 15 | ['id' => 2, 'name' => 'Ford'], 16 | ]; 17 | } 18 | -------------------------------------------------------------------------------- /resources/views/loading.blade.php: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /vendor/ 3 | node_modules/ 4 | npm-debug.log 5 | yarn-error.log 6 | 7 | # Laravel 4 specific 8 | bootstrap/compiled.php 9 | app/storage/ 10 | 11 | # Laravel 5 & Lumen specific 12 | public/storage 13 | public/hot 14 | 15 | # Laravel 5 & Lumen specific with changed public path 16 | public_html/storage 17 | public_html/hot 18 | 19 | storage/*.key 20 | .env 21 | Homestead.yaml 22 | Homestead.json 23 | /.vagrant 24 | .phpunit.result.cache 25 | -------------------------------------------------------------------------------- /resources/views/combobox.blade.php: -------------------------------------------------------------------------------- 1 |
2 | 3 | {{-- Elements --}} 4 | @foreach ($elements as $element) 5 | {{-- Include a select field --}} 6 | @includeWhen( 7 | $element->getType() === 'select', 8 | 'livewire-combobox::type.select' 9 | ) 10 | @endforeach 11 | 12 | {{-- Loading --}} 13 | @if ($config['loading']) 14 | @include('livewire-combobox::loading') 15 | @endif 16 |
17 | -------------------------------------------------------------------------------- /tests/App/Models/Extra.php: -------------------------------------------------------------------------------- 1 | 1, 'option_id' => 1, 'extra' => 'Extra Renault 1'], 15 | ['id' => 2, 'option_id' => 1, 'extra' => 'Extra Renault 2'], 16 | ['id' => 3, 'option_id' => 5, 'extra' => 'Extra Ford 1'], 17 | ['id' => 4, 'option_id' => 5, 'extra' => 'Extra Ford 2'], 18 | ]; 19 | } 20 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `Livewire Combobox` will be documented in this file: 4 | 5 | ## 1.2 - 2021-05-11 6 | 7 | - New code structure. All the code has been refactoring and optimized. 8 | - Major security improvements, and now, the properties are `protected` not `public` as before. 9 | - New method `disabledOnEmpty()`. 10 | - The variable `public string $comboboxContainerClass` now is `protected string $containerClass`. 11 | 12 | ## 1.1 - 2021-05-04 13 | 14 | - New loading indicator. 15 | - New method `withoutResponse()`. 16 | - Fixed bug when main selector was reset. 17 | 18 | ## 1.0 - 2021-04-30 19 | 20 | - Initial commit. 21 | -------------------------------------------------------------------------------- /resources/css/combobox.min.css: -------------------------------------------------------------------------------- 1 | /* Loading color */ 2 | :root { 3 | --background: rgba(129, 140, 248, 1); 4 | } 5 | 6 | /* Minify css */ 7 | .loading-ellipsis{position:relative;width:80px;height:25px}.loading-ellipsis div{position:absolute;width:13px;height:13px;border-radius:50%;background:var(--background);animation-timing-function:cubic-bezier(0,1,1,0)}.loading-ellipsis div:nth-child(1){left:8px;animation:loading-ellipsis1 .6s infinite}.loading-ellipsis div:nth-child(2){left:8px;animation:loading-ellipsis2 .6s infinite}.loading-ellipsis div:nth-child(3){left:32px;animation:loading-ellipsis2 .6s infinite}.loading-ellipsis div:nth-child(4){left:56px;animation:loading-ellipsis3 .6s infinite}@keyframes loading-ellipsis1{0%{transform:scale(0)}100%{transform:scale(1)}}@keyframes loading-ellipsis3{0%{transform:scale(1)}100%{transform:scale(0)}}@keyframes loading-ellipsis2{0%{transform:translate(0,0)}100%{transform:translate(24px,0)}} 8 | -------------------------------------------------------------------------------- /src/LivewireComboboxServiceProvider.php: -------------------------------------------------------------------------------- 1 | name('livewire-combobox') 17 | ->hasViews(); 18 | } 19 | 20 | public function bootingPackage(): void 21 | { 22 | // Blade directives 23 | Blade::directive('LivewireComboboxCss', static function ($expression) { 24 | $file = file_get_contents(__DIR__.'/../resources/css/combobox.min.css'); 25 | 26 | return sprintf('', trim($file)); 27 | }); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/Feature/ComboboxComponentTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(count($combobox->elements()), 3); 23 | 24 | // Testing the number of parent elements 25 | $totalParanetElements = collect($combobox->elements()) 26 | ->filter(function ($element) { 27 | return ! $element->getParentUriKey(); 28 | })->count(); 29 | 30 | $this->assertEquals($totalParanetElements, 1); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Damián Aguilar 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | ./tests/Browser 16 | 17 | 18 | ./tests/Feature 19 | 20 | 21 | 22 | 23 | ./src 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /tests/App/Models/Option.php: -------------------------------------------------------------------------------- 1 | 1, 'car_id' => 1, 'option' => 'Option Renault 1', 'option_date' => 'Manufactured by Renault in 2010'], 15 | ['id' => 2, 'car_id' => 1, 'option' => 'Option Renault 2', 'option_date' => 'Manufactured by Renault in 2011'], 16 | ['id' => 3, 'car_id' => 1, 'option' => 'Option Renault 3', 'option_date' => 'Manufactured by Renault in 2012'], 17 | ['id' => 4, 'car_id' => 1, 'option' => 'Option Renault 4', 'option_date' => 'Manufactured by Renault in 2013'], 18 | ['id' => 5, 'car_id' => 2, 'option' => 'Option Ford 1', 'option_date' => 'Manufactured by Ford in 2014'], 19 | ['id' => 6, 'car_id' => 2, 'option' => 'Option Ford 2', 'option_date' => 'Manufactured by Ford in 2015'], 20 | ['id' => 7, 'car_id' => 2, 'option' => 'Option Ford 3', 'option_date' => 'Manufactured by Ford in 2016'], 21 | ['id' => 8, 'car_id' => 2, 'option' => 'Option Ford 4', 'option_date' => 'Manufactured by Ford in 2017'], 22 | ['id' => 9, 'car_id' => 2, 'option' => 'Option Ford 5', 'option_date' => 'Manufactured by Ford in 2018'], 23 | ]; 24 | } 25 | -------------------------------------------------------------------------------- /tests/App/Http/Livewire/ComboboxSelects.php: -------------------------------------------------------------------------------- 1 | uriKey('key-for-car') 22 | ->options(function ($model) { 23 | return $model->pluck('name', 'id')->toArray(); 24 | }), 25 | // Testing dependOn attributes 26 | Select::make('Options for cars') 27 | ->uriKey('key-for-options') 28 | ->model(Option::class) 29 | ->dependOn('key-for-car') 30 | ->foreignKey('car_id') 31 | ->selectRows('id', 'option'), 32 | Select::make('Extras for cars', Extra::class) 33 | ->firstRemoved() 34 | ->uriKey('key-for-extras') 35 | ->dependOn('key-for-options') 36 | ->foreignKey('option_id') 37 | ->selectRows('id', 'extra'), 38 | ]; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/DuskElements.php: -------------------------------------------------------------------------------- 1 | breakIntoATinkerShell($browsers, $e); 31 | } 32 | 33 | throw $e; 34 | } catch (Throwable $e) { 35 | if (DuskOptions::hasUI()) { 36 | $this->breakIntoATinkerShell($browsers, $e); 37 | } 38 | 39 | throw $e; 40 | } 41 | }); 42 | } 43 | 44 | public function breakIntoATinkerShell($browsers, $e) 45 | { 46 | $sh = new Shell(); 47 | 48 | $sh->add(new DuskCommand($this, $e)); 49 | 50 | $sh->setScopeVariables([ 51 | 'browsers' => $browsers, 52 | ]); 53 | 54 | $sh->addInput('dusk'); 55 | 56 | $sh->setBoundObject($this); 57 | 58 | $sh->run(); 59 | 60 | return $sh->getScopeVariables(false); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /tests/App/Http/Livewire/ComboboxOptions.php: -------------------------------------------------------------------------------- 1 | uriKey('key-for-car') 22 | ->options(function ($model) { 23 | return $model->pluck('name', 'id')->toArray(); 24 | }) 25 | ->class( 26 | container: 'bg-green-500', 27 | field: 'text-green-600', 28 | label: 'text-white' 29 | ), 30 | // Testing dependOn attributes 31 | Select::make('Options for cars') 32 | ->uriKey('key-for-options') 33 | ->model(Option::class) 34 | ->dependOn('key-for-car') 35 | ->foreignKey('car_id') 36 | ->selectRows('id', 'option'), 37 | Select::make('Extras for cars', Extra::class) 38 | ->firstRemoved() 39 | ->hideOnEmpty() 40 | ->uriKey('key-for-extras') 41 | ->dependOn('key-for-options') 42 | ->foreignKey('option_id') 43 | ->selectRows('id', 'extra') 44 | ->withoutResponse(), 45 | ]; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Components/Fields/Select.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | public array $options = []; 19 | 20 | protected string $type = 'select'; 21 | protected bool $firstRemoved = false; 22 | protected bool $hideOnEmpty = false; 23 | 24 | /** 25 | * @var Closure 26 | */ 27 | protected $optionsCallback; 28 | 29 | /** 30 | * Init the class. 31 | * 32 | * @see Daguilarm\LivewireCombobox\Components\FieldComponent::make() 33 | */ 34 | public function __construct(public string $label, public ?string $model = null) 35 | { 36 | } 37 | 38 | /** 39 | * Show or hide empty first element. 40 | */ 41 | public function firstRemoved(bool $value = true): self 42 | { 43 | $this->firstRemoved = $value; 44 | 45 | return $this; 46 | } 47 | 48 | /** 49 | * Hide the element if has not values. 50 | */ 51 | public function hideOnEmpty(bool $value = true): self 52 | { 53 | $this->hideOnEmpty = $value; 54 | 55 | return $this; 56 | } 57 | 58 | /** 59 | * Set the options. 60 | */ 61 | public function options(array | callable $value): self 62 | { 63 | // If callback 64 | if (is_callable($value)) { 65 | $this->options = call_user_func($value, new $this->model()); 66 | 67 | // If array 68 | } else { 69 | $this->options = $value; 70 | } 71 | 72 | return $this; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "daguilarm/livewire-combobox", 3 | "description": "A Livewire select combobox for Laravel", 4 | "keywords": [ 5 | "livewire", 6 | "alpineJS", 7 | "package", 8 | "combobox", 9 | "select", 10 | "laravel", 11 | "daguilarm" 12 | ], 13 | "homepage": "https://github.com/daguilarm/livewire-combobox", 14 | "license": "MIT", 15 | "type": "library", 16 | "authors": [ 17 | { 18 | "name": "Damián Aguilar", 19 | "email": "damian.aguilarm@gmail.com", 20 | "homepage": "https://daguilar.dev", 21 | "role": "Developer" 22 | } 23 | ], 24 | "require": { 25 | "php": "^8.0", 26 | "livewire/livewire": "^2.3", 27 | "mattlibera/livewire-flash": "^0.5.0", 28 | "spatie/laravel-package-tools": "^1.6", 29 | "symfony/http-kernel": "^5.0" 30 | }, 31 | "require-dev": { 32 | "calebporzio/sushi": "^2.2", 33 | "nunomaduro/phpinsights": "dev-master", 34 | "orchestra/testbench": "^6.13", 35 | "orchestra/testbench-dusk": "^6.7", 36 | "phpunit/phpunit": "^9.4", 37 | "psy/psysh": "^0.10.5" 38 | }, 39 | "autoload": { 40 | "psr-4": { 41 | "Daguilarm\\LivewireCombobox\\": "src" 42 | } 43 | }, 44 | "autoload-dev": { 45 | "psr-4": { 46 | "Daguilarm\\LivewireCombobox\\Tests\\": "tests" 47 | } 48 | }, 49 | "scripts": { 50 | "psalm": "vendor/bin/psalm", 51 | "test": "vendor/bin/phpunit --colors=always", 52 | "test-coverage": "vendor/bin/phpunit --coverage-html coverage" 53 | }, 54 | "config": { 55 | "sort-packages": true 56 | }, 57 | "extra": { 58 | "laravel": { 59 | "providers": [ 60 | "Daguilarm\\LivewireCombobox\\LivewireComboboxServiceProvider" 61 | ] 62 | } 63 | }, 64 | "minimum-stability": "dev", 65 | "prefer-stable": true 66 | } 67 | -------------------------------------------------------------------------------- /src/Components/FieldGetters.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | public function getOptions(): array 16 | { 17 | return $this->options; 18 | } 19 | 20 | public function getFieldCss(): ?string 21 | { 22 | return $this->fieldCss; 23 | } 24 | 25 | public function getFieldContainerCss(): ?string 26 | { 27 | return $this->fieldContainerCss; 28 | } 29 | 30 | public function getFieldLabelCss(): ?string 31 | { 32 | return $this->fieldLabelCss; 33 | } 34 | 35 | public function getLabel(): string 36 | { 37 | return $this->label; 38 | } 39 | 40 | public function getModel(): string 41 | { 42 | return $this->model; 43 | } 44 | 45 | public function getType(): string 46 | { 47 | return $this->type; 48 | } 49 | 50 | public function getUriKey(): string 51 | { 52 | return $this->uriKey; 53 | } 54 | 55 | public function getDefaultValue(): int | float | string | null 56 | { 57 | return $this->defaultValue; 58 | } 59 | 60 | public function getWithoutResponse(): bool 61 | { 62 | return $this->withoutResponse; 63 | } 64 | 65 | public function getHideOnEmpty(): bool 66 | { 67 | return $this->hideOnEmpty; 68 | } 69 | 70 | public function getFirstRemoved(): bool 71 | { 72 | return $this->firstRemoved; 73 | } 74 | 75 | public function getForeignKey(): ?string 76 | { 77 | return $this->foreignKey; 78 | } 79 | 80 | public function getParentUriKey(): ?string 81 | { 82 | return $this->parentUriKey; 83 | } 84 | 85 | public function getChildTableRowLabel(): string 86 | { 87 | return $this->childTableRowLabel ?? ''; 88 | } 89 | 90 | public function getChildTableRowValue(): string 91 | { 92 | return $this->childTableRowValue ?? ''; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /tests/DuskCommand.php: -------------------------------------------------------------------------------- 1 | e = $e; 22 | $this->testCase = $testCase; 23 | 24 | parent::__construct(); 25 | } 26 | 27 | protected function configure() 28 | { 29 | $this->setName('dusk'); 30 | } 31 | 32 | protected function execute(InputInterface $input, OutputInterface $output) 33 | { 34 | $file = (new ReflectionClass($this->testCase))->getFilename(); 35 | 36 | $line = collect($this->e->getTrace()) 37 | ->first(function ($entry) use ($file) { 38 | return ($entry['file'] ?? '') === $file; 39 | })['line']; 40 | 41 | $info = [ 42 | 'file' => $file, 43 | 'line' => $line, 44 | ]; 45 | 46 | $num = 2; 47 | $lineNum = $info['line']; 48 | $startLine = max($lineNum - $num, 1); 49 | $endLine = $lineNum + $num; 50 | $code = file_get_contents($info['file']); 51 | 52 | if ($output instanceof ShellOutput) { 53 | $output->startPaging(); 54 | } 55 | 56 | $output->writeln(sprintf('From %s:%s:', $this->replaceCwd($info['file']), $lineNum)); 57 | $output->write(CodeFormatter::formatCode($code, $startLine, $endLine, $lineNum), false); 58 | 59 | $output->writeln("\n".$this->e->getMessage()); 60 | 61 | if ($output instanceof ShellOutput) { 62 | $output->stopPaging(); 63 | } 64 | 65 | return 0; 66 | } 67 | 68 | private function replaceCwd($file) 69 | { 70 | $cwd = getcwd(); 71 | if ($cwd === false) { 72 | return $file; 73 | } 74 | 75 | $cwd = rtrim($cwd, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR; 76 | 77 | return preg_replace('/^'.preg_quote($cwd, '/').'/', '', $file); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /resources/views/type/select.blade.php: -------------------------------------------------------------------------------- 1 | @php 2 | $totalOptions = count($element->options); 3 | @endphp 4 | 5 | @unless ($totalOptions <= 0 && $element->getHideOnEmpty() === true) 6 | {{-- Field main container --}} 7 |
11 | {{-- Label --}} 12 | 19 | {{-- Field --}} 20 | 55 |
56 | @endunless 57 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | tweakApplication(function () { 37 | // Enable debug 38 | config()->set('app.debug', true); 39 | 40 | // Configure the view folder 41 | app('config')->set('view.paths', [ 42 | __DIR__.'/../tests/Browser/resources/views', 43 | resource_path('views'), 44 | ]); 45 | 46 | // CSRF hack 47 | app('session')->put('_token', 'this-is-a-hack-because-something-about-validating-the-csrf-token-is-broken'); 48 | 49 | // Components for testing 50 | Livewire::component('combobox-selects', ComboboxSelects::class); 51 | Livewire::component('combobox-options', ComboboxOptions::class); 52 | 53 | //Routes for testing 54 | Route::get('/testing', function () { 55 | return view('select'); 56 | }); 57 | Route::get('/testing/options', function () { 58 | return view('options'); 59 | }); 60 | }); 61 | } 62 | 63 | /** 64 | * Setup environment. 65 | */ 66 | protected function getEnvironmentSetUp($app) 67 | { 68 | // Setup the application 69 | $app['config']->set('app.key', 'base64:Hupx3yAySikrM2/edkZQNQHslgDWYfiBfCuSThJ5SK8='); 70 | } 71 | 72 | /** 73 | * Load the Service Providers. 74 | */ 75 | protected function getPackageProviders($app) 76 | { 77 | return [ 78 | LivewireComboboxServiceProvider::class, 79 | LivewireServiceProvider::class, 80 | ]; 81 | } 82 | 83 | /** 84 | * Resolve the application http kernel. 85 | */ 86 | protected function resolveApplicationHttpKernel($app) 87 | { 88 | $app->singleton('Illuminate\Contracts\Http\Kernel', '\Daguilarm\LivewireCombobox\Tests\HttpKernel'); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /tests/HttpKernel.php: -------------------------------------------------------------------------------- 1 | [ 32 | \Illuminate\Cookie\Middleware\EncryptCookies::class, 33 | \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, 34 | \Illuminate\Session\Middleware\StartSession::class, 35 | \Illuminate\View\Middleware\ShareErrorsFromSession::class, 36 | \Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class, 37 | \Illuminate\Routing\Middleware\SubstituteBindings::class, 38 | ], 39 | 40 | 'api' => [ 41 | 'throttle:60,1', 42 | 'bindings', 43 | ], 44 | ]; 45 | 46 | /** 47 | * The application's route middleware. 48 | * 49 | * These middleware may be assigned to groups or used individually. 50 | * 51 | * @var array 52 | */ 53 | protected $routeMiddleware = [ 54 | 'auth' => \Illuminate\Auth\Middleware\Authenticate::class, 55 | 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 56 | 'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class, 57 | 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, 58 | 'can' => \Illuminate\Auth\Middleware\Authorize::class, 59 | 'guest' => \Orchestra\Testbench\Http\Middleware\RedirectIfAuthenticated::class, 60 | 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, 61 | 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 62 | 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, 63 | ]; 64 | 65 | /** 66 | * The priority-sorted list of middleware. 67 | * 68 | * This forces non-global middleware to always be in the given order. 69 | * 70 | * @var array 71 | */ 72 | protected $middlewarePriority = [ 73 | \Illuminate\Session\Middleware\StartSession::class, 74 | \Illuminate\View\Middleware\ShareErrorsFromSession::class, 75 | \Illuminate\Auth\Middleware\Authenticate::class, 76 | \Illuminate\Session\Middleware\AuthenticateSession::class, 77 | \Illuminate\Routing\Middleware\SubstituteBindings::class, 78 | \Illuminate\Auth\Middleware\Authorize::class, 79 | ]; 80 | } 81 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are **welcome** and will be fully **credited**. 4 | 5 | Please read and understand the contribution guide before creating an issue or pull request. 6 | 7 | ## Etiquette 8 | 9 | This project is open source, and as such, the maintainers give their free time to build and maintain the source code 10 | held within. They make the code freely available in the hope that it will be of use to other developers. It would be 11 | extremely unfair for them to suffer abuse or anger for their hard work. 12 | 13 | Please be considerate towards maintainers when raising issues or presenting pull requests. Let's show the 14 | world that developers are civilized and selfless people. 15 | 16 | It's the duty of the maintainer to ensure that all submissions to the project are of sufficient 17 | quality to benefit the project. Many developers have different skillsets, strengths, and weaknesses. Respect the maintainer's decision, and do not be upset or abusive if your submission is not used. 18 | 19 | ## Viability 20 | 21 | When requesting or submitting new features, first consider whether it might be useful to others. Open 22 | source projects are used by many developers, who may have entirely different needs to your own. Think about 23 | whether or not your feature is likely to be used by other users of the project. 24 | 25 | ## Procedure 26 | 27 | Before filing an issue: 28 | 29 | - Attempt to replicate the problem, to ensure that it wasn't a coincidental incident. 30 | - Check to make sure your feature suggestion isn't already present within the project. 31 | - Check the pull requests tab to ensure that the bug doesn't have a fix in progress. 32 | - Check the pull requests tab to ensure that the feature isn't already in progress. 33 | 34 | Before submitting a pull request: 35 | 36 | - Check the codebase to ensure that your feature doesn't already exist. 37 | - Check the pull requests to ensure that another person hasn't already submitted the feature or fix. 38 | 39 | ## Requirements 40 | 41 | If the project maintainer has any additional requirements, you will find them listed here. 42 | 43 | - **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](https://pear.php.net/package/PHP_CodeSniffer). 44 | 45 | - **Add tests!** - Your patch won't be accepted if it doesn't have tests. 46 | 47 | - **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date. 48 | 49 | - **Consider our release cycle** - We try to follow [SemVer v2.0.0](https://semver.org/). Randomly breaking public APIs is not an option. 50 | 51 | - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. 52 | 53 | - **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](https://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting. 54 | 55 | **Happy coding**! 56 | -------------------------------------------------------------------------------- /tests/Feature/FieldComponentTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf( 22 | $instance = Select::class, 23 | $class = Select::make('Cars', Car::class), 24 | ); 25 | } 26 | 27 | // test --filter=test_field_select_has_the_correct_attributes 28 | public function test_field_select_has_the_correct_attributes(): void 29 | { 30 | $options = [1 => 'one', 2 => 'two', 3 => 'three']; 31 | $select = Select::make('Cars', Car::class) 32 | ->uriKey('key-for-car') 33 | ->options($options); 34 | 35 | // Testing the attributes 36 | $this->assertEquals($select->getLabel(), 'Cars'); 37 | $this->assertEquals($select->getModel(), Car::class); 38 | $this->assertEquals($select->getUriKey(), 'key-for-car'); 39 | $this->assertEquals($select->options, $options); 40 | } 41 | 42 | // test --filter=test_field_select_with_callback_has_the_correct_attributes 43 | public function test_field_select_with_callback_has_the_correct_attributes(): void 44 | { 45 | $model = Car::pluck('name', 'id')->toArray(); 46 | $arrayModel = [ 47 | 1 => 'Renault', 48 | 2 => 'Ford', 49 | ]; 50 | $select = Select::make('Cars', Car::class) 51 | ->options(function ($model) { 52 | return $model 53 | ->pluck('name', 'id') 54 | ->toArray(); 55 | }); 56 | 57 | // Testing the callback 58 | $this->assertEquals($select->options, $model); 59 | // Testing array vs elequent 60 | $this->assertEquals($arrayModel, $model); 61 | } 62 | 63 | // test --filter=test_field_select_with_dependOn_has_the_correct_attributes 64 | public function test_field_select_with_dependOn_has_the_correct_attributes(): void 65 | { 66 | $select_1 = Select::make('Options 2', Option::class) 67 | ->uriKey('key-for-option-2') 68 | ->dependOn('key-for-car', 'cars_id'); 69 | 70 | $select_2 = Select::make('Options 2', Option::class) 71 | ->dependOn('key-for-car') 72 | ->foreignKey('cars_id'); 73 | 74 | // Testing the attributes 75 | $this->assertEquals($select_1->getLabel(), 'Options 2'); 76 | $this->assertEquals($select_1->getModel(), Option::class); 77 | $this->assertEquals($select_1->options, []); 78 | $this->assertEquals($select_1->getUriKey(), 'key-for-option-2'); 79 | $this->assertEquals($select_1->getParentUriKey(), 'key-for-car'); 80 | $this->assertEquals($select_1->getForeignKey(), 'cars_id'); 81 | // Testing foreignKey as method 82 | $this->assertEquals($select_2->getParentUriKey(), 'key-for-car'); 83 | $this->assertEquals($select_2->getForeignKey(), 'cars_id'); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/Components/FieldComponent.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | public array $comboboxValues = []; 22 | 23 | // Css values 24 | protected ?string $fieldCss = null; 25 | protected ?string $fieldContainerCss = null; 26 | protected ?string $fieldLabelCss = null; 27 | 28 | // Base values 29 | protected string $label; 30 | protected string $childTableRowLabel; 31 | protected string $childTableRowValue; 32 | protected string $type; 33 | protected string $uriKey; 34 | protected ?string $model; 35 | protected ?string $foreignKey = null; 36 | protected ?string $parentUriKey = null; 37 | protected int | float | string | null $defaultValue = null; 38 | protected bool $withoutResponse = false; 39 | protected bool $disabledOnEmpty = false; 40 | 41 | /** 42 | * Field maker. 43 | */ 44 | public static function make(string $label, ?string $model = null): FieldComponent 45 | { 46 | return new static($label, $model); 47 | } 48 | 49 | /** 50 | * Depending element base on uriKey. 51 | */ 52 | public function class(?string $container = null, ?string $label = null, ?string $field = null): self 53 | { 54 | $this->fieldContainerCss = $container; 55 | $this->fieldLabelCss = $label; 56 | $this->fieldCss = $field; 57 | 58 | return $this; 59 | } 60 | 61 | /** 62 | * Depending element base on uriKey. 63 | */ 64 | public function dependOn(?string $parentUriKey = null, ?string $foreignKey = null): self 65 | { 66 | $this->parentUriKey = $parentUriKey; 67 | 68 | if ($foreignKey) { 69 | $this->foreignKey = $foreignKey; 70 | } 71 | 72 | return $this; 73 | } 74 | 75 | /** 76 | * Disable field if it is empty. 77 | */ 78 | public function disabledOnEmpty(bool $value = true): void 79 | { 80 | $this->disabledOnEmpty = $value; 81 | } 82 | 83 | /** 84 | * Set the child element foreignKey. 85 | */ 86 | public function foreignKey(string $value): self 87 | { 88 | $this->foreignKey = $value; 89 | 90 | return $this; 91 | } 92 | 93 | /** 94 | * Set the element model. 95 | */ 96 | public function model(string $value): self 97 | { 98 | $this->model = $value; 99 | 100 | return $this; 101 | } 102 | 103 | /** 104 | * Set the table rows for the child to be render in the child select. 105 | */ 106 | public function selectRows(string $label, string $value): self 107 | { 108 | $this->childTableRowLabel = $label; 109 | $this->childTableRowValue = $value; 110 | 111 | return $this; 112 | } 113 | 114 | /** 115 | * Set the element uriKey. 116 | */ 117 | public function uriKey(string $value): self 118 | { 119 | $this->uriKey = $value; 120 | 121 | return $this; 122 | } 123 | 124 | /** 125 | * Element without livewire response. 126 | */ 127 | public function withoutResponse(): self 128 | { 129 | $this->withoutResponse = true; 130 | 131 | return $this; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /tests/Browser/ComboboxComponentTest.php: -------------------------------------------------------------------------------- 1 | browse(function ($browser) { 14 | // Test simple dynamic selection 15 | $browser->visit('/testing') 16 | ->assertPresent('#key-for-car') 17 | ->assertSelectHasOptions('#key-for-car', [1, 2]) 18 | ->select('#key-for-car', 1) 19 | ->assertSelected('#key-for-car', 1) 20 | // Loading 21 | ->waitUntilMissing('#livewire-combobox-loading') 22 | ->assertSelectHasOptions('#key-for-options', [1, 2, 3, 4]) 23 | ->select('#key-for-options', 1) 24 | ->assertSelected('#key-for-options', 1) 25 | // Loading 26 | ->waitUntilMissing('#livewire-combobox-loading') 27 | ->assertSelectHasOptions('#key-for-extras', [1, 2]) 28 | ->select('#key-for-extras', 1) 29 | ->assertSelected('#key-for-extras', 1) 30 | // Test change value 31 | ->select('#key-for-car', 2) 32 | ->assertSelected('#key-for-car', 2) 33 | // Loading 34 | ->waitUntilMissing('#livewire-combobox-loading') 35 | ->assertSelectHasOptions('#key-for-options', [5, 6, 7, 8]) 36 | ->assertSelectHasOptions('#key-for-extras', []); 37 | }); 38 | } 39 | 40 | // test --filter=test_combobox_options 41 | public function test_combobox_options(): void 42 | { 43 | $this->browse(function ($browser) { 44 | // Test options 45 | $browser->visit('/testing/options') 46 | // Testing custom css and attributes 47 | // @see Daguilarm\LivewireCombobox\Components\FieldComponent::class() 48 | ->assertSourceHas('id="field-container-for-key-for-car" class="bg-green-500"') 49 | ->assertSourceHas('id="label-for-key-for-car" class="text-white"') 50 | // Dusk attribute 51 | ->assertSourceHas('id="key-for-car" dusk="key_for_car" name="Cars" class="text-green-600"') 52 | // Empty option is visible 53 | ->assertPresent('#key-for-options') 54 | // Empty option is not visible. 55 | // @see Daguilarm\LivewireCombobox\Components\FieldComponent::hideOnEmpty() 56 | ->assertNotPresent('#key-for-extras'); 57 | // Test options with selection 58 | $browser->visit('/testing/options') 59 | // Testing selection 60 | ->select('#key-for-car', 1) 61 | ->assertSelected('#key-for-car', 1) 62 | // Has livewire 63 | ->assertSourceHas('wire:model.defer="comboboxValues.key-for-car"') 64 | // Verify is not present 65 | ->assertNotPresent('#key-for-extras') 66 | // Loading 67 | ->waitUntilMissing('#livewire-combobox-loading') 68 | ->select('#key-for-options', 1) 69 | ->assertSelected('#key-for-options', 1) 70 | // Has livewire 71 | ->assertSourceHas('wire:model.defer="comboboxValues.key-for-options"') 72 | // Loading 73 | ->waitUntilMissing('#livewire-combobox-loading') 74 | // Verify is present 75 | ->assertPresent('#key-for-extras') 76 | ->select('#key-for-extras', 1) 77 | ->assertSelected('#key-for-extras', 1) 78 | // Testing withoutResponse() 79 | ->assertSourceMissing('wire:model.defer="comboboxValues.key-for-extras"'); 80 | }); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Components/ComboboxComponent.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | public array $comboboxValues = []; 19 | 20 | /** 21 | * @var array 22 | */ 23 | protected array $elements = []; 24 | 25 | // Customized variables 26 | protected string $containerClass; 27 | protected bool $loading = true; 28 | 29 | // Current element 30 | protected string $currentUriKey = ''; 31 | 32 | /** 33 | * Listeners. 34 | * 35 | * @var array 36 | */ 37 | protected $listeners = ['dependOn']; 38 | 39 | /** 40 | * Init constructor. 41 | * 42 | * @see Daguilarm\LivewireCombobox\Components\ComboboxComponent 43 | */ 44 | public function __construct() 45 | { 46 | $this->elements = $this->elements(); 47 | } 48 | 49 | /** 50 | * Render the view in the blade template. 51 | */ 52 | public function render(): View 53 | { 54 | return view('livewire-combobox::combobox', [ 55 | 'config' => [ 56 | 'containerClass' => $this->containerClass ?? null, 57 | 'loading' => $this->loading, 58 | ], 59 | 'elements' => $this->elements, 60 | ]); 61 | } 62 | 63 | /** 64 | * Listener for element change on view. 65 | */ 66 | public function dependOn(string $parentUriKey): void 67 | { 68 | $this->currentUriKey = $parentUriKey; 69 | $this->elements = $this->resolveElements($this->elements); 70 | } 71 | 72 | /** 73 | * Resolve all the elements. 74 | * 75 | * @param array $elements 76 | * 77 | * @return array 78 | */ 79 | private function resolveElements(array $elements): array 80 | { 81 | // Reset all the elements if parent element is empty 82 | $this->resetValuesIfParentIsEmpty(); 83 | 84 | // Update elements on event (change) 85 | $this->updateValues(); 86 | 87 | // Resolve the child elements 88 | return collect($elements) 89 | ->map(function ($element) { 90 | return $this->resolveChildElements($element); 91 | }) 92 | ->filter() 93 | ->toArray(); 94 | } 95 | 96 | /** 97 | * Get child element from parent. 98 | */ 99 | private function resolveChildElements(object $element): ?object 100 | { 101 | // Get the parent element value 102 | $parentValue = array_key_exists($element->getParentUriKey(), $this->comboboxValues) 103 | ? $this->comboboxValues[$element->getParentUriKey()] 104 | : []; 105 | 106 | // If is the parent element 107 | if (! $parentValue) { 108 | return $element; 109 | } 110 | 111 | // Populate the options 112 | $element->options = app($element->model) 113 | ->where($element->getForeignKey(), $parentValue) 114 | ->pluck($element->getChildTableRowValue(), $element->getChildTableRowLabel()) 115 | ->toArray(); 116 | 117 | return $element; 118 | } 119 | 120 | /** 121 | * Reset all the elements if parent element is empty. 122 | * When you change the first element. 123 | */ 124 | private function resetValuesIfParentIsEmpty(): void 125 | { 126 | // Parent element 127 | $parent = $this->elements[0]->getUriKey(); 128 | 129 | // Reset the values 130 | if (! $this->comboboxValues[$parent]) { 131 | $this->comboboxValues = []; 132 | $this->comboboxValues[$parent] = []; 133 | } 134 | } 135 | 136 | /** 137 | * Reset the all the elements if parent element is empty. 138 | */ 139 | private function updateValues(): void 140 | { 141 | // Get current position in the array and set the limit 142 | // of the visible elements (current + 1) 143 | $limit = $this->getPosition() + 1; 144 | 145 | // Update the values 146 | $this->comboboxValues = $this->getComboboxValues($limit); 147 | } 148 | 149 | /** 150 | * Get the current loop position. 151 | */ 152 | private function getPosition(): int 153 | { 154 | return (int) array_search( 155 | $this->currentUriKey, 156 | array_keys($this->comboboxValues), 157 | ); 158 | } 159 | 160 | /** 161 | * Get the visible combobox values. 162 | * 163 | * @return array 164 | */ 165 | private function getComboboxValues(int $limit): array 166 | { 167 | return array_slice($this->comboboxValues, 0, $limit); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Package Logo](https://banners.beyondco.de/A%20Combobox%20for%20Laravel%20Livewire.png?theme=light&packageManager=composer+require&packageName=daguilarm%2Flivewire-combobox&pattern=architect&style=style_1&description=An+infinite+dynamic+selects.&md=1&showWatermark=1&fontSize=100px&images=selector) 2 | 3 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/daguilarm/livewire-combobox.svg?style=flat-square)](https://packagist.org/packages/daguilarm/livewire-combobox) 4 | [![StyleCI](https://styleci.io/repos/363116482/shield?style=plastic)](https://github.styleci.io/repos/363116482) 5 | ![GitHub last commit](https://img.shields.io/github/last-commit/daguilarm/livewire-combobox) 6 | 7 | 8 | # Livewire Combobox: A dynamic selects for Laravel Livewire 9 | 10 | A Laravel Livewire multiple selects depending on each other values, with infinite levels of dependency and totally configurable. 11 | 12 | ## Requirements 13 | 14 | This package need at least: 15 | 16 | - PHP ^8.0 17 | - Laravel ^8.0 18 | - Laravel Livewire ^2.0 19 | - TailwindCSS ^2.0 20 | 21 | ## Installation 22 | 23 | You can install the package via composer: 24 | 25 | composer require daguilarm/livewire-combobox 26 | 27 | Add the package styles in the `` using the helper `@LivewireComboboxCss`: 28 | 29 | ```html 30 | 31 | 32 | @LivewireComboboxCss 33 | 34 | 35 | ... 36 | 37 | 38 | ``` 39 | 40 | # Documentation 41 | 42 | ## General Methods 43 | 44 | The first thing you have to do is create a component in your folder **Livewire**. Below you can see an example using three selects: 45 | 46 | ```php 47 | uriKey('key-for-car') 67 | ->options(function($model) { 68 | return $model 69 | ->pluck('name', 'id') 70 | ->toArray(); 71 | }), 72 | Select::make('Options for cars', Option::class) 73 | ->uriKey('key-for-options') 74 | ->dependOn('key-for-car') 75 | ->foreignKey('car_id') 76 | ->selectRows('id', 'option'), 77 | Select::make('Extras for cars') 78 | ->model(Extra::class) 79 | ->firstRemoved() 80 | ->hideOnEmpty() 81 | ->uriKey('key-for-extras') 82 | ->dependOn('key-for-options') 83 | ->foreignKey('option_id') 84 | ->selectRows('id', 'extra') 85 | ->withoutResponse(), 86 | ]; 87 | } 88 | } 89 | ``` 90 | 91 | The **package** supports infinite dependent elements. The method `elements()` should return an array with all the elements. 92 | 93 | Let's see how the class works `Select::class` and its methods: 94 | 95 | #### make() 96 | 97 | The method `make()`, has the following structure: 98 | 99 | ```php 100 | Select::make(string $label, ?string $model = null); 101 | ``` 102 | 103 | #### model() 104 | 105 | As it can be seen, the attribute `$model` is optional in the `make()` method, and it can be added using the method `model()`: 106 | 107 | ```php 108 | Select::make('My label')->model(User::class); 109 | ``` 110 | 111 | > :warning: Defining the model is mandatory, but it can be done in the two ways described. 112 | 113 | #### uriKey() 114 | 115 | This method is mandatory, it is used to define a unique key for the element. 116 | 117 | #### hideOnEmpty() 118 | 119 | Dependent children are removed if they are empty, instead of showing an empty field. 120 | 121 | #### withoutResponse() 122 | 123 | When we want an element does not send a response to the component and works only as a form field, that is, remove all the Laravel Livewire code from it. Very useful when it comes to the last selectable element and we don't want to send a request to the server. 124 | 125 | ## Child elements 126 | 127 | These elements have their own methods, apart from those described above. 128 | These child elements do not need the method `options()`, although it can be added if desired. The child specific methods are described below: 129 | 130 | ### dependOn() 131 | 132 | With this method we define the parent element on which our child element depends. We must use the `uriKey` from the parent element. The basic structure of the method is: 133 | 134 | ```php 135 | dependOn(?string $parentUriKey = null, ?string $foreignKey = null) 136 | ``` 137 | 138 | As can be seen, it admits a second value which is the *foreing key* that links the two models: **Parent** and **Child**. 139 | 140 | ### foreignKey() 141 | 142 | This second field can also be added in two ways: 143 | 144 | ```php 145 | // Option 1 146 | Select::make(...) 147 | ->dependOn('key-for-options', 'option_id'); 148 | 149 | // Option 2 150 | Select::make(...) 151 | ->dependOn('key-for-options') 152 | ->foreignKey('option_id'); 153 | ``` 154 | 155 | ### selectRows() 156 | 157 | It is used to select the fields from the table that we want to load in the child element. 158 | 159 | #### disabledOnEmpty() 160 | 161 | If you want to disabled the field while it is empty... 162 | 163 | ## Field Types 164 | 165 | At the moment, the package support the folowing field types: 166 | 167 | ### Select field 168 | 169 | These fields have the following methods: 170 | 171 | #### options() 172 | 173 | It is used to add the values ​​that will be shown in the element **select**. We can directly add an `array` with the values, or define a `callback`. The two values ​​returned by the `array`: key and value, are shown as follows in the **Blade** template: 174 | 175 | ```php 176 | // The array 177 | [ 178 | 1 => 'Car', 179 | 2 => 'Bike', 180 | 3 => 'Plane' 181 | ] 182 | 183 | //Will be render as 184 | 185 | 186 | 187 | ``` 188 | 189 | Therefore, in the component example (will be reverse): 190 | 191 | ```php 192 | // The array 193 | Select::make(...) 194 | ->options(function($model) { 195 | return $model 196 | ->pluck('name', 'id') 197 | ->toArray(); 198 | }) 199 | 200 | //Will be render as 201 | 202 | ``` 203 | 204 | #### firstRemoved() 205 | 206 | By default, each item will show a select field with an empty `option` element: 207 | 208 | ```html 209 | // Element 210 | 214 | ``` 215 | 216 | If you want to remove it, you can add the method `firstRemoved()`. 217 | 218 | ### Search field 219 | 220 | comming soon... 221 | 222 | ## Loading... 223 | 224 | You can activate or deactivate the loading state, modifying the attribute `$loading`, in your component: 225 | 226 | ```php 227 | 259 |
260 | 261 | 262 |
263 | 264 | 265 |
266 | 267 | 268 |
269 | 270 | 271 |
272 | 273 |
274 | ``` 275 | 276 | We can modify the styles of the *Main Container* from the component that we created at the beginning of the documentation, using the `$comboboxContainerClass`: 277 | 278 | ```php 279 | uriKey('key-for-car') 308 | ->options(function($model) { 309 | return $model 310 | ->pluck('id', 'name') 311 | ->toArray(); 312 | }) 313 | ->class('p-4', 'text-green-600', 'text-lg'), 314 | ``` 315 | 316 | We can use the new functionality of **php 8** to modify only those parts that interest us, or we can use the method directly: 317 | 318 | ```php 319 | // Method 1 320 | Select::make(...) 321 | ->class( 322 | container: 'p-4', 323 | field: 'text-lg', 324 | ), 325 | 326 | // Method 2 327 | Select::make(...) 328 | ->class('p-4', null, 'text-lg'), 329 | ``` 330 | 331 | The order of the parameters is: 332 | 333 | ```php 334 | class(?string $container = null, ?string $label = null, ?string $field = null) 335 | ``` 336 | 337 | ## Changelog 338 | 339 | Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently. 340 | 341 | ## Contributing 342 | 343 | Please see [CONTRIBUTING](CONTRIBUTING.md) for details. 344 | 345 | ### Security 346 | 347 | If you discover any security related issues, please email damian.aguilarm@gmail.com instead of using the issue tracker. 348 | 349 | ## Credits 350 | 351 | - [Damián Aguilar](https://github.com/daguilarm) 352 | - [All Contributors](../../contributors) 353 | 354 | ## License 355 | 356 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 357 | --------------------------------------------------------------------------------