feat(ui) : état readonly visuel sur les inputs floating-label

Applique le traitement readonly canonique (isReadonly, shouldFloatLabel,
mergedInputClass sans grow-height, bordure noire fixe, sans focus:border-m-primary,
mergedLabelClass sans peer-focus, iconStateClass sans isFocused) sur les 6 composants
InputText, InputEmail, InputAmount, InputAutocomplete, InputPassword et InputTextArea.
L'œil de InputPassword reste cliquable en readonly. Tests TDD ajoutés (3 cas par fichier).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-03 15:18:20 +02:00
parent 289ff036d2
commit e622380916
12 changed files with 246 additions and 39 deletions
+13 -6
View File
@@ -8,14 +8,14 @@
:autocomplete="autocomplete"
class="floating-input peer w-full border bg-white pl-3 pr-3 py-1 outline-none placeholder:text-transparent overflow-auto"
:class="[
isFilled ? 'border-black' : 'border-m-muted',
isReadonly ? 'border-black' : (isFilled ? 'border-black' : 'border-m-muted'),
disabled ? 'cursor-not-allowed text-black/60 border-m-muted' : 'cursor-text',
hasError
? 'border-m-danger focus:border-m-danger'
: hasSuccess
? 'border-m-success focus:border-m-success'
: 'focus:border-m-primary',
isFocused ? 'textarea-scrollbar-primary' : '',
: isReadonly ? '' : 'focus:border-m-primary',
isReadonly ? '' : (isFocused ? 'textarea-scrollbar-primary' : ''),
textInput,
showCounterComputed ? 'pb-6' : '',
rounded,
@@ -47,7 +47,9 @@
? 'text-m-success'
: disabled
? 'text-m-muted'
: isFocused ? 'text-m-primary' : shouldFloatLabel ? 'text-black' : 'text-m-muted',
: isReadonly
? (isFilled ? 'text-black' : 'text-m-muted')
: (isFocused ? 'text-m-primary' : shouldFloatLabel ? 'text-black' : 'text-m-muted'),
textLabel,
]"
>
@@ -153,9 +155,15 @@ const isFocused = ref(false)
const inputId = computed(() => props.id?.toString() || `malio-input-textarea-${generatedId}`)
const isControlled = computed(() => props.modelValue !== undefined)
const currentValue = computed(() => (isControlled.value ? (props.modelValue ?? '') : localValue.value))
const shouldFloatLabel = computed(() => isFocused.value || currentValue.value.length > 0)
const hasError = computed(() => !!props.error)
const hasSuccess = computed(() => !!props.success && !hasError.value)
const isFilled = computed(() => currentValue.value.trim().length > 0)
const isReadonly = computed(() => props.readonly && !props.disabled)
const shouldFloatLabel = computed(() =>
isReadonly.value
? isFilled.value
: isFocused.value || currentValue.value.length > 0,
)
const rowsCount = computed(() => Math.max(1, Number(props.size || 3)))
const currentLength = computed(() => (currentValue.value ?? '').length)
const showCounterComputed = computed(() =>
@@ -169,7 +177,6 @@ const textareaStyle = computed(() => ({
minHeight: toCssSize(props.minResizeHeight),
maxHeight: toCssSize(props.maxResizeHeight),
}))
const isFilled = computed(() => currentValue.value.trim().length > 0)
const describedBy = computed(() =>
(hasError.value || hasSuccess.value || !!props.hint) ? `${inputId.value}-describedby` : undefined,
)