Skip to content

Instantly share code, notes, and snippets.

@RayyanNafees
Created July 30, 2023 18:09
Show Gist options
  • Save RayyanNafees/c77343a7f8fb6372b4be95fead5a41e1 to your computer and use it in GitHub Desktop.
Save RayyanNafees/c77343a7f8fb6372b4be95fead5a41e1 to your computer and use it in GitHub Desktop.
Solidjs UnoCSS Combobox from TailwindUI (Iconify for Icons)
/* @unocss-include */
import {
onMount,
createSignal,
Show,
For,
onCleanup,
createEffect,
createMemo,
} from 'solid-js'
import type { JSX } from 'solid-js'
import countryList, { CountryData } from 'country-codes-list'
interface Country {
name: string
code: string
icon: JSX.Element
}
const India: Country = {
name: 'India',
code: '91',
icon: <span class='i-twemoji-flag-for-flag-india'></span>,
}
const countryfy = (countryArr: CountryData[]): Country[] =>
countryArr.map((c) => ({
name: c.countryNameEn,
icon: (
<span class={'i-twemoji-flag-' + c.countryNameEn.toLowerCase()}></span>
),
code: c.countryCallingCode,
}))
const countries: Country[] = countryfy(countryList.all())
export default () => {
const [search, setSearch] = createSignal<string>('+' + India.code)
const [show, setShow] = createSignal<boolean>(false)
const [items, setItems] = createSignal<Country[]>(countries)
const [selectedItem, setSelectedItem] = createSignal<{
item: Country
idx: number
}>({
item: India,
idx: 0,
})
const handleSearch = (e: InputEvent) => {
setShow(true)
setSearch((e.target as HTMLInputElement).value)
setItems(
countries.filter((item) =>
item.name!.toLowerCase().includes(search().toLowerCase())
)
)
}
const handleItemClick = (item: Country, idx: number) => {
setSelectedItem({ item, idx })
setSearch(item.code)
setShow(false)
}
//* Click Outside
let divRef: HTMLDivElement
onMount(() => {
const handleClick = (event: MouseEvent) => {
if (!divRef.contains(event.target as Node)) {
// withuot `as Node` highlights in red
setShow(false)
}
}
document.addEventListener('click', handleClick)
onCleanup(() => {
document.removeEventListener('click', handleClick)
})
})
return (
<div
class='relative max-w-xs px-4 text-base'
onClick={() => setShow(true)}
ref={divRef!}
>
<div class='label-button flex items-center gap-1 px-2 border rounded-lg shadow-sm'>
{selectedItem().item!.icon}
<input
type='text'
placeholder='Type to search'
class='w-full px-2 py-2 text-gray-500 bg-transparent rounded-md outline-none'
onFocus={() => setShow(true)}
value={search()}
onInput={handleSearch}
/>
<Show
when={Boolean(search())}
fallback={
<button onClick={() => setShow((s) => s)}>
<div class='i-mdi:chevron-down'></div>
</button>
}
>
<button
onClick={() => {
setSearch('')
setSelectedItem({ item: India, idx: 0 })
// setShow(false)
}}
>
<div class='i-mdi:close'></div>
</button>
</Show>
</div>
<Show when={show()}>
<div class='relative w-full'>
<ul
class='absolute w-full mt-3 overflow-y-auto bg-white border rounded-md shadow-sm max-h-64'
role='listbox'
>
<li
id='li-alert'
class='hidden px-3 py-2 text-center text-gray-600'
>
Not results available
</li>
<For
each={items()}
fallback={<div class='loading loading-dots loading-xs'></div>}
>
{({ name, code, icon }, idx) => (
<li
onClick={() => handleItemClick({ name, code, icon }, idx())}
role='option'
aria-selected={selectedItem().idx == idx()}
class='menu-el-js flex items-center justify-between gap-2 px-3 py-2 cursor-default duration-150 text-gray-500 hover:text-gray-900 hover:bg-indigo-50'
classList={{
'text-indigo-600 bg-indigo-50': selectedItem().idx == idx(),
}}
>
<div class={'w-6 h-6 rounded-full ' + icon} />
<div class='flex-1 text-left flex items-center gap-x-1'>
{name}
</div>
<div class='text-gray-800'>+{code}</div>
</li>
)}
</For>
</ul>
</div>
</Show>
</div>
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment