feat : ajouts du composant select

This commit is contained in:
2026-03-02 14:05:40 +01:00
parent b4ffe0d29e
commit 6e70bc4d3e
12 changed files with 1142 additions and 182 deletions

View File

@@ -1,26 +1,13 @@
<template>
<div
class="relative mt-4 flex h-12 w-full items-center"
:class="[minWidth, maxWidth]"
:class="mergedGroupClass"
>
<input
:id="inputId"
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 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 [&:not(:placeholder-shown)]:border-m-error'
: hasSuccess
? 'border-m-success focus:border-m-success [&:not(:placeholder-shown)]:border-m-success'
: 'border-m-muted focus:border-m-primary',
textInput,
iconInputPaddingClass,
focusPaddingClass,
rounded,
]"
:class="mergedInputClass"
:required="required"
:maxlength="maxLength"
:minlength="minLength"
@@ -40,18 +27,7 @@
<label
v-if="label"
:for="inputId"
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'
: hasSuccess
? 'text-m-success'
: 'peer-placeholder-shown:text-m-muted peer-[&:not(:placeholder-shown):not(:focus)]:text-black peer-focus:text-m-primary',
textLabel,
]"
:class="mergedLabelClass"
>
{{ label }}
</label>
@@ -63,9 +39,11 @@
:height="iconSize"
data-test="icon"
:class="[
hasError ? 'text-m-error' : hasSuccess ? 'text-m-success' : '',
hasError
? 'text-m-error'
: hasSuccess
? 'text-m-success' : iconColor,
iconPositionClass,
iconColor,
]"
/>
@@ -92,6 +70,7 @@ import type {MaskInputOptions} from 'maska'
import {vMaska} from 'maska/vue'
import {computed, ref, useAttrs, useId} from 'vue'
import { Icon as IconifyIcon } from '@iconify/vue'
import {twMerge} from 'tailwind-merge'
defineOptions({name: 'MalioInputText', inheritAttrs: false})
@@ -102,10 +81,9 @@ const props = withDefaults(
name?: string
autocomplete?: string
modelValue?: string | null | undefined
minWidth?: string
maxWidth?: string
textInput?: string
textLabel?: string
inputClass?: string
labelClass?: string
groupClass?: string
required?: boolean
maxLength?: number | string
minLength?: number | string
@@ -116,7 +94,7 @@ const props = withDefaults(
success?: string
iconName?: string
iconPosition?: 'left' | 'right'
rounded?: string
iconSize?: string | number
iconColor?: string
mask?: string | MaskInputOptions
@@ -129,16 +107,14 @@ const props = withDefaults(
iconName: '',
iconPosition: 'right',
label: '',
minWidth: 'w-96',
maxWidth: '',
textInput: 'text-lg',
inputClass: '',
labelClass: '',
groupClass: '',
required: false,
maxLength: undefined,
minLength: undefined,
readonly: false,
textLabel: 'text-sm',
disabled: false,
rounded: 'rounded-md',
hint: '',
error: '',
success: '',
@@ -159,6 +135,42 @@ const currentValue = computed(() => (isControlled.value ? (props.modelValue ?? '
const shouldFloatLabel = computed(() => isFocused.value || currentValue.value.length > 0)
const hasError = computed(() => !!props.error)
const hasSuccess = computed(() => !!props.success)
const isFilled = computed(() => currentValue.value.trim().length > 0)
const mergedGroupClass = computed(() =>
twMerge(
'relative mt-4 flex h-12 w-full items-center',
props.groupClass,
),
)
const mergedInputClass = computed(() =>
twMerge(
'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 text-lg rounded-md',
isFilled.value ? 'border-black' : 'border-m-muted',
disabled.value ? 'cursor-not-allowed text-black/60 [&:not(:placeholder-shown)]:border-m-muted border-m-muted' : 'cursor-text',
hasError.value
? 'border-m-error focus:border-m-error [&:not(:placeholder-shown)]:border-m-error'
: hasSuccess.value
? 'border-m-success focus:border-m-success [&:not(:placeholder-shown)]:border-m-success'
: 'focus:border-m-primary',
props.inputClass,
iconInputPaddingClass.value,
focusPaddingClass.value,
),
)
const mergedLabelClass = computed(() =>
twMerge(
'floating-label absolute top-2 mt-[5px] inline-block origin-left transition-transform duration-150 font-medium text-sm',
labelPositionClass.value,
shouldFloatLabel.value ? '-translate-y-[1.25rem] peer-focus:-translate-y-[1.55rem] scale-90' : '',
disabled.value ? 'peer-[&:not(:placeholder-shown):not(:focus)]:text-black/60' : '',
hasError.value
? 'text-m-error'
: hasSuccess.value
? 'text-m-success'
: 'peer-placeholder-shown:text-m-muted peer-[&:not(:placeholder-shown):not(:focus)]:text-black peer-focus:text-m-primary',
props.labelClass,
),
)
const describedBy = computed(() => {
const ids: string[] = []
@@ -185,6 +197,8 @@ const iconInputPaddingClass = computed(() => {
return props.iconPosition === 'left' ? '!pl-11 !pr-3' : '!pl-3'
})
const disabled = computed(() => props.disabled)
const labelPositionClass = computed(() => {
if (props.iconName && props.iconPosition === 'left') return 'left-8'
return 'left-3'