feat(frontend): add PhoneInput component with UK validation
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
<script lang="ts">
|
||||
import { Input } from '$lib/components/ui/input/index.js';
|
||||
import { isValidUKPhone, formatPhoneDisplay } from '$lib/utils/phone';
|
||||
|
||||
interface Props {
|
||||
value?: string;
|
||||
error?: string;
|
||||
placeholder?: string;
|
||||
required?: boolean;
|
||||
disabled?: boolean;
|
||||
class?: string;
|
||||
id?: string;
|
||||
onvaluechange?: (value: string) => void;
|
||||
onerrorchange?: (error: string) => void;
|
||||
}
|
||||
|
||||
let {
|
||||
value = $bindable(''),
|
||||
error = $bindable(''),
|
||||
placeholder = '07123 456789 or +44 7123 456789',
|
||||
required = false,
|
||||
disabled = false,
|
||||
class: className = '',
|
||||
id = 'phone',
|
||||
onvaluechange,
|
||||
onerrorchange
|
||||
}: Props = $props();
|
||||
|
||||
let touched = $state(false);
|
||||
|
||||
function handleKeyDown(e: KeyboardEvent) {
|
||||
const target = e.target as HTMLInputElement;
|
||||
const allowedKeys = ['Backspace', 'Delete', 'Tab', 'ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End'];
|
||||
if (allowedKeys.includes(e.key)) return;
|
||||
if (e.key === '+') return; // allow + anywhere; normalisePhoneInput removes extras
|
||||
if (!/^[0-9]$/.test(e.key)) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
function normalizePhone(value: string): string {
|
||||
let cleaned = value.replace(/[\s\-\(\)]/g, '');
|
||||
if (cleaned.startsWith('07')) {
|
||||
cleaned = '+44' + cleaned.substring(1);
|
||||
}
|
||||
return cleaned;
|
||||
}
|
||||
|
||||
function handleInput(e: Event) {
|
||||
const target = e.target as HTMLInputElement;
|
||||
const formatted = formatPhoneDisplay(target.value);
|
||||
value = formatted;
|
||||
onvaluechange?.(formatted);
|
||||
// Only show live validation after the field has been blurred at least once
|
||||
if (touched) {
|
||||
if (formatted && !isValidUKPhone(formatted)) {
|
||||
error = 'Invalid UK phone number';
|
||||
onerrorchange?.(error);
|
||||
} else {
|
||||
error = '';
|
||||
onerrorchange?.('');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleBlur() {
|
||||
touched = true;
|
||||
if (value && !isValidUKPhone(value)) {
|
||||
error = 'Invalid UK phone number';
|
||||
onerrorchange?.(error);
|
||||
} else {
|
||||
error = '';
|
||||
onerrorchange?.('');
|
||||
if (value) {
|
||||
const normalized = normalizePhone(value);
|
||||
if (normalized !== value) {
|
||||
value = normalized;
|
||||
onvaluechange?.(normalized);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="space-y-1.5">
|
||||
<Input
|
||||
{id}
|
||||
type="text"
|
||||
inputmode="tel"
|
||||
{placeholder}
|
||||
maxlength={15}
|
||||
{value}
|
||||
oninput={handleInput}
|
||||
onblur={handleBlur}
|
||||
onkeydown={handleKeyDown}
|
||||
{disabled}
|
||||
aria-invalid={!!error || undefined}
|
||||
class={className}
|
||||
/>
|
||||
{#if error}
|
||||
<p class="text-sm text-red-500">{error}</p>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -0,0 +1,3 @@
|
||||
import PhoneInput from './PhoneInput.svelte';
|
||||
|
||||
export { PhoneInput };
|
||||
Reference in New Issue
Block a user