mobile optimizations
Build And Push Image / docker (push) Successful in 3m13s
Details
Build And Push Image / docker (push) Successful in 3m13s
Details
This commit is contained in:
parent
d2057cc878
commit
497e0134d8
|
|
@ -2,7 +2,7 @@
|
||||||
<div class="demo-container">
|
<div class="demo-container">
|
||||||
<v-card class="pa-6" elevation="2">
|
<v-card class="pa-6" elevation="2">
|
||||||
<v-card-title class="text-h5 mb-4">
|
<v-card-title class="text-h5 mb-4">
|
||||||
📱 Perfect Phone Input - Like Screenshot 2
|
📱 Professional Phone Input - Desktop & Mobile Optimized
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
|
|
||||||
<v-row>
|
<v-row>
|
||||||
|
|
@ -11,30 +11,42 @@
|
||||||
v-model="phoneNumber"
|
v-model="phoneNumber"
|
||||||
label="Phone Number"
|
label="Phone Number"
|
||||||
placeholder="Enter your phone number"
|
placeholder="Enter your phone number"
|
||||||
help-text="Clean Vuetify design with compact flag dropdown"
|
help-text="Clean Vuetify design with advanced mobile optimization"
|
||||||
@phone-data="handlePhoneData"
|
@phone-data="handlePhoneData"
|
||||||
@country-changed="handleCountryChange"
|
@country-changed="handleCountryChange"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<v-btn
|
<div class="mt-4">
|
||||||
color="primary"
|
<v-btn
|
||||||
class="mt-4"
|
color="primary"
|
||||||
@click="testUSNumber"
|
@click="testUSNumber"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="small"
|
size="small"
|
||||||
>
|
class="mr-2 mb-2"
|
||||||
Test: (917) 932-4061
|
>
|
||||||
</v-btn>
|
Test: (917) 932-4061
|
||||||
|
</v-btn>
|
||||||
<v-btn
|
|
||||||
color="secondary"
|
<v-btn
|
||||||
class="mt-4 ml-2"
|
color="secondary"
|
||||||
@click="testMonacoNumber"
|
@click="testMonacoNumber"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="small"
|
size="small"
|
||||||
>
|
class="mr-2 mb-2"
|
||||||
Test: Monaco +377
|
>
|
||||||
</v-btn>
|
Test: Monaco +377
|
||||||
|
</v-btn>
|
||||||
|
|
||||||
|
<v-btn
|
||||||
|
color="accent"
|
||||||
|
@click="testFrenchNumber"
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
class="mr-2 mb-2"
|
||||||
|
>
|
||||||
|
Test: France +33
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
</v-col>
|
</v-col>
|
||||||
|
|
||||||
<v-col cols="12" md="6">
|
<v-col cols="12" md="6">
|
||||||
|
|
@ -45,13 +57,14 @@
|
||||||
<p><strong>Valid:</strong> {{ phoneData?.isValid ? '✅ Valid' : '❌ Invalid' }}</p>
|
<p><strong>Valid:</strong> {{ phoneData?.isValid ? '✅ Valid' : '❌ Invalid' }}</p>
|
||||||
<p><strong>Country:</strong> {{ phoneData?.country?.name }} ({{ phoneData?.country?.iso2 }})</p>
|
<p><strong>Country:</strong> {{ phoneData?.country?.name }} ({{ phoneData?.country?.iso2 }})</p>
|
||||||
<p><strong>Dial Code:</strong> {{ phoneData?.country?.dialCode }}</p>
|
<p><strong>Dial Code:</strong> {{ phoneData?.country?.dialCode }}</p>
|
||||||
|
<p><strong>Device:</strong> {{ isMobile ? '📱 Mobile' : '🖥️ Desktop' }}</p>
|
||||||
</div>
|
</div>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
|
||||||
<v-alert type="success" variant="tonal" class="mt-6">
|
<v-alert type="success" variant="tonal" class="mt-6">
|
||||||
<template #title>✅ Perfect Implementation:</template>
|
<template #title>🎯 Perfect Desktop Implementation:</template>
|
||||||
<ul class="mt-2">
|
<ul class="mt-2">
|
||||||
<li><strong>Clean Design:</strong> Exactly like your screenshot - Vuetify text field with flag inside</li>
|
<li><strong>Clean Design:</strong> Exactly like your screenshot - Vuetify text field with flag inside</li>
|
||||||
<li><strong>Compact Dropdown:</strong> Max 240px height, not oversized</li>
|
<li><strong>Compact Dropdown:</strong> Max 240px height, not oversized</li>
|
||||||
|
|
@ -63,13 +76,28 @@
|
||||||
</v-alert>
|
</v-alert>
|
||||||
|
|
||||||
<v-alert type="info" variant="tonal" class="mt-4">
|
<v-alert type="info" variant="tonal" class="mt-4">
|
||||||
<template #title>🎯 Design Features:</template>
|
<template #title>📱 Advanced Mobile Optimization:</template>
|
||||||
<ul class="mt-2">
|
<ul class="mt-2">
|
||||||
<li>Flag size: 24x18px (perfect for text field)</li>
|
<li><strong>Mobile Detection:</strong> Automatic device detection with resize handling</li>
|
||||||
<li>Dropdown: Compact with search bar</li>
|
<li><strong>Full-Screen Modal:</strong> Mobile dropdown opens as centered modal with overlay</li>
|
||||||
<li>Input field: Full width for phone number</li>
|
<li><strong>Touch-Friendly:</strong> All elements sized for proper touch targets (44px+)</li>
|
||||||
<li>Validation: Real-time phone number validation</li>
|
<li><strong>iOS Safari Fixes:</strong> Prevents zoom on focus, proper appearance handling</li>
|
||||||
<li>Responsive: Works perfectly on mobile</li>
|
<li><strong>Android Material:</strong> Material Design touch targets and interactions</li>
|
||||||
|
<li><strong>Accessibility:</strong> Reduced motion, high contrast, screen reader support</li>
|
||||||
|
<li><strong>Safe Areas:</strong> Handles notched devices with proper padding</li>
|
||||||
|
<li><strong>Landscape Support:</strong> Optimized layout for landscape orientation</li>
|
||||||
|
<li><strong>Backdrop Blur:</strong> Modern glass morphism effects on supported devices</li>
|
||||||
|
<li><strong>Smooth Scrolling:</strong> Native touch scrolling with momentum</li>
|
||||||
|
</ul>
|
||||||
|
</v-alert>
|
||||||
|
|
||||||
|
<v-alert type="warning" variant="tonal" class="mt-4">
|
||||||
|
<template #title>🧪 Test Mobile Features:</template>
|
||||||
|
<p class="mt-2 mb-2">To test mobile features, try:</p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Resize Window:</strong> Make browser window narrow (< 768px)</li>
|
||||||
|
<li><strong>Developer Tools:</strong> Toggle device emulation in Chrome DevTools</li>
|
||||||
|
<li><strong>Touch Device:</strong> Test on actual phone/tablet for best experience</li>
|
||||||
</ul>
|
</ul>
|
||||||
</v-alert>
|
</v-alert>
|
||||||
</v-card>
|
</v-card>
|
||||||
|
|
@ -81,6 +109,21 @@ import PhoneInputWrapper from './PhoneInputWrapper.vue';
|
||||||
|
|
||||||
const phoneNumber = ref('');
|
const phoneNumber = ref('');
|
||||||
const phoneData = ref<any>(null);
|
const phoneData = ref<any>(null);
|
||||||
|
const isMobile = ref(false);
|
||||||
|
|
||||||
|
// Mobile detection
|
||||||
|
onMounted(() => {
|
||||||
|
const checkMobile = () => {
|
||||||
|
isMobile.value = window.innerWidth <= 768 || 'ontouchstart' in window;
|
||||||
|
};
|
||||||
|
|
||||||
|
checkMobile();
|
||||||
|
window.addEventListener('resize', checkMobile);
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('resize', checkMobile);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const handlePhoneData = (data: any) => {
|
const handlePhoneData = (data: any) => {
|
||||||
phoneData.value = data;
|
phoneData.value = data;
|
||||||
|
|
@ -98,6 +141,10 @@ const testUSNumber = () => {
|
||||||
const testMonacoNumber = () => {
|
const testMonacoNumber = () => {
|
||||||
phoneNumber.value = '+37799123456';
|
phoneNumber.value = '+37799123456';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const testFrenchNumber = () => {
|
||||||
|
phoneNumber.value = '+33123456789';
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="phone-input-wrapper">
|
<div class="phone-input-wrapper" :class="{ 'phone-input-wrapper--mobile': isMobile }">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="localNumber"
|
v-model="localNumber"
|
||||||
:label="label"
|
:label="label"
|
||||||
|
|
@ -11,7 +11,8 @@
|
||||||
:required="required"
|
:required="required"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
density="comfortable"
|
:density="isMobile ? 'default' : 'comfortable'"
|
||||||
|
class="phone-text-field"
|
||||||
@input="handleInput"
|
@input="handleInput"
|
||||||
@blur="handleBlur"
|
@blur="handleBlur"
|
||||||
>
|
>
|
||||||
|
|
@ -20,16 +21,22 @@
|
||||||
<v-menu
|
<v-menu
|
||||||
v-model="dropdownOpen"
|
v-model="dropdownOpen"
|
||||||
:close-on-content-click="false"
|
:close-on-content-click="false"
|
||||||
location="bottom"
|
:location="isMobile ? 'bottom' : 'bottom'"
|
||||||
offset="4"
|
:offset="isMobile ? 8 : 4"
|
||||||
max-height="300"
|
:max-height="isMobile ? '70vh' : '300'"
|
||||||
|
:full-width="isMobile"
|
||||||
|
:transition="isMobile ? 'slide-y-transition' : 'fade-transition'"
|
||||||
>
|
>
|
||||||
<template #activator="{ props: menuProps }">
|
<template #activator="{ props: menuProps }">
|
||||||
<div
|
<div
|
||||||
v-bind="menuProps"
|
v-bind="menuProps"
|
||||||
class="country-selector"
|
class="country-selector"
|
||||||
:class="{ 'country-selector--open': dropdownOpen }"
|
:class="{
|
||||||
|
'country-selector--open': dropdownOpen,
|
||||||
|
'country-selector--mobile': isMobile
|
||||||
|
}"
|
||||||
@click="toggleDropdown"
|
@click="toggleDropdown"
|
||||||
|
@touchstart="handleTouchStart"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
:src="flagUrl"
|
:src="flagUrl"
|
||||||
|
|
@ -39,7 +46,7 @@
|
||||||
/>
|
/>
|
||||||
<span class="country-code">{{ selectedCountry.dialCode }}</span>
|
<span class="country-code">{{ selectedCountry.dialCode }}</span>
|
||||||
<v-icon
|
<v-icon
|
||||||
size="16"
|
:size="isMobile ? 18 : 16"
|
||||||
class="dropdown-icon"
|
class="dropdown-icon"
|
||||||
:class="{ 'dropdown-icon--rotated': dropdownOpen }"
|
:class="{ 'dropdown-icon--rotated': dropdownOpen }"
|
||||||
>
|
>
|
||||||
|
|
@ -48,52 +55,105 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<!-- Mobile Full-Screen Overlay -->
|
||||||
|
<div
|
||||||
|
v-if="isMobile && dropdownOpen"
|
||||||
|
class="mobile-overlay"
|
||||||
|
@click="closeDropdown"
|
||||||
|
@touchstart="handleOverlayTouch"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- Dropdown Content -->
|
<!-- Dropdown Content -->
|
||||||
<v-card class="country-dropdown" elevation="8">
|
<v-card
|
||||||
|
class="country-dropdown"
|
||||||
|
:class="{ 'country-dropdown--mobile': isMobile }"
|
||||||
|
elevation="8"
|
||||||
|
>
|
||||||
|
<!-- Mobile Header -->
|
||||||
|
<div v-if="isMobile" class="mobile-header">
|
||||||
|
<h3 class="mobile-title">Select Country</h3>
|
||||||
|
<v-btn
|
||||||
|
icon="mdi-close"
|
||||||
|
variant="text"
|
||||||
|
size="small"
|
||||||
|
@click="closeDropdown"
|
||||||
|
class="close-btn"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Search Bar -->
|
<!-- Search Bar -->
|
||||||
<div class="search-container">
|
<div class="search-container">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="searchQuery"
|
v-model="searchQuery"
|
||||||
placeholder="Search countries..."
|
placeholder="Search countries..."
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
density="compact"
|
:density="isMobile ? 'default' : 'compact'"
|
||||||
prepend-inner-icon="mdi-magnify"
|
prepend-inner-icon="mdi-magnify"
|
||||||
hide-details
|
hide-details
|
||||||
class="search-input"
|
class="search-input"
|
||||||
autofocus
|
:autofocus="!isMobile"
|
||||||
|
clearable
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Country List -->
|
<!-- Country List -->
|
||||||
<v-list class="country-list" density="compact">
|
<v-list
|
||||||
|
class="country-list"
|
||||||
|
:class="{ 'country-list--mobile': isMobile }"
|
||||||
|
:density="isMobile ? 'default' : 'compact'"
|
||||||
|
>
|
||||||
<v-list-item
|
<v-list-item
|
||||||
v-for="country in filteredCountries"
|
v-for="country in filteredCountries"
|
||||||
:key="country.iso2"
|
:key="country.iso2"
|
||||||
:class="{
|
:class="{
|
||||||
'country-item': true,
|
'country-item': true,
|
||||||
'country-item--selected': country.iso2 === selectedCountry.iso2,
|
'country-item--selected': country.iso2 === selectedCountry.iso2,
|
||||||
'country-item--preferred': isPreferredCountry(country.iso2)
|
'country-item--preferred': isPreferredCountry(country.iso2),
|
||||||
|
'country-item--mobile': isMobile
|
||||||
}"
|
}"
|
||||||
@click="selectCountry(country)"
|
@click="selectCountry(country)"
|
||||||
|
@touchstart="handleCountryTouch"
|
||||||
|
:ripple="isMobile"
|
||||||
>
|
>
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<img
|
<img
|
||||||
:src="getCountryFlagUrl(country.iso2)"
|
:src="getCountryFlagUrl(country.iso2)"
|
||||||
:alt="`${country.name} flag`"
|
:alt="`${country.name} flag`"
|
||||||
class="list-flag"
|
class="list-flag"
|
||||||
|
:class="{ 'list-flag--mobile': isMobile }"
|
||||||
@error="handleFlagError"
|
@error="handleFlagError"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<v-list-item-title class="country-name">
|
<v-list-item-title
|
||||||
|
class="country-name"
|
||||||
|
:class="{ 'country-name--mobile': isMobile }"
|
||||||
|
>
|
||||||
{{ country.name }}
|
{{ country.name }}
|
||||||
</v-list-item-title>
|
</v-list-item-title>
|
||||||
|
|
||||||
<template #append>
|
<template #append>
|
||||||
<span class="dial-code">{{ country.dialCode }}</span>
|
<span
|
||||||
|
class="dial-code"
|
||||||
|
:class="{ 'dial-code--mobile': isMobile }"
|
||||||
|
>
|
||||||
|
{{ country.dialCode }}
|
||||||
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
</v-list>
|
</v-list>
|
||||||
|
|
||||||
|
<!-- Mobile Footer -->
|
||||||
|
<div v-if="isMobile" class="mobile-footer">
|
||||||
|
<v-btn
|
||||||
|
block
|
||||||
|
variant="text"
|
||||||
|
@click="closeDropdown"
|
||||||
|
class="cancel-btn"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-menu>
|
</v-menu>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -183,9 +243,26 @@ const selectedCountry = ref<Country>(
|
||||||
countries.find(c => c.iso2 === props.defaultCountry) || countries[0]
|
countries.find(c => c.iso2 === props.defaultCountry) || countries[0]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Mobile detection
|
||||||
|
const isMobile = ref(false);
|
||||||
|
|
||||||
// Computed
|
// Computed
|
||||||
const flagUrl = computed(() => getCountryFlagUrl(selectedCountry.value.iso2));
|
const flagUrl = computed(() => getCountryFlagUrl(selectedCountry.value.iso2));
|
||||||
|
|
||||||
|
// Mobile detection on mount
|
||||||
|
onMounted(() => {
|
||||||
|
const checkMobile = () => {
|
||||||
|
isMobile.value = window.innerWidth <= 768 || 'ontouchstart' in window;
|
||||||
|
};
|
||||||
|
|
||||||
|
checkMobile();
|
||||||
|
window.addEventListener('resize', checkMobile);
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('resize', checkMobile);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const filteredCountries = computed(() => {
|
const filteredCountries = computed(() => {
|
||||||
if (!searchQuery.value) {
|
if (!searchQuery.value) {
|
||||||
// Show preferred countries first, then alphabetical
|
// Show preferred countries first, then alphabetical
|
||||||
|
|
@ -278,6 +355,32 @@ const handleFlagError = (event: Event) => {
|
||||||
img.style.display = 'none';
|
img.style.display = 'none';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Mobile-specific handlers
|
||||||
|
const closeDropdown = () => {
|
||||||
|
dropdownOpen.value = false;
|
||||||
|
searchQuery.value = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTouchStart = (event: TouchEvent) => {
|
||||||
|
// Prevent default to avoid unwanted behaviors on mobile
|
||||||
|
if (isMobile.value) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOverlayTouch = (event: TouchEvent) => {
|
||||||
|
// Close dropdown when touching overlay
|
||||||
|
event.preventDefault();
|
||||||
|
closeDropdown();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCountryTouch = (event: TouchEvent) => {
|
||||||
|
// Prevent default touch behaviors for better mobile experience
|
||||||
|
if (isMobile.value) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Initialize from modelValue
|
// Initialize from modelValue
|
||||||
watch(() => props.modelValue, (newValue) => {
|
watch(() => props.modelValue, (newValue) => {
|
||||||
if (newValue && newValue !== selectedCountry.value.dialCode + localNumber.value.replace(/\D/g, '')) {
|
if (newValue && newValue !== selectedCountry.value.dialCode + localNumber.value.replace(/\D/g, '')) {
|
||||||
|
|
@ -435,7 +538,132 @@ watch(() => props.modelValue, (newValue) => {
|
||||||
font-family: 'Roboto Mono', monospace;
|
font-family: 'Roboto Mono', monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Responsive */
|
/* Mobile Overlay */
|
||||||
|
.mobile-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
z-index: 999;
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile Header */
|
||||||
|
.mobile-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 16px 20px;
|
||||||
|
border-bottom: 1px solid rgba(var(--v-theme-outline), 0.12);
|
||||||
|
background: rgba(var(--v-theme-primary), 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-title {
|
||||||
|
font-size: 1.125rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: rgba(var(--v-theme-on-surface), var(--v-high-emphasis-opacity));
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn {
|
||||||
|
color: rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile Footer */
|
||||||
|
.mobile-footer {
|
||||||
|
padding: 16px 20px;
|
||||||
|
border-top: 1px solid rgba(var(--v-theme-outline), 0.12);
|
||||||
|
background: rgba(var(--v-theme-surface), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-btn {
|
||||||
|
color: rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile-specific styling */
|
||||||
|
.phone-input-wrapper--mobile {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.country-selector--mobile {
|
||||||
|
padding: 6px 10px;
|
||||||
|
margin-right: 6px;
|
||||||
|
border-radius: 8px;
|
||||||
|
min-height: 44px; /* Touch-friendly size */
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.country-selector--mobile:hover {
|
||||||
|
background: rgba(var(--v-theme-primary), 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.country-dropdown--mobile {
|
||||||
|
position: fixed !important;
|
||||||
|
top: 50% !important;
|
||||||
|
left: 50% !important;
|
||||||
|
transform: translate(-50%, -50%) !important;
|
||||||
|
width: 90vw !important;
|
||||||
|
max-width: 400px !important;
|
||||||
|
max-height: 80vh !important;
|
||||||
|
border-radius: 16px !important;
|
||||||
|
z-index: 1000 !important;
|
||||||
|
box-shadow: 0 24px 48px rgba(0, 0, 0, 0.2) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.country-list--mobile {
|
||||||
|
max-height: calc(60vh - 120px) !important;
|
||||||
|
overflow-y: auto !important;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.country-list--mobile::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.country-list--mobile::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(var(--v-theme-primary), 0.4);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.country-item--mobile {
|
||||||
|
min-height: 56px !important;
|
||||||
|
padding: 12px 20px !important;
|
||||||
|
border-left-width: 4px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.country-item--mobile:active {
|
||||||
|
background: rgba(var(--v-theme-primary), 0.16) !important;
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-flag--mobile {
|
||||||
|
width: 24px !important;
|
||||||
|
height: 18px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.country-name--mobile {
|
||||||
|
font-size: 1rem !important;
|
||||||
|
font-weight: 500 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dial-code--mobile {
|
||||||
|
font-size: 0.9375rem !important;
|
||||||
|
font-weight: 600 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Touch-friendly input field */
|
||||||
|
.phone-input-wrapper--mobile .phone-text-field :deep(.v-field) {
|
||||||
|
min-height: 56px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phone-input-wrapper--mobile .phone-text-field :deep(.v-field__input) {
|
||||||
|
font-size: 16px !important; /* Prevent zoom on iOS */
|
||||||
|
padding: 16px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive Breakpoints */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.country-dropdown {
|
.country-dropdown {
|
||||||
min-width: 260px;
|
min-width: 260px;
|
||||||
|
|
@ -445,6 +673,136 @@ watch(() => props.modelValue, (newValue) => {
|
||||||
.country-list {
|
.country-list {
|
||||||
max-height: 200px;
|
max-height: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.country-selector {
|
||||||
|
min-height: 48px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.country-flag {
|
||||||
|
width: 24px;
|
||||||
|
height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.country-code {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
min-width: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-container {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input :deep(.v-field) {
|
||||||
|
font-size: 16px !important; /* Prevent zoom */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.country-dropdown--mobile {
|
||||||
|
width: 95vw !important;
|
||||||
|
max-height: 85vh !important;
|
||||||
|
border-radius: 12px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.country-list--mobile {
|
||||||
|
max-height: calc(65vh - 140px) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.country-item--mobile {
|
||||||
|
min-height: 52px !important;
|
||||||
|
padding: 10px 16px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-header,
|
||||||
|
.mobile-footer,
|
||||||
|
.search-container {
|
||||||
|
padding: 12px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-title {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Landscape orientation adjustments */
|
||||||
|
@media (max-height: 500px) and (orientation: landscape) {
|
||||||
|
.country-dropdown--mobile {
|
||||||
|
max-height: 90vh !important;
|
||||||
|
width: 60vw !important;
|
||||||
|
max-width: 500px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.country-list--mobile {
|
||||||
|
max-height: calc(75vh - 120px) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* High DPI displays */
|
||||||
|
@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
|
||||||
|
.country-flag,
|
||||||
|
.list-flag {
|
||||||
|
image-rendering: -webkit-optimize-contrast;
|
||||||
|
image-rendering: crisp-edges;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* iOS specific fixes */
|
||||||
|
@supports (-webkit-touch-callout: none) {
|
||||||
|
.phone-input-wrapper--mobile .phone-text-field :deep(.v-field__input) {
|
||||||
|
font-size: 16px !important; /* Prevent zoom on focus */
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.country-dropdown--mobile {
|
||||||
|
-webkit-backdrop-filter: blur(8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-overlay {
|
||||||
|
-webkit-backdrop-filter: blur(4px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Android specific fixes */
|
||||||
|
@media (pointer: coarse) {
|
||||||
|
.country-item--mobile {
|
||||||
|
min-height: 56px !important; /* Material Design touch target */
|
||||||
|
}
|
||||||
|
|
||||||
|
.country-selector--mobile {
|
||||||
|
min-height: 48px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Accessibility improvements for mobile */
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.country-item--mobile,
|
||||||
|
.country-selector--mobile {
|
||||||
|
transition: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-overlay {
|
||||||
|
backdrop-filter: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.country-dropdown--mobile {
|
||||||
|
backdrop-filter: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* High contrast mode for mobile */
|
||||||
|
@media (prefers-contrast: high) {
|
||||||
|
.country-dropdown--mobile {
|
||||||
|
border: 3px solid rgb(var(--v-theme-outline));
|
||||||
|
}
|
||||||
|
|
||||||
|
.country-item--mobile {
|
||||||
|
border-bottom: 1px solid rgb(var(--v-theme-outline));
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-header {
|
||||||
|
border-bottom: 2px solid rgb(var(--v-theme-outline));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Dark theme support */
|
/* Dark theme support */
|
||||||
|
|
@ -452,5 +810,23 @@ watch(() => props.modelValue, (newValue) => {
|
||||||
.country-dropdown {
|
.country-dropdown {
|
||||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.country-dropdown--mobile {
|
||||||
|
box-shadow: 0 24px 48px rgba(0, 0, 0, 0.6) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-overlay {
|
||||||
|
background: rgba(0, 0, 0, 0.7);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Safe area handling for notched devices */
|
||||||
|
@supports (padding: max(0px)) {
|
||||||
|
.country-dropdown--mobile {
|
||||||
|
padding-top: max(16px, env(safe-area-inset-top));
|
||||||
|
padding-bottom: max(16px, env(safe-area-inset-bottom));
|
||||||
|
padding-left: max(16px, env(safe-area-inset-left));
|
||||||
|
padding-right: max(16px, env(safe-area-inset-right));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue