import React, { useEffect, useMemo, useState } from 'react'; import { buildClassName } from '../../shared/utils/class-util'; import { compact } from 'lodash'; import { Country } from '../types'; interface PhoneInputProps { name: string; required?: boolean; value?: string | number; label?: string; placeholder?: string; hasError?: boolean; extraClassName?: string; countries: Country[]; countryIso2?: string; // Initial country selection (ISO2 code) onChange?: React.ChangeEventHandler; onBlur?: React.FocusEventHandler; } const normalize = (v?: string | number) => (v == null ? '' : String(v)); const parseCombined = (raw: string, countries: Country[]): { prefix: string; number: string } => { const value = normalize(raw).trim(); if (!value) return { prefix: '', number: '' }; // Try to match a known prefix at the start. Prefer longest match. const sorted = [...countries].sort((a, b) => b.phonePrefix.length - a.phonePrefix.length); const hit = sorted.find((c) => value.startsWith(c.phonePrefix)); if (hit) { const rest = value .slice(hit.phonePrefix.length) .trim() .replace(/^[-\s]+/, ''); return { prefix: hit.phonePrefix, number: rest }; } // Fallback: split on first space if it looks like a +prefix number const m = value.match(/^(\+\d{1,4})[\s-]*(.*)$/); if (m) return { prefix: m[1], number: m[2] }; return { prefix: '', number: value }; }; const PhoneInput: React.FC = ({ name, required, value, label, placeholder, extraClassName, hasError, countries, countryIso2, onChange, onBlur }) => { // Derive initial state from `value` const initialCountry = countries.find((c) => c.iso2 === countryIso2); const initial = useMemo(() => parseCombined(normalize(value), countries), [value, countries]); const [prefix, setPrefix] = useState(initial.prefix); const [number, setNumber] = useState(initial.number); // Keep state in sync if the parent changes `value` useEffect(() => { const parsed = !prefix && !number && initialCountry ? { prefix: initialCountry.phonePrefix, number: '' } : parseCombined(normalize(value), countries); if (parsed.prefix) setPrefix(parsed.prefix); if (parsed.number) setNumber(parsed.number); }, [value, countries, countryIso2]); const emitCombinedChange = (e: React.ChangeEvent, nextPrefix: string, nextNumber: string) => { // Combine with a space, unless the number already starts with a dash or space const combined = nextPrefix && nextNumber ? compact([nextPrefix, nextNumber]).join(' ') : null; onChange?.({ ...e, type: 'change', target: { name, value: combined }, currentTarget: { name, value: combined } } as any); }; const handleCountryBlur = (e: React.FocusEvent) => { onBlur?.(e); }; const onCountryChange = (e: React.ChangeEvent) => { const next = e.target.value; setPrefix(next); emitCombinedChange(e, next, number); }; const onPhoneChange = (e: React.ChangeEvent) => { const cleaned = e.target.value.replace(/[^\d\s-]/g, ''); setNumber(cleaned); emitCombinedChange(e, prefix, cleaned); }; const handleNumberBlur = (e: React.FocusEvent) => { onBlur?.(e); }; // Ensure the select shows a reasonable default when prefix is empty const selectValue = prefix || ''; return ( ); }; export default PhoneInput;