Files
chatwoot/app/javascript/dashboard/components-next/Countries/CountryDropdown.vue
T

150 lines
3.8 KiB
Vue

<script setup>
import { ref, computed, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { vOnClickOutside } from '@vueuse/components';
import { useToggle } from '@vueuse/core';
import countries from 'shared/constants/countries.js';
import Button from 'dashboard/components-next/button/Button.vue';
import Flag from 'dashboard/components-next/flag/Flag.vue';
import DropdownMenu from 'dashboard/components-next/dropdown-menu/DropdownMenu.vue';
const props = defineProps({
placeholder: {
type: String,
default: '',
},
});
const emit = defineEmits(['change']);
const countryCode = defineModel({
type: String,
default: '',
});
const { t } = useI18n();
const [showCountryDropdown, toggleCountryDropdown] = useToggle();
const searchQuery = ref('');
const clearOption = computed(() => ({
label: t('DROPDOWN_MENU.CLEAR_SELECTION'),
value: 'clear',
action: 'clear',
}));
const countryOptions = computed(() =>
countries.map(({ name, id }) => ({
label: name,
value: id,
action: 'select',
isSelected: countryCode.value === id,
}))
);
const filteredCountries = computed(() => {
if (!searchQuery.value) return countryOptions.value;
const query = searchQuery.value.toLowerCase();
return countryOptions.value.filter(country =>
country.label.toLowerCase().includes(query)
);
});
const menuItems = computed(() => {
const items = [...filteredCountries.value];
if (countryCode.value) {
items.unshift(clearOption.value);
}
return items;
});
const selectedCountry = computed(() => {
return countries.find(country => country.id === countryCode.value);
});
const buttonLabel = computed(() => {
if (selectedCountry.value) {
return selectedCountry.value.name;
}
return props.placeholder;
});
const handleAction = ({ value, action }) => {
if (action === 'clear') {
countryCode.value = '';
emit('change', null);
toggleCountryDropdown(false);
return;
}
if (countryCode.value === value) {
countryCode.value = '';
emit('change', null);
} else {
countryCode.value = value;
const country = countries.find(c => c.id === value);
emit('change', country);
}
toggleCountryDropdown(false);
};
const handleSearch = query => {
searchQuery.value = query;
};
const handleClickOutside = () => {
toggleCountryDropdown(false);
};
watch(showCountryDropdown, isOpen => {
if (isOpen) searchQuery.value = '';
});
</script>
<template>
<div v-on-click-outside="handleClickOutside" class="relative w-full min-w-0">
<Button
slate
sm
:label="buttonLabel"
:icon="!selectedCountry ? 'i-lucide-flag' : ''"
:variant="showCountryDropdown ? 'faded' : 'solid'"
type="button"
no-animation
justify="start"
class="w-full -outline-offset-1"
@click="toggleCountryDropdown()"
>
<template v-if="selectedCountry" #icon>
<Flag :country="selectedCountry.id" class="size-3" />
</template>
</Button>
<Transition
enter-active-class="transition duration-100 ease-out"
enter-from-class="transform scale-95 opacity-0"
enter-to-class="transform scale-100 opacity-100"
leave-active-class="transition duration-75 ease-in"
leave-from-class="transform scale-100 opacity-100"
leave-to-class="transform scale-95 opacity-0"
>
<DropdownMenu
v-if="showCountryDropdown"
:menu-items="menuItems"
show-search
:search-placeholder="t('DROPDOWN_MENU.SEARCH_PLACEHOLDER')"
class="max-h-60 overflow-y-auto absolute z-50 w-full mt-1 top-full min-w-40"
@action="handleAction"
@search="handleSearch"
>
<template #icon="{ item }">
<Flag
v-if="item.value !== 'clear'"
:country="item.value"
class="size-3"
/>
</template>
</DropdownMenu>
</Transition>
</div>
</template>