Files
MeshChatX/meshchatx/src/frontend/components/LanguageSelector.vue

117 lines
4.0 KiB
Vue

<template>
<div class="relative">
<button
type="button"
class="relative rounded-full p-1.5 sm:p-2 text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-zinc-800 transition-colors"
:title="$t('app.language')"
@click.stop="toggleDropdown"
>
<MaterialDesignIcon icon-name="translate" class="w-5 h-5 sm:w-6 sm:h-6" />
</button>
<Teleport to="body">
<div
v-if="isDropdownOpen"
v-click-outside="closeDropdown"
class="fixed w-48 bg-white dark:bg-zinc-900 border border-gray-200 dark:border-zinc-800 rounded-2xl shadow-xl z-[9999] overflow-hidden"
:style="dropdownStyle"
>
<div class="p-2">
<button
v-for="lang in languages"
:key="lang.code"
type="button"
class="w-full px-4 py-2 text-left rounded-lg hover:bg-gray-100 dark:hover:bg-zinc-800 transition-colors flex items-center justify-between"
:class="{
'bg-blue-50 dark:bg-blue-900/20 text-blue-600 dark:text-blue-400':
currentLanguage === lang.code,
'text-gray-900 dark:text-zinc-100': currentLanguage !== lang.code,
}"
@click="selectLanguage(lang.code)"
>
<span class="font-medium">{{ lang.name }}</span>
<MaterialDesignIcon v-if="currentLanguage === lang.code" icon-name="check" class="w-5 h-5" />
</button>
</div>
</div>
</Teleport>
</div>
</template>
<script>
import MaterialDesignIcon from "./MaterialDesignIcon.vue";
export default {
name: "LanguageSelector",
components: {
MaterialDesignIcon,
},
directives: {
"click-outside": {
mounted(el, binding) {
el.clickOutsideEvent = function (event) {
if (!(el === event.target || el.contains(event.target))) {
binding.value();
}
};
document.addEventListener("click", el.clickOutsideEvent);
},
unmounted(el) {
document.removeEventListener("click", el.clickOutsideEvent);
},
},
},
emits: ["language-change"],
data() {
return {
isDropdownOpen: false,
dropdownPosition: { top: 0, left: 0 },
languages: [
{ code: "en", name: "English" },
{ code: "de", name: "Deutsch" },
{ code: "ru", name: "Русский" },
],
};
},
computed: {
currentLanguage() {
return this.$i18n.locale;
},
dropdownStyle() {
return {
top: `${this.dropdownPosition.top}px`,
left: `${this.dropdownPosition.left}px`,
};
},
},
methods: {
toggleDropdown(event) {
this.isDropdownOpen = !this.isDropdownOpen;
if (this.isDropdownOpen) {
this.updateDropdownPosition(event);
}
},
updateDropdownPosition(event) {
const button = event.currentTarget;
const rect = button.getBoundingClientRect();
this.dropdownPosition = {
top: rect.bottom + 8,
left: Math.max(8, rect.right - 192), // 192px is w-48
};
},
closeDropdown() {
this.isDropdownOpen = false;
},
async selectLanguage(langCode) {
if (this.currentLanguage === langCode) {
this.closeDropdown();
return;
}
this.$emit("language-change", langCode);
this.closeDropdown();
},
},
};
</script>