2025-08-07 19:20:29 +02:00
|
|
|
<template>
|
|
|
|
|
<div class="multiple-nationality-input">
|
|
|
|
|
<div class="nationality-list">
|
|
|
|
|
<div
|
|
|
|
|
v-for="(nationality, index) in nationalities"
|
|
|
|
|
:key="`nationality-${index}`"
|
|
|
|
|
class="nationality-item d-flex align-center gap-2 mb-2"
|
|
|
|
|
>
|
2025-08-09 18:53:26 +02:00
|
|
|
<!-- Mobile Safari optimized country selector -->
|
|
|
|
|
<v-text-field
|
|
|
|
|
v-if="useMobileInterface"
|
|
|
|
|
:model-value="getSelectedCountryName(nationalities[index])"
|
|
|
|
|
:label="index === 0 && label ? label : `Nationality ${index + 1}`"
|
|
|
|
|
variant="outlined"
|
|
|
|
|
density="comfortable"
|
|
|
|
|
readonly
|
|
|
|
|
:error="hasError && index === 0"
|
|
|
|
|
:error-messages="hasError && index === 0 ? errorMessage : undefined"
|
|
|
|
|
@click="openMobileSelector(index)"
|
|
|
|
|
append-inner-icon="mdi-chevron-down"
|
|
|
|
|
class="nationality-select mobile-optimized"
|
|
|
|
|
>
|
|
|
|
|
<template #prepend-inner v-if="nationalities[index]">
|
|
|
|
|
<CountryFlag
|
|
|
|
|
:country-code="nationalities[index]"
|
|
|
|
|
:show-name="false"
|
|
|
|
|
size="small"
|
|
|
|
|
class="flag-icon me-2"
|
|
|
|
|
/>
|
|
|
|
|
</template>
|
|
|
|
|
</v-text-field>
|
|
|
|
|
|
|
|
|
|
<!-- Traditional v-select for desktop -->
|
2025-08-07 19:20:29 +02:00
|
|
|
<v-select
|
2025-08-09 18:53:26 +02:00
|
|
|
v-else
|
2025-08-07 19:20:29 +02:00
|
|
|
v-model="nationalities[index]"
|
|
|
|
|
:items="countryOptions"
|
2025-08-08 13:50:01 +02:00
|
|
|
:label="index === 0 && label ? label : `Nationality ${index + 1}`"
|
2025-08-07 19:20:29 +02:00
|
|
|
variant="outlined"
|
2025-08-07 23:13:31 +02:00
|
|
|
density="comfortable"
|
2025-08-08 13:50:01 +02:00
|
|
|
:error="hasError && index === 0"
|
|
|
|
|
:error-messages="hasError && index === 0 ? errorMessage : undefined"
|
2025-08-07 19:20:29 +02:00
|
|
|
@update:model-value="updateNationalities"
|
2025-08-07 23:13:31 +02:00
|
|
|
class="nationality-select"
|
2025-08-07 19:20:29 +02:00
|
|
|
>
|
|
|
|
|
<template #selection="{ item }">
|
2025-08-07 23:05:46 +02:00
|
|
|
<div class="flag-selection d-flex align-center">
|
2025-08-07 19:20:29 +02:00
|
|
|
<CountryFlag
|
|
|
|
|
:country-code="item.value"
|
|
|
|
|
:show-name="false"
|
|
|
|
|
size="small"
|
2025-08-07 23:13:31 +02:00
|
|
|
class="flag-icon me-2"
|
2025-08-07 19:20:29 +02:00
|
|
|
/>
|
2025-08-07 23:05:46 +02:00
|
|
|
<span class="country-name">{{ item.title }}</span>
|
2025-08-07 19:20:29 +02:00
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<template #item="{ props: itemProps, item }">
|
2025-08-07 23:05:46 +02:00
|
|
|
<v-list-item v-bind="itemProps" class="flag-list-item">
|
2025-08-07 19:20:29 +02:00
|
|
|
<template #prepend>
|
2025-08-07 23:05:46 +02:00
|
|
|
<div class="flag-prepend">
|
|
|
|
|
<CountryFlag
|
|
|
|
|
:country-code="item.raw.code"
|
|
|
|
|
:show-name="false"
|
|
|
|
|
size="small"
|
|
|
|
|
class="flag-icon"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
2025-08-07 19:20:29 +02:00
|
|
|
</template>
|
2025-08-07 23:05:46 +02:00
|
|
|
<v-list-item-title class="country-name">{{ item.raw.name }}</v-list-item-title>
|
2025-08-07 19:20:29 +02:00
|
|
|
</v-list-item>
|
|
|
|
|
</template>
|
|
|
|
|
</v-select>
|
|
|
|
|
|
|
|
|
|
<v-btn
|
|
|
|
|
v-if="nationalities.length > 1"
|
|
|
|
|
icon="mdi-delete"
|
|
|
|
|
size="small"
|
|
|
|
|
variant="text"
|
|
|
|
|
color="error"
|
|
|
|
|
@click="removeNationality(index)"
|
|
|
|
|
:title="`Remove ${getCountryName(nationality) || 'nationality'}`"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="nationality-actions mt-2">
|
|
|
|
|
<v-btn
|
|
|
|
|
variant="outlined"
|
|
|
|
|
color="primary"
|
|
|
|
|
size="small"
|
|
|
|
|
prepend-icon="mdi-plus"
|
|
|
|
|
@click="addNationality"
|
|
|
|
|
:disabled="disabled || nationalities.length >= maxNationalities"
|
|
|
|
|
>
|
|
|
|
|
Add Nationality
|
|
|
|
|
</v-btn>
|
|
|
|
|
|
|
|
|
|
<span v-if="nationalities.length >= maxNationalities" class="text-caption text-medium-emphasis ml-2">
|
|
|
|
|
Maximum {{ maxNationalities }} nationalities allowed
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Preview of selected nationalities -->
|
|
|
|
|
<div v-if="nationalities.length > 0 && !hasEmptyNationality" class="nationality-preview mt-3">
|
|
|
|
|
<v-label class="text-caption mb-1">Selected Nationalities:</v-label>
|
|
|
|
|
<div class="d-flex flex-wrap gap-1">
|
|
|
|
|
<v-chip
|
|
|
|
|
v-for="nationality in validNationalities"
|
|
|
|
|
:key="nationality"
|
|
|
|
|
size="small"
|
|
|
|
|
variant="tonal"
|
|
|
|
|
color="primary"
|
|
|
|
|
>
|
|
|
|
|
<CountryFlag
|
|
|
|
|
:country-code="nationality"
|
|
|
|
|
:show-name="false"
|
|
|
|
|
size="small"
|
|
|
|
|
class="mr-1"
|
|
|
|
|
/>
|
|
|
|
|
{{ getCountryName(nationality) }}
|
|
|
|
|
</v-chip>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-08-09 18:53:26 +02:00
|
|
|
|
|
|
|
|
<!-- Mobile Safari Country Selection Dialog -->
|
|
|
|
|
<v-dialog
|
|
|
|
|
v-model="showMobileSelector"
|
|
|
|
|
:fullscreen="useMobileInterface"
|
|
|
|
|
:max-width="useMobileInterface ? undefined : '500px'"
|
|
|
|
|
:transition="useMobileInterface ? 'dialog-bottom-transition' : 'dialog-transition'"
|
|
|
|
|
class="mobile-country-dialog"
|
|
|
|
|
>
|
|
|
|
|
<v-card class="mobile-country-selector">
|
|
|
|
|
<v-card-title class="d-flex align-center justify-space-between pa-4">
|
|
|
|
|
<span class="text-h6">Select Country</span>
|
|
|
|
|
<v-btn
|
|
|
|
|
icon="mdi-close"
|
|
|
|
|
variant="text"
|
|
|
|
|
size="small"
|
|
|
|
|
@click="showMobileSelector = false"
|
|
|
|
|
/>
|
|
|
|
|
</v-card-title>
|
|
|
|
|
|
|
|
|
|
<v-divider />
|
|
|
|
|
|
|
|
|
|
<v-card-text class="pa-0">
|
|
|
|
|
<!-- Search field -->
|
|
|
|
|
<div class="search-container pa-4 pb-2">
|
|
|
|
|
<v-text-field
|
|
|
|
|
v-model="searchQuery"
|
|
|
|
|
placeholder="Search countries..."
|
|
|
|
|
prepend-inner-icon="mdi-magnify"
|
|
|
|
|
variant="outlined"
|
|
|
|
|
density="comfortable"
|
|
|
|
|
hide-details
|
|
|
|
|
clearable
|
|
|
|
|
class="country-search"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Country list -->
|
|
|
|
|
<v-list class="country-list" density="comfortable">
|
|
|
|
|
<template v-for="country in filteredCountries" :key="country.code">
|
|
|
|
|
<v-list-item
|
|
|
|
|
@click="selectCountry(country.code)"
|
|
|
|
|
class="country-list-item"
|
|
|
|
|
:class="{ 'selected': nationalities[currentEditingIndex] === country.code }"
|
|
|
|
|
>
|
|
|
|
|
<template #prepend>
|
|
|
|
|
<div class="country-flag-container">
|
|
|
|
|
<CountryFlag
|
|
|
|
|
:country-code="country.code"
|
|
|
|
|
:show-name="false"
|
|
|
|
|
size="small"
|
|
|
|
|
class="country-flag"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<v-list-item-title class="country-title">
|
|
|
|
|
{{ country.name }}
|
|
|
|
|
</v-list-item-title>
|
|
|
|
|
|
|
|
|
|
<template #append v-if="nationalities[currentEditingIndex] === country.code">
|
|
|
|
|
<v-icon color="primary" size="small">mdi-check</v-icon>
|
|
|
|
|
</template>
|
|
|
|
|
</v-list-item>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<v-list-item v-if="filteredCountries.length === 0" class="no-results">
|
|
|
|
|
<v-list-item-title class="text-center text-medium-emphasis">
|
|
|
|
|
No countries found
|
|
|
|
|
</v-list-item-title>
|
|
|
|
|
</v-list-item>
|
|
|
|
|
</v-list>
|
|
|
|
|
</v-card-text>
|
|
|
|
|
|
|
|
|
|
<v-divider />
|
|
|
|
|
|
|
|
|
|
<v-card-actions class="pa-4">
|
|
|
|
|
<v-spacer />
|
|
|
|
|
<v-btn
|
|
|
|
|
variant="text"
|
|
|
|
|
@click="showMobileSelector = false"
|
|
|
|
|
class="text-none"
|
|
|
|
|
>
|
|
|
|
|
Cancel
|
|
|
|
|
</v-btn>
|
|
|
|
|
</v-card-actions>
|
|
|
|
|
</v-card>
|
|
|
|
|
</v-dialog>
|
2025-08-07 19:20:29 +02:00
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
2025-08-09 19:27:15 +02:00
|
|
|
import { getAllCountries, searchCountries } from '~/utils/countries';
|
2025-08-14 15:08:40 +02:00
|
|
|
// Simple device detection utilities
|
|
|
|
|
const detectMobile = () => {
|
|
|
|
|
if (typeof window === 'undefined') return false;
|
|
|
|
|
const userAgent = navigator.userAgent;
|
|
|
|
|
return /Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(userAgent) || window.innerWidth <= 768;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const detectMobileSafari = () => {
|
|
|
|
|
if (typeof window === 'undefined') return false;
|
|
|
|
|
const userAgent = navigator.userAgent;
|
|
|
|
|
return /iPhone|iPad|iPod/i.test(userAgent) && /Safari/i.test(userAgent);
|
|
|
|
|
};
|
2025-08-07 19:20:29 +02:00
|
|
|
|
|
|
|
|
interface Props {
|
|
|
|
|
modelValue?: string; // Comma-separated string like "FR,MC,US"
|
|
|
|
|
label?: string;
|
|
|
|
|
error?: boolean;
|
|
|
|
|
errorMessage?: string;
|
|
|
|
|
disabled?: boolean;
|
|
|
|
|
maxNationalities?: number;
|
|
|
|
|
required?: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface Emits {
|
|
|
|
|
(e: 'update:modelValue', value: string): void;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
|
|
|
modelValue: '',
|
|
|
|
|
maxNationalities: 5,
|
|
|
|
|
error: false,
|
|
|
|
|
disabled: false,
|
|
|
|
|
required: false
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const emit = defineEmits<Emits>();
|
|
|
|
|
|
2025-08-14 15:08:40 +02:00
|
|
|
// Device detection
|
|
|
|
|
const isMobile = ref(false);
|
|
|
|
|
const isMobileSafari = ref(false);
|
|
|
|
|
const needsPerformanceMode = ref(false);
|
|
|
|
|
|
|
|
|
|
// Initialize device detection on mount
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
if (process.client) {
|
|
|
|
|
isMobile.value = detectMobile();
|
|
|
|
|
isMobileSafari.value = detectMobileSafari();
|
|
|
|
|
needsPerformanceMode.value = isMobileSafari.value || isMobile.value;
|
|
|
|
|
}
|
|
|
|
|
});
|
2025-08-09 18:53:26 +02:00
|
|
|
|
2025-08-07 19:20:29 +02:00
|
|
|
// Parse initial nationalities from comma-separated string
|
|
|
|
|
const parseNationalities = (value: string): string[] => {
|
|
|
|
|
if (!value || value.trim() === '') return [''];
|
|
|
|
|
return value.split(',').map(n => n.trim()).filter(n => n.length > 0);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Reactive nationalities array
|
|
|
|
|
const nationalities = ref<string[]>(parseNationalities(props.modelValue));
|
|
|
|
|
|
|
|
|
|
// Ensure there's always at least one empty nationality field
|
|
|
|
|
if (nationalities.value.length === 0) {
|
|
|
|
|
nationalities.value = [''];
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-09 18:53:26 +02:00
|
|
|
// Mobile optimization flags
|
|
|
|
|
const useMobileInterface = computed(() => isMobileSafari.value || needsPerformanceMode.value);
|
|
|
|
|
|
|
|
|
|
// Mobile dialog state
|
|
|
|
|
const showMobileSelector = ref(false);
|
|
|
|
|
const currentEditingIndex = ref(-1);
|
|
|
|
|
const searchQuery = ref('');
|
|
|
|
|
|
|
|
|
|
// Filtered countries for mobile selector
|
|
|
|
|
const filteredCountries = computed(() => {
|
|
|
|
|
const countries = getAllCountries();
|
|
|
|
|
if (!searchQuery.value) return countries;
|
|
|
|
|
|
|
|
|
|
const query = searchQuery.value.toLowerCase();
|
|
|
|
|
return countries.filter(country =>
|
|
|
|
|
country.name.toLowerCase().includes(query) ||
|
|
|
|
|
country.code.toLowerCase().includes(query)
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2025-08-07 19:20:29 +02:00
|
|
|
// Watch for external model changes
|
|
|
|
|
watch(() => props.modelValue, (newValue) => {
|
|
|
|
|
const newNationalities = parseNationalities(newValue || '');
|
|
|
|
|
if (newNationalities.length === 0) newNationalities.push('');
|
|
|
|
|
|
|
|
|
|
// Only update if different to prevent loops
|
|
|
|
|
const current = nationalities.value.filter(n => n).join(',');
|
|
|
|
|
const incoming = newNationalities.filter(n => n).join(',');
|
|
|
|
|
|
|
|
|
|
if (current !== incoming) {
|
|
|
|
|
nationalities.value = newNationalities;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Country options for dropdowns
|
|
|
|
|
const countryOptions = computed(() => {
|
|
|
|
|
const countries = getAllCountries();
|
|
|
|
|
return countries.map(country => ({
|
|
|
|
|
title: country.name,
|
|
|
|
|
value: country.code,
|
|
|
|
|
code: country.code,
|
|
|
|
|
name: country.name
|
|
|
|
|
}));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Computed properties
|
|
|
|
|
const validNationalities = computed(() => {
|
|
|
|
|
return nationalities.value.filter(n => n && n.trim().length > 0);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const hasEmptyNationality = computed(() => {
|
|
|
|
|
return nationalities.value.some(n => !n || n.trim() === '');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const hasError = computed(() => {
|
|
|
|
|
return props.error || !!props.errorMessage;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Methods
|
|
|
|
|
const addNationality = () => {
|
|
|
|
|
if (nationalities.value.length < props.maxNationalities) {
|
|
|
|
|
nationalities.value.push('');
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const removeNationality = (index: number) => {
|
|
|
|
|
if (nationalities.value.length > 1) {
|
|
|
|
|
nationalities.value.splice(index, 1);
|
|
|
|
|
updateNationalities();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const updateNationalities = () => {
|
|
|
|
|
// Remove duplicates and empty values for the model
|
|
|
|
|
const uniqueValid = [...new Set(validNationalities.value)];
|
|
|
|
|
const result = uniqueValid.join(',');
|
|
|
|
|
|
|
|
|
|
emit('update:modelValue', result);
|
|
|
|
|
};
|
|
|
|
|
|
2025-08-09 19:27:15 +02:00
|
|
|
// Helper methods
|
|
|
|
|
const getCountryName = (countryCode: string): string => {
|
|
|
|
|
if (!countryCode) return '';
|
|
|
|
|
const countries = getAllCountries();
|
|
|
|
|
const country = countries.find(c => c.code === countryCode);
|
|
|
|
|
return country?.name || '';
|
|
|
|
|
};
|
|
|
|
|
|
2025-08-09 18:53:26 +02:00
|
|
|
// Mobile Safari specific methods
|
|
|
|
|
const getSelectedCountryName = (countryCode: string): string => {
|
|
|
|
|
if (!countryCode) return '';
|
|
|
|
|
return getCountryName(countryCode) || '';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const openMobileSelector = (index: number) => {
|
|
|
|
|
currentEditingIndex.value = index;
|
|
|
|
|
showMobileSelector.value = true;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const selectCountry = (countryCode: string) => {
|
|
|
|
|
if (currentEditingIndex.value >= 0) {
|
|
|
|
|
nationalities.value[currentEditingIndex.value] = countryCode;
|
|
|
|
|
updateNationalities();
|
|
|
|
|
}
|
|
|
|
|
showMobileSelector.value = false;
|
|
|
|
|
currentEditingIndex.value = -1;
|
|
|
|
|
};
|
|
|
|
|
|
2025-08-07 19:20:29 +02:00
|
|
|
// Watch nationalities array for changes
|
|
|
|
|
watch(nationalities, () => {
|
|
|
|
|
updateNationalities();
|
|
|
|
|
}, { deep: true });
|
|
|
|
|
|
|
|
|
|
// Initialize the model value on mount if needed
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
if (!props.modelValue && validNationalities.value.length > 0) {
|
|
|
|
|
updateNationalities();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
.multiple-nationality-input {
|
|
|
|
|
width: 100%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.nationality-item {
|
|
|
|
|
position: relative;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.nationality-item .v-select {
|
|
|
|
|
flex: 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.nationality-actions {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.nationality-preview {
|
|
|
|
|
padding: 12px;
|
|
|
|
|
background: rgba(var(--v-theme-surface-variant), 0.1);
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
border-left: 4px solid rgb(var(--v-theme-primary));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.nationality-preview .v-chip {
|
|
|
|
|
margin: 2px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Animation for adding/removing items */
|
|
|
|
|
.nationality-item {
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.nationality-item.v-enter-active,
|
|
|
|
|
.nationality-item.v-leave-active {
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.nationality-item.v-enter-from,
|
|
|
|
|
.nationality-item.v-leave-to {
|
|
|
|
|
opacity: 0;
|
|
|
|
|
transform: translateY(-10px);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Error styling */
|
|
|
|
|
.error-message {
|
|
|
|
|
color: rgb(var(--v-theme-error));
|
|
|
|
|
font-size: 0.75rem;
|
|
|
|
|
margin-top: 4px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Focus and hover states */
|
|
|
|
|
.nationality-item .v-btn:hover {
|
|
|
|
|
background-color: rgba(var(--v-theme-error), 0.08);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Responsive adjustments */
|
|
|
|
|
@media (max-width: 600px) {
|
|
|
|
|
.nationality-item {
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
align-items: stretch;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.nationality-item .v-btn {
|
|
|
|
|
align-self: flex-end;
|
|
|
|
|
width: fit-content;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-07 23:13:31 +02:00
|
|
|
/* Enhanced nationality select styling */
|
|
|
|
|
.nationality-select {
|
|
|
|
|
min-height: 56px;
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-07 23:05:46 +02:00
|
|
|
/* Flag alignment fixes */
|
|
|
|
|
.flag-selection {
|
2025-08-07 23:13:31 +02:00
|
|
|
display: flex;
|
2025-08-07 23:05:46 +02:00
|
|
|
align-items: center;
|
2025-08-07 23:13:31 +02:00
|
|
|
gap: 8px;
|
2025-08-07 23:05:46 +02:00
|
|
|
width: 100%;
|
2025-08-07 23:13:31 +02:00
|
|
|
min-height: 24px;
|
|
|
|
|
padding: 2px 0;
|
2025-08-07 23:05:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.flag-prepend {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
2025-08-07 23:13:31 +02:00
|
|
|
width: 28px;
|
|
|
|
|
min-width: 28px;
|
|
|
|
|
height: 28px;
|
|
|
|
|
margin-right: 12px;
|
|
|
|
|
flex-shrink: 0;
|
2025-08-07 23:05:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.flag-icon {
|
|
|
|
|
display: flex !important;
|
|
|
|
|
align-items: center !important;
|
|
|
|
|
justify-content: center !important;
|
|
|
|
|
flex-shrink: 0;
|
2025-08-07 23:13:31 +02:00
|
|
|
width: 20px;
|
|
|
|
|
height: 15px;
|
2025-08-07 23:05:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.country-name {
|
2025-08-07 23:13:31 +02:00
|
|
|
line-height: 1.4;
|
|
|
|
|
font-size: 0.875rem;
|
|
|
|
|
color: rgba(var(--v-theme-on-surface), 0.87);
|
2025-08-07 23:05:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.flag-list-item {
|
2025-08-07 23:13:31 +02:00
|
|
|
min-height: 48px;
|
|
|
|
|
padding: 8px 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Vuetify overrides for better styling */
|
|
|
|
|
:deep(.nationality-select .v-field) {
|
|
|
|
|
min-height: 56px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.nationality-select .v-field__input) {
|
|
|
|
|
align-items: center;
|
|
|
|
|
padding: 14px 16px;
|
|
|
|
|
min-height: 24px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.nationality-select .v-field__field) {
|
|
|
|
|
align-items: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.nationality-select .v-field__overlay) {
|
|
|
|
|
border-radius: 8px;
|
2025-08-07 23:05:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.flag-list-item .v-list-item__prepend) {
|
|
|
|
|
align-self: center;
|
2025-08-07 23:13:31 +02:00
|
|
|
margin-inline-end: 12px;
|
2025-08-07 23:05:46 +02:00
|
|
|
}
|
|
|
|
|
|
2025-08-07 23:13:31 +02:00
|
|
|
:deep(.flag-selection) {
|
|
|
|
|
padding: 0;
|
|
|
|
|
margin: 0;
|
2025-08-07 23:05:46 +02:00
|
|
|
}
|
|
|
|
|
|
2025-08-07 23:13:31 +02:00
|
|
|
:deep(.v-select__selection) {
|
2025-08-07 23:05:46 +02:00
|
|
|
align-items: center;
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-07 23:13:31 +02:00
|
|
|
/* Better dropdown menu styling */
|
|
|
|
|
:deep(.v-overlay__content .v-list) {
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.v-list-item:hover) {
|
|
|
|
|
background-color: rgba(var(--v-theme-primary), 0.08);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.v-list-item--active) {
|
|
|
|
|
background-color: rgba(var(--v-theme-primary), 0.12);
|
|
|
|
|
color: rgb(var(--v-theme-primary));
|
2025-08-07 23:05:46 +02:00
|
|
|
}
|
|
|
|
|
|
2025-08-07 19:20:29 +02:00
|
|
|
/* Priority countries styling in dropdowns */
|
|
|
|
|
:deep(.v-list-item[data-country="MC"]) {
|
|
|
|
|
background-color: rgba(var(--v-theme-primary), 0.04);
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.v-list-item[data-country="FR"]) {
|
|
|
|
|
background-color: rgba(var(--v-theme-primary), 0.04);
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.v-list-item[data-country="US"]) {
|
|
|
|
|
background-color: rgba(var(--v-theme-primary), 0.02);
|
|
|
|
|
}
|
2025-08-09 18:53:26 +02:00
|
|
|
|
|
|
|
|
/* Mobile Safari Country Dialog Styles */
|
|
|
|
|
.mobile-country-dialog .v-dialog {
|
|
|
|
|
margin: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.mobile-country-selector {
|
|
|
|
|
height: 100%;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
max-height: 100vh;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.mobile-country-selector .v-card-text {
|
|
|
|
|
flex: 1;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.search-container {
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
border-bottom: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.country-list {
|
|
|
|
|
flex: 1;
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
-webkit-overflow-scrolling: touch; /* Smooth scrolling on iOS */
|
|
|
|
|
max-height: calc(100vh - 200px);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.country-list-item {
|
|
|
|
|
min-height: 56px;
|
|
|
|
|
padding: 12px 16px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
transition: background-color 0.2s ease;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.country-list-item:hover {
|
|
|
|
|
background-color: rgba(var(--v-theme-primary), 0.08);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.country-list-item.selected {
|
|
|
|
|
background-color: rgba(var(--v-theme-primary), 0.12);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.country-flag-container {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
width: 32px;
|
|
|
|
|
min-width: 32px;
|
|
|
|
|
height: 24px;
|
|
|
|
|
margin-right: 16px;
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.country-flag {
|
|
|
|
|
width: 24px;
|
|
|
|
|
height: 18px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.country-title {
|
|
|
|
|
font-size: 1rem;
|
|
|
|
|
line-height: 1.5;
|
|
|
|
|
color: rgba(var(--v-theme-on-surface), 0.87);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.no-results {
|
|
|
|
|
padding: 32px 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Mobile optimized text field */
|
|
|
|
|
.nationality-select.mobile-optimized {
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.nationality-select.mobile-optimized :deep(.v-field__input) {
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.nationality-select.mobile-optimized :deep(.v-field__field) {
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Mobile Safari specific fixes */
|
|
|
|
|
@media (max-width: 768px) {
|
|
|
|
|
.mobile-country-dialog :deep(.v-overlay__content) {
|
|
|
|
|
margin: 0 !important;
|
|
|
|
|
max-height: none !important;
|
|
|
|
|
height: 100% !important;
|
|
|
|
|
width: 100% !important;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.mobile-country-selector {
|
|
|
|
|
border-radius: 0 !important;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.country-list {
|
|
|
|
|
max-height: calc(100vh - 160px);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.country-list-item {
|
|
|
|
|
min-height: 60px; /* Larger touch targets */
|
|
|
|
|
padding: 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.country-flag-container {
|
|
|
|
|
width: 36px;
|
|
|
|
|
min-width: 36px;
|
|
|
|
|
height: 27px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.country-flag {
|
|
|
|
|
width: 28px;
|
|
|
|
|
height: 21px;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Performance optimizations for mobile Safari */
|
|
|
|
|
.is-mobile-safari .mobile-country-selector,
|
|
|
|
|
.performance-mode .mobile-country-selector {
|
|
|
|
|
-webkit-transform: translateZ(0); /* Force hardware acceleration */
|
|
|
|
|
transform: translateZ(0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.is-mobile-safari .country-list,
|
|
|
|
|
.performance-mode .country-list {
|
|
|
|
|
will-change: scroll-position;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.is-mobile-safari .country-list-item,
|
|
|
|
|
.performance-mode .country-list-item {
|
|
|
|
|
transition: none; /* Disable transitions for better performance */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Smooth scrolling fix for mobile Safari */
|
|
|
|
|
.mobile-country-dialog :deep(.v-overlay__scrim) {
|
|
|
|
|
background: rgba(0, 0, 0, 0.5);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Fix dialog transition on mobile */
|
|
|
|
|
@media (max-width: 768px) {
|
|
|
|
|
.mobile-country-dialog :deep(.v-dialog-transition-enter-active),
|
|
|
|
|
.mobile-country-dialog :deep(.v-dialog-transition-leave-active) {
|
|
|
|
|
transition: transform 0.3s ease-out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.mobile-country-dialog :deep(.v-dialog-transition-enter-from) {
|
|
|
|
|
transform: translateY(100%);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.mobile-country-dialog :deep(.v-dialog-transition-leave-to) {
|
|
|
|
|
transform: translateY(100%);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-08-07 19:20:29 +02:00
|
|
|
</style>
|