Merge remote-tracking branch 'origin/develop' into feature/MUI-22-developper-le-composant-datatable
This commit is contained in:
@@ -82,6 +82,16 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="rounded-lg border p-4">
|
||||||
|
<h2 class="mb-4 text-xl font-bold">Peu d'elements (2)</h2>
|
||||||
|
<MalioSelect
|
||||||
|
v-model="shortListValue"
|
||||||
|
:options="shortOptions"
|
||||||
|
label="Civilite"
|
||||||
|
empty-option-label="Aucune selection"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="rounded-lg border p-4 md:col-span-2">
|
<div class="rounded-lg border p-4 md:col-span-2">
|
||||||
<h2 class="mb-4 text-xl font-bold">Liste longue</h2>
|
<h2 class="mb-4 text-xl font-bold">Liste longue</h2>
|
||||||
<MalioSelect
|
<MalioSelect
|
||||||
@@ -121,6 +131,11 @@ const options = [
|
|||||||
{label: 'Portugal', value: 'pt'},
|
{label: 'Portugal', value: 'pt'},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const shortOptions = [
|
||||||
|
{label: 'Monsieur', value: 'M'},
|
||||||
|
{label: 'Madame', value: 'Mme'},
|
||||||
|
]
|
||||||
|
|
||||||
const longOptions = [
|
const longOptions = [
|
||||||
...options,
|
...options,
|
||||||
{label: 'Pays-Bas', value: 'nl'},
|
{label: 'Pays-Bas', value: 'nl'},
|
||||||
@@ -144,6 +159,7 @@ const errorValue = ref<string | number | null>(null)
|
|||||||
const successValue = ref<string | number | null>('be')
|
const successValue = ref<string | number | null>('be')
|
||||||
const disabledValue = ref<string | number | null>('ca')
|
const disabledValue = ref<string | number | null>('ca')
|
||||||
const emptyValue = ref<string | number | null>(null)
|
const emptyValue = ref<string | number | null>(null)
|
||||||
|
const shortListValue = ref<string | number | null>(null)
|
||||||
const longListValue = ref<string | number | null>(null)
|
const longListValue = ref<string | number | null>(null)
|
||||||
const bottomValue = ref<string | number | null>(null)
|
const bottomValue = ref<string | number | null>(null)
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -163,8 +163,17 @@ Liste déroulante.
|
|||||||
| `options` | `{ value: string \| number, text: string }[]` | `[]` | Options disponibles |
|
| `options` | `{ value: string \| number, text: string }[]` | `[]` | Options disponibles |
|
||||||
| `emptyOptionLabel` | `string` | `''` | Placeholder option vide |
|
| `emptyOptionLabel` | `string` | `''` | Placeholder option vide |
|
||||||
| `label` | `string` | `''` | Label |
|
| `label` | `string` | `''` | Label |
|
||||||
| `disabled` | `boolean` | `false` | Désactivé |
|
| `hint` | `string` | `''` | Message d'aide |
|
||||||
| `error` | `string` | `''` | Message d'erreur |
|
| `error` | `string` | `''` | Message d'erreur |
|
||||||
|
| `success` | `string` | `''` | Message de succès |
|
||||||
|
| `disabled` | `boolean` | `false` | Désactivé |
|
||||||
|
| `groupClass` | `string` | `''` | Classes CSS conteneur (twMerge) |
|
||||||
|
| `minWidth` | `string` | `'w-96'` | Classe largeur minimum |
|
||||||
|
| `maxWidth` | `string` | `''` | Classe largeur maximum |
|
||||||
|
| `rounded` | `string` | `'rounded-md'` | Classe border-radius |
|
||||||
|
| `textField` | `string` | `'text-lg'` | Classe taille texte bouton |
|
||||||
|
| `textValue` | `string` | `'text-lg'` | Classe taille texte valeur |
|
||||||
|
| `textLabel` | `string` | `'text-sm'` | Classe taille texte label |
|
||||||
|
|
||||||
**Events :** `update:modelValue(value: string | number | null)`
|
**Events :** `update:modelValue(value: string | number | null)`
|
||||||
**Slots :** `icon` (icône dropdown custom)
|
**Slots :** `icon` (icône dropdown custom)
|
||||||
@@ -172,6 +181,7 @@ Liste déroulante.
|
|||||||
```vue
|
```vue
|
||||||
<MalioSelect v-model="pays" label="Pays" :options="[{ value: 'FR', text: 'France' }, { value: 'BE', text: 'Belgique' }]" />
|
<MalioSelect v-model="pays" label="Pays" :options="[{ value: 'FR', text: 'France' }, { value: 'BE', text: 'Belgique' }]" />
|
||||||
<MalioSelect v-model="ville" label="Ville" :options="villes" empty-option-label="Choisir..." />
|
<MalioSelect v-model="ville" label="Ville" :options="villes" empty-option-label="Choisir..." />
|
||||||
|
<MalioSelect v-model="civilite" label="Civilité" :options="civilites" group-class="mt-0" />
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ describe('MalioCheckbox', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
expect(wrapper.get('input').attributes('aria-invalid')).toBe('true')
|
expect(wrapper.get('input').attributes('aria-invalid')).toBe('true')
|
||||||
expect(wrapper.get('label').classes()).toContain('text-m-danger')
|
expect(wrapper.get('label').classes()).toContain('text-m-error')
|
||||||
expect(wrapper.get('p').text()).toBe('You must accept')
|
expect(wrapper.get('p').text()).toBe('You must accept')
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -125,7 +125,7 @@ describe('MalioCheckbox', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
expect(wrapper.get('p').text()).toBe('Invalid')
|
expect(wrapper.get('p').text()).toBe('Invalid')
|
||||||
expect(wrapper.get('p').classes()).toContain('text-m-danger')
|
expect(wrapper.get('p').classes()).toContain('text-m-error')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('shows success styles and message when there is no error', () => {
|
it('shows success styles and message when there is no error', () => {
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ const mergedLabelClass = computed(() =>
|
|||||||
twMerge(
|
twMerge(
|
||||||
'cbx text-black',
|
'cbx text-black',
|
||||||
disabled.value ? 'cursor-not-allowed text-black/60' : '',
|
disabled.value ? 'cursor-not-allowed text-black/60' : '',
|
||||||
hasError.value ? 'text-m-danger' : '',
|
hasError.value ? 'text-m-error' : '',
|
||||||
hasSuccess.value ? 'text-m-success' : '',
|
hasSuccess.value ? 'text-m-success' : '',
|
||||||
props.labelClass,
|
props.labelClass,
|
||||||
),
|
),
|
||||||
@@ -120,7 +120,7 @@ const mergedMessageClass = computed(() =>
|
|||||||
twMerge(
|
twMerge(
|
||||||
'text-xs',
|
'text-xs',
|
||||||
hasError.value
|
hasError.value
|
||||||
? 'text-m-danger'
|
? 'text-m-error'
|
||||||
: hasSuccess.value
|
: hasSuccess.value
|
||||||
? 'text-m-success'
|
? 'text-m-success'
|
||||||
: 'text-m-muted',
|
: 'text-m-muted',
|
||||||
@@ -200,14 +200,14 @@ const onChange = (event: Event) => {
|
|||||||
stroke-dashoffset: 0;
|
stroke-dashoffset: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.inp-cbx + .cbx.text-m-danger span:first-child {
|
.inp-cbx + .cbx.text-m-error span:first-child {
|
||||||
border-color: rgb(var(--m-danger) / 1);
|
border-color: rgb(var(--m-error) / 1);
|
||||||
}
|
}
|
||||||
.cbx.text-m-danger span:first-child svg {
|
.cbx.text-m-error span:first-child svg {
|
||||||
stroke: rgb(var(--m-danger) / 1);
|
stroke: rgb(var(--m-error) / 1);
|
||||||
}
|
}
|
||||||
.inp-cbx:checked + .cbx.text-m-danger span:first-child {
|
.inp-cbx:checked + .cbx.text-m-error span:first-child {
|
||||||
border-color: rgb(var(--m-danger) / 1);
|
border-color: rgb(var(--m-error) / 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.inp-cbx + .cbx.text-m-success span:first-child {
|
.inp-cbx + .cbx.text-m-success span:first-child {
|
||||||
|
|||||||
@@ -1,25 +1,25 @@
|
|||||||
<template>
|
<template>
|
||||||
<Teleport to="body">
|
<Teleport to="body">
|
||||||
<div
|
<Transition
|
||||||
v-if="isOpen"
|
name="drawer"
|
||||||
:id="componentId"
|
appear
|
||||||
class="fixed inset-0 z-50"
|
@after-leave="isRendered = false"
|
||||||
v-bind="attrs"
|
|
||||||
>
|
>
|
||||||
<Transition name="drawer-backdrop">
|
<div
|
||||||
|
v-if="isRendered && isOpen"
|
||||||
|
:id="componentId"
|
||||||
|
class="fixed inset-0 z-50 flex justify-end"
|
||||||
|
v-bind="attrs"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
v-if="isOpen"
|
|
||||||
class="absolute inset-0 bg-black/40"
|
class="absolute inset-0 bg-black/40"
|
||||||
data-test="backdrop"
|
data-test="backdrop"
|
||||||
@click="close"
|
@click="close"
|
||||||
/>
|
/>
|
||||||
</Transition>
|
|
||||||
|
|
||||||
<Transition name="drawer-panel">
|
|
||||||
<div
|
<div
|
||||||
v-if="isOpen"
|
|
||||||
:class="twMerge(
|
:class="twMerge(
|
||||||
'absolute right-0 top-0 h-full w-full max-w-md bg-white shadow-xl',
|
'relative z-50 flex h-full w-full max-w-md flex-col bg-white shadow-xl',
|
||||||
drawerClass,
|
drawerClass,
|
||||||
)"
|
)"
|
||||||
role="dialog"
|
role="dialog"
|
||||||
@@ -51,19 +51,18 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="overflow-y-auto px-5"
|
class="flex-1 overflow-y-auto px-5"
|
||||||
style="max-height: calc(100% - 96px)"
|
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
</div>
|
||||||
</div>
|
</Transition>
|
||||||
</Teleport>
|
</Teleport>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref, useAttrs, useId } from 'vue'
|
import { computed, ref, useAttrs, useId, watch } from 'vue'
|
||||||
import { Icon as IconifyIcon } from '@iconify/vue'
|
import { Icon as IconifyIcon } from '@iconify/vue'
|
||||||
import { twMerge } from 'tailwind-merge'
|
import { twMerge } from 'tailwind-merge'
|
||||||
|
|
||||||
@@ -103,6 +102,12 @@ const isOpen = computed(() =>
|
|||||||
isControlled.value ? props.modelValue! : localValue.value,
|
isControlled.value ? props.modelValue! : localValue.value,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const isRendered = ref(isOpen.value)
|
||||||
|
|
||||||
|
watch(isOpen, (val) => {
|
||||||
|
if (val) isRendered.value = true
|
||||||
|
})
|
||||||
|
|
||||||
function close() {
|
function close() {
|
||||||
if (!isControlled.value) {
|
if (!isControlled.value) {
|
||||||
localValue.value = false
|
localValue.value = false
|
||||||
@@ -112,24 +117,23 @@ function close() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.drawer-backdrop-enter-active,
|
.drawer-enter-active,
|
||||||
.drawer-backdrop-leave-active {
|
.drawer-leave-active {
|
||||||
transition: opacity 0.2s ease;
|
transition: opacity 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.drawer-backdrop-enter-from,
|
.drawer-enter-active > div:last-child,
|
||||||
.drawer-backdrop-leave-to {
|
.drawer-leave-active > div:last-child {
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-enter-from,
|
||||||
|
.drawer-leave-to {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.drawer-panel-enter-active,
|
.drawer-enter-from > div:last-child,
|
||||||
.drawer-panel-leave-active {
|
.drawer-leave-to > div:last-child {
|
||||||
transition: transform 0.2s ease, opacity 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.drawer-panel-enter-from,
|
|
||||||
.drawer-panel-leave-to {
|
|
||||||
transform: translateX(100%);
|
transform: translateX(100%);
|
||||||
opacity: 0;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,67 +1,69 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div>
|
||||||
:class="mergedGroupClass"
|
<div
|
||||||
>
|
:class="mergedGroupClass"
|
||||||
<input
|
|
||||||
:id="inputId"
|
|
||||||
:name="name"
|
|
||||||
:autocomplete="autocomplete"
|
|
||||||
:class="mergedInputClass"
|
|
||||||
:required="required"
|
|
||||||
:maxlength="maxLength"
|
|
||||||
:minlength="minLength"
|
|
||||||
:disabled="disabled"
|
|
||||||
:value="currentValue"
|
|
||||||
:readonly="readonly"
|
|
||||||
:aria-invalid="!!error"
|
|
||||||
:aria-describedby="describedBy"
|
|
||||||
v-bind="attrs"
|
|
||||||
type="text"
|
|
||||||
inputmode="decimal"
|
|
||||||
placeholder="_"
|
|
||||||
@input="onInput"
|
|
||||||
@focus="isFocused = true"
|
|
||||||
@blur="onBlur"
|
|
||||||
>
|
>
|
||||||
|
<input
|
||||||
|
:id="inputId"
|
||||||
|
:name="name"
|
||||||
|
:autocomplete="autocomplete"
|
||||||
|
:class="mergedInputClass"
|
||||||
|
:required="required"
|
||||||
|
:maxlength="maxLength"
|
||||||
|
:minlength="minLength"
|
||||||
|
:disabled="disabled"
|
||||||
|
:value="currentValue"
|
||||||
|
:readonly="readonly"
|
||||||
|
:aria-invalid="!!error"
|
||||||
|
:aria-describedby="describedBy"
|
||||||
|
v-bind="attrs"
|
||||||
|
type="text"
|
||||||
|
inputmode="decimal"
|
||||||
|
placeholder="_"
|
||||||
|
@input="onInput"
|
||||||
|
@focus="isFocused = true"
|
||||||
|
@blur="onBlur"
|
||||||
|
>
|
||||||
|
|
||||||
<label
|
<label
|
||||||
v-if="label"
|
v-if="label"
|
||||||
:for="inputId"
|
:for="inputId"
|
||||||
:class="mergedLabelClass"
|
:class="mergedLabelClass"
|
||||||
>
|
>
|
||||||
{{ label }}
|
{{ label }}
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<IconifyIcon
|
<IconifyIcon
|
||||||
v-if="iconName"
|
v-if="iconName"
|
||||||
:icon="iconName"
|
:icon="iconName"
|
||||||
:width="iconSize"
|
:width="iconSize"
|
||||||
:height="iconSize"
|
:height="iconSize"
|
||||||
data-test="icon"
|
data-test="icon"
|
||||||
:class="[
|
:class="[
|
||||||
hasError
|
|
||||||
? 'text-m-danger'
|
|
||||||
: hasSuccess
|
|
||||||
? 'text-m-success' : iconColor,
|
|
||||||
iconPositionClass,
|
|
||||||
]"
|
|
||||||
/>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<p
|
|
||||||
v-if="hint || hasError || hasSuccess"
|
|
||||||
:id="`${inputId}-describedby`"
|
|
||||||
:class="[
|
|
||||||
hasError
|
hasError
|
||||||
? 'text-m-danger'
|
? 'text-m-danger'
|
||||||
: hasSuccess
|
: hasSuccess
|
||||||
? 'text-m-success'
|
? 'text-m-success' : iconColor,
|
||||||
: 'text-m-muted',
|
iconPositionClass,
|
||||||
'mt-1 text-xs ml-[2px] ',
|
|
||||||
]"
|
]"
|
||||||
>
|
/>
|
||||||
{{ hint || error || success }}
|
|
||||||
</p>
|
</div>
|
||||||
|
<p
|
||||||
|
v-if="hint || hasError || hasSuccess"
|
||||||
|
:id="`${inputId}-describedby`"
|
||||||
|
:class="[
|
||||||
|
hasError
|
||||||
|
? 'text-m-danger'
|
||||||
|
: hasSuccess
|
||||||
|
? 'text-m-success'
|
||||||
|
: 'text-m-muted',
|
||||||
|
'mt-1 text-xs ml-[2px] ',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
{{ hint || error || success }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@@ -133,7 +135,7 @@ const isFilled = computed(() => currentValue.value.trim().length > 0)
|
|||||||
|
|
||||||
const mergedGroupClass = computed(() =>
|
const mergedGroupClass = computed(() =>
|
||||||
twMerge(
|
twMerge(
|
||||||
'relative mt-4 flex h-12 w-full items-center',
|
'relative flex h-12 w-full items-center',
|
||||||
props.groupClass,
|
props.groupClass,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,68 +1,70 @@
|
|||||||
<template>
|
<template>
|
||||||
<div :class="mergedGroupClass" >
|
<div>
|
||||||
<label
|
<div :class="mergedGroupClass" >
|
||||||
v-if="label"
|
<label
|
||||||
:for="inputId"
|
v-if="label"
|
||||||
:class="mergedLabelClass"
|
:for="inputId"
|
||||||
>
|
:class="mergedLabelClass"
|
||||||
{{ label }}
|
>
|
||||||
</label>
|
{{ label }}
|
||||||
<button
|
</label>
|
||||||
type="button"
|
<button
|
||||||
:disabled="isMinusDisabled"
|
type="button"
|
||||||
@click="decrement"
|
:disabled="isMinusDisabled"
|
||||||
>
|
@click="decrement"
|
||||||
<IconifyIcon
|
>
|
||||||
icon="mdi:minus"
|
<IconifyIcon
|
||||||
:class="mergedButtonMinusClass"
|
icon="mdi:minus"
|
||||||
/>
|
:class="mergedButtonMinusClass"
|
||||||
</button>
|
/>
|
||||||
<input
|
</button>
|
||||||
:id="inputId"
|
<input
|
||||||
:name="name"
|
:id="inputId"
|
||||||
autocomplete="off"
|
:name="name"
|
||||||
:class="mergedInputClass"
|
autocomplete="off"
|
||||||
:style="inputWidthStyle"
|
:class="mergedInputClass"
|
||||||
:value="displayedValue"
|
:style="inputWidthStyle"
|
||||||
:required="required"
|
:value="displayedValue"
|
||||||
:disabled="disabled"
|
:required="required"
|
||||||
:readonly="readonly"
|
:disabled="disabled"
|
||||||
:aria-invalid="!!error"
|
:readonly="readonly"
|
||||||
:aria-describedby="describedBy"
|
:aria-invalid="!!error"
|
||||||
v-bind="attrs"
|
:aria-describedby="describedBy"
|
||||||
type="text"
|
v-bind="attrs"
|
||||||
inputmode="numeric"
|
type="text"
|
||||||
placeholder="_"
|
inputmode="numeric"
|
||||||
@input="onInput"
|
placeholder="_"
|
||||||
@focus="isFocused = true"
|
@input="onInput"
|
||||||
@blur="isFocused = false"
|
@focus="isFocused = true"
|
||||||
>
|
@blur="isFocused = false"
|
||||||
<button
|
>
|
||||||
type="button"
|
<button
|
||||||
:disabled="isPlusDisabled"
|
type="button"
|
||||||
@click="increment"
|
:disabled="isPlusDisabled"
|
||||||
>
|
@click="increment"
|
||||||
<IconifyIcon
|
>
|
||||||
icon="mdi:plus"
|
<IconifyIcon
|
||||||
:class="mergedButtonPlusClass"
|
icon="mdi:plus"
|
||||||
/>
|
:class="mergedButtonPlusClass"
|
||||||
</button>
|
/>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
v-if="hint || hasError || hasSuccess"
|
||||||
|
:id="`${inputId}-describedby`"
|
||||||
|
:class="[
|
||||||
|
hasError
|
||||||
|
? 'text-m-danger'
|
||||||
|
: hasSuccess
|
||||||
|
? 'text-m-success'
|
||||||
|
: 'text-m-muted',
|
||||||
|
'text-xs ml-[2px] ',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
{{ hint || error || success }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<p
|
|
||||||
v-if="hint || hasError || hasSuccess"
|
|
||||||
:id="`${inputId}-describedby`"
|
|
||||||
:class="[
|
|
||||||
hasError
|
|
||||||
? 'text-m-danger'
|
|
||||||
: hasSuccess
|
|
||||||
? 'text-m-success'
|
|
||||||
: 'text-m-muted',
|
|
||||||
'text-xs ml-[2px] ',
|
|
||||||
]"
|
|
||||||
>
|
|
||||||
{{ hint || error || success }}
|
|
||||||
</p>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@@ -170,7 +172,7 @@ const isPlusDisabled = computed(() =>
|
|||||||
|
|
||||||
const mergedGroupClass = computed(() =>
|
const mergedGroupClass = computed(() =>
|
||||||
twMerge(
|
twMerge(
|
||||||
'relative mt-4 flex h-12 w-full items-center',
|
'relative flex h-12 w-full items-center',
|
||||||
props.groupClass,
|
props.groupClass,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,67 +1,69 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div>
|
||||||
:class="mergedGroupClass"
|
<div
|
||||||
>
|
:class="mergedGroupClass"
|
||||||
<input
|
|
||||||
:id="inputId"
|
|
||||||
:name="name"
|
|
||||||
:autocomplete="autocomplete"
|
|
||||||
:class="mergedInputClass"
|
|
||||||
:required="required"
|
|
||||||
:maxlength="maxLength"
|
|
||||||
:minlength="minLength"
|
|
||||||
:disabled="disabled"
|
|
||||||
:value="currentValue"
|
|
||||||
:readonly="readonly"
|
|
||||||
:aria-invalid="!!error"
|
|
||||||
:aria-describedby="describedBy"
|
|
||||||
v-bind="attrs"
|
|
||||||
placeholder="_"
|
|
||||||
:type="isPasswordVisible ? 'text' : 'password'"
|
|
||||||
@input="onInput"
|
|
||||||
@focus="isFocused = true"
|
|
||||||
@blur="isFocused = false"
|
|
||||||
>
|
>
|
||||||
|
<input
|
||||||
|
:id="inputId"
|
||||||
|
:name="name"
|
||||||
|
:autocomplete="autocomplete"
|
||||||
|
:class="mergedInputClass"
|
||||||
|
:required="required"
|
||||||
|
:maxlength="maxLength"
|
||||||
|
:minlength="minLength"
|
||||||
|
:disabled="disabled"
|
||||||
|
:value="currentValue"
|
||||||
|
:readonly="readonly"
|
||||||
|
:aria-invalid="!!error"
|
||||||
|
:aria-describedby="describedBy"
|
||||||
|
v-bind="attrs"
|
||||||
|
placeholder="_"
|
||||||
|
:type="isPasswordVisible ? 'text' : 'password'"
|
||||||
|
@input="onInput"
|
||||||
|
@focus="isFocused = true"
|
||||||
|
@blur="isFocused = false"
|
||||||
|
>
|
||||||
|
|
||||||
<label
|
<label
|
||||||
v-if="label"
|
v-if="label"
|
||||||
:for="inputId"
|
:for="inputId"
|
||||||
:class="mergedLabelClass"
|
:class="mergedLabelClass"
|
||||||
>
|
>
|
||||||
{{ label }}
|
{{ label }}
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<IconifyIcon
|
<IconifyIcon
|
||||||
v-if="displayIcon"
|
v-if="displayIcon"
|
||||||
:icon="isPasswordVisible ? 'mdi:eye-outline' : 'mdi:eye-off-outline'"
|
:icon="isPasswordVisible ? 'mdi:eye-outline' : 'mdi:eye-off-outline'"
|
||||||
:width="24"
|
:width="24"
|
||||||
:height="24"
|
:height="24"
|
||||||
data-test="icon"
|
data-test="icon"
|
||||||
:class="[
|
:class="[
|
||||||
hasError
|
|
||||||
? 'text-m-danger'
|
|
||||||
: hasSuccess
|
|
||||||
? 'text-m-success' : 'text-m-muted',
|
|
||||||
'cursor-pointer absolute right-[10px] top-1/2 -translate-y-1/2',
|
|
||||||
]"
|
|
||||||
@click="toggleVisibility"
|
|
||||||
/>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<p
|
|
||||||
v-if="hint || hasError || hasSuccess"
|
|
||||||
:id="`${inputId}-describedby`"
|
|
||||||
:class="[
|
|
||||||
hasError
|
hasError
|
||||||
? 'text-m-danger'
|
? 'text-m-danger'
|
||||||
: hasSuccess
|
: hasSuccess
|
||||||
? 'text-m-success'
|
? 'text-m-success' : 'text-m-muted',
|
||||||
: 'text-m-muted',
|
'cursor-pointer absolute right-[10px] top-1/2 -translate-y-1/2',
|
||||||
'mt-1 text-xs ml-[2px] ',
|
|
||||||
]"
|
]"
|
||||||
>
|
@click="toggleVisibility"
|
||||||
{{ hint || error || success }}
|
/>
|
||||||
</p>
|
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
v-if="hint || hasError || hasSuccess"
|
||||||
|
:id="`${inputId}-describedby`"
|
||||||
|
:class="[
|
||||||
|
hasError
|
||||||
|
? 'text-m-danger'
|
||||||
|
: hasSuccess
|
||||||
|
? 'text-m-success'
|
||||||
|
: 'text-m-muted',
|
||||||
|
'mt-1 text-xs ml-[2px] ',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
{{ hint || error || success }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@@ -132,7 +134,7 @@ const hasSuccess = computed(() => !!props.success)
|
|||||||
const isFilled = computed(() => currentValue.value.trim().length > 0)
|
const isFilled = computed(() => currentValue.value.trim().length > 0)
|
||||||
const mergedGroupClass = computed(() =>
|
const mergedGroupClass = computed(() =>
|
||||||
twMerge(
|
twMerge(
|
||||||
'relative mt-4 flex h-12 w-full items-center',
|
'relative flex h-12 w-full items-center',
|
||||||
props.groupClass,
|
props.groupClass,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,67 +1,69 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div>
|
||||||
:class="mergedGroupClass"
|
<div
|
||||||
>
|
:class="mergedGroupClass"
|
||||||
<input
|
|
||||||
:id="inputId"
|
|
||||||
v-maska="mask"
|
|
||||||
:name="name"
|
|
||||||
:autocomplete="autocomplete"
|
|
||||||
:class="mergedInputClass"
|
|
||||||
:required="required"
|
|
||||||
:maxlength="maxLength"
|
|
||||||
:minlength="minLength"
|
|
||||||
:disabled="disabled"
|
|
||||||
:value="currentValue"
|
|
||||||
:readonly="readonly"
|
|
||||||
:aria-invalid="!!error"
|
|
||||||
:aria-describedby="describedBy"
|
|
||||||
v-bind="attrs"
|
|
||||||
placeholder="_"
|
|
||||||
type="text"
|
|
||||||
@input="onInput"
|
|
||||||
@focus="isFocused = true"
|
|
||||||
@blur="isFocused = false"
|
|
||||||
>
|
>
|
||||||
|
<input
|
||||||
|
:id="inputId"
|
||||||
|
v-maska="mask"
|
||||||
|
:name="name"
|
||||||
|
:autocomplete="autocomplete"
|
||||||
|
:class="mergedInputClass"
|
||||||
|
:required="required"
|
||||||
|
:maxlength="maxLength"
|
||||||
|
:minlength="minLength"
|
||||||
|
:disabled="disabled"
|
||||||
|
:value="currentValue"
|
||||||
|
:readonly="readonly"
|
||||||
|
:aria-invalid="!!error"
|
||||||
|
:aria-describedby="describedBy"
|
||||||
|
v-bind="attrs"
|
||||||
|
placeholder="_"
|
||||||
|
type="text"
|
||||||
|
@input="onInput"
|
||||||
|
@focus="isFocused = true"
|
||||||
|
@blur="isFocused = false"
|
||||||
|
>
|
||||||
|
|
||||||
<label
|
<label
|
||||||
v-if="label"
|
v-if="label"
|
||||||
:for="inputId"
|
:for="inputId"
|
||||||
:class="mergedLabelClass"
|
:class="mergedLabelClass"
|
||||||
>
|
>
|
||||||
{{ label }}
|
{{ label }}
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<IconifyIcon
|
<IconifyIcon
|
||||||
v-if="iconName"
|
v-if="iconName"
|
||||||
:icon="iconName"
|
:icon="iconName"
|
||||||
:width="iconSize"
|
:width="iconSize"
|
||||||
:height="iconSize"
|
:height="iconSize"
|
||||||
data-test="icon"
|
data-test="icon"
|
||||||
:class="[
|
:class="[
|
||||||
hasError
|
|
||||||
? 'text-m-danger'
|
|
||||||
: hasSuccess
|
|
||||||
? 'text-m-success' : iconColor,
|
|
||||||
iconPositionClass,
|
|
||||||
]"
|
|
||||||
/>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<p
|
|
||||||
v-if="hint || hasError || hasSuccess"
|
|
||||||
:id="`${inputId}-describedby`"
|
|
||||||
:class="[
|
|
||||||
hasError
|
hasError
|
||||||
? 'text-m-danger'
|
? 'text-m-danger'
|
||||||
: hasSuccess
|
: hasSuccess
|
||||||
? 'text-m-success'
|
? 'text-m-success' : iconColor,
|
||||||
: 'text-m-muted',
|
iconPositionClass,
|
||||||
'mt-1 text-xs ml-[2px] ',
|
|
||||||
]"
|
]"
|
||||||
>
|
/>
|
||||||
{{ hint || error || success }}
|
|
||||||
</p>
|
</div>
|
||||||
|
<p
|
||||||
|
v-if="hint || hasError || hasSuccess"
|
||||||
|
:id="`${inputId}-describedby`"
|
||||||
|
:class="[
|
||||||
|
hasError
|
||||||
|
? 'text-m-danger'
|
||||||
|
: hasSuccess
|
||||||
|
? 'text-m-success'
|
||||||
|
: 'text-m-muted',
|
||||||
|
'mt-1 text-xs ml-[2px] ',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
{{ hint || error || success }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@@ -138,7 +140,7 @@ const hasSuccess = computed(() => !!props.success)
|
|||||||
const isFilled = computed(() => currentValue.value.trim().length > 0)
|
const isFilled = computed(() => currentValue.value.trim().length > 0)
|
||||||
const mergedGroupClass = computed(() =>
|
const mergedGroupClass = computed(() =>
|
||||||
twMerge(
|
twMerge(
|
||||||
'relative mt-4 flex h-12 w-full items-center',
|
'relative flex h-12 w-full items-center',
|
||||||
props.groupClass,
|
props.groupClass,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,70 +1,72 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div>
|
||||||
:class="mergedGroupClass"
|
<div
|
||||||
>
|
:class="mergedGroupClass"
|
||||||
<input
|
|
||||||
ref="fileInputRef"
|
|
||||||
type="file"
|
|
||||||
:accept="accept"
|
|
||||||
class="hidden"
|
|
||||||
:disabled="disabled"
|
|
||||||
@change="onFileChange"
|
|
||||||
>
|
>
|
||||||
|
<input
|
||||||
|
ref="fileInputRef"
|
||||||
|
type="file"
|
||||||
|
:accept="accept"
|
||||||
|
class="hidden"
|
||||||
|
:disabled="disabled"
|
||||||
|
@change="onFileChange"
|
||||||
|
>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
:id="inputId"
|
:id="inputId"
|
||||||
:class="mergedInputClass"
|
:class="mergedInputClass"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:value="currentDisplayValue"
|
:value="currentDisplayValue"
|
||||||
:readonly="true"
|
:readonly="true"
|
||||||
:aria-invalid="!!error"
|
:aria-invalid="!!error"
|
||||||
:aria-describedby="describedBy"
|
:aria-describedby="describedBy"
|
||||||
v-bind="attrs"
|
v-bind="attrs"
|
||||||
placeholder="_"
|
placeholder="_"
|
||||||
type="text"
|
type="text"
|
||||||
@click="openFilePicker"
|
@click="openFilePicker"
|
||||||
@focus="isFocused = true"
|
@focus="isFocused = true"
|
||||||
@blur="isFocused = false"
|
@blur="isFocused = false"
|
||||||
>
|
>
|
||||||
|
|
||||||
<label
|
<label
|
||||||
v-if="label"
|
v-if="label"
|
||||||
:for="inputId"
|
:for="inputId"
|
||||||
:class="mergedLabelClass"
|
:class="mergedLabelClass"
|
||||||
>
|
>
|
||||||
{{ label }}
|
{{ label }}
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<IconifyIcon
|
<IconifyIcon
|
||||||
v-if="displayIcon"
|
v-if="displayIcon"
|
||||||
icon="mdi:cloud-arrow-up-outline"
|
icon="mdi:cloud-arrow-up-outline"
|
||||||
:width="24"
|
:width="24"
|
||||||
:height="24"
|
:height="24"
|
||||||
data-test="icon"
|
data-test="icon"
|
||||||
:class="[
|
:class="[
|
||||||
hasError
|
|
||||||
? 'text-m-danger'
|
|
||||||
: hasSuccess
|
|
||||||
? 'text-m-success' : 'text-m-muted',
|
|
||||||
'pointer-events-none absolute right-[10px] top-1/2 -translate-y-1/2',
|
|
||||||
]"
|
|
||||||
/>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<p
|
|
||||||
v-if="hint || hasError || hasSuccess"
|
|
||||||
:id="`${inputId}-describedby`"
|
|
||||||
:class="[
|
|
||||||
hasError
|
hasError
|
||||||
? 'text-m-danger'
|
? 'text-m-danger'
|
||||||
: hasSuccess
|
: hasSuccess
|
||||||
? 'text-m-success'
|
? 'text-m-success' : 'text-m-muted',
|
||||||
: 'text-m-muted',
|
'pointer-events-none absolute right-[10px] top-1/2 -translate-y-1/2',
|
||||||
'mt-1 text-xs ml-[2px] ',
|
|
||||||
]"
|
]"
|
||||||
>
|
/>
|
||||||
{{ hint || error || success }}
|
|
||||||
</p>
|
</div>
|
||||||
|
<p
|
||||||
|
v-if="hint || hasError || hasSuccess"
|
||||||
|
:id="`${inputId}-describedby`"
|
||||||
|
:class="[
|
||||||
|
hasError
|
||||||
|
? 'text-m-danger'
|
||||||
|
: hasSuccess
|
||||||
|
? 'text-m-success'
|
||||||
|
: 'text-m-muted',
|
||||||
|
'mt-1 text-xs ml-[2px] ',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
{{ hint || error || success }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@@ -121,7 +123,7 @@ const hasSuccess = computed(() => !!props.success)
|
|||||||
const isFilled = computed(() => currentDisplayValue.value.trim().length > 0)
|
const isFilled = computed(() => currentDisplayValue.value.trim().length > 0)
|
||||||
const mergedGroupClass = computed(() =>
|
const mergedGroupClass = computed(() =>
|
||||||
twMerge(
|
twMerge(
|
||||||
'relative mt-4 flex h-12 w-full items-center',
|
'relative flex h-12 w-full items-center',
|
||||||
props.groupClass,
|
props.groupClass,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div>
|
||||||
ref="root"
|
<div
|
||||||
class="relative mt-4 w-full"
|
ref="root"
|
||||||
:class="[minWidth, maxWidth]"
|
:class="mergedGroupClass"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
:id="buttonId"
|
:id="buttonId"
|
||||||
type="button"
|
type="button"
|
||||||
@@ -134,26 +134,28 @@
|
|||||||
{{ opt.label || '\u00A0' }}
|
{{ opt.label || '\u00A0' }}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
v-if="hint || hasError || hasSuccess"
|
||||||
|
:id="`${buttonId}-describedby`"
|
||||||
|
:class="[
|
||||||
|
hasError
|
||||||
|
? 'text-m-danger'
|
||||||
|
: hasSuccess
|
||||||
|
? 'text-m-success'
|
||||||
|
: 'text-m-muted',
|
||||||
|
'mt-1 ml-[2px] text-xs',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
{{ error || success || hint }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<p
|
|
||||||
v-if="hint || hasError || hasSuccess"
|
|
||||||
:id="`${buttonId}-describedby`"
|
|
||||||
:class="[
|
|
||||||
hasError
|
|
||||||
? 'text-m-danger'
|
|
||||||
: hasSuccess
|
|
||||||
? 'text-m-success'
|
|
||||||
: 'text-m-muted',
|
|
||||||
'mt-1 ml-[2px] text-xs',
|
|
||||||
]"
|
|
||||||
>
|
|
||||||
{{ error || success || hint }}
|
|
||||||
</p>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {computed, onBeforeUnmount, onMounted, ref, useId, nextTick} from 'vue'
|
import {computed, onBeforeUnmount, onMounted, ref, useId, nextTick} from 'vue'
|
||||||
import {Icon as IconifyIcon} from '@iconify/vue'
|
import {Icon as IconifyIcon} from '@iconify/vue'
|
||||||
|
import {twMerge} from 'tailwind-merge'
|
||||||
|
|
||||||
defineOptions({name: 'MalioSelect', inheritAttrs: false})
|
defineOptions({name: 'MalioSelect', inheritAttrs: false})
|
||||||
|
|
||||||
@@ -176,6 +178,7 @@ const props = withDefaults(defineProps<{
|
|||||||
textLabel?: string
|
textLabel?: string
|
||||||
rounded?: string
|
rounded?: string
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
|
groupClass?: string
|
||||||
}>(), {
|
}>(), {
|
||||||
options: () => [],
|
options: () => [],
|
||||||
emptyOptionLabel: '',
|
emptyOptionLabel: '',
|
||||||
@@ -190,6 +193,7 @@ const props = withDefaults(defineProps<{
|
|||||||
textLabel: 'text-sm',
|
textLabel: 'text-sm',
|
||||||
rounded: 'rounded-md',
|
rounded: 'rounded-md',
|
||||||
disabled: false,
|
disabled: false,
|
||||||
|
groupClass: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@@ -208,6 +212,9 @@ const normalizedOptions = computed<Option[]>(() => [
|
|||||||
{label: props.emptyOptionLabel, value: null},
|
{label: props.emptyOptionLabel, value: null},
|
||||||
...props.options,
|
...props.options,
|
||||||
])
|
])
|
||||||
|
const mergedGroupClass = computed(() =>
|
||||||
|
twMerge('relative w-full', props.minWidth, props.maxWidth, props.groupClass),
|
||||||
|
)
|
||||||
const hasError = computed(() => !!props.error)
|
const hasError = computed(() => !!props.error)
|
||||||
const hasSuccess = computed(() => !!props.success && !hasError.value)
|
const hasSuccess = computed(() => !!props.success && !hasError.value)
|
||||||
const isOptionSelected = computed(() =>
|
const isOptionSelected = computed(() =>
|
||||||
@@ -315,7 +322,6 @@ onBeforeUnmount(() => document.removeEventListener('mousedown', onClickOutside))
|
|||||||
|
|
||||||
:deep(ul[role="listbox"]) {
|
:deep(ul[role="listbox"]) {
|
||||||
scrollbar-width: auto;
|
scrollbar-width: auto;
|
||||||
scrollbar-gutter: stable;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.select-scrollbar-primary) {
|
:deep(.select-scrollbar-primary) {
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div>
|
||||||
ref="root"
|
<div
|
||||||
class="relative mt-4 w-full"
|
ref="root"
|
||||||
:class="[minWidth, maxWidth]"
|
class="relative w-full"
|
||||||
>
|
:class="[minWidth, maxWidth]"
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
:id="buttonId"
|
:id="buttonId"
|
||||||
type="button"
|
type="button"
|
||||||
@@ -184,21 +185,22 @@
|
|||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
v-if="hint || hasError || hasSuccess"
|
||||||
|
:id="`${buttonId}-describedby`"
|
||||||
|
:class="[
|
||||||
|
hasError
|
||||||
|
? 'text-m-danger'
|
||||||
|
: hasSuccess
|
||||||
|
? 'text-m-success'
|
||||||
|
: 'text-m-muted',
|
||||||
|
'mt-1 ml-[2px] text-xs',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
{{ error || success || hint }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<p
|
|
||||||
v-if="hint || hasError || hasSuccess"
|
|
||||||
:id="`${buttonId}-describedby`"
|
|
||||||
:class="[
|
|
||||||
hasError
|
|
||||||
? 'text-m-danger'
|
|
||||||
: hasSuccess
|
|
||||||
? 'text-m-success'
|
|
||||||
: 'text-m-muted',
|
|
||||||
'mt-1 ml-[2px] text-xs',
|
|
||||||
]"
|
|
||||||
>
|
|
||||||
{{ error || success || hint }}
|
|
||||||
</p>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
"files": [
|
"files": [
|
||||||
"app/**",
|
"app/**",
|
||||||
"nuxt.config.ts",
|
"nuxt.config.ts",
|
||||||
"README.md"
|
"README.md",
|
||||||
|
"COMPONENTS.md"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "nuxi dev .playground",
|
"dev": "nuxi dev .playground",
|
||||||
|
|||||||
Reference in New Issue
Block a user