Skip to content

Instantly share code, notes, and snippets.

@artworkad
Last active January 11, 2021 14:59
Show Gist options
  • Save artworkad/f59066a35f3f6e29d4ddd1cfb785caa0 to your computer and use it in GitHub Desktop.
Save artworkad/f59066a35f3f6e29d4ddd1cfb785caa0 to your computer and use it in GitHub Desktop.
<div class="mt-1 relative" x-data="Components.customSelect({ open: false, value: 0, selected: 0 })" x-init="init()">
<button
type="button"
x-ref="button"
@keydown.arrow-up.stop.prevent="onButtonClick()"
@keydown.arrow-down.stop.prevent="onButtonClick()"
@click="onButtonClick()"
aria-haspopup="listbox"
:aria-expanded="open"
aria-labelledby="listbox-label"
class="bg-gray-50 relative w-full border border-gray-300 rounded-md shadow-sm px-3 py-2 pr-10 text-left cursor-default focus:outline-none focus:ring-1 focus:ring-primary-500 focus:border-primary-500 sm:text-lg"
>
<span x-text="['Wade Cooper','Arlene Mccoy'][value]" class="block truncate">Wade Cooper</span>
</button>
<div
x-show="open"
@click.away="open = false"
x-transition:leave="transition ease-in duration-100"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
class="absolute mt-1 w-full rounded-md bg-white shadow-lg z-50" style="display: none;"
>
<ul
@keydown.enter.stop.prevent="onOptionSelect()"
@keydown.space.stop.prevent="onOptionSelect()"
@keydown.escape="onEscape()"
@keydown.arrow-up.prevent="onArrowUp()"
@keydown.arrow-down.prevent="onArrowDown()"
x-ref="listbox"
tabindex="-1"
role="listbox"
aria-labelledby="listbox-label"
:aria-activedescendant="activeDescendant"
class="max-h-60 rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-lg"
x-max="1"
aria-activedescendant="listbox-option-4"
>
<li
x-state:on="Highlighted"
x-state:off="Not Highlighted"
id="listbox-option-0"
role="option"
@click="choose(0)"
@mouseenter="selected = 0"
@mouseleave="selected = null"
:class="{ 'text-white bg-primary-600': selected === 0, 'text-gray-900': !(selected === 0) }"
class="cursor-default select-none relative py-2 pl-3 pr-9 text-gray-900"
>
<span
x-state:on="Selected"
x-state:off="Not Selected"
:class="{ 'font-semibold': value === 0, 'font-normal': !(value === 0) }"
class="font-normal block truncate"
>
Wade Cooper
</span>
</li>
<!-- More options... -->
</ul>
</div>
<script>
window.Components = {
customSelect(options) {
return {
init() {
this.optionCount = this.$refs.listbox.children.length
this.$watch('selected', (value) => {
if (!this.open) return
if (this.selected === null) {
this.activeDescendant = ''
return
}
this.activeDescendant = this.$refs.listbox.children[this.selected].id
})
},
activeDescendant: null,
optionCount: null,
open: false,
selected: null,
value: 0,
choose(option) {
this.value = option
this.open = false
},
onButtonClick() {
if (this.open) return
this.selected = this.value
this.open = true
this.$nextTick(() => {
this.$refs.listbox.focus()
this.$refs.listbox.children[this.selected].scrollIntoView({ block: 'nearest' })
})
},
onOptionSelect() {
if (this.selected !== null) {
this.value = this.selected
}
this.open = false
this.$refs.button.focus()
},
onEscape() {
this.open = false
this.$refs.button.focus()
},
onArrowUp() {
this.selected = this.selected - 1 < 0 ? this.optionCount - 1 : this.selected - 1
this.$refs.listbox.children[this.selected].scrollIntoView({ block: 'nearest' })
},
onArrowDown() {
this.selected = this.selected + 1 > this.optionCount - 1 ? 1 : this.selected + 1
this.$refs.listbox.children[this.selected].scrollIntoView({ block: 'nearest' })
},
...options,
}
},
}
</script>
</div>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment