Merges the full git history of Inventory_frontend into the monorepo under frontend/. Removes the submodule in favor of a unified repo. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
113 lines
3.1 KiB
Vue
113 lines
3.1 KiB
Vue
<template>
|
|
<div class="form-control">
|
|
<label v-if="label" class="label" :for="inputId">
|
|
<span class="label-text">{{ label }}</span>
|
|
<span v-if="required" class="label-text-alt text-error">*</span>
|
|
</label>
|
|
<input
|
|
:id="inputId"
|
|
:value="modelValue"
|
|
type="email"
|
|
class="input input-bordered"
|
|
:class="{ 'input-error': Boolean(errorMessage) }"
|
|
:placeholder="placeholder"
|
|
:required="required"
|
|
:disabled="disabled"
|
|
:name="name"
|
|
:autocomplete="autocomplete"
|
|
:pattern="EMAIL_INPUT_PATTERN"
|
|
:aria-invalid="Boolean(errorMessage)"
|
|
:aria-describedby="describedBy"
|
|
@input="onInput"
|
|
@blur="onBlur"
|
|
@focus="(event) => emit('focus', event)"
|
|
/>
|
|
<p v-if="help" :id="helpId" class="mt-2 text-xs text-gray-500">
|
|
{{ help }}
|
|
</p>
|
|
<p v-if="errorMessage" :id="errorId" class="mt-2 text-xs text-error">
|
|
{{ errorMessage }}
|
|
</p>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed, useId } from 'vue'
|
|
import { normalizeEmail } from '~/utils/formatters/email'
|
|
import { EMAIL_INPUT_PATTERN, EMAIL_VALIDATION_ERROR, emailSchema } from '~/shared/validation/email'
|
|
|
|
type Emits = {
|
|
(event: 'update:modelValue', value: string): void
|
|
(event: 'blur', value: FocusEvent): void
|
|
(event: 'focus', value: FocusEvent): void
|
|
}
|
|
|
|
const emit = defineEmits<Emits>()
|
|
|
|
const props = withDefaults(
|
|
defineProps<{
|
|
modelValue: string
|
|
label?: string
|
|
required?: boolean
|
|
error?: string | null
|
|
help?: string | null
|
|
placeholder?: string
|
|
disabled?: boolean
|
|
name?: string
|
|
id?: string
|
|
autocomplete?: string
|
|
normalizeOnBlur?: boolean
|
|
validateOnBlur?: boolean
|
|
}>(),
|
|
{
|
|
modelValue: '',
|
|
label: 'Email',
|
|
required: false,
|
|
error: null,
|
|
help: null,
|
|
placeholder: 'ex: contact@example.com',
|
|
disabled: false,
|
|
name: undefined,
|
|
id: undefined,
|
|
autocomplete: 'email',
|
|
normalizeOnBlur: false,
|
|
validateOnBlur: false,
|
|
}
|
|
)
|
|
|
|
const fallbackId = useId()
|
|
const inputId = computed(() => props.id || fallbackId)
|
|
const helpId = computed(() => (props.help ? `${inputId.value}-help` : undefined))
|
|
const errorMessage = computed(() => props.error || null)
|
|
const errorId = computed(() => (errorMessage.value ? `${inputId.value}-error` : undefined))
|
|
const describedBy = computed(() => [helpId.value, errorId.value].filter(Boolean).join(' ') || undefined)
|
|
|
|
const onInput = (event: Event) => {
|
|
const target = event.target as HTMLInputElement
|
|
emit('update:modelValue', target.value)
|
|
}
|
|
|
|
const onBlur = (event: FocusEvent) => {
|
|
const target = event.target as HTMLInputElement
|
|
|
|
if (props.normalizeOnBlur) {
|
|
const normalized = normalizeEmail(target.value)
|
|
if (normalized !== target.value) {
|
|
target.value = normalized
|
|
emit('update:modelValue', normalized)
|
|
}
|
|
}
|
|
|
|
if (props.validateOnBlur && !errorMessage.value) {
|
|
const validation = emailSchema.validate(target.value)
|
|
if (!validation.valid) {
|
|
target.setCustomValidity(EMAIL_VALIDATION_ERROR)
|
|
} else {
|
|
target.setCustomValidity('')
|
|
}
|
|
}
|
|
|
|
emit('blur', event)
|
|
}
|
|
</script>
|