Compare commits

..

28 Commits

Author SHA1 Message Date
tristan 2df5aff1b7 feat : wip ajout du composant Datatable 2026-03-25 08:20:04 +01:00
tristan f09f8a91ac [#MUI-15] Création d'un composant drawer (#21)
| 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é

Reviewed-on: #21
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-03-24 10:49:27 +00:00
tristan bcadd46ce2 [#MUI-2] Faire un MCP pour la librairie de composant (#20)
| 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é

Reviewed-on: #20
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-03-24 10:31:20 +00:00
tristan e76337502a [#MUI-10] Création d'un composant bouton (#19)
| 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é

Reviewed-on: #19
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-03-24 10:12:28 +00:00
tristan 968b7087b5 [#MUI-23] Revoir la config couleur tailwind (#18)
| 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é

Reviewed-on: #18
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-03-24 09:05:23 +00:00
tristan 3deba3f369 [#MUI-20] Développer le composant Menu (#17)
| 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é

Reviewed-on: #17
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-03-23 16:36:16 +00:00
tristan cf46ab0c85 [#MUI-11] Création d'un composant navigation par onglets (#16)
| 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é

Reviewed-on: #16
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-03-23 07:48:55 +00:00
tristan 09cc3edf6f feat : reorganisation de la structure projet 2026-03-20 14:22:40 +01:00
tristan c95a3657c0 [#MUI-14] Création d'un composant bouton icône (#15)
| 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é

Reviewed-on: #15
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-03-20 11:00:38 +00:00
tristan 9843f4d032 feat : ajout de state dans les histoires des composants 2026-03-19 17:45:03 +01:00
tristan 9d9b9c9dc4 feat : ajout d'un sélecteur "Tout cocher" dans le composant SelectCheckbox 2026-03-19 17:30:52 +01:00
tristan 187ef52865 [#MUI-9] Ajout composant upload (#14)
| 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é

Reviewed-on: #14
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-03-19 09:51:37 +00:00
tristan 9925f1ced4 [#MUI-8] Ajout composant mot de passe (#13)
| 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é

Reviewed-on: #13
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-03-19 09:43:55 +00:00
tristan ded414ba1a [#MUI12] Correction composant Nombre et Taux horaire (#12)
| Numéro du ticket | Titre du ticket |
|------------------|-----------------|
|                  |                 |

## Description de la PR

## Modification du .env

## Check list

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

Reviewed-on: #12
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-03-19 08:22:37 +00:00
kevin 11d60e687b [#366] Création d'un composant de type Select checkbox (#11)
| Numéro du ticket | Titre du ticket |
|------------------|-----------------|
|           #366       |            Création d'un composant de type Select checkbox     |

## Description de la PR

## Modification du .env

## Check list

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

Reviewed-on: #11
Co-authored-by: kevin <kevin@yuno.malio.fr>
Co-committed-by: kevin <kevin@yuno.malio.fr>
2026-03-17 12:28:57 +00:00
kevin d3038994c3 [#407] Création d'un composant de type time (#10)
| Numéro du ticket | Titre du ticket |
|------------------|-----------------|
|       #407         |           Création d'un composant de type time      |

## Description de la PR

## Modification du .env

## Check list

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

Reviewed-on: #10
Co-authored-by: kevin <kevin@yuno.malio.fr>
Co-committed-by: kevin <kevin@yuno.malio.fr>
2026-03-17 12:28:33 +00:00
kevin 0d350e12c6 Merge pull request '[#365] Création d'un composant de type Number' (#9) from feat/365-creation-composant-number into develop
Reviewed-on: #9
2026-03-11 15:16:18 +00:00
tristan c6acaace27 Merge remote-tracking branch 'origin/develop' into develop 2026-03-08 20:10:32 +01:00
tristan 927c7c3c70 Merge remote-tracking branch 'origin/main' into develop 2026-03-08 20:10:02 +01:00
kevin bf0aa92497 [#363] Création d'un composant de type checkbox (#5)
| Numéro du ticket | Titre du ticket |
|------------------|-----------------|
|        #363          |        Création d'un composant de type checkbox       |

## Description de la PR

## Modification du .env

## Check list

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

Co-authored-by: tristan <tristan@yuno.malio.fr>
Reviewed-on: #5
Co-authored-by: kevin <kevin@yuno.malio.fr>
Co-committed-by: kevin <kevin@yuno.malio.fr>
2026-03-08 19:07:53 +00:00
kevin 88dd76a0e4 [#364 ] Création d'un composant de type radio (#6)
| Numéro du ticket | Titre du ticket |
|------------------|-----------------|
|           #364        |       Création d'un composant de type radio          |

## Description de la PR

## Modification du .env

## Check list

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

Reviewed-on: #6
Co-authored-by: kevin <kevin@yuno.malio.fr>
Co-committed-by: kevin <kevin@yuno.malio.fr>
2026-03-08 18:59:50 +00:00
kevin cc04114f89 feat : ajout du composant input number 2026-03-05 09:38:56 +01:00
kevin f456ea4ddf feat : ajout du composant input number 2026-03-04 13:15:43 +01:00
kevin 77364daa67 [#362] Création d'un composant de type Montant (#4)
| Numéro du ticket | Titre du ticket |
|------------------|-----------------|
|          #362        |       Création d'un composant de type Montant          |

## Description de la PR

## Modification du .env

## Check list

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

Reviewed-on: #4
Reviewed-by: Autin <tristan@yuno.malio.fr>
Co-authored-by: kevin <kevin@yuno.malio.fr>
Co-committed-by: kevin <kevin@yuno.malio.fr>
2026-03-03 10:42:39 +00:00
kevin 1ab7b2427a [#337] Création d'un composant Select (#3)
| Numéro du ticket | Titre du ticket |
|------------------|-----------------|
|       #337           |           Création d'un composant Select      |

## Description de la PR

## Modification du .env

## Check list

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

Co-authored-by: tristan <tristan@yuno.malio.fr>
Reviewed-on: #3
Reviewed-by: Autin <tristan@yuno.malio.fr>
Co-authored-by: kevin <kevin@yuno.malio.fr>
Co-committed-by: kevin <kevin@yuno.malio.fr>
2026-03-02 13:24:58 +00:00
tristan 82ecc9cfe2 feat : ajout config vitest/make/pre-commit/commit-msg + un exemple de test vitest 2026-02-23 11:29:16 +01:00
tristan 65d9060e26 feat : ajout du template de MR + CHANGELOG.md 2026-02-23 11:11:31 +01:00
tristan ec4c157226 fix: readme.md 2026-02-19 11:18:36 +01:00
13 changed files with 372 additions and 421 deletions
@@ -82,16 +82,6 @@
/> />
</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
@@ -131,11 +121,6 @@ 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'},
@@ -159,7 +144,6 @@ 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>
+1 -11
View File
@@ -163,17 +163,8 @@ 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 |
| `hint` | `string` | `''` | Message d'aide |
| `error` | `string` | `''` | Message d'erreur |
| `success` | `string` | `''` | Message de succès |
| `disabled` | `boolean` | `false` | Désactivé | | `disabled` | `boolean` | `false` | Désactivé |
| `groupClass` | `string` | `''` | Classes CSS conteneur (twMerge) | | `error` | `string` | `''` | Message d'erreur |
| `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)
@@ -181,7 +172,6 @@ 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-error') expect(wrapper.get('label').classes()).toContain('text-m-danger')
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-error') expect(wrapper.get('p').classes()).toContain('text-m-danger')
}) })
it('shows success styles and message when there is no error', () => { it('shows success styles and message when there is no error', () => {
+8 -8
View File
@@ -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-error' : '', hasError.value ? 'text-m-danger' : '',
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-error' ? 'text-m-danger'
: 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-error span:first-child { .inp-cbx + .cbx.text-m-danger span:first-child {
border-color: rgb(var(--m-error) / 1); border-color: rgb(var(--m-danger) / 1);
} }
.cbx.text-m-error span:first-child svg { .cbx.text-m-danger span:first-child svg {
stroke: rgb(var(--m-error) / 1); stroke: rgb(var(--m-danger) / 1);
} }
.inp-cbx:checked + .cbx.text-m-error span:first-child { .inp-cbx:checked + .cbx.text-m-danger span:first-child {
border-color: rgb(var(--m-error) / 1); border-color: rgb(var(--m-danger) / 1);
} }
.inp-cbx + .cbx.text-m-success span:first-child { .inp-cbx + .cbx.text-m-success span:first-child {
+28 -32
View File
@@ -1,25 +1,25 @@
<template> <template>
<Teleport to="body"> <Teleport to="body">
<Transition <div
name="drawer" v-if="isOpen"
appear :id="componentId"
@after-leave="isRendered = false" class="fixed inset-0 z-50"
v-bind="attrs"
> >
<div <Transition name="drawer-backdrop">
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(
'relative z-50 flex h-full w-full max-w-md flex-col bg-white shadow-xl', 'absolute right-0 top-0 h-full w-full max-w-md bg-white shadow-xl',
drawerClass, drawerClass,
)" )"
role="dialog" role="dialog"
@@ -51,18 +51,19 @@
</div> </div>
<div <div
class="flex-1 overflow-y-auto px-5" class="overflow-y-auto px-5"
style="max-height: calc(100% - 96px)"
> >
<slot /> <slot />
</div> </div>
</div> </div>
</div> </Transition>
</Transition> </div>
</Teleport> </Teleport>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref, useAttrs, useId, watch } from 'vue' import { computed, ref, useAttrs, useId } 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'
@@ -102,12 +103,6 @@ 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
@@ -117,23 +112,24 @@ function close() {
</script> </script>
<style scoped> <style scoped>
.drawer-enter-active, .drawer-backdrop-enter-active,
.drawer-leave-active { .drawer-backdrop-leave-active {
transition: opacity 0.2s ease; transition: opacity 0.2s ease;
} }
.drawer-enter-active > div:last-child, .drawer-backdrop-enter-from,
.drawer-leave-active > div:last-child { .drawer-backdrop-leave-to {
transition: transform 0.3s ease;
}
.drawer-enter-from,
.drawer-leave-to {
opacity: 0; opacity: 0;
} }
.drawer-enter-from > div:last-child, .drawer-panel-enter-active,
.drawer-leave-to > div:last-child { .drawer-panel-leave-active {
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>
+57 -59
View File
@@ -1,69 +1,67 @@
<template> <template>
<div> <div
<div :class="mergedGroupClass"
: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' : iconColor, ? 'text-m-success'
iconPositionClass, : 'text-m-muted',
'mt-1 text-xs ml-[2px] ',
]" ]"
/> >
{{ hint || error || success }}
</div> </p>
<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">
@@ -135,7 +133,7 @@ const isFilled = computed(() => currentValue.value.trim().length > 0)
const mergedGroupClass = computed(() => const mergedGroupClass = computed(() =>
twMerge( twMerge(
'relative flex h-12 w-full items-center', 'relative mt-4 flex h-12 w-full items-center',
props.groupClass, props.groupClass,
), ),
) )
+63 -65
View File
@@ -1,70 +1,68 @@
<template> <template>
<div> <div :class="mergedGroupClass" >
<div :class="mergedGroupClass" > <label
<label v-if="label"
v-if="label" :for="inputId"
:for="inputId" :class="mergedLabelClass"
:class="mergedLabelClass"
>
{{ label }}
</label>
<button
type="button"
:disabled="isMinusDisabled"
@click="decrement"
>
<IconifyIcon
icon="mdi:minus"
:class="mergedButtonMinusClass"
/>
</button>
<input
:id="inputId"
:name="name"
autocomplete="off"
:class="mergedInputClass"
:style="inputWidthStyle"
:value="displayedValue"
:required="required"
:disabled="disabled"
:readonly="readonly"
:aria-invalid="!!error"
:aria-describedby="describedBy"
v-bind="attrs"
type="text"
inputmode="numeric"
placeholder="_"
@input="onInput"
@focus="isFocused = true"
@blur="isFocused = false"
>
<button
type="button"
:disabled="isPlusDisabled"
@click="increment"
>
<IconifyIcon
icon="mdi:plus"
:class="mergedButtonPlusClass"
/>
</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 }} {{ label }}
</p> </label>
<button
type="button"
:disabled="isMinusDisabled"
@click="decrement"
>
<IconifyIcon
icon="mdi:minus"
:class="mergedButtonMinusClass"
/>
</button>
<input
:id="inputId"
:name="name"
autocomplete="off"
:class="mergedInputClass"
:style="inputWidthStyle"
:value="displayedValue"
:required="required"
:disabled="disabled"
:readonly="readonly"
:aria-invalid="!!error"
:aria-describedby="describedBy"
v-bind="attrs"
type="text"
inputmode="numeric"
placeholder="_"
@input="onInput"
@focus="isFocused = true"
@blur="isFocused = false"
>
<button
type="button"
:disabled="isPlusDisabled"
@click="increment"
>
<IconifyIcon
icon="mdi:plus"
:class="mergedButtonPlusClass"
/>
</button>
</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">
@@ -172,7 +170,7 @@ const isPlusDisabled = computed(() =>
const mergedGroupClass = computed(() => const mergedGroupClass = computed(() =>
twMerge( twMerge(
'relative flex h-12 w-full items-center', 'relative mt-4 flex h-12 w-full items-center',
props.groupClass, props.groupClass,
), ),
) )
+57 -59
View File
@@ -1,69 +1,67 @@
<template> <template>
<div> <div
<div :class="mergedGroupClass"
: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-muted', ? 'text-m-success'
'cursor-pointer absolute right-[10px] top-1/2 -translate-y-1/2', : 'text-m-muted',
'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">
@@ -134,7 +132,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 flex h-12 w-full items-center', 'relative mt-4 flex h-12 w-full items-center',
props.groupClass, props.groupClass,
), ),
) )
+57 -59
View File
@@ -1,69 +1,67 @@
<template> <template>
<div> <div
<div :class="mergedGroupClass"
: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' : iconColor, ? 'text-m-success'
iconPositionClass, : 'text-m-muted',
'mt-1 text-xs ml-[2px] ',
]" ]"
/> >
{{ hint || error || success }}
</div> </p>
<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">
@@ -140,7 +138,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 flex h-12 w-full items-center', 'relative mt-4 flex h-12 w-full items-center',
props.groupClass, props.groupClass,
), ),
) )
+59 -61
View File
@@ -1,72 +1,70 @@
<template> <template>
<div> <div
<div :class="mergedGroupClass"
: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-muted', ? 'text-m-success'
'pointer-events-none absolute right-[10px] top-1/2 -translate-y-1/2', : 'text-m-muted',
'mt-1 text-xs ml-[2px] ',
]" ]"
/> >
{{ hint || error || success }}
</div> </p>
<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">
@@ -123,7 +121,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 flex h-12 w-full items-center', 'relative mt-4 flex h-12 w-full items-center',
props.groupClass, props.groupClass,
), ),
) )
+20 -26
View File
@@ -1,9 +1,9 @@
<template> <template>
<div> <div
<div ref="root"
ref="root" class="relative mt-4 w-full"
:class="mergedGroupClass" :class="[minWidth, maxWidth]"
> >
<button <button
:id="buttonId" :id="buttonId"
type="button" type="button"
@@ -134,28 +134,26 @@
{{ 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})
@@ -178,7 +176,6 @@ const props = withDefaults(defineProps<{
textLabel?: string textLabel?: string
rounded?: string rounded?: string
disabled?: boolean disabled?: boolean
groupClass?: string
}>(), { }>(), {
options: () => [], options: () => [],
emptyOptionLabel: '', emptyOptionLabel: '',
@@ -193,7 +190,6 @@ 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<{
@@ -212,9 +208,6 @@ 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(() =>
@@ -322,6 +315,7 @@ 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) {
+19 -21
View File
@@ -1,10 +1,9 @@
<template> <template>
<div> <div
<div ref="root"
ref="root" class="relative mt-4 w-full"
class="relative w-full" :class="[minWidth, maxWidth]"
:class="[minWidth, maxWidth]" >
>
<button <button
:id="buttonId" :id="buttonId"
type="button" type="button"
@@ -185,22 +184,21 @@
/> />
</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">
+1 -2
View File
@@ -6,8 +6,7 @@
"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",