Files
InvoiceShelf/resources/scripts/components/GlobalSearchBar.vue
Darko Gjorgjijoski a10d4d2de9 Theme scrollbars for dark mode
Add global webkit and Firefox scrollbar styling using semantic
color tokens. Fix component scrollbar classes in GlobalSearchBar
and CompanySwitcher from hardcoded gray to theme-aware colors.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 03:15:00 +02:00

208 lines
5.8 KiB
Vue

<template>
<div ref="searchBar" class="hidden rounded md:block relative">
<div>
<BaseInput
v-model="name"
:placeholder="$t('global_search.search')"
container-class="!rounded-lg !shadow-none"
class="h-8 md:h-9 !rounded-lg !bg-white/20 !border-white/10 !text-white !placeholder-white/60"
@input="onSearch"
>
<template #left>
<BaseIcon name="MagnifyingGlassIcon" class="!text-white/70" />
</template>
<template #right>
<SpinnerIcon v-if="isSearching" class="h-5 text-primary-500" />
</template>
</BaseInput>
</div>
<transition
enter-active-class="transition duration-200 ease-out"
enter-from-class="translate-y-1 opacity-0"
enter-to-class="translate-y-0 opacity-100"
leave-active-class="transition duration-150 ease-in"
leave-from-class="translate-y-0 opacity-100"
leave-to-class="translate-y-1 opacity-0"
>
<div
v-if="isShow"
class="
scrollbar-thin
scrollbar-thumb-rounded-full
scrollbar-thumb-surface-muted
scrollbar-track-surface-secondary
overflow-y-auto
bg-surface
rounded-md
mt-2
shadow-lg
p-3
absolute
w-[300px]
h-[200px]
right-0
"
>
<div
v-if="
usersStore.userList.length < 1 && usersStore.customerList.length < 1
"
class="
flex
items-center
justify-center
text-subtle text-base
flex-col
mt-4
"
>
<BaseIcon name="ExclamationCircleIcon" class="text-subtle" />
{{ $t('global_search.no_results_found') }}
</div>
<div v-else>
<div v-if="usersStore.customerList.length > 0">
<label class="text-sm text-subtle mb-0.5 block px-2 uppercase">
{{ $t('global_search.customers') }}
</label>
<div
v-for="(customer, index) in usersStore.customerList"
:key="index"
class="p-2 hover:bg-hover-strong cursor-pointer rounded-md"
>
<router-link
:to="{ path: `/admin/customers/${customer.id}/view` }"
class="flex items-center"
>
<span
class="
flex
items-center
justify-center
w-9
h-9
mr-3
text-base
font-semibold
bg-surface-muted
rounded-full
text-primary-500
"
>
{{ initGenerator(customer.name) }}
</span>
<div class="flex flex-col">
<span class="text-sm">{{ customer.name }}</span>
<span
v-if="customer.contact_name"
class="text-xs text-subtle"
>
{{ customer.contact_name }}
</span>
<span v-else class="text-xs text-subtle">{{
customer.email
}}</span>
</div>
</router-link>
</div>
</div>
<div v-if="usersStore.userList.length > 0" class="mt-2">
<label
class="text-sm text-subtle mb-2 block px-2 mb-0.5 uppercase"
>
{{ $t('global_search.users') }}
</label>
<div
v-for="(user, index) in usersStore.userList"
:key="index"
class="p-2 hover:bg-hover-strong cursor-pointer rounded-md"
>
<router-link
:to="{ path: `/admin/members/${user.id}/edit` }"
class="flex items-center"
>
<span
class="
flex
items-center
justify-center
w-9
h-9
mr-3
text-base
font-semibold
bg-surface-muted
rounded-full
text-primary-500
"
>
{{ initGenerator(user.name) }}
</span>
<div class="flex flex-col">
<span class="text-sm">{{ user.name }}</span>
<span class="text-xs text-subtle">{{ user.email }}</span>
</div>
</router-link>
</div>
</div>
</div>
</div>
</transition>
</div>
</template>
<script setup>
import { ref, watch } from 'vue'
import { useMembersStore } from '@/scripts/admin/stores/members'
import { onClickOutside } from '@vueuse/core'
import { useRoute } from 'vue-router'
import SpinnerIcon from '@/scripts/components/icons/SpinnerIcon.vue'
import { debounce } from 'lodash'
const usersStore = useMembersStore()
const isShow = ref(false)
const name = ref('')
const searchBar = ref(null)
const isSearching = ref(false)
const route = useRoute()
watch(route, () => {
isShow.value = false
name.value = ''
})
onSearch = debounce(onSearch, 500)
onClickOutside(searchBar, () => {
isShow.value = false
name.value = ''
})
function onSearch() {
let data = {
search: name.value,
}
if (name.value) {
isSearching.value = true
usersStore.searchUsers(data).then(() => {
isShow.value = true
})
isSearching.value = false
}
if (name.value === '') {
isShow.value = false
}
}
function initGenerator(name) {
if (name) {
const nameSplit = name.split(' ')
const initials = nameSplit[0].charAt(0).toUpperCase()
return initials
}
}
</script>