Repository: jeroennoten/Laravel-AdminLTE (language: php)

Issue:
[FEATURE] Add a way to add options to select component
### Is your request related to a problem? Please describe.
We currently add options to select component using the slot.
```xml
<x-adminlte-select name="myoption">
    @foreach($options as $option)
          <option @if($option->id == $model->myoption) selected @endif
                  value="{{ $option->id }}">{{ $option->name }}</option>
    @endforeach
</x-adminlte-select>
```

### Describe the solution you'd like
It would be great if we can pass the select options to the component so that it generates them automatically. Something like this:
```xml
<x-adminlte-select name="myoption" :model="$model" :array="$options"/>
<x-adminlte-select name="myoption" :value="$model->myoption" :array="$options"/>
<x-adminlte-select name="myoption" :model="$model" 
        :options="$options" option-key="id" option-value="name" 
         empty="true" empty-text="Select ..."/>
```

By combining with model attribute from #889 or using a value attribute, we can also mark correct option as selected.

This way we can offer support for both simple array (key-value pairs) as well as for collections.


Discussion / hints:
@adwiv I'm not so comfortable with adding a lot of extra attributes to the component to manage the `options` collection you propose. The idea is good, but maybe we can use `:options-cfg="$optionsCfg"` and `:options-data="$options"` and avoid using the `option-key`, `option-value`, `empty`, `empty-text`, etc...

The `$optionsCfg` will be an array with the configuration, example:

```php
[
    'optionKey' => 'id',
    'optionValue' => 'name',
    'enableEmptyOption' => true,
    'emptyOptionText' => 'Select...'
]
```
@Shidersz A config object would be great. If we give sensible defaults like in the example given by you, most users might not need options-cfg attribute at all and make it work with options-data only.
@adwiv I will address this after the first release with components.
@Shidersz It appears that the solution is much simpler. 

We can create an options component which gets nested within the select component.

```html
<x-adminlte-select name="myoption">
    <option value="">Select option...</option>
    <x-adminlte-options :options="$options" selected="1"/>
    <x-adminlte-options :options="['Red','Blue','Green']"/>
    <x-adminlte-options :options="\App\Models\User::pluck('name','id')->all()" :selected="$model->user_id"/>
</x-adminlte-select>
```

I have created a component and tested the above code. Only two parameters - options and selected are supported. options parameter is always an array and selected can be either a value or an array (to support multiple selections). All the requirements of the original post can be supported

If this sounds okay, will send a PR.
@adwiv It sounds good to me, if you create a PR I will review as soon as I get some time...

Fix patch:
diff --git a/resources/views/components/form/options.blade.php b/resources/views/components/form/options.blade.php
new file mode 100644
index 00000000..3c63fdce
--- /dev/null
+++ b/resources/views/components/form/options.blade.php
@@ -0,0 +1,26 @@
+{{-- Empty option --}}
+@if(isset($emptyOption))
+
+    <option value>
+        {{ is_string($emptyOption) ? $emptyOption : '' }}
+    </option>
+
+{{-- Placeholder option --}}
+@elseif(isset($placeholder))
+
+    <option class="d-none" value>
+        {{ is_string($placeholder) ? $placeholder : '' }}
+    </option>
+
+@endif
+
+{{-- Other options --}}
+@foreach($options as $key => $value)
+
+    <option value="{{ $key }}"
+        @if($isSelected($key)) selected @endif
+        @if($isDisabled($key)) disabled @endif>
+        {{ $value }}
+    </option>
+
+@endforeach
diff --git a/src/AdminLteServiceProvider.php b/src/AdminLteServiceProvider.php
index 508a2a09..6bf2697c 100644
--- a/src/AdminLteServiceProvider.php
+++ b/src/AdminLteServiceProvider.php
@@ -41,6 +41,7 @@ class AdminLteServiceProvider extends BaseServiceProvider
         Components\Form\InputFile::class,
         Components\Form\InputSlider::class,
         Components\Form\InputSwitch::class,
+        Components\Form\Options::class,
         Components\Form\Select::class,
         Components\Form\Select2::class,
         Components\Form\SelectBs::class,
diff --git a/src/Components/Form/Options.php b/src/Components/Form/Options.php
new file mode 100644
index 00000000..05993919
--- /dev/null
+++ b/src/Components/Form/Options.php
@@ -0,0 +1,103 @@
+<?php
+
+namespace JeroenNoten\LaravelAdminLte\Components\Form;
+
+use Illuminate\Support\Arr;
+use Illuminate\View\Component;
+
+class Options extends Component
+{
+    /**
+     * The list of options as key value pairs.
+     *
+     * @var array
+     */
+    public $options;
+
+    /**
+     * The list of selected option keys.
+     *
+     * @var array
+     */
+    public $selected;
+
+    /**
+     * The list of disabled option keys.
+     *
+     * @var array
+     */
+    public $disabled;
+
+    /**
+     * Whether to use strict comparison between the option keys and the keys of
+     * the selected/disabled options.
+     *
+     * @var bool
+     */
+    public $strict;
+
+    /**
+     * Whether to add a selectable empty option to the list of options. If the
+     * value is a string, it will be used as the option label, otherwise no
+     * label will be available for the empty option.
+     *
+     * @var bool|string
+     */
+    public $emptyOption;
+
+    /**
+     * Whether to add a placeholder (non-selectable option) to the list of
+     * options. If the value is a string, it will be used as the placeholder
+     * label, otherwise no label will be available for the placeholder.
+     *
+     * @var bool|string
+     */
+    public $placeholder;
+
+    /**
+     * Create a new component instance.
+     */
+    public function __construct(
+        $options, $selected = null, $disabled = null,
+        $strict = null, $emptyOption = null, $placeholder = null
+    ) {
+        $this->options = Arr::wrap($options);
+        $this->selected = Arr::wrap($selected);
+        $this->disabled = Arr::wrap($disabled);
+        $this->strict = isset($strict);
+        $this->emptyOption = $emptyOption;
+        $this->placeholder = $placeholder;
+    }
+
+    /**
+     * Determines if an option's key is on selected state.
+     *
+     * @param string $key The option's key.
+     * @return bool
+     */
+    public function isSelected($key)
+    {
+        return in_array($key, $this->selected, $this->strict);
+    }
+
+    /**
+     * Determines if an option's key is on disabled state.
+     *
+     * @param string $key The option's key.
+     * @return bool
+     */
+    public function isDisabled($key)
+    {
+        return in_array($key, $this->disabled, $this->strict);
+    }
+
+    /**
+     * Get the view / contents that represent the component.
+     *
+     * @return \Illuminate\View\View|string
+     */
+    public function render()
+    {
+        return view('adminlte::components.form.options');
+    }
+}


Test patch:
diff --git a/tests/Components/FormComponentsTest.php b/tests/Components/FormComponentsTest.php
index 29a92a28..0aeab5ce 100644
--- a/tests/Components/FormComponentsTest.php
+++ b/tests/Components/FormComponentsTest.php
@@ -27,6 +27,7 @@ protected function getComponents()
             "{$base}.select-bs"    => new Components\Form\SelectBs('name'),
             "{$base}.textarea"     => new Components\Form\Textarea('name'),
             "{$base}.text-editor"  => new Components\Form\TextEditor('name'),
+            "{$base}.options"      => new Components\Form\Options(['o1, o2']),
         ];
     }
 
@@ -141,6 +142,64 @@ public function testInputSwitchComponent()
         $this->assertStringContainsString('is-invalid', $iClass);
     }
 
+    public function testOptionsComponent()
+    {
+        $options = ['m' => 'Male', 'f' => 'Female', 'o' => 'Other'];
+        $component = new Components\Form\Options($options, 'f', 'o');
+
+        // Test selected / disabled options.
+
+        $this->assertFalse($component->isSelected('m'));
+        $this->assertTrue($component->isSelected('f'));
+        $this->assertFalse($component->isDisabled('m'));
+        $this->assertTrue($component->isDisabled('o'));
+
+        // Test rendered HTML.
+
+        $html = $component->resolveView()->with($component->data());
+        $format = '%A<option%avalue="m"%A>%AMale%A</option>%A';
+        $format .= '<option%avalue="f"%Aselected%A>%AFemale%A</option>%A';
+        $format .= '<option%avalue="o"%Adisabled%A>%AOther%A</option>%A';
+
+        $this->assertStringMatchesFormat($format, $html);
+
+        // Test rendered HTML with empty option (no label).
+
+        $component = new Components\Form\Options($options, 'f', 'o', null, true);
+
+        $html = $component->resolveView()->with($component->data());
+        $format = '%A<option%Avalue%A>%A</option>%A';
+        $format .= '%A<option%avalue="m"%A>%AMale%A</option>%A';
+        $format .= '<option%avalue="f"%Aselected%A>%AFemale%A</option>%A';
+        $format .= '<option%avalue="o"%Adisabled%A>%AOther%A</option>%A';
+
+        $this->assertStringMatchesFormat($format, $html);
+
+        // Test rendered HTML with empty option (and label).
+
+        $component = new Components\Form\Options($options, 'f', 'o', null, 'Label');
+
+        $html = $component->resolveView()->with($component->data());
+        $format = '%A<option%Avalue%A>%ALabel%A</option>%A';
+        $format .= '%A<option%avalue="m"%A>%AMale%A</option>%A';
+        $format .= '<option%avalue="f"%Aselected%A>%AFemale%A</option>%A';
+        $format .= '<option%avalue="o"%Adisabled%A>%AOther%A</option>%A';
+
+        $this->assertStringMatchesFormat($format, $html);
+
+        // Test rendered HTML with placeholder.
+
+        $component = new Components\Form\Options($options, 'f', 'o', null, null, 'Placeholder');
+
+        $html = $component->resolveView()->with($component->data());
+        $format = '%A<option%Aclass="d-none"%Avalue%A>%APlaceholder%A</option>%A';
+        $format .= '%A<option%avalue="m"%A>%AMale%A</option>%A';
+        $format .= '<option%avalue="f"%Aselected%A>%AFemale%A</option>%A';
+        $format .= '<option%avalue="o"%Adisabled%A>%AOther%A</option>%A';
+
+        $this->assertStringMatchesFormat($format, $html);
+    }
+
     public function testSelectComponent()
     {
         $component = new Components\Form\Select('name');