Initialer Laravel Commit für BetiX
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (8.4) (push) Has been cancelled
tests / ci (8.5) (push) Has been cancelled

This commit is contained in:
2026-04-04 18:01:50 +02:00
commit 0280278978
374 changed files with 65210 additions and 0 deletions

View File

@@ -0,0 +1,142 @@
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted } from 'vue';
import { ChevronDown, Search, Check } from 'lucide-vue-next';
const props = defineProps<{
modelValue: string;
placeholder?: string;
error?: boolean;
}>();
const emit = defineEmits(['update:modelValue']);
const isOpen = ref(false);
const searchQuery = ref('');
const containerRef = ref<HTMLElement | null>(null);
// Full Country List with Codes
const countries = [
{ code: 'DE', name: 'Germany' }, { code: 'AT', name: 'Austria' }, { code: 'CH', name: 'Switzerland' },
{ code: 'US', name: 'United States' }, { code: 'GB', name: 'United Kingdom' }, { code: 'FR', name: 'France' },
{ code: 'IT', name: 'Italy' }, { code: 'ES', name: 'Spain' }, { code: 'NL', name: 'Netherlands' },
{ code: 'BE', name: 'Belgium' }, { code: 'PL', name: 'Poland' }, { code: 'CZ', name: 'Czech Republic' },
{ code: 'DK', name: 'Denmark' }, { code: 'SE', name: 'Sweden' }, { code: 'NO', name: 'Norway' },
{ code: 'FI', name: 'Finland' }, { code: 'PT', name: 'Portugal' }, { code: 'GR', name: 'Greece' },
{ code: 'TR', name: 'Turkey' }, { code: 'RU', name: 'Russia' }, { code: 'UA', name: 'Ukraine' },
{ code: 'CA', name: 'Canada' }, { code: 'AU', name: 'Australia' }, { code: 'JP', name: 'Japan' },
{ code: 'CN', name: 'China' }, { code: 'BR', name: 'Brazil' }, { code: 'MX', name: 'Mexico' },
{ code: 'AR', name: 'Argentina' }, { code: 'IN', name: 'India' }, { code: 'ZA', name: 'South Africa' },
{ code: 'AE', name: 'United Arab Emirates' }, { code: 'KR', name: 'South Korea' }, { code: 'SG', name: 'Singapore' }
];
const filteredCountries = computed(() => {
if (!searchQuery.value) return countries;
return countries.filter(c => c.name.toLowerCase().includes(searchQuery.value.toLowerCase()));
});
const selectedCountry = computed(() => {
return countries.find(c => c.code === props.modelValue);
});
const toggle = () => isOpen.value = !isOpen.value;
const select = (code: string) => {
emit('update:modelValue', code);
isOpen.value = false;
searchQuery.value = '';
};
const handleClickOutside = (e: MouseEvent) => {
if (containerRef.value && !containerRef.value.contains(e.target as Node)) {
isOpen.value = false;
}
};
onMounted(() => document.addEventListener('click', handleClickOutside));
onUnmounted(() => document.removeEventListener('click', handleClickOutside));
</script>
<template>
<div class="relative" ref="containerRef">
<!-- Trigger Button -->
<div
@click="toggle"
class="flex items-center justify-between h-10 w-full rounded-md border bg-[#0a0a0a] px-3 py-2 text-sm cursor-pointer transition-all duration-200"
:class="[
error ? 'border-red-500' : isOpen ? 'border-[#00f2ff] ring-1 ring-[#00f2ff]' : 'border-[#151515] hover:border-[#333]'
]"
>
<div class="flex items-center gap-2" v-if="selectedCountry">
<img :src="`https://flagcdn.com/w20/${selectedCountry.code.toLowerCase()}.png`" class="w-5 h-3.5 object-cover rounded-sm" />
<span class="text-white font-medium">{{ selectedCountry.name }}</span>
</div>
<span v-else class="text-[#888]">{{ placeholder || 'Select Country' }}</span>
<ChevronDown class="w-4 h-4 text-[#666] transition-transform duration-200" :class="{ 'rotate-180': isOpen }" />
</div>
<!-- Dropdown Menu -->
<transition name="dropdown">
<div v-if="isOpen" class="absolute z-50 mt-2 w-full rounded-md border border-[#151515] bg-[#0a0a0a] shadow-[0_10px_40px_rgba(0,0,0,0.8)] overflow-hidden">
<!-- Search -->
<div class="p-2 border-b border-[#151515]">
<div class="relative">
<Search class="absolute left-2 top-2.5 w-3.5 h-3.5 text-[#666]" />
<input
v-model="searchQuery"
type="text"
placeholder="Search..."
class="w-full bg-[#111] border border-[#222] rounded-md py-1.5 pl-8 pr-3 text-xs text-white focus:outline-none focus:border-[#00f2ff] transition-colors"
autofocus
/>
</div>
</div>
<!-- List -->
<div class="max-h-60 overflow-y-auto custom-scrollbar">
<div
v-for="country in filteredCountries"
:key="country.code"
@click="select(country.code)"
class="flex items-center justify-between px-3 py-2 cursor-pointer hover:bg-[#151515] transition-colors group"
:class="{ 'bg-[#151515]': modelValue === country.code }"
>
<div class="flex items-center gap-3">
<img :src="`https://flagcdn.com/w20/${country.code.toLowerCase()}.png`" class="w-5 h-3.5 object-cover rounded-sm opacity-80 group-hover:opacity-100 transition-opacity" />
<span class="text-sm text-[#ccc] group-hover:text-white transition-colors">{{ country.name }}</span>
</div>
<Check v-if="modelValue === country.code" class="w-3.5 h-3.5 text-[#00f2ff]" />
</div>
<div v-if="filteredCountries.length === 0" class="px-3 py-4 text-center text-xs text-[#666]">
No country found.
</div>
</div>
</div>
</transition>
</div>
</template>
<style scoped>
.dropdown-enter-active,
.dropdown-leave-active {
transition: all 0.2s ease;
}
.dropdown-enter-from,
.dropdown-leave-to {
opacity: 0;
transform: translateY(-10px);
}
.custom-scrollbar::-webkit-scrollbar {
width: 4px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: #0a0a0a;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: #333;
border-radius: 2px;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
background: #555;
}
</style>