feat(frontend): add EmailInput component with validation
EmailInput wraps shadcn Input with built-in validation (regex + browser checkValidity), normalization (trim + lowercase on blur), and inline error display. Follows PhoneInput.svelte pattern with value/error and onvaluechange/onerrorchange/onblur callbacks. 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,91 @@
|
||||
<script lang="ts">
|
||||
import { Input } from '$lib/components/ui/input/index.js';
|
||||
import { isValidEmail, normalizeEmail } from '$lib/utils/email';
|
||||
|
||||
interface Props {
|
||||
value?: string;
|
||||
error?: string;
|
||||
placeholder?: string;
|
||||
required?: boolean;
|
||||
disabled?: boolean;
|
||||
class?: string;
|
||||
id?: string;
|
||||
onvaluechange?: (value: string) => void;
|
||||
onerrorchange?: (error: string) => void;
|
||||
onblur?: () => void;
|
||||
}
|
||||
|
||||
let {
|
||||
value = $bindable(''),
|
||||
error = $bindable(''),
|
||||
placeholder = 'email@example.com',
|
||||
required = false,
|
||||
disabled = false,
|
||||
class: className = '',
|
||||
id = 'email',
|
||||
onvaluechange,
|
||||
onerrorchange,
|
||||
onblur
|
||||
}: Props = $props();
|
||||
|
||||
let touched = $state(false);
|
||||
|
||||
function handleInput(e: Event) {
|
||||
const target = e.target as HTMLInputElement;
|
||||
value = target.value;
|
||||
onvaluechange?.(value);
|
||||
if (touched) {
|
||||
validate(value);
|
||||
}
|
||||
}
|
||||
|
||||
function handleBlur() {
|
||||
touched = true;
|
||||
const normalized = normalizeEmail(value);
|
||||
if (normalized !== value) {
|
||||
value = normalized;
|
||||
onvaluechange?.(normalized);
|
||||
}
|
||||
validate(value);
|
||||
onblur?.();
|
||||
}
|
||||
|
||||
function validate(val: string) {
|
||||
if (!val) {
|
||||
if (required) {
|
||||
error = 'Email is required';
|
||||
onerrorchange?.(error);
|
||||
} else {
|
||||
error = '';
|
||||
onerrorchange?.('');
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!isValidEmail(val)) {
|
||||
error = 'Invalid email address';
|
||||
onerrorchange?.(error);
|
||||
} else {
|
||||
error = '';
|
||||
onerrorchange?.('');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="space-y-1.5">
|
||||
<Input
|
||||
{id}
|
||||
type="email"
|
||||
inputmode="email"
|
||||
autocomplete="email"
|
||||
{placeholder}
|
||||
{value}
|
||||
oninput={handleInput}
|
||||
onblur={handleBlur}
|
||||
{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 EmailInput from './EmailInput.svelte';
|
||||
|
||||
export { EmailInput };
|
||||
Reference in New Issue
Block a user