Rework CSS theme (app.css), navbar layout, dashboard page, machine detail, catalog pages, and various form/display components for better consistency and mobile responsiveness. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
114 lines
3.1 KiB
Vue
114 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="tel"
|
|
class="input input-bordered"
|
|
:class="{ 'input-error': Boolean(errorMessage) }"
|
|
:placeholder="placeholder"
|
|
:required="required"
|
|
:disabled="disabled"
|
|
:name="name"
|
|
:autocomplete="autocomplete"
|
|
:pattern="PHONE_INPUT_PATTERN"
|
|
inputmode="tel"
|
|
: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-base-content/50">
|
|
{{ 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 { formatPhone } from '~/utils/formatters/phone'
|
|
import { PHONE_INPUT_PATTERN, PHONE_VALIDATION_ERROR, phoneSchema } from '~/shared/validation/phone'
|
|
|
|
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: 'Téléphone',
|
|
required: false,
|
|
error: null,
|
|
help: null,
|
|
placeholder: 'Ex: 06 00 00 00 00',
|
|
disabled: false,
|
|
name: undefined,
|
|
id: undefined,
|
|
autocomplete: 'tel',
|
|
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 formatted = formatPhone(target.value)
|
|
if (formatted !== target.value) {
|
|
target.value = formatted
|
|
emit('update:modelValue', formatted)
|
|
}
|
|
}
|
|
|
|
if (props.validateOnBlur && !errorMessage.value) {
|
|
const validation = phoneSchema.validate(target.value)
|
|
if (!validation.valid) {
|
|
target.setCustomValidity(PHONE_VALIDATION_ERROR)
|
|
} else {
|
|
target.setCustomValidity('')
|
|
}
|
|
}
|
|
|
|
emit('blur', event)
|
|
}
|
|
</script>
|