fix: readonly component style + TabList + required component (#61)
Release / release (push) Successful in 1m24s

| Numéro du ticket | Titre du ticket |
|------------------|-----------------|
|                  |                 |

## Description de la PR

## Modification du .env

## Check list

- [ ] Pas de régression
- [ ] TU/TI/TF rédigée
- [ ] TU/TI/TF OK
- [ ] CHANGELOG modifié

---------

Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
Co-authored-by: matthieu <matthieu@yuno.malio.fr>
Reviewed-on: #61
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
This commit was merged in pull request #61.
This commit is contained in:
2026-06-04 06:45:24 +00:00
committed by Autin
parent b55050b2ad
commit 9ff3e83c03
58 changed files with 3192 additions and 167 deletions
+38 -13
View File
@@ -9,6 +9,7 @@
:accept="accept"
class="hidden"
:disabled="disabled"
:required="required"
@change="onFileChange"
>
@@ -19,6 +20,7 @@
:value="currentDisplayValue"
:readonly="true"
:aria-invalid="!!error"
:aria-required="required || undefined"
:aria-describedby="describedBy"
v-bind="attrs"
placeholder="_"
@@ -33,7 +35,7 @@
:for="inputId"
:class="mergedLabelClass"
>
{{ label }}
{{ label }}<MalioRequiredMark v-if="required" />
</label>
<IconifyIcon
@@ -50,6 +52,7 @@
</div>
<p
v-if="reserveMessageSpace || hint || error || success"
:id="`${inputId}-describedby`"
:class="[
hasError
@@ -57,7 +60,8 @@
: hasSuccess
? 'text-m-success'
: 'text-m-muted',
'mt-1 text-xs ml-[2px] min-h-[1rem]',
'mt-1 text-xs ml-[2px]',
reserveMessageSpace ? 'min-h-[1rem]' : '',
]"
>
{{ hint || error || success }}
@@ -70,6 +74,7 @@
import {computed, ref, useAttrs, useId} from 'vue'
import { Icon as IconifyIcon } from '@iconify/vue'
import {twMerge} from 'tailwind-merge'
import MalioRequiredMark from '../shared/RequiredMark.vue'
defineOptions({name: 'MalioInputUpload', inheritAttrs: false})
@@ -82,11 +87,14 @@ const props = withDefaults(
labelClass?: string
groupClass?: string
disabled?: boolean
readonly?: boolean
hint?: string
error?: string
success?: string
displayIcon?: boolean
accept?: string
required?: boolean
reserveMessageSpace?: boolean
}>(),
{
id: '',
@@ -96,11 +104,14 @@ const props = withDefaults(
labelClass: '',
groupClass: '',
disabled: false,
readonly: false,
hint: '',
error: '',
success: '',
displayIcon: true,
accept: '',
required: false,
reserveMessageSpace: true,
},
)
@@ -113,10 +124,16 @@ const fileInputRef = ref<HTMLInputElement | null>(null)
const inputId = computed(() => props.id?.toString() || `malio-input-upload-${generatedId}`)
const isControlled = computed(() => props.modelValue !== undefined)
const currentDisplayValue = computed(() => (isControlled.value ? (props.modelValue ?? '') : localValue.value))
const shouldFloatLabel = computed(() => isFocused.value || currentDisplayValue.value.length > 0)
const hasError = computed(() => !!props.error)
const hasSuccess = computed(() => !!props.success)
const isFilled = computed(() => currentDisplayValue.value.trim().length > 0)
const disabled = computed(() => props.disabled)
const isReadonly = computed(() => props.readonly && !props.disabled)
const shouldFloatLabel = computed(() =>
isReadonly.value
? isFilled.value
: isFocused.value || currentDisplayValue.value.length > 0,
)
const mergedGroupClass = computed(() =>
twMerge(
'relative flex h-12 w-full items-center',
@@ -125,16 +142,21 @@ const mergedGroupClass = computed(() =>
)
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 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-pointer',
'floating-input peer min-h-[40px] w-full border bg-white pl-3 pr-3 py-1 outline-none placeholder:text-transparent text-lg rounded-md cursor-pointer',
isReadonly.value ? '' : 'grow-height',
isReadonly.value
? 'border-black'
: isFilled.value ? 'border-black' : 'border-m-muted',
disabled.value ? 'text-black/60 [&:not(:placeholder-shown)]:border-m-muted border-m-muted' : '',
hasError.value
? 'border-m-danger focus:border-m-danger [&:not(:placeholder-shown)]:border-m-danger'
: hasSuccess.value
? 'border-m-success focus:border-m-success [&:not(:placeholder-shown)]:border-m-success'
: 'focus:border-m-primary',
: isReadonly.value ? '' : 'focus:border-m-primary',
props.displayIcon ? '!pr-10' : '',
'focus:pl-[11px]',
isReadonly.value ? '' : 'focus:pl-[11px]',
isReadonly.value ? 'cursor-default' : '',
disabled.value ? 'cursor-not-allowed' : '',
props.inputClass,
),
)
@@ -142,14 +164,18 @@ const mergedLabelClass = computed(() =>
twMerge(
'floating-label absolute top-2 mt-[5px] inline-block origin-left transition-transform duration-150 font-medium text-sm',
'left-3',
shouldFloatLabel.value ? '-translate-y-[1.25rem] peer-focus:-translate-y-[1.55rem] scale-90' : '',
shouldFloatLabel.value
? `-translate-y-[1.25rem] scale-90${isReadonly.value ? '' : ' peer-focus:-translate-y-[1.55rem]'}`
: '',
hasError.value
? 'text-m-danger'
: hasSuccess.value
? 'text-m-success'
: disabled.value
? 'text-m-muted'
: 'peer-placeholder-shown:text-m-muted peer-[&:not(:placeholder-shown):not(:focus)]:text-black peer-focus:text-m-primary',
: isReadonly.value
? isFilled.value ? 'text-black' : 'text-m-muted'
: 'peer-placeholder-shown:text-m-muted peer-[&:not(:placeholder-shown):not(:focus)]:text-black peer-focus:text-m-primary',
props.labelClass,
),
)
@@ -168,7 +194,7 @@ const emit = defineEmits<{
}>()
const openFilePicker = () => {
if (props.disabled) return
if (props.disabled || props.readonly) return
fileInputRef.value?.click()
}
@@ -185,12 +211,11 @@ const onFileChange = (event: Event) => {
}
}
const disabled = computed(() => props.disabled)
const iconStateClass = computed(() => {
if (hasError.value) return 'text-m-danger'
if (hasSuccess.value) return 'text-m-success'
if (disabled.value) return 'text-m-muted'
if (isReadonly.value) return isFilled.value ? 'text-black' : 'text-m-muted'
if (isFocused.value) return 'text-m-primary'
if (isFilled.value) return 'text-black'
return 'text-m-muted'