|
| 1 | +<script setup lang="ts"> |
| 2 | +import { deviceKindService } from '~/services'; |
| 3 | +
|
| 4 | +const emits = defineEmits<{ |
| 5 | + 'device-select': [any], |
| 6 | +}>(); |
| 7 | +
|
| 8 | +const searchText = ref(''); |
| 9 | +const { isActive: isDropdownActive, setInactive } = useClick(useTemplateRef('dropdown')); |
| 10 | +
|
| 11 | +const focusedSearchItemIndex = ref<number | null>(null); |
| 12 | +const numberOfSearchItemsShown = 6; |
| 13 | +const searchItems = ref<{ id: string; name: string; mainImage: string, subImages: string[], category: string }[]>([]); |
| 14 | +watch(searchText, async () => { |
| 15 | + focusedSearchItemIndex.value = null; |
| 16 | + if (searchText.value === '') { |
| 17 | + searchItems.value = []; |
| 18 | + return; |
| 19 | + } |
| 20 | + const data = await deviceKindService.getDeviceKinds(0, numberOfSearchItemsShown, { searchText: searchText.value || undefined, searchFields: ['device_name', 'device_id'] }); |
| 21 | + searchItems.value = data.deviceKinds.map(({ |
| 22 | + name, |
| 23 | + mainImage, |
| 24 | + subImages, |
| 25 | + id, |
| 26 | + category, |
| 27 | + brand, |
| 28 | + description |
| 29 | + }) => ({ |
| 30 | + name, |
| 31 | + mainImage, |
| 32 | + subImages, |
| 33 | + id, |
| 34 | + category, |
| 35 | + brand, |
| 36 | + description |
| 37 | + })); |
| 38 | +}); |
| 39 | +function focusNextSearchItem () { |
| 40 | + if (focusedSearchItemIndex.value === null) focusedSearchItemIndex.value = -1; |
| 41 | + focusedSearchItemIndex.value = (focusedSearchItemIndex.value + 1) % numberOfSearchItemsShown; |
| 42 | +} |
| 43 | +function focusPrevSearchItem () { |
| 44 | + if (focusedSearchItemIndex.value === null) focusedSearchItemIndex.value = 0; |
| 45 | + focusedSearchItemIndex.value = (focusedSearchItemIndex.value - 1 + numberOfSearchItemsShown) % numberOfSearchItemsShown; |
| 46 | +} |
| 47 | +function goToSearchItem (deviceInfo: any) { |
| 48 | + searchText.value = deviceInfo.name; |
| 49 | + setInactive(); |
| 50 | + emits('device-select', deviceInfo); |
| 51 | +} |
| 52 | +function unfocusSearchItem () { |
| 53 | + focusedSearchItemIndex.value = null; |
| 54 | +} |
| 55 | +</script> |
| 56 | + |
| 57 | +<template> |
| 58 | + <div ref="dropdown" class="relative"> |
| 59 | + <div class="relative"> |
| 60 | + <input |
| 61 | +v-model="searchText" |
| 62 | + class="bg-white text-primary-light placeholder:text-primary-light border-2 h-11 w-[100%] pl-10 pr-3 rounded-md text-md placeholder:text-normal" |
| 63 | + type="search" placeholder="Tên/Mã loại thiết bị" @keydown.down="focusNextSearchItem" |
| 64 | + @keydown.up="focusPrevSearchItem" |
| 65 | + @keydown.enter="focusedSearchItemIndex !== null && goToSearchItem(searchItems[focusedSearchItemIndex!])" |
| 66 | + @keydown.esc="unfocusSearchItem"> |
| 67 | + <Icon |
| 68 | +aria-hidden class="absolute left-3 top-[12px] text-xl text-primary-dark" |
| 69 | + name="i-heroicons-magnifying-glass" /> |
| 70 | + </div> |
| 71 | + |
| 72 | + <div |
| 73 | + :class="`${isDropdownActive && searchItems.length ? 'flex' : 'hidden'} flex-col gap-1 absolute bg-white p-1 mt-1 w-[120%] z-50 shadow-[0_0px_16px_-3px_rgba(0,0,0,0.3)]`"> |
| 74 | + <a |
| 75 | +v-for="(item, index) in searchItems" :key="item.id" |
| 76 | + :class="`cursor-pointer px-2 text-normal p-1 flex justify-start gap-2 hover:bg-gray-100 ${focusedSearchItemIndex === index ? 'bg-secondary-light' : ''}`" |
| 77 | + @click="goToSearchItem(searchItems[index])"> |
| 78 | + <img :src="item.mainImage" class="h-6 w-6 block"> |
| 79 | + <p |
| 80 | + class="p-1 px-2 text-nowrap bg-gray-100 border border-gray-300 rounded-md text-normal font-normal leading-none"> |
| 81 | + {{ item.id.toUpperCase() }} |
| 82 | + </p> |
| 83 | + <HighlightText class="line-clamp-1" :text="item.name" :match-text="searchText || undefined" /> |
| 84 | + </a> |
| 85 | + </div> |
| 86 | + </div> |
| 87 | +</template> |
0 commit comments