haditha_ui/components/haditha/AutoComplation.vue

452 lines
10 KiB
Vue
Raw Permalink Normal View History

2025-02-18 12:56:13 +00:00
<script setup lang="ts">
2025-03-15 10:11:29 +00:00
import type { InputMenuItem } from "@nuxt/ui";
import hadithaApi from "../../apis/hadithaApi";
import type { HadithResponseModel } from "../../types/hadithType";
import { useStorage } from "@vueuse/core";
import type { MaybeArrayOfArray } from "@nuxt/ui/runtime/types/utils.js";
// #region props
2025-02-18 12:56:13 +00:00
const props = defineProps({
2025-02-22 13:07:20 +00:00
showFilter: {
default: false,
2025-02-11 10:17:22 +00:00
},
2025-02-26 07:52:21 +00:00
showPrevSearch: {
default: false,
},
2025-02-18 12:56:13 +00:00
});
2025-03-15 10:11:29 +00:00
// #endregion props
// #region emits
2025-02-18 12:56:13 +00:00
const emit = defineEmits(["response-ready"]);
2025-03-15 10:11:29 +00:00
// #endregion emits
2025-02-18 12:56:13 +00:00
2025-03-15 10:11:29 +00:00
// #region refs
const userSearchHistory = useStorage(
"userSearchHistory",
new Set() // Initial value
);
const searchTerm = ref("");
const open = ref(false);
const loading = ref(false);
// If you want to share state across multiple components,
// you can use the same key in useState. Nuxt will ensure
// that the state is shared and reactive across your application.
// const typingTimer = useState<number>("typingTimer", () => 0);
// const doneTypingInterval = useState<number>("doneTypingInterval", () => 1000);
const typingTimer = ref<number | any>(0);
const doneTypingInterval = ref<number>(1000);
// #endregion refs
2025-02-18 12:56:13 +00:00
2025-03-15 10:11:29 +00:00
// #region reactive
2025-02-22 13:07:20 +00:00
const state = reactive({
2025-03-15 10:11:29 +00:00
list: [],
2025-02-22 13:07:20 +00:00
filters: [
{
label: "معنایی",
items: [],
},
{
label: "ترجمه",
items: [],
},
{
label: "مترادف",
items: [
{
label: "جستجو در همه",
},
{
label: "فقط در متن عربی حدیث",
},
{
label: "فقط در ترجمه ها",
},
{
label: "فقط در شروح",
},
],
},
{
label: "نوع",
items: [
{
label: "جستجو در همه",
},
{
label: "فقط در متن عربی حدیث",
},
{
label: "فقط در ترجمه ها",
},
{
label: "فقط در شروح",
},
],
},
{
label: "عین عبارت",
items: [
{
label: "جستجو در همه",
},
{
label: "فقط در متن عربی حدیث",
},
{
label: "فقط در ترجمه ها",
},
{
label: "فقط در شروح",
},
],
},
],
});
2025-03-15 10:11:29 +00:00
// #endregion reactive
2025-02-18 12:56:13 +00:00
2025-03-15 10:11:29 +00:00
// #region methods
2025-02-18 12:56:13 +00:00
2025-02-26 07:52:21 +00:00
const clearSimilar = () => {
console.info("clearSimilar");
};
2025-02-18 12:56:13 +00:00
const onBlur = () => {
2025-03-15 10:11:29 +00:00
console.info("onBlur");
2025-02-18 12:56:13 +00:00
};
const onChange = () => {
2025-03-15 10:11:29 +00:00
console.info("onChange");
sendQuery();
2025-02-18 12:56:13 +00:00
};
2025-03-15 10:11:29 +00:00
const onUpdateModel = (newVal: boolean | InputMenuItem | any) => {
2025-02-26 07:52:21 +00:00
console.info("onUpdateModel", newVal);
2025-02-18 12:56:13 +00:00
};
2025-03-15 10:11:29 +00:00
2025-02-18 12:56:13 +00:00
const onKeyDown = () => {
2025-03-15 10:11:29 +00:00
clearTimeout(typingTimer.value);
2025-02-18 12:56:13 +00:00
};
const onKeyUp = () => {
2025-03-15 10:11:29 +00:00
clearTimeout(typingTimer.value);
typingTimer.value = setTimeout(() => {
sendQuery();
}, doneTypingInterval.value);
2025-02-18 12:56:13 +00:00
};
2025-02-11 10:17:22 +00:00
2025-03-15 10:11:29 +00:00
const sendQuery = async () => {
if (loading.value) return;
loading.value = true;
let url = hadithaApi.search;
url = url.replace("@index_key", "dhparag");
url = url.replace("@search_type", "normal");
url = url.replace("@type_key", "hadith");
url = url.replace("@offset", "0");
url = url.replace("@limit", "10");
url = url.replace("@listkey", "normal");
url = url.replace("@field_collapsed", "normal");
url = url.replace(
"@q=none",
searchTerm.value.length ? `q=${searchTerm.value}` : "q=none"
);
// fetch search list from backend(ssr)
const { data, status, error, refresh, clear } =
await useHadithaSearchComposable<HadithResponseModel>(url, {
method: "post",
});
if (status.value == "success") {
loading.value = false;
}
// pass res and search query to the parent.
2025-02-22 13:07:20 +00:00
emit("response-ready", {
2025-03-15 10:11:29 +00:00
res: data.value,
2025-02-22 13:07:20 +00:00
searchQuery: searchTerm.value,
});
2025-03-15 10:11:29 +00:00
// check if search term is not empty
if (searchTerm.value) userSearchHistory.value.add(searchTerm.value); // Add the value to the Set
// close the history dropdown menu
open.value = false;
2025-02-11 10:17:22 +00:00
};
2025-03-15 10:11:29 +00:00
// #endregion methods
2025-02-11 10:17:22 +00:00
</script>
2025-02-18 12:56:13 +00:00
<template>
2025-03-06 11:28:20 +00:00
<div class="haditha-search-root-wrapper">
<div class="haditha-search-root">
2025-02-26 07:52:21 +00:00
<div v-if="showPrevSearch" class="prev-search-item flex items-center">
<span class="total">۴۷ مشابه </span>
<span class="text me-auto">
عَنِ الْحَسَنِ بْنِ عَلِيِّ بْنِ يُوسُفَ، عَنْ جَدِّهِ، قَالَ:
</span>
<UButton
icon="i-lucide:x"
color="neutral"
variant="ghost"
class="clear-similar-btn"
@click="clearSimilar"
/>
</div>
<div class="search-input">
<UInputMenu
class="w-full focus:placeholder-gray-800"
2025-03-15 10:11:29 +00:00
:items="<any>Array.from(userSearchHistory)"
2025-02-26 07:52:21 +00:00
v-model="searchTerm"
v-model:open="open"
v-model:search-term="searchTerm"
placeholder="هوشمند جستجو کنید..."
:ui="{
2025-03-15 10:11:29 +00:00
base: 'haditha-search-input',
2025-02-26 07:52:21 +00:00
}"
:content="{
align: 'start',
side: 'bottom',
sideOffset: 4,
}"
:loading="loading"
highlight
highlightOnHover
@focus="open = true"
@blur="open = false"
@change="onChange"
@update:modelValue="onUpdateModel"
@update:searchTerm="onUpdateModel"
@keydown="onKeyDown"
@keyup="onKeyUp"
2025-03-15 10:11:29 +00:00
@keydown.enter="sendQuery"
2025-02-26 07:52:21 +00:00
>
</UInputMenu>
</div>
2025-03-15 10:11:29 +00:00
<UButton
class="my-trailing-button"
@click.prevent="sendQuery"
icon="i-haditha-search"
>
2025-03-01 12:44:34 +00:00
<!-- <UIcon name="i-lucide-search" /> -->
2025-02-26 07:52:21 +00:00
</UButton>
2025-02-22 13:07:20 +00:00
</div>
<div class="search-filter my-3 space-x-2" v-if="props.showFilter">
<UDropdownMenu
v-for="(filter, index) in state.filters"
:items="filter.items"
:content="{
align: 'start',
side: 'bottom',
sideOffset: 8,
}"
:ui="{
content: 'w-48',
}"
>
<UButton
class="filter-item"
:label="filter.label"
2025-03-06 11:28:20 +00:00
:trailingIcon="filter.items?.length ? 'i-haditha-chevron-down' : ''"
2025-02-22 13:07:20 +00:00
/>
</UDropdownMenu>
</div>
2025-02-18 12:56:13 +00:00
</div>
</template>
2025-02-26 07:52:21 +00:00
<style scoped>
2025-03-06 11:28:20 +00:00
.haditha-search-root-wrapper {
2025-02-18 12:56:13 +00:00
max-width: 656px;
width: 100%;
margin: 0 1em;
2025-03-06 11:28:20 +00:00
.haditha-search-root {
2025-02-26 07:52:21 +00:00
position: relative;
2025-02-22 13:07:20 +00:00
2025-02-26 07:52:21 +00:00
&::before {
content: "";
2025-02-22 13:07:20 +00:00
2025-02-26 07:52:21 +00:00
position: absolute;
left: 1em;
right: 1em;
top: 50%;
2025-02-22 13:07:20 +00:00
2025-02-26 07:52:21 +00:00
backdrop-filter: blur(60px);
background: linear-gradient(137.41deg, #ffffff -42.82%, #e5e0ff 87.9%);
filter: blur(60px);
width: 626px;
height: 68px;
z-index: 0;
2025-02-22 13:07:20 +00:00
}
2025-02-26 07:52:21 +00:00
.prev-search-item {
width: 328;
height: 49;
gap: 6px;
2025-02-22 13:07:20 +00:00
border-radius: 12px;
2025-02-26 07:52:21 +00:00
border-width: 0.5px;
2025-02-22 13:07:20 +00:00
padding-top: 8px;
padding-right: 12px;
padding-bottom: 8px;
padding-left: 12px;
2025-02-26 07:52:21 +00:00
background: #626b84;
border: 0.5px solid;
margin-bottom: 0.7em;
2025-02-22 13:07:20 +00:00
2025-02-26 07:52:21 +00:00
border-image-source: linear-gradient(
102.02deg,
#4be8ae 7.38%,
#00a762 91.78%
);
2025-02-22 13:07:20 +00:00
2025-02-26 07:52:21 +00:00
.total {
width: 53;
height: 24;
gap: 4px;
border-radius: 6px;
padding: 5px 7px;
background: #1b213266;
font-family: IRANSansX;
font-weight: 500;
font-size: 10px;
line-height: 15px;
letter-spacing: 0%;
text-align: right;
color: #ffffff;
}
.text {
font-family: Takrim;
font-weight: 400;
font-size: 16px;
line-height: 32px;
letter-spacing: 0%;
text-align: right;
color: #ffffff;
}
.clear-similar-btn {
width: 32px;
height: 32px;
gap: 4px;
border-radius: 60px;
padding-top: 11px;
padding-right: 6px;
padding-bottom: 11px;
padding-left: 6px;
background: #1b213266;
color: #fff;
2025-02-22 13:07:20 +00:00
}
}
2025-02-26 07:52:21 +00:00
.search-input {
position: relative;
2025-02-22 13:07:20 +00:00
}
2025-02-26 07:52:21 +00:00
.search-filter {
.filter-item {
/* width: 81px; */
height: 40px;
border-radius: 12px;
border-width: 0.3px;
padding-top: 8px;
padding-right: 12px;
padding-bottom: 8px;
padding-left: 12px;
gap: 4px;
background-color: #fff;
border: 0.3px solid #e0e0e0;
box-shadow: 0px 1px 4px 0px #0000000d;
color: #8a92a8;
font-family: IRANSansX;
font-weight: 400;
font-size: 13px;
line-height: 20px;
letter-spacing: 0%;
text-align: right;
&.active {
color: linear-gradient(102.02deg, #4be8ae 7.38%, #00a762 91.78%);
* {
color: #fff;
}
}
}
2025-02-22 13:07:20 +00:00
}
2025-02-26 07:52:21 +00:00
&.search-page {
&::before {
content: none;
}
.my-trailing-button {
2025-03-06 11:28:20 +00:00
/* width: 32px; */
/* height: 32px; */
2025-02-26 07:52:21 +00:00
}
2025-03-06 11:28:20 +00:00
.haditha-search-input {
2025-02-26 07:52:21 +00:00
height: 56px;
}
2025-02-22 13:07:20 +00:00
}
}
2025-02-18 12:56:13 +00:00
}
2025-02-26 07:52:21 +00:00
</style>
2025-02-18 12:56:13 +00:00
2025-02-26 07:52:21 +00:00
<style>
2025-03-06 11:28:20 +00:00
.haditha-search-root-wrapper {
2025-02-26 07:52:21 +00:00
.my-trailing-button {
position: absolute;
2025-02-18 12:56:13 +00:00
2025-02-26 07:52:21 +00:00
z-index: 1;
width: 48px;
height: 48px;
justify-content: center;
align-items: center;
2025-02-18 12:56:13 +00:00
2025-02-26 07:52:21 +00:00
padding: 0;
border-radius: 50px;
background: linear-gradient(320.71deg, #b9fde0 6.56%, #e4f9f0 69.69%);
left: 12px;
top: 0;
bottom: 0;
margin: auto;
2025-02-16 12:51:52 +00:00
transition: all 0.2s ease-in-out;
2025-02-26 07:52:21 +00:00
&:hover {
transition: all 0.2s ease-in-out;
background: linear-gradient(320.71deg, #54ecaa 6.56%, #b6f0d9 69.69%);
}
& > span {
2025-03-06 11:28:20 +00:00
/* width: 18px; */
/* height: 18px; */
2025-02-16 12:51:52 +00:00
2025-03-06 11:28:20 +00:00
/* background-image: linear-gradient(
2025-02-26 07:52:21 +00:00
102.02deg,
#4be8ae 7.38%,
#00a762 91.78%
2025-03-06 11:28:20 +00:00
); */
2025-02-26 07:52:21 +00:00
}
2025-02-11 10:17:22 +00:00
}
2025-03-06 11:28:20 +00:00
.haditha-search-input {
2025-02-26 07:52:21 +00:00
z-index: 0;
2025-02-16 12:51:52 +00:00
2025-02-26 07:52:21 +00:00
height: 72px;
justify-content: space-between;
padding-top: 12px;
padding-right: 12px;
padding-bottom: 12px;
padding-left: 12px;
border-radius: 12px;
border-width: 0.3px;
2025-02-16 12:51:52 +00:00
2025-02-26 07:52:21 +00:00
background-color: #fff;
border: 0.3px solid #e0e0e0;
box-shadow: 0px 1px 4px 0px #0000000d;
2025-02-16 12:51:52 +00:00
2025-02-26 07:52:21 +00:00
font-family: IRANSansX;
font-weight: 300;
font-size: 14px;
line-height: 21px;
letter-spacing: 0%;
text-align: right;
color: #a7acbe;
}
2025-02-11 10:17:22 +00:00
}
</style>