Files
kompress_eshop/penguinui-components/combobox/combobox-with-checkboxes.html

130 lines
6.7 KiB
HTML

<div x-data="{
options: [
{
value: 'C++',
label: 'C++',
},
{
value: 'CSS',
label: 'CSS',
},
{
value: 'Golang',
label: 'Golang',
},
{
value: 'HTML',
label: 'HTML',
},
{
value: 'Java',
label: 'Java',
},
{
value: 'Javascript',
label: 'Javascript',
},
{
value: 'Kotlin',
label: 'Kotlin',
},
{
value: 'Perl',
label: 'Perl',
},
{
value: 'PHP',
label: 'PHP',
},
{
value: 'Python',
label: 'Python',
},
{
value: 'Ruby',
label: 'Ruby',
},
{
value: 'Rust',
label: 'Rust',
},
{
value: 'TypeScript',
label: 'TypeScript',
},
],
isOpen: false,
openedWithKeyboard: false,
selectedOptions: [],
setLabelText() {
const count = this.selectedOptions.length;
// if there are no selected options
if (count === 0) return 'Please Select';
// join the selected options with a comma
return this.selectedOptions.join(', ');
},
highlightFirstMatchingOption(pressedKey) {
// if Enter pressed, do nothing
if (pressedKey === 'Enter') return
// find and focus the option that starts with the pressed key
const option = this.options.find((item) =>
item.label.toLowerCase().startsWith(pressedKey.toLowerCase()),
)
if (option) {
const index = this.options.indexOf(option)
const allOptions = document.querySelectorAll('.combobox-option')
if (allOptions[index]) {
allOptions[index].focus()
}
}
},
handleOptionToggle(option) {
if (option.checked) {
this.selectedOptions.push(option.value)
} else {
// remove the unchecked option from the selectedOptions array
this.selectedOptions = this.selectedOptions.filter(
(opt) => opt !== option.value,
)
}
// set the value of the hidden field to the selectedOptions array
this.$refs.hiddenTextField.value = this.selectedOptions
},
}" class="w-full max-w-xs flex flex-col gap-1" x-on:keydown="highlightFirstMatchingOption($event.key)" x-on:keydown.esc.window="isOpen = false, openedWithKeyboard = false">
<label for="skills" class="w-fit pl-0.5 text-sm text-on-surface dark:text-on-surface-dark">Skills(s)</label>
<div class="relative">
<!-- trigger button -->
<button type="button" role="combobox" class="inline-flex w-full items-center justify-between gap-2 whitespace-nowrap border-outline bg-surface-alt px-4 py-2 text-sm font-medium capitalize tracking-wide text-on-surface transition hover:opacity-75 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary dark:border-outline-dark dark:bg-surface-dark-alt/50 dark:text-on-surface-dark dark:focus-visible:outline-primary-dark border rounded-radius" aria-haspopup="listbox" aria-controls="skillsList" x-on:click="isOpen = ! isOpen" x-on:keydown.down.prevent="openedWithKeyboard = true" x-on:keydown.enter.prevent="openedWithKeyboard = true" x-on:keydown.space.prevent="openedWithKeyboard = true" x-bind:aria-label="setLabelText()" x-bind:aria-expanded="isOpen || openedWithKeyboard">
<span class="text-sm w-full font-normal text-start overflow-hidden text-ellipsis whitespace-nowrap" x-text="setLabelText()"></span>
<!-- Chevron -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-5">
<path fill-rule="evenodd" d="M5.22 8.22a.75.75 0 0 1 1.06 0L10 11.94l3.72-3.72a.75.75 0 1 1 1.06 1.06l-4.25 4.25a.75.75 0 0 1-1.06 0L5.22 9.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd"/>
</svg>
</button>
<!-- hidden input to grab the selected value -->
<input id="skills" name="skills" type="text" x-ref="hiddenTextField" hidden />
<ul x-cloak x-show="isOpen || openedWithKeyboard" id="skillsList" class="absolute z-10 left-0 top-11 flex max-h-44 w-full flex-col overflow-hidden overflow-y-auto border-outline bg-surface-alt py-1.5 dark:border-outline-dark dark:bg-surface-dark-alt border rounded-radius" role="listbox" x-on:click.outside="isOpen = false, openedWithKeyboard = false" x-on:keydown.down.prevent="$focus.wrap().next()" x-on:keydown.up.prevent="$focus.wrap().previous()" x-transition x-trap="openedWithKeyboard">
<template x-for="(item, index) in options" x-bind:key="item.value">
<!-- option -->
<li role="option">
<label class="flex items-center gap-2 px-4 py-3 text-sm font-medium text-on-surface hover:bg-surface-dark/5 has-focus:bg-surface-dark/5 dark:text-on-surface-dark dark:hover:bg-surface/5 dark:has-focus:bg-surface/5 has-checked:text-on-surface-strong dark:has-checked:text-on-surface-dark-strong has-disabled:cursor-not-allowed has-disabled:opacity-75" x-bind:for="'checkboxOption' + index">
<div class="relative flex items-center">
<input type="checkbox" class="combobox-option before:content[''] peer relative size-4 appearance-none overflow-hidden border border-outline bg-surface-alt before:absolute before:inset-0 checked:border-primary checked:before:bg-primary focus:outline-2 focus:outline-offset-2 focus:outline-outline-strong checked:focus:outline-primary active:outline-offset-0 disabled:cursor-not-allowed dark:border-outline-dark rounded-sm dark:bg-surface-dark-alt dark:checked:border-primary-dark dark:checked:before:bg-primary-dark dark:focus:outline-outline-dark-strong dark:checked:focus:outline-primary-dark" x-on:change="handleOptionToggle($el)" x-on:keydown.enter.prevent="$el.checked = ! $el.checked; handleOptionToggle($el)" x-bind:value="item.value" x-bind:id="'checkboxOption' + index" />
<!-- Checkmark -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke="currentColor" fill="none" stroke-width="4" class="pointer-events-none invisible absolute left-1/2 top-1/2 size-3 -translate-x-1/2 -translate-y-1/2 text-on-primary peer-checked:visible dark:text-on-primary-dark" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"/>
</svg>
</div>
<span x-text="item.label"></span>
</label>
</li>
</template>
</ul>
</div>
</div>