feat : ajouts du composant input text area et fix sur le input text

This commit is contained in:
2026-02-27 09:24:37 +01:00
parent 0a3cf50576
commit 594708e71c
8 changed files with 767 additions and 85 deletions

View File

@@ -2,7 +2,7 @@
<div class="grid grid-cols-1 items-start gap-6 md:grid-cols-2">
<div class="rounded-lg border p-4">
<h2 class="mb-4 text-xl font-bold">Simple</h2>
<MalioInputText v-model="simpleValue"/>
<MalioInputText/>
</div>
<div class="rounded-lg border p-4">
@@ -16,7 +16,7 @@
</div>
<div class="rounded-lg border p-4">
<h2 class="mb-4 text-xl font-bold">Avec icône</h2>
<h2 class="mb-4 text-xl font-bold">Avec icône à droite</h2>
<MalioInputText
v-model="searchValue"
label="Recherche"
@@ -25,6 +25,16 @@
/>
</div>
<div class="rounded-lg border p-4">
<h2 class="mb-4 text-xl font-bold">Avec icône à gauche</h2>
<MalioInputText
label="Recherche"
icon-name="mdi:magnify"
icon-size="20"
icon-position="left"
/>
</div>
<div class="rounded-lg border p-4">
<h2 class="mb-4 text-xl font-bold">Désactivé</h2>
<MalioInputText
@@ -142,7 +152,7 @@
</template>
<script setup lang="ts">
const simpleValue = ref('')
import { computed, ref } from 'vue'
const nameValue = ref('')
const searchValue = ref('')
const codeValue = ref('')

View File

@@ -0,0 +1,104 @@
<template>
<div class="grid grid-cols-1 items-start gap-6 p-4 md:grid-cols-2">
<div class="rounded-lg border p-4">
<h2 class="mb-4 text-xl font-bold">Simple</h2>
<MalioInputTextArea/>
</div>
<div class="rounded-lg border p-4">
<h2 class="mb-4 text-xl font-bold">Avec label + hint</h2>
<MalioInputTextArea
v-model="hintValue"
label="Description"
hint="Ajoutez un contexte clair"
:size="4"
/>
</div>
<div class="rounded-lg border p-4">
<h2 class="mb-4 text-xl font-bold">Avec icône</h2>
<MalioInputTextArea
v-model="iconValue"
label="Commentaire"
icon-name="mdi:comment-text-outline"
:size="3"
/>
</div>
<div class="rounded-lg border p-4">
<h2 class="mb-4 text-xl font-bold">Erreur / Succès</h2>
<div class="space-y-4">
<MalioInputTextArea
v-model="errorValue"
label="Message"
error="Le message est trop court"
:size="3"
/>
<MalioInputTextArea
v-model="successValue"
label="Message"
success="Message valide"
:size="3"
/>
</div>
</div>
<div class="rounded-lg border p-4">
<h2 class="mb-4 text-xl font-bold">Readonly / Disabled</h2>
<div class="space-y-4">
<MalioInputTextArea
model-value="Contenu en lecture seule"
label="Readonly"
readonly
:size="3"
/>
<MalioInputTextArea
model-value="Champ indisponible"
label="Disabled"
disabled
:size="3"
/>
</div>
</div>
<div class="rounded-lg border p-4">
<h2 class="mb-4 text-xl font-bold">Resize avec limites</h2>
<MalioInputTextArea
v-model="resizeValue"
label="Notes"
resize="both"
:size="4"
:min-resize-width="300"
:max-resize-width="700"
:min-resize-height="120"
:max-resize-height="280"
hint="Resize limite en largeur et hauteur"
/>
</div>
<div class="rounded-lg border p-4">
<h2 class="mb-4 text-xl font-bold">Compteur (interne au composant)</h2>
<MalioInputTextArea
v-model="counterValue"
label="Message"
:size="5"
:max-length="120"
:show-counter="true"
hint="Le compteur est en bas a gauche"
/>
</div>
</div>
</template>
<script setup lang="ts">
import {ref} from 'vue'
import MalioInputTextArea from '../../../app/components/malio/InputTextArea.vue'
const hintValue = ref('')
const iconValue = ref('')
const errorValue = ref('abc')
const successValue = ref('Contenu ok')
const resizeValue = ref('Vous pouvez redimensionner ce champ.')
const counterValue = ref('')
</script>

View File

@@ -15,7 +15,7 @@
:key="item.name"
type="button"
class="rounded px-3 py-2 text-left text-black font-bold hover:bg-m-primary hover:text-white"
:class="selectedName === item.name ? 'bg-m-secondary text-white ' : ''"
:class="selectedName === item.name ? 'bg-m-secondary text-white' : ''"
@click="selectOrToggle(item.name)"
>
{{ item.label }}
@@ -32,10 +32,13 @@
v-else-if="selectedName"
class="text-gray-700"
>
Page de demo introuvable: <code>.playground/pages/composant/{{ selectedDemoFileName }}.vue</code>
Page de demo introuvable:
<code>.playground/pages/composant/{{ selectedDemoFileName }}.vue</code>
</p>
<div v-else>
<h1 class="text-2xl font-semibold text-gray-900">Playground composants</h1>
<h1 class="text-2xl font-semibold text-gray-900">
Playground composants
</h1>
<p class="mt-2 text-gray-600">
Selectionne un composant dans la liste pour afficher sa page de demo.
</p>
@@ -45,6 +48,8 @@
</template>
<script setup lang="ts">
import { computed, ref, watch, shallowRef } from 'vue'
type LoadedModule = {
default: unknown
}
@@ -52,40 +57,42 @@ type LoadedModule = {
type Item = {
name: string
label: string
demoComponent?: unknown
}
const componentModules = import.meta.glob('../../app/components/malio/*.vue', { eager: true }) as Record<string, LoadedModule>
const demoModules = import.meta.glob('./composant/*.vue', { eager: true }) as Record<string, LoadedModule>
const componentModules = import.meta.glob('../../app/components/malio/*.vue')
const demoModules = import.meta.glob('./composant/*.vue')
const demoByName = Object.fromEntries(
Object.entries(demoModules).map(([file, mod]) => {
const demoByName: Record<string, () => Promise<LoadedModule>> =
Object.fromEntries(
Object.entries(demoModules).map(([file, loader]) => {
const name = file.split('/').pop()?.replace('.vue', '') ?? ''
return [name.toLowerCase(), loader as () => Promise<LoadedModule>]
}),
)
const items = computed<Item[]>(() =>
Object.keys(componentModules).map((file) => {
const name = file.split('/').pop()?.replace('.vue', '') ?? ''
return [name.toLowerCase(), mod.default]
}),
)
const items = computed(() =>
Object.entries(componentModules).map(([file]) => {
const name = file.split('/').pop()?.replace('.vue', '') ?? ''
return {
name,
label: name,
demoComponent: demoByName[name.toLowerCase()],
}
}) as Item[],
}),
)
const selectedName = ref('')
const hasInitializedSelection = ref(false)
watchEffect(() => {
if (!hasInitializedSelection.value && items.value.length > 0) {
selectedName.value = items.value[0].name
hasInitializedSelection.value = true
}
})
watch(
items,
(val) => {
if (!hasInitializedSelection.value && val.length > 0) {
selectedName.value = val[0].name
hasInitializedSelection.value = true
}
},
{ immediate: true },
)
function selectOrToggle(name: string) {
selectedName.value = selectedName.value === name ? '' : name
@@ -95,9 +102,23 @@ function clearSelection() {
selectedName.value = ''
}
const selectedDemoComponent = computed(() =>
items.value.find((item) => item.name === selectedName.value)?.demoComponent,
)
const selectedDemoComponent = shallowRef<unknown>(null)
watch(selectedName, async (name) => {
if (!name) {
selectedDemoComponent.value = null
return
}
const loader = demoByName[name.toLowerCase()]
if (!loader) {
selectedDemoComponent.value = null
return
}
const mod = await loader()
selectedDemoComponent.value = mod.default
})
const selectedDemoFileName = computed(() => {
const name = selectedName.value

View File

@@ -8,37 +8,42 @@
v-maska="mask"
:name="name"
:autocomplete="autocomplete"
class="floating-input grow-height peer min-h-[40px] w-full border bg-white pl-3 pr-3 py-1 outline-none focus:border-2"
class="floating-input grow-height peer min-h-[40px] w-full border bg-white pl-3 pr-3 py-1 outline-none placeholder:text-transparent focus:border-2 "
:class="[
disabled ? 'cursor-not-allowed text-black/60 [&:not(:placeholder-shown)]:border-m-muted border-m-muted' : 'cursor-text',
hasError
? 'border-m-error focus:border-m-error focus:pl-[11px] [&:not(:placeholder-shown)]:border-m-error'
? 'border-m-error focus:border-m-error [&:not(:placeholder-shown)]:border-m-error'
: hasSuccess
? 'border-m-success focus:border-m-success focus:pl-[11px] [&:not(:placeholder-shown)]:border-m-success'
: 'border-m-muted focus:border-m-primary focus:pl-[11px]',
? 'border-m-success focus:border-m-success [&:not(:placeholder-shown)]:border-m-success'
: 'border-m-muted focus:border-m-primary',
textInput,
iconInputPaddingClass,
focusPaddingClass,
rounded,
]"
:required="required"
:maxlength="maxLength"
:minlength="minLength"
:disabled="disabled"
:value="modelValue ?? ''"
:value="currentValue"
:readonly="readonly"
:aria-invalid="!!error"
:aria-describedby="describedBy"
v-bind="attrs"
placeholder=" "
placeholder="_"
type="text"
@input="onInput"
@focus="isFocused = true"
@blur="isFocused = false"
>
<label
v-if="label"
:for="inputId"
class="floating-label absolute left-3 top-2 mt-1 origin-left transition-transform duration-150 font-medium"
class="floating-label absolute top-2 mt-1 inline-block origin-left transition-transform duration-150 font-medium"
:class="[
labelPositionClass,
shouldFloatLabel ? '-translate-y-[1.15rem] scale-90' : '',
disabled ? 'peer-[&:not(:placeholder-shown):not(:focus)]:text-black/60' : '',
hasError
? 'text-m-error'
@@ -59,7 +64,7 @@
data-test="icon"
:class="[
hasError ? 'text-m-error' : hasSuccess ? 'text-m-success' : '',
'pointer-events-none absolute right-2 top-1/2 -translate-y-1/2',
iconPositionClass,
iconColor,
]"
/>
@@ -85,7 +90,7 @@
import type {MaskInputOptions} from 'maska'
import {vMaska} from 'maska/vue'
import {computed, useAttrs, useId} from 'vue'
import {computed, ref, useAttrs, useId} from 'vue'
import { Icon as IconifyIcon } from '@iconify/vue'
defineOptions({name: 'MalioInputText', inheritAttrs: false})
@@ -110,6 +115,7 @@ const props = withDefaults(
error?: string
success?: string
iconName?: string
iconPosition?: 'left' | 'right'
rounded?: string
iconSize?: string | number
iconColor?: string
@@ -118,9 +124,10 @@ const props = withDefaults(
{
id: '',
name: '',
autocomplete: '',
autocomplete: 'off',
modelValue: undefined,
iconName: '',
iconPosition: 'right',
label: '',
minWidth: 'w-96',
maxWidth: '',
@@ -143,8 +150,13 @@ const props = withDefaults(
const attrs = useAttrs()
const generatedId = useId()
const localValue = ref('')
const isFocused = ref(false)
const inputId = computed(() => props.id?.toString() || `malio-input-text-${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)
@@ -162,20 +174,34 @@ const emit = defineEmits<{
const onInput = (event: Event) => {
const target = event.target as HTMLInputElement
if (!isControlled.value) {
localValue.value = target.value
}
emit('update:modelValue', target.value)
}
const iconInputPaddingClass = computed(() => {
return props.iconName ? 'pr-10' : ''
if (!props.iconName) return ''
return props.iconPosition === 'left' ? '!pl-11 !pr-3' : '!pl-3'
})
const labelPositionClass = computed(() => {
if (props.iconName && props.iconPosition === 'left') return 'left-8'
return 'left-3'
})
const focusPaddingClass = computed(() => {
if (props.iconName && props.iconPosition === 'left') return 'focus:!pl-11'
return 'focus:pl-[11px]'
})
const iconPositionClass = computed(() => {
const sideClass = props.iconPosition === 'left' ? 'left-2' : 'right-2'
return `pointer-events-none absolute ${sideClass} top-1/2 -translate-y-1/2`
})
</script>
<style scoped>
.floating-input:focus + label,
.floating-input:not(:placeholder-shown) + label {
transform: translateY(-1.15rem) scale(0.9);
}
.floating-label {
background: white;
padding: 0 0.25rem;

View File

@@ -0,0 +1,185 @@
<template>
<div
class="relative mt-4 w-full"
>
<textarea
:id="inputId"
:name="name"
:autocomplete="autocomplete"
class="floating-input peer w-full border bg-white pl-3 pr-3 py-1 outline-none placeholder:text-transparent focus:border-2 overflow-auto"
:class="[
disabled ? 'cursor-not-allowed text-black/60 border-m-muted' : 'cursor-text',
hasError
? 'border-m-error focus:border-m-error focus:pl-[11px]'
: hasSuccess
? 'border-m-success focus:border-m-success focus:pl-[11px]'
: 'border-m-muted focus:border-m-primary focus:pl-[11px]',
textInput,
showCounterComputed ? 'pb-6' : '',
rounded,
]"
:required="required"
:maxlength="maxLength"
:rows="rowsCount"
:disabled="disabled"
:value="currentValue"
:readonly="readonly"
:aria-invalid="hasError"
:aria-describedby="describedBy"
:style="textareaStyle"
v-bind="attrs"
placeholder="_"
@input="onInput"
@focus="isFocused = true"
@blur="isFocused = false"
/>
<label
v-if="label"
:for="inputId"
class="floating-label absolute left-3 top-2 mt-1 inline-block origin-left transition-transform duration-150 font-medium"
:class="[
shouldFloatLabel ? '-translate-y-[1.15rem] scale-90' : '',
disabled ? 'text-black/60' : '',
hasError
? 'text-m-error'
: hasSuccess
? 'text-m-success'
: isFocused ? 'text-m-primary' : shouldFloatLabel ? 'text-black' : 'text-m-muted',
textLabel,
]"
>
{{ label }}
</label>
<span
v-if="showCounterComputed"
class="pointer-events-none absolute bottom-2 left-3 text-xs text-m-muted"
>
{{ currentLength }}/{{ maxLength }}
</span>
</div>
<div
v-if="hasError || hasSuccess || hint"
class="mt-1 flex items-center justify-between gap-2 text-xs"
>
<p
:id="`${inputId}-describedby`"
:class="[
hasError
? 'text-m-error'
: hasSuccess
? 'text-m-success'
: 'text-m-muted',
'ml-[2px]',
]"
>
{{ error || success || hint }}
</p>
</div>
</template>
<script setup lang="ts">
import {computed, ref, useAttrs, useId} from 'vue'
defineOptions({name: 'MalioInputTextArea', inheritAttrs: false})
const props = withDefaults(
defineProps<{
id?: string
label?: string
name?: string
autocomplete?: string
modelValue?: string | null | undefined
size?: number | string
textInput?: string
textLabel?: string
resize?: 'none' | 'both' | 'horizontal' | 'vertical'
minResizeWidth?: number
maxResizeWidth?: number
minResizeHeight?: number
maxResizeHeight?: number
required?: boolean
maxLength?: number
showCounter?: boolean
disabled?: boolean
readonly?: boolean
hint?: string
error?: string
success?: string
rounded?: string
}>(),
{
id: '',
name: '',
autocomplete: 'off',
modelValue: undefined,
label: '',
size: 2,
textInput: 'text-lg',
required: false,
maxLength: 800,
showCounter: false,
readonly: false,
textLabel: 'text-sm',
disabled: false,
rounded: 'rounded-md',
hint: '',
error: '',
success: '',
resize: 'both',
minResizeWidth: 280,
maxResizeWidth: 640,
minResizeHeight: 40,
maxResizeHeight: 320,
},
)
const attrs = useAttrs()
const generatedId = useId()
const localValue = ref('')
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 rowsCount = computed(() => Math.max(1, Number(props.size || 3)))
const currentLength = computed(() => (currentValue.value ?? '').length)
const showCounterComputed = computed(() =>
props.showCounter && Number(props.maxLength) > 0
)
const toCssSize = (value: number | string) => (typeof value === 'number' ? `${value}px` : value)
const textareaStyle = computed(() => ({
resize: props.resize,
minWidth: toCssSize(props.minResizeWidth),
maxWidth: toCssSize(props.maxResizeWidth),
minHeight: toCssSize(props.minResizeHeight),
maxHeight: toCssSize(props.maxResizeHeight),
}))
const describedBy = computed(() =>
(hasError.value || hasSuccess.value || !!props.hint) ? `${inputId.value}-describedby` : undefined,
)
const emit = defineEmits<{
(event: 'update:modelValue', value: string): void
}>()
const onInput = (event: Event) => {
const target = event.target as HTMLTextAreaElement
if (!isControlled.value) {
localValue.value = target.value
}
emit('update:modelValue', target.value)
}
</script>
<style scoped>
.floating-label {
background: white;
padding: 0 0.25rem;
}
</style>

View File

@@ -1,56 +1,200 @@
<template>
<Story title="Input/Text">
<Story
title="Input/Text"
>
<MalioInputText/>
</Story>
</template>
<docs lang="md">
# Input Text
# MalioInputText
## Liste des props
Composant input texte avec masque optionnel (maska), label flottant,
états visuels (erreur / succès) et icône configurable.
Le composant Input Text permet de saisir du texte. Il peut afficher des messages d'erreur, de succès ou d'information.
On peut lui passer plusieurs props pour personnaliser son comportement et son apparence.
- id: Identifiant HTML du champ. Si non fourni, un id est généré automatiquement.
- label: Texte du label affiché au-dessus du champ (floating label).
- name: Attribut name de linput.
- autocomplete: Attribut autocomplete de linput.
- modelValue: Valeur du champ (utilisée avec v-model) ou si valeur brut mettre des " ".
- minWidth: Classe utilitaire pour la largeur minimale du conteneur. Classe Tailwind de largeur (ex: w-64, w-full).
- maxWidth: Classe utilitaire pour la largeur maximale du conteneur. Classe Tailwind de largeur maximale.
- textInput: Classe(s) de style du texte de linput. Classe(s) Tailwind de couleur ou de typographie (ex : text-gray-700, text-sm).
- textLabel: Classe(s) de style du texte du label. Classe(s) Tailwind de couleur ou de typographie (ex : text-gray-700, text-sm).
- required: Rend le champ obligatoire (required).
- maxLength: Nombre de caractère maximal autorisé.
- minLength: Nombre de caractère minimal autorisé.
- disabled: Désactive le champ et applique le style désactivé.
- readonly: Met le champ en lecture seule.
- hint: Message informatif affiché sous le champ.
- error: Message derreur affiché sous le champ. Active le style erreur.
- success: Message de succès affiché sous le champ. Active le style succès.
- iconName: Nom de licône affichée à droite dans le champ.
- rounded: Classe utilitaire pour le rayon des coins ( rounded- ).
- iconSize: Taille de licône. (ex : 24, 26, 85 ,99, ... ).
- iconColor: Classe(s) personnalisée(s) de couleur pour licône ( text- ).
- mask: Masque de saisie pour formater la valeur :
- \# : chiffre
- A : lettre majuscule
- a : lettre minuscule
- \* : chiffre ou lettre
------------------------------------------------------------------------
Événement émis :
## Props détaillées
- update:modelValue: Émis à chaque saisie pour mettre à jour v-model.
### id
Règles daffichage des messages :
- Type: string
- Description: Identifiant HTML de linput.
- Comportement: Si non fourni, un id unique est généré
automatiquement.
Priorité daffichage :
1) error
2) success
3) hint
### label
- Type: string
- Description: Texte affiché comme label flottant.
- Comportement: Si absent, aucun label nest rendu.
### name
- Type: string
- Description: Attribut name de linput (utile pour les formulaires).
### autocomplete
- Type: string
- Description: Active ou configure lautocomplétion navigateur.
- Défaut: vide
### modelValue
- Type: string | null | undefined
- Description: Valeur contrôlée du composant.
- Comportement:
- Si défini composant contrôlé (v-model).
- Sinon gestion interne de létat.
### mask
- Type: string | undefined
- Description: Masque appliqué via la directive maska.
- Comportement: Formate la saisie selon les tokens définis.
------------------------------------------------------------------------
## Apparence & Style
### textInput
- Type: string
- Description: Classes CSS appliquées à linput (taille de texte,
etc.).
### textLabel
- Type: string
- Description: Classes CSS appliquées au label.
### rounded
- Type: string
- Description: Classe Tailwind pour le border-radius.
- Défaut: rounded-md
### minWidth / maxWidth
- Type: string
- Description: Classes utilitaires Tailwind pour contraindre la
largeur.
------------------------------------------------------------------------
## Validation & Contraintes
### required
- Type: boolean
- Description: Ajoute lattribut HTML required.
### maxLength
- Type: number | string
- Description: Longueur maximale autorisée.
### minLength
- Type: number | string
- Description: Longueur minimale autorisée.
### disabled
- Type: boolean
- Description: Désactive complètement le champ.
### readonly
- Type: boolean
- Description: Rend le champ non modifiable mais focusable.
------------------------------------------------------------------------
## États & Messages
### hint
- Type: string
- Description: Message daide affiché sous le champ.
### error
- Type: string
- Description: Message derreur.
- Effet:
- Active létat visuel erreur.
- aria-invalid=true
- Prioritaire sur success et hint.
### success
- Type: string
- Description: Message de succès.
- Effet:
- Actif uniquement si error est absent.
------------------------------------------------------------------------
## Icône
### iconName
- Type: string
- Description: Nom de licône (ex: mdi:magnify).
### iconSize
- Type: string | number
- Description: Taille de licône.
### iconColor
- Type: string
- Description: Couleur de licône.
------------------------------------------------------------------------
## Comportement de validation
- Aucune validation interne.
- Les états sont pilotés uniquement par les props.
## Priorité visuelle
1. error
2. success
3. neutre
------------------------------------------------------------------------
## Tokens de masque
- \# : chiffre
- A : lettre majuscule
- a : lettre minuscule
- \* : chiffre ou lettre
------------------------------------------------------------------------
## Accessibilité
- aria-invalid est activé si error existe.
- aria-describedby référence dynamiquement le message affiché.
- Fonctionne avec ou sans v-model.
------------------------------------------------------------------------
## Events
### update:modelValue
- Émis à chaque modification de linput.
- Permet lutilisation avec v-model.
</docs>
<script setup lang="ts">
import MalioInputText from '../components/malio/InputText.vue'
</script>

View File

@@ -0,0 +1,192 @@
<template>
<Story
title="Input/TextArea"
>
<MalioInputTextArea/>
</Story>
</template>
<docs lang="md">
# MalioInputTextArea
Composant textarea avec label flottant, états visuels (erreur / succès),
gestion du redimensionnement et compteur optionnel.
------------------------------------------------------------------------
## Props détaillées
### id
- Type: string
- Description: Identifiant HTML du textarea.
- Comportement: Si non fourni, un id unique est généré
automatiquement.
### label
- Type: string
- Description: Texte affiché comme label flottant.
- Comportement: Si absent, aucun label nest rendu.
### name
- Type: string
- Description: Attribut name du textarea (utile pour les formulaires).
### autocomplete
- Type: string
- Description: Active ou configure lautocomplétion navigateur.
### modelValue
- Type: string | null | undefined
- Description: Valeur contrôlée du composant.
- Comportement:
- Si défini composant contrôlé (v-model).
- Sinon gestion interne de létat.
------------------------------------------------------------------------
## Apparence & Style
### textInput
- Type: string
- Description: Classes CSS appliquées au textarea (taille de texte,
etc.).
- Défaut: text-lg
### textLabel
- Type: string
- Description: Classes CSS appliquées au label.
- Défaut: text-sm
### rounded
- Type: string
- Description: Classe Tailwind pour le border-radius.
- Défaut: rounded-md
------------------------------------------------------------------------
## Dimensions
### size
- Type: number | string
- Description: Nombre de lignes initiales (rows).
- Défaut: 2
### minResizeWidth / maxResizeWidth
- Type: number | string
- Description: Largeur minimale / maximale autorisée lors du
redimensionnement.
### minResizeHeight / maxResizeHeight
- Type: number | string
- Description: Hauteur minimale / maximale autorisée lors du
redimensionnement.
### resize
- Type: 'none' | 'both' | 'horizontal' | 'vertical'
- Description: Définit le comportement de redimensionnement CSS.
- Défaut: both
------------------------------------------------------------------------
## Validation & États
### required
- Type: boolean
- Description: Ajoute lattribut HTML required.
### maxLength
- Type: number | string
- Description: Longueur maximale autorisée.
- Effet: Limite la saisie et alimente le compteur.
### disabled
- Type: boolean
- Description: Désactive complètement le champ.
### readonly
- Type: boolean
- Description: Rend le champ non modifiable mais focusable.
### hint
- Type: string
- Description: Message daide affiché sous le champ.
### error
- Type: string
- Description: Message derreur.
- Effet:
- Active létat visuel erreur.
- aria-invalid=true
- Prioritaire sur success et hint.
### success
- Type: string
- Description: Message de succès.
- Effet:
- Actif uniquement si error est absent.
------------------------------------------------------------------------
## Compteur
### showCounter
- Type: boolean
- Description: Affiche le compteur x / maxLength.
- Condition:
- showCounter = true
- maxLength défini et > 0
- Position: Bas gauche du textarea.
- Mise à jour: Dynamique à chaque saisie.
------------------------------------------------------------------------
## Priorité daffichage
### Messages
1. error
2. success
3. hint
------------------------------------------------------------------------
## Accessibilité
- aria-invalid est activé si error existe.
- aria-describedby référence le message affiché.
- Fonctionne avec ou sans v-model.
------------------------------------------------------------------------
## Events
### update:modelValue
- Émis à chaque modification du textarea.
- Permet lutilisation avec v-model.
</docs>
<script setup lang="ts">
import MalioInputTextArea from '../components/malio/InputTextArea.vue'
</script>

View File

@@ -9,7 +9,7 @@ export default {
],
safelist: [
{
pattern: /(sm:|md:|lg:|xl:|2xl:)?(text|rounded|w|min-w|max-w)-.+/,
pattern: /^(?:(?:sm|md|lg|xl|2xl):)?(?:text|rounded|w|min-w|max-w)-[^\s]+$/,
},
],
theme: {