150 lines
7.3 KiB
HTML
150 lines
7.3 KiB
HTML
{# Phone input component with country code selector, flags, dynamic placeholder, and auto-formatting.
|
|
Usage: {% include "components/phone_input.html" %}
|
|
Expects: name, value (optional), id (optional)
|
|
#}
|
|
<div class="phone-input-wrapper" id="phoneWrapper">
|
|
<label for="contact_phone_input" class="block text-sm font-medium text-gray-700 mb-1">Contact Phone</label>
|
|
<div class="flex rounded-lg border border-gray-300 overflow-hidden focus-within:ring-2 focus-within:ring-accent focus-within:border-transparent transition">
|
|
<!-- Country selector button -->
|
|
<button type="button" id="phoneCountryBtn"
|
|
class="flex items-center gap-1.5 px-3 bg-gray-50 border-r border-gray-300 hover:bg-gray-100 transition text-sm flex-shrink-0"
|
|
onclick="togglePhoneDropdown()">
|
|
<span id="phoneFlag" class="text-lg leading-none">🇺🇸</span>
|
|
<span id="phoneDialCode" class="text-gray-700 font-medium">+1</span>
|
|
<svg class="w-3 h-3 text-gray-500 ml-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
|
</svg>
|
|
</button>
|
|
|
|
<!-- Hidden input for actual phone value (with dial code) -->
|
|
<input type="hidden" name="contact_phone" id="phoneHidden" value="{{ phone_value|default('') }}">
|
|
|
|
<!-- Visible phone display -->
|
|
<input type="tel" id="phoneDisplay"
|
|
class="flex-1 px-3 py-2.5 outline-none bg-white text-sm min-w-0"
|
|
placeholder="(555) 123-4567"
|
|
autocomplete="tel"
|
|
oninput="formatPhoneInput(this)"
|
|
onfocus="formatPhoneInput(this)">
|
|
</div>
|
|
|
|
<!-- Dropdown -->
|
|
<div id="phoneDropdown"
|
|
class="hidden absolute z-50 mt-1 w-72 bg-white border border-gray-200 rounded-xl shadow-xl max-h-64 overflow-y-auto">
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
(function() {
|
|
const COUNTRIES = [
|
|
{ code: 'US', flag: '🇺🇸', dial: '+1', name: 'United States', format: '(###) ###-####', digitCount: 10 },
|
|
{ code: 'CA', flag: '🇨🇦', dial: '+1', name: 'Canada', format: '(###) ###-####', digitCount: 10 },
|
|
{ code: 'GB', flag: '🇬🇧', dial: '+44', name: 'United Kingdom', format: '#### ######', digitCount: 10 },
|
|
{ code: 'AU', flag: '🇦🇺', dial: '+61', name: 'Australia', format: '#### ### ###', digitCount: 9 },
|
|
{ code: 'CN', flag: '🇨🇳', dial: '+86', name: 'China', format: '### #### ####', digitCount: 11 },
|
|
{ code: 'JP', flag: '🇯🇵', dial: '+81', name: 'Japan', format: '##-####-####', digitCount: 10 },
|
|
{ code: 'KR', flag: '🇰🇷', dial: '+82', name: 'South Korea', format: '##-####-####', digitCount: 10 },
|
|
{ code: 'DE', flag: '🇩🇪', dial: '+49', name: 'Germany', format: '### ########', digitCount: 11 },
|
|
{ code: 'FR', flag: '🇫🇷', dial: '+33', name: 'France', format: '# ## ## ## ##', digitCount: 9 },
|
|
{ code: 'IN', flag: '🇮🇳', dial: '+91', name: 'India', format: '##### #####', digitCount: 10 },
|
|
{ code: 'BR', flag: '🇧🇷', dial: '+55', name: 'Brazil', format: '## #####-####', digitCount: 11 },
|
|
{ code: 'MX', flag: '🇲🇽', dial: '+52', name: 'Mexico', format: '## #### ####', digitCount: 10 },
|
|
{ code: 'SG', flag: '🇸🇬', dial: '+65', name: 'Singapore', format: '#### ####', digitCount: 8 },
|
|
{ code: 'HK', flag: '🇭🇰', dial: '+852', name: 'Hong Kong(China)', format: '#### ####', digitCount: 8 },
|
|
{ code: 'TW', flag: '🇹🇼', dial: '+886', name: 'Taiwan(China)', format: '## ### ####', digitCount: 9 },
|
|
];
|
|
|
|
let selectedCountry = COUNTRIES[0]; // Default: US
|
|
let rawDigits = '';
|
|
|
|
// Parse initial value
|
|
const hiddenInput = document.getElementById('phoneHidden');
|
|
const displayInput = document.getElementById('phoneDisplay');
|
|
if (hiddenInput.value) {
|
|
for (const c of COUNTRIES) {
|
|
if (hiddenInput.value.startsWith(c.dial)) {
|
|
selectedCountry = c;
|
|
rawDigits = hiddenInput.value.replace(c.dial, '').replace(/\D/g, '');
|
|
break;
|
|
}
|
|
}
|
|
if (!rawDigits && hiddenInput.value) {
|
|
rawDigits = hiddenInput.value.replace(/\D/g, '');
|
|
}
|
|
}
|
|
|
|
function updateDisplay() {
|
|
document.getElementById('phoneFlag').textContent = selectedCountry.flag;
|
|
document.getElementById('phoneDialCode').textContent = selectedCountry.dial;
|
|
displayInput.placeholder = formatDigits('', selectedCountry);
|
|
displayInput.value = formatDigits(rawDigits, selectedCountry);
|
|
hiddenInput.value = rawDigits.length > 0 ? selectedCountry.dial + rawDigits : '';
|
|
}
|
|
|
|
function formatDigits(digits, country) {
|
|
if (!digits) return '';
|
|
let result = '';
|
|
let di = 0;
|
|
for (let i = 0; i < country.format.length && di < digits.length; i++) {
|
|
if (country.format[i] === '#') {
|
|
result += digits[di++];
|
|
} else {
|
|
result += country.format[i];
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
window.formatPhoneInput = function(input) {
|
|
const cursorPos = input.selectionStart;
|
|
const prevLength = input.value.length;
|
|
rawDigits = input.value.replace(/\D/g, '').substring(0, selectedCountry.digitCount);
|
|
updateDisplay();
|
|
// Restore approximate cursor position
|
|
const newLength = input.value.length;
|
|
const newPos = cursorPos + (newLength - prevLength);
|
|
input.setSelectionRange(newPos, newPos);
|
|
};
|
|
|
|
window.togglePhoneDropdown = function() {
|
|
const dd = document.getElementById('phoneDropdown');
|
|
dd.classList.toggle('hidden');
|
|
if (!dd.classList.contains('hidden')) {
|
|
renderDropdown();
|
|
}
|
|
};
|
|
|
|
window.selectCountry = function(code) {
|
|
selectedCountry = COUNTRIES.find(c => c.code === code);
|
|
rawDigits = rawDigits.substring(0, selectedCountry.digitCount);
|
|
updateDisplay();
|
|
document.getElementById('phoneDropdown').classList.add('hidden');
|
|
};
|
|
|
|
function renderDropdown() {
|
|
const dd = document.getElementById('phoneDropdown');
|
|
dd.innerHTML = COUNTRIES.map(c => `
|
|
<button type="button"
|
|
onclick="selectCountry('${c.code}')"
|
|
class="w-full flex items-center gap-3 px-3 py-2.5 hover:bg-accent/5 transition text-left
|
|
${c.code === selectedCountry.code ? 'bg-accent/10' : ''}">
|
|
<span class="text-xl">${c.flag}</span>
|
|
<span class="text-sm font-medium text-gray-700">${c.name}</span>
|
|
<span class="text-sm text-gray-500 ml-auto">${c.dial}</span>
|
|
</button>
|
|
`).join('');
|
|
}
|
|
|
|
// Close dropdown on outside click
|
|
document.addEventListener('click', function(e) {
|
|
const wrapper = document.getElementById('phoneWrapper');
|
|
if (!wrapper.contains(e.target)) {
|
|
document.getElementById('phoneDropdown').classList.add('hidden');
|
|
}
|
|
});
|
|
|
|
// Initial render
|
|
updateDisplay();
|
|
})();
|
|
</script>
|