https://github.com/user-attachments/assets/fc4d15f9-2b54-4627-940f-94772ec739b1 --------- Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com> Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
127 lines
3.3 KiB
Vue
127 lines
3.3 KiB
Vue
<script>
|
|
import { ref, computed, nextTick } from 'vue';
|
|
import { useKeyboardNavigableList } from 'dashboard/composables/useKeyboardNavigableList';
|
|
import { useMessageFormatter } from 'shared/composables/useMessageFormatter';
|
|
|
|
export default {
|
|
props: {
|
|
items: {
|
|
type: Array,
|
|
default: () => [],
|
|
},
|
|
isLoading: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
emptyPlaceholder: {
|
|
type: String,
|
|
default: '',
|
|
},
|
|
loadingPlaceholder: {
|
|
type: String,
|
|
default: '',
|
|
},
|
|
searchTerm: {
|
|
type: String,
|
|
default: '',
|
|
},
|
|
},
|
|
setup(props) {
|
|
const selectedIndex = ref(-1);
|
|
const portalSearchSuggestionsRef = ref(null);
|
|
const { highlightContent, getPlainText } = useMessageFormatter();
|
|
const adjustScroll = () => {
|
|
nextTick(() => {
|
|
portalSearchSuggestionsRef.value.scrollTop = 102 * selectedIndex.value;
|
|
});
|
|
};
|
|
|
|
const isSearchItemActive = index => {
|
|
return index === selectedIndex.value ? 'bg-n-portal-soft' : '';
|
|
};
|
|
|
|
useKeyboardNavigableList({
|
|
items: computed(() => props.items),
|
|
adjustScroll,
|
|
selectedIndex,
|
|
});
|
|
|
|
return {
|
|
selectedIndex,
|
|
portalSearchSuggestionsRef,
|
|
isSearchItemActive,
|
|
highlightContent,
|
|
getPlainText,
|
|
};
|
|
},
|
|
|
|
computed: {
|
|
showEmptyResults() {
|
|
return !this.items.length && !this.isLoading;
|
|
},
|
|
shouldShowResults() {
|
|
return this.items.length && !this.isLoading;
|
|
},
|
|
},
|
|
|
|
methods: {
|
|
prepareContent(content) {
|
|
return this.highlightContent(
|
|
content,
|
|
this.searchTerm,
|
|
'bg-n-portal-soft text-n-portal font-semibold rounded-sm px-1'
|
|
);
|
|
},
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<div
|
|
ref="portalSearchSuggestionsRef"
|
|
class="mt-2 overflow-y-auto bg-white dark:bg-n-slate-2 border border-solid border-n-weak rounded-xl shadow-2xl max-h-96 p-2"
|
|
>
|
|
<div v-if="isLoading" class="px-3 py-6 text-sm text-n-slate-11 text-center">
|
|
{{ loadingPlaceholder }}
|
|
</div>
|
|
<ul v-if="shouldShowResults" class="flex flex-col gap-0.5" role="listbox">
|
|
<li
|
|
v-for="(article, index) in items"
|
|
:id="article.id"
|
|
:key="article.id"
|
|
class="rounded-md cursor-pointer select-none group transition-colors hover:bg-n-alpha-2"
|
|
:class="isSearchItemActive(index)"
|
|
role="option"
|
|
tabindex="-1"
|
|
>
|
|
<a
|
|
class="flex items-start gap-3 px-3 py-2.5 overflow-hidden"
|
|
:href="article.link"
|
|
>
|
|
<span
|
|
class="i-lucide-file-text size-4 mt-0.5 flex-shrink-0 text-n-slate-10 group-hover:text-n-slate-11"
|
|
aria-hidden="true"
|
|
/>
|
|
<span class="min-w-0 flex-1 flex flex-col gap-1">
|
|
<span
|
|
v-dompurify-html="prepareContent(getPlainText(article.title))"
|
|
class="block text-base font-520 text-n-slate-12 truncate"
|
|
/>
|
|
<span
|
|
v-dompurify-html="prepareContent(article.content)"
|
|
class="block text-sm text-n-slate-11 line-clamp-1"
|
|
/>
|
|
</span>
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
|
|
<div
|
|
v-if="showEmptyResults"
|
|
class="px-3 py-6 text-sm text-n-slate-11 text-center"
|
|
>
|
|
{{ emptyPlaceholder }}
|
|
</div>
|
|
</div>
|
|
</template>
|