Skip to content

Commit 1ef8083

Browse files
committed
Rework CHOICES + Fix css DROPDOWN
1 parent 5811b25 commit 1ef8083

File tree

2 files changed

+119
-59
lines changed

2 files changed

+119
-59
lines changed

src/View/Components/Choices.php

Lines changed: 118 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@
99

1010
class Choices extends Component
1111
{
12+
public string $uuid;
13+
1214
public function __construct(
1315
public ?string $label = null,
1416
public ?string $icon = null,
1517
public ?string $hint = null,
16-
public ?bool $inline = false,
1718
public ?bool $searchable = false,
1819
public ?bool $single = false,
20+
public ?string $searchFunction = 'search',
1921
public ?string $optionValue = 'id',
2022
public ?string $optionLabel = 'name',
2123
public ?string $optionSubLabel = 'description',
@@ -26,6 +28,7 @@ public function __construct(
2628
// slots
2729
public mixed $item = null
2830
) {
31+
$this->uuid = md5(serialize($this));
2932
}
3033

3134
public function modelName()
@@ -36,68 +39,125 @@ public function modelName()
3639
public function render(): View|Closure|string
3740
{
3841
return <<<'HTML'
39-
<div>
42+
<div x-data="{
43+
open: false,
44+
focused: false,
45+
items: [],
46+
selection: @entangle($attributes->wire('model')),
47+
get selectedItems() {
48+
@if ($single)
49+
return this.items.filter(i => i.{{ $optionValue }} == this.selection);
50+
@else
51+
return this.items.filter(i => this.selection.includes(i.{{ $optionValue }}));
52+
@endif
53+
}
54+
}"
55+
>
56+
4057
<!-- STANDARD LABEL -->
41-
@if($label && !$inline)
58+
@if($label)
4259
<label class="pt-0 label label-text font-semibold">{{ $label }}</label>
43-
@endif
44-
45-
<div x-data="{ open: false, display: ''}" @click.outside="open = false" class="relative">
46-
<div class="relative">
47-
48-
<!-- SELECTION DISPLAY -->
49-
<span
50-
class="bg-base-100 top-2 pt-1.5 h-8 @if($icon) left-8 @else left-3 @endif px-2 rounded-md font-semibold text-sm underline decoration-dashed hover:bg-base-300 cursor-pointer absolute"
51-
x-show="!open && display"
52-
x-text="display"
53-
@click="open = true; @if($searchable) $refs.searchInput.focus(); @endif">
54-
</span>
55-
56-
<!-- SEARCH INPUT -->
57-
<input
58-
x-ref="searchInput"
59-
@focus="open = true;"
60-
:value="display"
61-
placeholder="{{ $attributes->whereStartsWith('placeholder')->first() }} "
62-
63-
@if($searchable)
64-
wire:keyup.debounce="search($el.value)"
60+
@endif
61+
62+
<div
63+
x-data="
64+
{
65+
options: @js($options),
66+
toggle(option) {
67+
value = option.{{ $optionValue }};
68+
69+
@if($single)
70+
this.items = [];
71+
this.items.push(option);
72+
this.selection = value;
6573
@else
66-
readonly
67-
@endif
74+
if(this.selection.includes(value)){
75+
this.items = this.items.filter(i => i.{{ $optionValue }} !== value);
76+
this.selection = this.selection.filter(i => i !== value);
77+
}else{
78+
this.items.push(option);
79+
this.selection.push(value);
80+
}
81+
@endif
82+
}
83+
}"
84+
85+
@click.outside="open = false"
86+
class="relative"
87+
>
88+
89+
<div @click="$refs.inputSearch.focus(); open = true">
90+
91+
<!-- DISPLAY SELECTION + INPUT -->
92+
<div class="peer absolute top-3 left-4" :class="{'focused-input': focused}">
6893
69-
{{
70-
$attributes
71-
->except('wire:model')
72-
->class([
73-
'select select-primary w-full',
74-
'pl-10' => ($icon),
75-
'h-14' => ($inline),
76-
'pt-3' => ($inline && $label),
77-
'select-error' => $errors->has($modelName())
78-
])
79-
}}
80-
/>
81-
82-
<!-- ICON -->
83-
@if($icon)
84-
<x-icon :name="$icon" class="absolute top-1/2 -translate-y-1/2 left-3 text-gray-400 " />
85-
@endif
94+
<!-- DISPLAY SELECTION -->
95+
<span @if($single && $searchable) x-show="!focused" @endif x-transition>
96+
<template x-for="item in selectedItems" :key="item.{{ $optionValue }}">
97+
<span
98+
x-text="item.{{ $optionLabel}}"
99+
class="bg-base-200 hover:bg-base-300 rounded px-2 py-1 mr-2 font-semibold text-sm cursor-pointer"
100+
101+
@if(!$single)
102+
@click.stop="toggle(item)"
103+
@endif
104+
>
105+
</span>
106+
</template>
107+
</span>
108+
109+
<!-- INPUT -->
110+
<input
111+
x-transition
112+
x-ref="inputSearch"
113+
@focus="focused = true"
114+
@blur="focused = false; $el.value = ''"
115+
class="outline-none bg-transparent"
116+
117+
@if(!$searchable)
118+
readonly
119+
@else
120+
wire:keydown.debounce="{{ $searchFunction }}($el.value);"
121+
@endif
122+
/>
123+
</div>
124+
125+
<!-- FAKE INPUT CONTAINER -->
126+
<div class="select select-primary w-full peer-[.focused-input]:border-2"></div>
86127
</div>
87-
88-
<!-- OPTIONS CONTAINER -->
89-
<div x-show="open" @click="open = false" class="relative">
90128
129+
130+
<!-- OPTIONS CONTAINER -->
131+
<div x-show="open" class="relative" wire:key="options-container">
132+
91133
<!-- PROGRESS -->
92134
<progress wire:loading.delay wire:target="search" class="progress absolute progress-primary h-0.5"></progress>
93135
94136
<!-- OPTIONS -->
95137
@if($options->count() || $noResultText)
96-
<div class="absolute w-full bg-base-100 z-10 top-2 border border-base-300 shadow-xl cursor-pointer rounded-lg">
97-
@forelse($options as $option)
98-
<div
99-
@click="$wire.{{ $modelName() }} = {{ $option->{$optionValue} }}; display = '{{ $option->{$optionLabel} }}'"
100-
:class="$wire.{{ $modelName() }} == {{ $option->{$optionValue} }} && 'bg-base-200'"
138+
<div class="absolute w-full bg-base-100 z-10 top-2 pb-0.5 border border-base-300 shadow-xl cursor-pointer rounded-lg">
139+
@foreach($options as $option)
140+
<div
141+
wire:key="option-{{ $option->{$optionValue} }}"
142+
@click="
143+
$refs.inputSearch.value = '';
144+
toggle({{ $option }});
145+
146+
@if($single)
147+
open = false;
148+
@endif
149+
150+
@if($searchable && !$single)
151+
$refs.inputSearch.focus();
152+
@endif
153+
"
154+
:class="
155+
@if($single)
156+
selection == {{ $option->{$optionValue} }} && 'bg-primary/5'
157+
@else
158+
selection.includes({{ $option->{$optionValue} }}) && 'bg-primary/5'
159+
@endif
160+
"
101161
>
102162
<!-- ITEM SLOT -->
103163
@if($item)
@@ -106,15 +166,14 @@ class="bg-base-100 top-2 pt-1.5 h-8 @if($icon) left-8 @else left-3 @endif px-2 r
106166
<x-list-item :item="$option" :value="$optionLabel" :sub-value="$optionSubLabel" :avatar="$optionAvatar" />
107167
@endif
108168
</div>
109-
@empty
110-
<div class="p-3">{{ $noResultText }}</div>
111-
@endforelse
169+
@endforeach
170+
@if($options->count() == 0 && $noResultText)
171+
<div class="p-3">{{ $noResultText }}</div>
172+
@endif
112173
</div>
113174
@endif
114175
</div>
115176
116-
<!-- HIDDEN SELECTED VALUE -->
117-
<input type="hidden" {{ $attributes->only('wire:model') }} />
118177
119178
<!-- ERROR -->
120179
@error($modelName())
@@ -124,8 +183,9 @@ class="bg-base-100 top-2 pt-1.5 h-8 @if($icon) left-8 @else left-3 @endif px-2 r
124183
<!-- HINT -->
125184
@if($hint)
126185
<div class="label-text-alt text-gray-400 p-1 pb-0">{{ $hint }}</div>
127-
@endif
186+
@endif
128187
</div>
188+
129189
</div>
130190
HTML;
131191
}

src/View/Components/Dropdown.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ class="dropdown @if($right) dropdown-end @endif"
4040
<x-icon :name="$icon" />
4141
</summary>
4242
@endif
43-
<ul @click="open = false" class="dropdown-content p-2 shadow menu z-[1] bg-base-100 rounded-box whitespace-nowrap">
43+
<ul @click="open = false" class="dropdown-content p-2 shadow menu z-[1] bg-base-100 dark:bg-base-200 rounded-box whitespace-nowrap">
4444
{{ $slot }}
4545
</ul>
4646
</details>

0 commit comments

Comments
 (0)