Combobox
Uzun listeler için aranabilir seçim kutusu: tek ya da çoklu seçim, silinebilir çipler, açıklamalı ve gruplu seçenekler, Livewire metodundan ya da bir JSON adresinden arama ve listede olmayan değeri ekleme. Arama büyük/küçük harfe ve Türkçe harflere duyarsızdır; klavye ve ekran okuyucularla tam kullanılır.
Tek seçim
Seçenekleri options ile değer => metin dizisi (ya da pluck() ile bir koleksiyon) olarak verin; başlangıçta seçili olanı value belirler. Kutuya tıklayıp yazmaya başlayın: arama büyük/küçük harfe ve Türkçe harflere duyarsızdır ("sisli" yazınca "Şişli Şubesi", "igdir" yazınca "Iğdır Şubesi" bulunur) ve eşleşen kısım vurgulanır. Aşağı/yukarı ok, Home/End, Enter ve Escape listeyi klavyeyle kullandırır; Enter formu göndermez.
<acun:combobox name="sube_id" label="Şube" value="2" :options="[
1 => 'Kadıköy Şubesi', 2 => 'Beşiktaş Şubesi', 3 => 'Üsküdar Şubesi',
4 => 'Çankaya Şubesi', 5 => 'Karşıyaka Şubesi', 6 => 'Nilüfer Şubesi', 7 => 'Ataşehir Şubesi',
8 => 'Şişli Şubesi', 9 => 'Çamlıca Şubesi', 10 => 'Iğdır Şubesi',
]" />
| Prop | Değerler |
|---|---|
options | değer => metin ya da seçenek dizileri |
value | Seçili değer (metin olarak karşılaştırılır) |
Çoklu seçim
multiple ile seçimler kutunun içinde silinebilir çipler olarak görünür; sığmazsa kutu satır satır uzar. Liste seçimden sonra açık kalır, böylece art arda seçebilirsiniz; seçili bir seçeneğe yeniden tıklamak onu kaldırır. Boş kutuda Backspace son seçimi siler. max-chips gösterilen çip sayısını sınırlar, kalanlar "+2" gibi sayılır (üzerine gelince adları görünür).
<acun:combobox name="subeler" label="Şubeler" multiple clearable :max-chips="3" :value="[1, 3, 4, 8, 9]" :options="[
1 => 'Kadıköy Şubesi', 2 => 'Beşiktaş Şubesi', 3 => 'Üsküdar Şubesi',
4 => 'Çankaya Şubesi', 5 => 'Karşıyaka Şubesi', 6 => 'Nilüfer Şubesi', 7 => 'Ataşehir Şubesi',
8 => 'Şişli Şubesi', 9 => 'Çamlıca Şubesi', 10 => 'Iğdır Şubesi',
]" />
| Prop | Değerler |
|---|---|
multiple | Çoklu seçim; değer bir dizi |
max-chips | Görünen çip sayısı |
clearable | Tümünü temizleyen × |
Zengin seçenekler ve gruplar
Seçenekler dizi olarak da verilebilir: value ve label dışında description (ikinci satır; aramaya da girer), avatar (yuvarlak resim adresi), icon (Heroicons adı), disabled ve group. Aynı group değerini taşıyan seçenekler, liste kayarken üstte kalan bir başlık altında toplanır. Pasif seçenekler görünür ama seçilemez; klavye onları atlar. İç içe diziler (['Yöneticiler' => [1 => 'Ayşe', …]]) grup, Durum::cases() gibi enum listeleri seçenek olur (varsa label() metodu metni verir); id ve name alanlı diziler ve modeller de okunur.
<acun:combobox name="sorumlu" label="Sorumlu" placeholder="Personel seçin" :options="[
['value' => 1, 'label' => 'Ayşe Yılmaz', 'description' => '[email protected]', 'icon' => 'shield-check', 'group' => 'Yöneticiler'],
['value' => 2, 'label' => 'Mehmet Kaya', 'description' => '[email protected]', 'icon' => 'shield-check', 'group' => 'Yöneticiler'],
['value' => 3, 'label' => 'Zeynep Arslan', 'description' => 'İzinde', 'icon' => 'briefcase', 'group' => 'Satış ekibi', 'disabled' => true],
['value' => 4, 'label' => 'Emre Demir', 'description' => '[email protected]', 'icon' => 'briefcase', 'group' => 'Satış ekibi'],
['value' => 5, 'label' => 'Şule Öztürk', 'description' => '[email protected]', 'icon' => 'briefcase', 'group' => 'Satış ekibi'],
['value' => 6, 'label' => 'Gökhan Çelik', 'description' => 'Muhasebe', 'icon' => 'calculator', 'group' => 'Merkez ofis'],
['value' => 7, 'label' => 'İsmail Işık', 'description' => 'Teknik destek', 'icon' => 'wrench-screwdriver', 'group' => 'Merkez ofis'],
]" />
| Prop | Değerler |
|---|---|
description | İkinci satır, aranır |
avatar | Resim adresi |
icon | Heroicons adı |
disabled | Seçilemez |
group | Başlık altında toplar |
Livewire metodundan arama
search, combobox'ı içeren Livewire bileşenindeki bir metodun adıdır. Kutuya yazıldıkça (varsayılan 300 ms beklemeyle, debounce ile değişir) metot yazılan metinle çağrılır ve döndürdüğü seçenekler listelenir; beklerken kutuda dönen bir gösterge, liste boşsa "Yükleniyor…" görünür. Liste her açılışta boş metinle de sorulur (min-chars verilmediyse). Sonuçlar değişse de seçili değerlerin adı kaybolmaz; sayfa ilk açıldığında seçili değerin adını selected-options verir. Bu sayfadaki önizleme sunucu yerine tarayıcıdaki sahte bir aramayı kullanır.
<acun:combobox
wire:model="sorumluId"
label="Sorumlu"
search="personelAra"
:selected-options="Personel::whereKey($sorumluId)->pluck('ad', 'id')"
placeholder="Personel arayın"
clearable
/>
| Prop | Değerler |
|---|---|
search | Livewire metodunun adı |
selected-options | Seçili değerlerin etiketleri |
debounce | 300 (ms) |
min-chars | 0 |
Arama metodu
Metot tek parametre alır: kutuya yazılan metin (string $query; boş olabilir). Dönüş değeri seçenek listesidir: [['value' => 1, 'label' => 'Ayşe Yılmaz', 'description' => …], …], değer => metin dizisi ya da ComboboxOptions::from(…) (ikon adlarını çizilebilir yola çevirir). #[Json] metodu bileşeni yeniden çizmeden çalıştırır ve sonucu doğrudan döndürür (#[Renderless] de olur). Seçim yapıldığında kutu aynı anda yeni arama göndermez; wire:model.live değişikliği kendi isteğiyle gider ve sayfa yeniden çizilir, liste de kullanıcı yazınca ya da yeniden açınca sorulur. Sıra önemliyse liste döndürün: sayı anahtarlı bir dizi (pluck()) JSON'da nesneye dönüşür ve tarayıcı anahtarları sıraya dizer. Aramayı Acun UI arama altyapısıyla yaparsanız (SearchManager sürücüsü, searchColumn() ile eklenen arama kolonları; bkz. Arama kılavuzu) sunucu da "ayse" ile "Ayşe"yi bulur.
use Acun\Ui\Search\SearchField;
use Acun\Ui\Search\SearchManager;
use Acun\Ui\Search\SearchMode;
use Acun\Ui\Support\ComboboxOptions;
use Livewire\Attributes\Json;
use Livewire\Component;
class TalepFormu extends Component
{
public ?int $sorumluId = null;
// <acun:combobox search="personelAra" />: yazılan metni alır, seçenekleri döndürür.
#[Json]
public function personelAra(string $query): array
{
// ad_search ve eposta_search: migration'da $table->searchColumn('ad'), searchColumn('eposta').
$personel = app(SearchManager::class)->driver()
->apply(Personel::query(), $query, [SearchField::make('ad', SearchMode::Contains), SearchField::make('eposta')])
->orderBy('ad')
->limit(20)
->get();
return ComboboxOptions::from($personel->map(fn (Personel $kisi) => [
'value' => $kisi->id,
'label' => $kisi->ad,
'description' => $kisi->eposta,
'group' => $kisi->birim,
]));
}
}
JSON adresinden arama
Livewire kullanmayan sayfalarda url bir JSON adresidir; yazılan metin ?q= ile eklenir (adresteki diğer parametreler korunur). Yanıt bir seçenek listesi, { "data": [...] } (API kaynakları, sayfalayıcılar) ya da değer => metin nesnesi olabilir; { id, name } biçimindeki kayıtlar da okunur. min-chars en az kaç karakterden sonra aranacağını belirler; geç gelen eski bir yanıt yeni sonuçların üzerine yazılmaz. Önizleme, adresi tarayıcıda yanıtlar.
<acun:combobox name="il_id" label="İl" url="/api/iller" :min-chars="2" placeholder="İl arayın" clearable />
| Prop | Değerler |
|---|---|
url | JSON adresi (?q= eklenir) |
min-chars | Aramadan önce en az karakter |
Uç nokta
Adres, yazılan metni q parametresinde alır ve seçenekleri döndürür. Oturum çerezi gönderilir (same-origin), yani auth ara katmanı olan rotalar da çalışır.
use Illuminate\Http\Request;
Route::get('/api/iller', function (Request $request) {
return Il::query()
->where('ad', 'like', '%'.$request->query('q').'%')
->orderBy('ad')
->limit(20)
->get()
->map(fn (Il $il) => ['value' => $il->id, 'label' => $il->ad]);
})->middleware('auth');
Yeni değer ekleme
creatable ile listede tam karşılığı olmayan metin için listenin sonunda "“…” ekle" satırı çıkar; büyük/küçük harf ve Türkçe harf farkı yeni sayılmaz ("oncelikli" yazınca "Öncelikli" eklenmez, bulunur). Enter ya da tıklama metni değer olarak seçer (değer = metin) ve kök öğeden combobox-created olayını yayınlar: detail içinde name ve value vardır.
<acun:combobox name="etiketler" label="Etiketler" multiple creatable :value="['acil']" :options="[
'acil' => 'Acil', 'oncelikli' => 'Öncelikli', 'toptan' => 'Toptan', 'kurumsal' => 'Kurumsal müşteri',
]" />
| Prop | Değerler |
|---|---|
creatable | Listede olmayan metni ekler |
combobox-created | Olay; detail: { name, value } |
Eklenen değeri kaydetmek
Livewire bileşeni olayı #[On] ile dinler; parametreler olayın detail alanlarıdır. Kaydettikten sonra özelliği yeni kaydın kimliğine çevirip seçenek listesini güncellerseniz combobox ikisini de yeni çizimle alır.
use Livewire\Attributes\On;
#[On('combobox-created')]
public function etiketEklendi(string $name, string $value): void
{
if ($name === 'etiketler') {
Etiket::firstOrCreate(['ad' => $value]);
}
}
Temizleme ve sınır
clearable kutunun sağına seçimi temizleyen × ekler; tek seçimde boş kutuda Backspace de temizler. max çoklu seçimde en fazla kaç değer seçilebileceğini belirler: sınıra gelince listenin üstünde bir uyarı çıkar, diğer seçenekler pasifleşir, seçili olanlar yine kaldırılabilir. :searchable="false" yazmayı kapatır; kısa listelerde telefonda klavye açılmaz. close-on-select listenin seçimden sonra kapanıp kapanmayacağını belirler (varsayılan: tek seçimde kapanır, çoklu seçimde açık kalır).
<div class="grid gap-4 sm:grid-cols-2">
<acun:combobox name="oncelik" label="Öncelik" value="2" clearable :searchable="false" :options="[1 => 'Düşük', 2 => 'Normal', 3 => 'Yüksek', 4 => 'Acil']" />
<acun:combobox name="kategoriler" label="Kategoriler (en fazla 3)" multiple :max="3" :value="['elektronik', 'kitap']" :options="[
'elektronik' => 'Elektronik', 'giyim' => 'Giyim', 'kitap' => 'Kitap', 'ev' => 'Ev ve yaşam', 'spor' => 'Spor',
]" />
</div>
| Prop | Değerler |
|---|---|
clearable | × ile temizler |
max | En fazla seçim (multiple) |
searchable | true | false |
close-on-select | tek: true · çoklu: false |
Klasik form
wire:model olmadan name verildiğinde değer gizli alanlarla gönderilir: tek seçimde name, çoklu seçimde her değer için name[] (sunucuda $request->input('etiketler', [])). required tarayıcı doğrulamasını da çalıştırır: seçim yapılmadan form gönderilmez. Önizlemede gönderilecek değerler formun altında görünür.
<form action="/siparisler" method="POST" class="space-y-4">
@csrf
<acun:combobox name="sube_id" label="Şube" required :options="[1 => 'Kadıköy Şubesi', 2 => 'Beşiktaş Şubesi', 3 => 'Çankaya Şubesi']" />
<acun:combobox name="etiketler" label="Etiketler" multiple :options="['acil' => 'Acil', 'oncelikli' => 'Öncelikli', 'toptan' => 'Toptan']" />
<acun:button type="submit">Kaydet</acun:button>
</form>
| Prop | Değerler |
|---|---|
name | Gizli alan adı (çoklu: name[]) |
required | Seçim zorunlu |
Doğrulama hatası ve pasif
Alan adı (name ya da wire:model) için bir doğrulama hatası varsa, acun:input gibi kutu kırmızı çerçeve alır ve mesaj altında gösterilir; çoklu seçimde etiketler.* hataları da gösterilir. Hata yokken hint yardım metni görünür. disabled kutuyu kilitler; klasik formda değer gönderilmez.
<div class="grid gap-4 sm:grid-cols-2">
<acun:combobox name="sube" label="Şube" required :options="[1 => 'Kadıköy Şubesi', 2 => 'Beşiktaş Şubesi']" />
<acun:combobox name="kapali_sube" label="Şube (kilitli)" value="2" disabled hint="Sipariş hazırlanmaya başlayınca değiştirilemez." :options="[1 => 'Kadıköy Şubesi', 2 => 'Beşiktaş Şubesi']" />
</div>
| Prop | Değerler |
|---|---|
hint | Hata yokken gösterilir |
disabled | Kilitler; değer gönderilmez |
Modal, çekmece ve tablo içinde
Liste sayfanın üstünde (sabit konumda) çizilir ve kutunun altına, yer yoksa üstüne açılır; bu yüzden modalın, çekmecenin ya da yatay kayan bir tablonun taşma sınırı onu kesmez. Sayfa ya da modal kaydırıldıkça liste kutuyu izler. Liste açıkken Escape yalnızca listeyi kapatır; modal açık kalır. Telefonda liste kutunun altında onun genişliğinde açılır ve satırlar en az 40 px yüksekliktedir.
<div class="flex flex-wrap gap-2">
<acun:modal.trigger name="talep-ata"><acun:button>Talep ata</acun:button></acun:modal.trigger>
<acun:drawer.trigger name="talep-filtreleri"><acun:button variant="white">Filtreler</acun:button></acun:drawer.trigger>
</div>
<acun:modal name="talep-ata" title="Talep ata">
<div class="space-y-4">
<acun:combobox name="temsilci" label="Temsilci" :options="[1 => 'Ayşe Yılmaz', 2 => 'Mehmet Kaya', 3 => 'Zeynep Arslan', 4 => 'Emre Demir', 5 => 'Şule Öztürk', 6 => 'Gökhan Çelik', 7 => 'İsmail Işık', 8 => 'Ömer Güneş']" />
<acun:combobox name="talep_etiketleri" label="Etiketler" multiple :options="['fatura' => 'Fatura', 'iade' => 'İade', 'kargo' => 'Kargo', 'teknik' => 'Teknik sorun']" />
</div>
<acun:slot:footer>
<acun:button variant="white" x-on:click="AcunUI.modal.close('talep-ata')">Vazgeç</acun:button>
<acun:button>Ata</acun:button>
</acun:slot:footer>
</acun:modal>
<acun:drawer name="talep-filtreleri" title="Filtreler">
<acun:combobox name="durumlar" label="Durum" multiple :options="['bekliyor' => 'Beklemede', 'inceleniyor' => 'İnceleniyor', 'onaylandi' => 'Onaylandı', 'reddedildi' => 'Reddedildi']" />
</acun:drawer>
<div class="overflow-x-auto rounded-sm ring-1 ring-gray-200 dark:ring-zinc-700">
<table class="w-full min-w-[34rem] text-left text-sm">
<thead class="bg-gray-50 text-xs text-gray-500 dark:bg-zinc-800 dark:text-zinc-400">
<tr><th class="px-3 py-2 font-medium">Talep</th><th class="px-3 py-2 font-medium">Durum</th><th class="px-3 py-2 font-medium">Temsilci</th></tr>
</thead>
<tbody class="text-gray-700 dark:text-zinc-300">
<tr>
<td class="px-3 py-2">#1042 · İade talebi</td>
<td class="px-3 py-2">Beklemede</td>
<td class="w-56 px-3 py-2"><acun:combobox name="temsilci_1042" aria-label="Temsilci" :options="[1 => 'Ayşe Yılmaz', 2 => 'Mehmet Kaya', 3 => 'Zeynep Arslan', 4 => 'Emre Demir', 5 => 'Şule Öztürk', 6 => 'Gökhan Çelik', 7 => 'İsmail Işık', 8 => 'Ömer Güneş']" /></td>
</tr>
</tbody>
</table>
</div>
| Prop | Değerler |
|---|---|
acun:modal · acun:drawer | Kırpılmadan açılır |
overflow-x-auto | Tablo sarmalayıcısı kesmez |
Alpine ile
Alpine sayfalarında x-model dışarıdaki bir değişkene bağlanır: tek seçimde bir değer, çoklu seçimde bir dizi. value => metin dizilerinin değerleri metindir ('3'); karşılaştırma her zaman metin olarak yapıldığından 3 sayısı da aynı seçeneği seçer. class, data-* gibi diğer nitelikler dış kapsayıcıya eklenir. Kullanıcı seçimi değiştirdiğinde kök öğe kabarcıklanan bir change olayı da yayınlar.
<div x-data="{ sube: '3', bolgeler: [] }" class="space-y-3">
<acun:combobox x-model="sube" label="Şube" :options="[1 => 'Kadıköy Şubesi', 2 => 'Beşiktaş Şubesi', 3 => 'Üsküdar Şubesi']" />
<acun:combobox x-model="bolgeler" label="Bölgeler" multiple :options="[1 => 'Marmara', 2 => 'Ege', 3 => 'İç Anadolu']" />
<p class="text-sm text-gray-600 dark:text-zinc-400">sube: <span x-text="JSON.stringify(sube)"></span> · bolgeler: <span x-text="JSON.stringify(bolgeler)"></span></p>
</div>
| Prop | Değerler |
|---|---|
x-model | tek: değer · çoklu: dizi |
change | Kullanıcı seçim yapınca (kabarcıklanır) |
Livewire ile
wire:model, .live ve .live.blur çalışır (tek başına .blur Livewire 4'te istek göndermez); Livewire'dan gelen 3 sayısı '3' anahtarlı seçeneği seçer. Kutunun iç kısmı wire:ignore ile korunur: bileşen yeniden çizildiğinde açık liste, yazılan metin ve seçim bozulmaz. options, disabled ve doğrulama hatası ise yeni çizimle güncellenir; il seçilince ilçe listesinin değişmesi gibi bağımlı seçimler böyle çalışır.
<acun:combobox wire:model.live="ilId" label="İl" :options="$iller->pluck('ad', 'id')" />
<acun:combobox wire:model.live.blur="ilceId" label="İlçe" :options="$ilceler->pluck('ad', 'id')" :disabled="! $ilId" />
<acun:combobox wire:model="personel" label="Personel" multiple :options="$personel->pluck('ad', 'id')" />
| Prop | Değerler |
|---|---|
wire:model | .live · .live.blur · .live.debounce.500ms |
wire:key | Döngüde çizerken verin |
Prop'lar ve slot'lar
Bileşenin kabul ettiği değerler.
| Prop | Varsayılan | Açıklama |
|---|---|---|
label | null | Üstteki etiket |
options | [] | değer => metin; seçenek dizileri; iç içe dizi (grup); enum; id/name alanlı kayıtlar |
value | null | Başlangıç değeri (multiple: dizi); wire:model / x-model varken onlar geçerlidir |
multiple | false | Çoklu seçim; seçimler silinebilir çiplerdir |
placeholder | Seçiniz | Seçim yokken görünen metin |
search-placeholder | Ara… | Liste açıkken boş kutudaki ipucu |
empty | Sonuç yok | Eşleşme yokken görünen metin |
hint | null | Hata yokken alttaki yardım metni |
clearable | false | Seçimi temizleyen × (tek seçimde Backspace de) |
creatable | false | Listede olmayan metni ekler; combobox-created olayı |
searchable | true | false: yazma kapalı (kısa listeler) |
max | null | multiple: en fazla seçim; sınırda uyarı gösterilir |
max-chips | null | multiple: görünen çip sayısı, gerisi "+N" |
close-on-select | tek: true · çoklu: false | Seçimden sonra liste kapanır mı |
search | null | Livewire metodu: fn (string $query): array (seçenek listesi) |
url | null | JSON adresi; ?q= ile çağrılır |
debounce | 300 | Uzaktan aramada bekleme (ms) |
min-chars | 0 | Uzaktan aramadan önce en az karakter |
selected-options | [] | Uzaktan listelerde seçili değerlerin etiketleri |
limit | 200 | Yerel listede aynı anda çizilen en fazla seçenek |
disabled | false | Kilitler; gizli alan gönderilmez |
required | false | Etikette *; tarayıcı doğrulaması ve aria-required |
name | null | Klasik form alanı (çoklu: name[]); hata anahtarı |
wire:model / x-model | — | tek: değer · çoklu: dizi; .live ve .live.blur çalışır |
Seçenek anahtarları | — | value, label, description, avatar, icon, disabled, group |
Olay: combobox-created | — | detail: { name, value }; Livewire'da #[On('combobox-created')] |
Olay: change | — | Kullanıcı seçimi değiştirince kök öğeden (kabarcıklanır) |