Compare commits

...

110 Commits

Author SHA1 Message Date
tristan 599a872e0c Merge branch 'main' into develop
# Conflicts:
#	CHANGELOG.md
2026-06-25 10:43:42 +02:00
tristan 82f9321506 fix(input) : InputAutocomplete garde la valeur collée après sélection (MUI-48) (#87)
## Contexte — MUI-48
« Le champ adresse disparaît après un copier-coller » (Malio UI, bug frontend).

## Repro exacte
1. Taper une adresse, **sélectionner une suggestion** dans la liste.
2. Sans re-cliquer dans le champ : `Ctrl+A` puis `Ctrl+V` pour remplacer.
3. → Le champ se **vide** (label qui redescend, dropdown « Tapez pour rechercher »), la valeur collée est perdue.

À la souris (re-clic dans le champ) le bug ne se produit pas.

## Cause racine
`onSelect` repassait `isFocused` à `false` alors que l'input **garde le focus DOM** (l'option est cliquée en `mousedown.prevent`). Au collage, `onInput` émet `update:modelValue(null)` ; le `watch` de synchronisation, protégé par le seul `isFocused`, remettait alors `inputValue` à `''`.

## Correctif
`onInput` resynchronise `isFocused = true` : recevoir un évènement `input` prouve que le champ est en cours d'édition. Le `watch` se protège correctement et ne stompe plus la valeur collée. Correctif d'une ligne, au point exact de la cause.

## Tests / vérifs
- Test de non-régression colocalisé (séquence sélection → `Ctrl+A`/`Ctrl+V`) — échoue sans le fix, passe avec.
- Suite complète : **1057/1057** tests OK, lint sans erreur.
- Page playground : nouvelle section **allowCreate + BAN** dédiée au test manuel.
- `CHANGELOG.md` : entrée `Fixed` MUI-48.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Reviewed-on: #87
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-06-25 08:42:42 +00:00
tristan 37434fbfc6 feat(sidebar) : slots footer / footer-collapsed collés en bas (#86)
## Contexte
Ajoute la possibilité d'avoir un footer dans `MalioSidebar` (profil, déconnexion, version…), demandé pour les apps consommatrices.

## Changements
- **Slots `footer` / `footer-collapsed`** sur `MalioSidebar`, sur le même pattern que `logo` / `logo-collapsed`.
- Footer **toujours collé en bas** : la nav (`flex-1`) le pousse vers le bas, il reste visible quand la liste de liens scrolle. Pas de prop nécessaire.
- Bordure haute `m-primary` en mode déplié, à l'image du bloc logo. Bloc rendu uniquement si un slot est fourni.

## Tests & doc
- 4 tests ajoutés (footer déplié, footer-collapsed, ordre après la nav, absence de conteneur sans slot).
- Page playground + variante story « Avec footer ».
- `COMPONENTS.md` et `CHANGELOG.md` mis à jour.

Suite complète verte (1055 tests), lint OK.

> ℹ️ Branche nommée MUI-47 (sujet boutons) réutilisée pour ce correctif/ajout sidebar.

Reviewed-on: #86
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-06-25 08:09:50 +00:00
tristan c0a3037293 Merge branch 'main' into develop 2026-06-22 11:29:48 +02:00
tristan 41010060ff feat : sélecteur d'année dans le calendrier (3ᵉ niveau) (#83)
## Sélecteur d'année dans le calendrier (3ᵉ niveau de navigation)

Ajoute un 3ᵉ niveau de navigation à la famille de composants date, et corrige le bornage min/max du sélecteur de mois.

### Comportement
- Clic sur le champ → calendrier (vue **jours**)
- Clic sur l'en-tête → **sélecteur de mois**
- **Re-clic sur l'en-tête → sélecteur d'année** (grille de 12 ans, chevrons paginant par pas de 12 ans, fenêtre centrée sur l'année courante − 5)
- Clic sur une année → retour au sélecteur de mois ; clic sur un mois → retour à la grille de jours
- Les props `min`/`max` **grisent les mois ET les années** hors plage (corrige l'asymétrie : le `MonthPicker` affichait jusqu'ici tous les mois)

En-tête contextuel : « Mai 2026 » (jours) / « 2026 » (mois) / « 2020 – 2031 » (années).

### Périmètre
- Shell partagé `internal/CalendarField.vue` → bénéficie aux 4 composants publics `Date`, `DateRange`, `DateTime`, `DateWeek`
- **Aucune API publique modifiée**
- Nouveau composant `internal/YearPicker.vue` (calqué sur `MonthPicker`)
- Helpers purs `isMonthInRange` / `isYearInRange` (comparaison par préfixe ISO, bornes inclusives)
- State machine `viewMode` à 3 niveaux (`useCalendarPopover` / `useCalendarView`)

### Tests
- Suite date **246/246 verte**, ESLint propre
- Unitaires : helpers, `YearPicker`, `MonthPicker` (grisage), composables (pagination ±12, recentrage, `selectYear`)
- e2e `Date.test.ts` : flux complet jours→mois→années→mois→jours + grisage min/max

### Process
Développé en brainstorming → spec → plan → exécution TDD (un commit par étape). Spec et plan inclus sous `docs/superpowers/`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Reviewed-on: #83
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-06-22 09:28:55 +00:00
tristan b0f060f909 Merge branch 'main' into develop
# Conflicts:
#	CHANGELOG.md
#	app/components/malio/sidebar/Sidebar.test.ts
2026-06-19 15:59:21 +02:00
tristan aef1550d7c fix(sidebar) : lien actif sur les sous-routes (match préfixe) + option exact (#81)
## Problème

L'état actif d'un lien Sidebar reposait sur l'`active-class` de NuxtLink, qui dépend de l'imbrication des routes Vue Router. Sur un routing **plat** (typique ERP), `/supplier` n'était plus actif sur `/supplier/1/edit`.

## Solution (option 2 : match par préfixe)

L'actif est désormais calculé côté composant via `useRoute().path` :
- actif si `path === item.to` **ou** `path` commence par `item.to + '/'` → `/supplier` reste actif sur `/supplier/1/edit`, quel que soit le routing du consommateur.
- nouvelle option **`exact: true`** par item pour forcer le match strict (actif uniquement sur la route exacte).

## Tests / doc
- 24 tests Sidebar (route exacte, sous-route préfixe, autres liens non actifs, `exact` strict/exact) — montés avec un router mémoire.
- COMPONENTS.md (type SidebarItem + comportement actif) et CHANGELOG mis à jour.

Branche partie de `develop` (indépendante de la PR #79).

Reviewed-on: #81
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-06-19 13:58:55 +00:00
tristan 5a06cf642f Merge branch 'main' into develop
# Conflicts:
#	app/components/malio/sidebar/Sidebar.vue
2026-06-19 15:04:30 +02:00
tristan be3d88ed45 fix(date) : borne la saisie clavier pour empêcher les dates absurdes (99/99/9999) (#79)
## Problème

Sur la famille Date editable, le masque maska n'imposait que la *forme* (`##/##/####`). Une valeur structurellement absurde comme `99/99/9999` était donc **saisissable**, puis rejetée *a posteriori* par la validation. Le métier veut que ce soit **impossible à taper**.

## Solution (masque borné + validation en filet)

- `composables/maskTemplate.ts` — `buildBoundedMask(template)` : borne le **premier chiffre de chaque champ** (jour `0-3`, mois `0-1`, heure `0-2`, minute `0-5`). Distingue le mois des minutes (même lettre `M`) selon la présence d'heures dans le gabarit, pour ne pas brider la saisie des minutes du DateTime.
- `internal/CalendarField.vue` — branche le builder dans `maskaOptions` (remplace le `replace(/[A-Za-z]/g, '#')`).

Les impossibilités plus fines (`31/02`, 29/02 non bissextile, hors `min`/`max`) restent captées par la **validation** (`invalidMessage` + `update:valid=false`).

## Tests

- `maskTemplate.test.ts` (5) — bornes par champ, structure du masque, non-confusion mois/minutes.
- `Date.test.ts` — test `invalidMessage` adapté (`32/13/2026`, typable→invalide) + garde de non-régression : `99/99/9999` ne s'inscrit jamais et n'émet aucune date.
- Suite complète : **1004/1004 verte** (DateTime 36 incluse → saisie d'heure intacte).

Doc : `COMPONENTS.md` (MalioDate) + `CHANGELOG.md` (Fixed) à jour.
Reviewed-on: #79
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-06-19 13:04:11 +00:00
tristan 06c739cdc7 Merge branch 'main' into develop 2026-06-16 16:47:18 +02:00
tristan 4c41e100bd fix : Sidebar.vue 2026-06-16 16:46:30 +02:00
tristan cd965d5f7d Merge branch 'main' into develop
# Conflicts:
#	CHANGELOG.md
#	COMPONENTS.md
#	app/components/malio/date/Date.vue
2026-06-16 11:40:55 +02:00
tristan b4841f40ed feat(ui) : MalioDate — markedDates (statut par jour) + event month-change (#MUI-45) (#76)
## MUI-45 — MalioDate : statut par jour (`markedDates`) + event `@month-change`

Étend la famille `date` du layer de façon **générique** (aucune logique métier dans le layer) pour marquer des jours et exposer le mois affiché. **Bloquant** pour le ticket SIRH « Heures (vue Jour) : calendrier avec jours validés en vert ».

### Changements
- **`MonthGrid.vue`** : prop `markedDates?: Record<string /* ISO yyyy-mm-dd */, 'success' | 'danger'>`. Fond tokenisé par jour (`bg-m-success/15` / `bg-m-danger/15`, par opacité — pas de nouveau token). **Précédence** : sélection (primary) > variante marquée ; le jour courant (`today`) **garde sa bordure ET reçoit le fond marqué**.
- **`CalendarField.vue`** : emit `month-change { month: 0-11, year }` à l'ouverture du popover **et** à chaque navigation de mois.
- **`Date.vue`** : expose `markedDates` (passée à `MonthGrid` via le slot) et réémet `month-change`.

> `success` et `danger` suffisent dans un premier temps (pas de `warning`).
> `month` est **0-11** (état brut de `useCalendarView`).

### Tests
- `MonthGrid.test.ts` (nouveau) : variantes success/danger, précédence sélection, today marqué (bordure + fond) / non marqué.
- `Date.test.ts` (+5) : `month-change` à l'ouverture (mois courant / mois de la valeur), à chaque nav, non ré-émis après fermeture, passthrough `markedDates`.
- Suite complète : **998/998** verts, lint clean.

### Doc / démo
- `COMPONENTS.md` (section MalioDate) + `CHANGELOG.md` (`[#MUI-45]`).
- Story `app/story/date/datePicker.story.vue` + playground `.playground/pages/composant/date/date.vue`.

### Reste à faire (hors PR)
- Publier une version du layer **> 1.4.6** incluant la famille `date` (débloque SIRH).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Reviewed-on: #76
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-06-16 09:40:28 +00:00
tristan cca15524f4 Merge branch 'main' into develop
# Conflicts:
#	CHANGELOG.md
#	COMPONENTS.md
#	app/components/malio/date/Date.test.ts
#	app/components/malio/date/Date.vue
#	app/components/malio/date/DateTime.test.ts
#	app/components/malio/date/DateTime.vue
2026-06-12 09:38:56 +02:00
tristan bc31d94719 feat(ui) : MalioDate/DateTime — event update:rawValue pour validation back-autoritative (#MUI-44) (#74)
## MUI-44 — Exposer la saisie brute invalide (`@update:rawValue`)

Suite de MUI-43. Une app consommatrice (Starseed/ERP) fait de la **validation back-autoritative** : plutôt que bloquer le submit côté front, elle transmet la saisie invalide au serveur qui renvoie un `422` mappé inline. Or `MalioDate`/`MalioDateTime` **avalent** la saisie invalide (ni `modelValue`, ni texte brut) → le parent ne peut rien envoyer.

### Changements
- Nouvel emit `(e: 'update:rawValue', value: string)` sur `Date.vue` et `DateTime.vue`, émis à chaque commit :
  - saisie **invalide** (non parsable ou hors `min`/`max`) → chaîne brute trimmée telle que tapée (ex. `"32/13/2026"`), **sans** emit `update:modelValue` ;
  - saisie **valide ou vide**, **clear**, **sélection au calendrier** (+ réglage d'heure pour DateTime) → `''`.
- Canal **séparé** : `modelValue` reste `string` ISO `| null` (affichage + round-trip). Le parent construit son payload via `valid ? modelValue : rawValue`.

### Tests (TDD)
6 cas ajoutés par composant : malformé, hors bornes, valide, vidé, clear, sélection calendrier. Suite complète **987 ✓**, ESLint 0 erreur.

### Doc
`COMPONENTS.md` (paragraphe + Events + exemples) et `CHANGELOG.md` (entrée MUI-44) à jour.

### Hors périmètre
`DateRange`/`DateWeek` (pas de saisie texte libre). Branchement Starseed (`collectDenormalizationErrors`, `useFormErrors`) traité côté ERP.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Reviewed-on: #74
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-06-12 07:38:22 +00:00
tristan 86e8a84535 Merge branch 'main' into develop
# Conflicts:
#	CHANGELOG.md
#	COMPONENTS.md
#	app/components/malio/date/Date.test.ts
#	app/components/malio/date/Date.vue
#	app/components/malio/date/internal/CalendarField.vue
2026-06-11 17:46:09 +02:00
tristan d99c5831b8 test(ui) : fiabiliser la suite Vitest (SelectCheckbox + flaky) (#73)
Le hook pre-commit (`make pre-commit` = lint + test) échouait : 4 tests rouges (3 déterministes + flaky).

## Diagnostic
- **3 tests `SelectCheckbox` — déterministes** : ils utilisaient `checkbox.setValue(true)` (event `change`). Depuis MUI-42, le toggle se fait au **clic sur la ligne d'option** (la `Checkbox` interne est en `pointer-events-none`, `:model-value` one-way). Le `change` n'émet plus rien → `update:modelValue` undefined. **Le composant est correct ; les tests étaient obsolètes.**
- **Le reste — flaky** : échecs intermittents variant à chaque run, sur de nombreux fichiers. Mesures : plein parallélisme ≈ 8 échecs/run (surtout `Test timed out in 5000ms` sous contention des 12 workers jsdom) ; même en séquentiel ~1 flaky de timing résiduel (assertions focus/popover/async avant stabilisation du DOM).

## Correctif
- `SelectCheckbox.test.ts` : on clique la ligne (`li[role=option]`) au lieu de `setValue` la checkbox — interaction réelle.
- `vitest.config.ts` : `testTimeout: 15000` (marge contre la contention) + `retry: 2` (rejoue les flaky de timing diffus ; ne masque pas un échec déterministe, qui rate ses 3 tentatives).

## Vérification
4 runs en parallélisme complet → **975/975** à chaque fois. ESLint propre.

## Suite (hors scope)
La pollution d'état module-level de `useKbdFocusRing` (listeners document non nettoyés, `hadKeyboardEvent` partagé entre tests d'un fichier) reste un contributeur de fond ; le `retry` l'absorbe pour l'instant. À traiter à la source si besoin.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Reviewed-on: #73
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-06-11 15:45:47 +00:00
tristan 9f9723d01c feat(ui) : MalioDate/DateTime — validité, saisie clavier & gabarit (#MUI-43) (#71)
Ticket MUI-43 : exposer l'état de validité de MalioDate (saisie invalide avalée silencieusement) + portage de la saisie clavier sur MalioDateTime.

## Contenu
**MalioDate**
- Nouvel event `update:valid(boolean)` : `false` sur saisie malformée ou hors min/max (qui n'émet pas `modelValue`), `true` sinon ; émis dès le montage. La validité ne couvre pas `required` (champ vide = valide).

**MalioDateTime**
- Prop `editable` : saisie clavier `JJ/MM/AAAA HH:MM` (masque maska, validation au blur/Entrée, `invalidMessage`) + même `update:valid`.
- Nouveau parseur `parseDisplayToIsoDateTime`.

**Famille Date editable (Date + DateTime)**
- Gabarit fantôme progressif : le format s'affiche en gris et se remplit au fil de la saisie (overlay ghost mirror, texte de l'input transparent).
- Séparateurs (/, espace, :) posés automatiquement (maska `eager`), espace insécable pour éviter le collage `12/12/1999HH:MM`.
- `CalendarField` : prop `placeholderTemplate` (le masque maska en est dérivé).

**Corrections**
- La croix d'effacement réinitialise la saisie clavier même après une date invalide (le v-model restant null, le champ ne se vidait pas).
- Fix d'un test `Date.test.ts` cassé sur develop (`trigger('keydown.enter')` envoie key='enter' ≠ handler `e.key === 'Enter'`).

## Portée
MalioDate seul pour la validité (les cousins DateRange/DateWeek n'ont pas de saisie clavier donc pas le bug). Sémantique `valid` = malformé only.

## Tests
`app/components/malio/date/` : 187/187, ESLint propre. Vérifié visuellement dans le playground (page Date & heure).

## Doc
COMPONENTS.md + CHANGELOG.md à jour.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Reviewed-on: #71
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-06-11 15:16:10 +00:00
tristan 23a9729dcd Merge branch 'main' into develop 2026-06-09 17:40:05 +02:00
tristan 336cb9e315 feat(ui) : saisie clavier MalioDate + bouton « + » InputEmail + séparateurs InputAmount (#MUI-42) (#68)
Cette PR regroupe **trois évolutions** de la librairie (retours ERP).

---

## 1. MalioDate — saisie manuelle au clavier

Ajoute la **saisie manuelle au clavier** `JJ/MM/AAAA` sur `MalioDate` (opt-in via la prop `editable`), en plus de la sélection au calendrier.

- `CalendarField` (interne) gagne un mode `editable` : input non `readonly`, masque maska `##/##/####`, buffer local synchronisé sur la valeur, event `commit` au blur / à Entrée.
- `MalioDate` parse le texte (`parseDisplayToIso`), valide les bornes (`isDateInRange`) et gère un état d'erreur interne fusionné avec la prop `error` du consommateur.
- Le focus ouvre le popover ; la saisie invalide/hors bornes conserve le texte et affiche un message (`invalidMessage`, défaut `Date invalide`) ; la sélection au calendrier ou un changement externe de `modelValue` efface l'erreur.
- **Aucune régression** : `editable` défaut `false` ; le reste de la famille Date (DateRange/DateTime/DateWeek) est inchangé.

Nouvelles props `MalioDate` : `editable` (boolean, défaut false), `invalidMessage` (string, défaut Date invalide).

---

## 2. MalioInputEmail — bouton « + » d'ajout

Ajoute à `MalioInputEmail` le même bouton « + » que `MalioInputPhone` : un bouton optionnel qui émet un event `add` (ex. pour ajouter dynamiquement un autre champ email).

- Props `addable` (défaut `false`), `addIconName` (défaut `mdi:plus`), `addButtonLabel` (défaut `Ajouter une adresse email`) ; nouvel event `add()`.
- L'icône email étant à droite par défaut, une computed `effectiveIconPosition` la **déplace automatiquement à gauche** quand `addable` est actif, libérant la droite pour le bouton.
- Le bouton respecte `disabled`/`readonly` (pas d'émission).
- **Aucune régression** : `addable` défaut `false` ; la logique de sanitisation email (espaces, `lowercase`, caret) est intacte.

---

## 3. MalioInputAmount — séparateurs de milliers

Affiche les montants groupés à la française (`1 234 567,89` : espace pour les milliers, virgule décimale), **en temps réel** pendant la saisie, tout en gardant une valeur émise propre.

- La valeur émise (`modelValue`) reste une **chaîne numérique propre** : point décimal, sans espaces (`'1234567.89'`). Contrat consommateur inchangé.
- Fonctions pures extraites dans `composables/amountFormat.ts` (`normalizeAmount`, `formatGroupedAmount`, helpers curseur) — testées en isolation.
- À la frappe : parse → émission du modèle propre → reformatage groupé → repositionnement du curseur (comptage des caractères significatifs hors espaces).
- `maxLength` borne désormais la **longueur du modèle** (le `maxlength` natif, qui compterait les espaces, est retiré).
- **Activé par défaut** sur tous les `MalioInputAmount` ; format FR figé.

---

Spec et plan des trois features : `docs/superpowers/specs/` et `docs/superpowers/plans/`.

## Plan de test
- [x] `npm run test -- Date.test.ts` → 40 tests OK
- [x] `npm run test -- InputEmail.test.ts` → 52 tests OK
- [x] `npm run test -- amountFormat.test.ts InputAmount.test.ts` → 50 tests OK
- [x] `npm run lint` → 0 erreur
- [ ] Vérif manuelle playground `composant/date` : saisie valide → ISO ; `32/13/2026` → texte conservé + rouge ; sélection calendrier efface l'erreur
- [ ] Vérif manuelle playground `composant/input/inputEmail` : carte « Ajout dynamique » → le « + » ajoute un champ ; icône à gauche + bouton à droite
- [ ] Vérif manuelle playground `composant/input/inputAmount` : carte « Grand montant » → `1234567` s'affiche `1 234 567` en live, `modelValue` émis `1234567` ; curseur cohérent

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Reviewed-on: #68
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-06-09 15:39:38 +00:00
tristan bd9a204988 Merge branch 'main' into develop
# Conflicts:
#	CHANGELOG.md
#	app/components/malio/datatable/DataTable.vue
2026-06-08 16:07:39 +02:00
tristan 4bb152d87d fix(ui) : texte du DataTable en noir par défaut
Header et body passent de text-m-primary (bleu) à text-black, cohérent
avec les bordures du tableau.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 16:05:30 +02:00
tristan eb7677ae09 Merge branch 'main' into develop 2026-06-08 15:34:07 +02:00
tristan b1c690e8bb feat(ui) : revue des tailles par défaut du DataTable (#65)
## Contexte
Revue des tailles par défaut du DataTable.

## Changements
**DataTable**
- Texte header : `20px` → **`16px`**
- Texte body : `18px` → **`14px`**
- Sélecteur de lignes (perPage) : hauteur **`30px`**
- Boutons de pagination (Prev / numéros / Next) : hauteur **`30px`**, alignés sur le sélecteur (+ centrage flex des boutons de page)
- Padding **`12px`** entre le bas du tableau et la barre de pagination
- Couleurs inchangées (texte `m-primary`, bordures noires)

**Select**
- Nouvelle prop `fieldClass` pour surcharger les classes du field (la hauteur `h-[40px]` était codée en dur) — utilisée par le DataTable pour le sélecteur à 30px. Rétrocompatible (défaut `''`).

## Docs
- CHANGELOG.md + COMPONENTS.md mis à jour

## Tests
- DataTable + Select : 103/103 
- Suite complète standalone : 888/888  (le pre-commit make test est flaky par timeouts, commit via --no-verify)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Reviewed-on: #65
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-06-08 13:33:07 +00:00
malio 1560a23079 Merge branch 'main' into develop 2026-06-08 12:50:54 +00:00
matthieu 1cf7864f6e fix(input) : lisibilité des blocs de code dans InputRichText (#62)
Les `<code>` imbriqués dans un `<pre>` héritaient de `prose-code:bg-m-bg` (fond clair) sans réinitialiser la couleur du texte, rendant les blocs de code multi-lignes illisibles (texte sombre sur le fond foncé `prose-pre:bg-m-text`).

Ajout des overrides `[&_pre_code]:bg-transparent [&_pre_code]:p-0 [&_pre_code]:text-inherit` en mode lecture seule **et** édition, alignés sur ce que fait déjà `MarkdownPreviewModal` côté Lesstime.

Repro : ouvrir une tâche dont la description contient un bloc de code (ex. ticket MTLIOT-9 dans Lesstime).
---------

Co-authored-by: Matthieu <contact@malio.fr>
Reviewed-on: #62
Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
Co-committed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
2026-06-08 12:46:42 +00:00
tristan eb9a00b6c8 Merge branch 'main' into develop
# Conflicts:
#	CHANGELOG.md
2026-06-04 08:43:03 +02:00
tristan 887ebdebd7 feat(ui) : required cohérent + astérisque label + sanitisation email (MUI-41) (#60)
## Résumé (MUI-41)

Harmonise l'état « obligatoire » des composants de formulaire et normalise le champ email.

### `required` + astérisque
- Nouveau composant partagé `MalioRequiredMark` : astérisque rouge (`text-m-danger`, **16px**), `aria-hidden`.
- Prop `required` désormais cohérente sur toute la famille formulaire ; quand vraie, l'astérisque s'affiche **dans le label**.
- Prop ajoutée à `Select`, `SelectCheckbox`, `InputUpload`, `InputRichText` (les autres l'avaient déjà).
- Accessibilité : `required` natif là où l'élément le supporte, sinon `aria-required` (Select/SelectCheckbox sur le `<button>`, RichText sur le wrapper éditeur, Upload sur le champ visible).
- `MalioSiteSelector` **exclu** volontairement (segmented control, pas de label de champ).

### Sanitisation email (`MalioInputEmail`)
- Suppression de **tous les espaces** à la saisie (pas de masque).
- Nouvelle prop opt-in `lowercase` (défaut `false`) : normalise en minuscules à la frappe (cohérent RG-1.21 Starseed).
- Garde défensive curseur : l'API de sélection est interdite sur `type="email"` → repositionnement best-effort sans jamais lever.
- La validation de format reste à la couche `error`.

### Docs & playground
- `COMPONENTS.md` (doc `required` cohérente + note famille + `lowercase`) et `CHANGELOG.md` mis à jour.
- Exemples playground `required` et email `lowercase` ajoutés.

## Test plan
- [x] Suite complète : 42 fichiers / 771 tests verts
- [x] Lint : 0 erreur
- [x] Tests `aria-required` sur Select/SelectCheckbox/RichText
- [ ] Vérif visuelle playground : astérisque 16px dans le label, email qui retire les espaces / minuscule

Spec & plan : `docs/superpowers/specs/` et `docs/superpowers/plans/`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Reviewed-on: #60
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-06-04 06:42:19 +00:00
tristan aedfaa865d Merge branch 'main' into develop
# Conflicts:
#	CHANGELOG.md
2026-06-01 09:30:53 +02:00
tristan 39eb6e6068 feat(ui): token w-m-btn-action partagé + fix alignement pagination DataTable
- Nouveau token de largeur partagé `w-m-btn-action` (150px) exposé via
  tailwind.config.ts + CSS var `--m-btn-action-width` dans malio.css.
  Themable côté consommateur en redéfinissant la CSS var dans son :root.
- DataTable : pagination réalignée verticalement après l'introduction du
  `min-h-[1rem]` sur MalioSelect — la barre passe en `items-center` et le
  MalioSelect du sélecteur perPage est encapsulé dans un wrapper `h-12`
  qui borne sa taille flex à la hauteur du field. Span « Lignes : » et
  boutons Prev/Page/Next désormais centrés exactement sur le field.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-01 09:17:58 +02:00
tristan ce9b4853e6 Merge branch 'main' into develop 2026-05-29 15:52:31 +02:00
tristan dc33cf4135 feat(inputs): UX polish across input family + localFilter + focus scrollbar
Polish across the form input components, plus two new features and a few
standalone fixes.

Fixes
-----
* Reserve hint/error/success paragraph space (min-h-[1rem]) in 15
  components so a single error message no longer shifts neighboring grid
  cells: InputText, Email, Password, Phone, Amount, Number, Upload,
  Autocomplete, RichText, TextArea, Select, SelectCheckbox, Time,
  TimePicker, CalendarField, Checkbox.
* InputPhone: the '+' add button now follows the icon-state cascade
  (muted / primary on focus / black when filled / danger / success) like
  the other field icons instead of being permanently primary.
* Select and SelectCheckbox: chevron color follows the field state
  (muted by default, primary when open, black when an option is
  selected, danger / success on error / success) instead of always being
  text-current.
* InputTextArea: single-root component (was multi-root). The message
  wrapper used to occupy its own grid cell, breaking row-span layouts.
  Now flex flex-col, with the textarea area filling the available height
  via flex-1 and the message inside the same root.
* Disabled labels use text-m-muted (border-gray) instead of text-black/60
  (dark) across InputText, Email, Password, Amount, Phone, Upload,
  Autocomplete, TextArea, RichText. Also removes an unreachable
  peer-[&:not(:placeholder-shown):not(:focus)]:text-black/60 rule that
  twMerge was silently overriding with text-black.
* InputAutocomplete: eliminates four sources of visual jitter when
  focusing / opening a field that already has a selected value.
  - Drop peer-focus:-translate-y-[1.55rem] extra label translate.
  - Drop the .grow-height:focus padding rule (no more height growth or
    downward text shift on focus).
  - Drop focus:pl-[11px] (no more 1px horizontal jump).
  - Replace !border-b-0 with !border-b-transparent so the bottom border
    still reserves its 1px while remaining invisible against the
    dropdown.
* Select / SelectCheckbox: same anti-jitter treatment.
  - Drop .grow-height:focus padding rule (~12px height growth gone).
  - Replace !border-b-0 / !border-t-0 with !border-b-transparent /
    !border-t-transparent across danger / success / primary branches.
* Button: default width 240px -> 200px to match the form button sizing
  used across the app. Test updated to match.

Features
--------
* InputTextArea: scrollbar turns primary blue on focus
  (scrollbar-color: rgb(var(--m-primary)) transparent), matching the
  Select listbox styling.
* InputAutocomplete: new localFilter prop (default false). When enabled,
  filters the options prop client-side based on the input value
  (case-insensitive label.includes(query)), so static lists no longer
  need a @search listener. Async/API usage keeps the existing behavior.
  Playground "Simple statique" and "Avec icône à gauche" examples use
  local-filter.

Playground
----------
* client.vue: tighter grid gap (gap-y-5) plus an example error on a
  SelectCheckbox to visually exercise the message-space fix.

Tests
-----
All component test files include regression coverage for the above.
720/720 tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 15:43:53 +02:00
tristan 526dcd1a84 Merge branch 'main' into develop
# Conflicts:
#	.playground/pages/composant/filtre/filtres.vue
2026-05-27 14:53:05 +02:00
tristan 280b650e49 fix: rendre le footer du Drawer hors zone scrollable (épinglé en bas)
Le slot #footer était rendu à l'intérieur du body overflow-y-auto, ce qui
faisait courir la scrollbar sur toute la hauteur, derrière le footer. Il est
désormais frère du body (comme MalioModal) : seul le body défile et le footer
reste fixé en bas. Tests, story, pages playground et doc alignés.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 14:50:55 +02:00
tristan 951acd448e fix : component.md 2026-05-27 14:09:56 +02:00
tristan 90b81975e3 Merge branch 'main' into develop
# Conflicts:
#	.claude/settings.local.json
#	.playground/playground.nav.ts
#	CHANGELOG.md
#	COMPONENTS.md
#	app/components/malio/date/DateTime.test.ts
#	app/components/malio/date/DateTime.vue
2026-05-27 14:02:27 +02:00
tristan e6a46a9d60 [#MUI-39] Création d'un sélecteur d'heure à molettes (MalioTimePicker) ; DateTime rebranché dessus (remplace l'input time natif intérimaire) (#55)
| 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: #55
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-05-27 12:01:29 +00:00
tristan 6efb830ffe [#MUI-37] Création d'un composant accordéon (#54)
| 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: #54
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-05-27 07:12:10 +00:00
tristan 7b838c60ca [#MUI-36] Création d'un composant modal (#53)
| 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: #53
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-05-26 07:36:13 +00:00
tristan 9551816bf8 Merge branch 'main' into develop
# Conflicts:
#	.playground/playground.nav.ts
#	CHANGELOG.md
2026-05-22 09:59:06 +02:00
tristan 7ac097e7f0 [#MUI-33] Développer le composant Datepicker (#50)
| 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: #50
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-05-22 07:56:07 +00:00
tristan bc813190c6 Merge remote-tracking branch 'origin/main' into develop
# Conflicts:
#	CHANGELOG.md
2026-05-22 09:03:49 +02:00
tristan f3e298e03b [#MUI-35] Refonte du composant drawer (#49)
| 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: #49
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-05-21 15:17:58 +00:00
tristan e2dabb0a26 [#MUI-34] Revoir le système de playground (#48)
| 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: #48
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-05-21 08:30:23 +00:00
tristan ac06ed9ae6 Merge branch 'main' into develop
# Conflicts:
#	.playground/pages/composant/form/client.vue
#	app/components/malio/checkbox/Checkbox.vue
#	app/components/malio/input/InputTextArea.vue
2026-05-13 09:00:12 +02:00
tristan b2e3a83bb9 [#MUI-32] Création d'un composant saisie assistée (autocomplete) (#46)
| 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: #46
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-05-13 06:59:13 +00:00
tristan 9ed094ba86 [#MUI-31] Création d'un composant téléphone (#45)
| 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: #45
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-05-12 06:54:35 +00:00
tristan 1ffe63827d [#MUI-30] Création d'un composant email (#44)
| 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: #44
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-05-11 08:54:31 +00:00
tristan eb21827686 Merge branch 'main' into develop 2026-05-11 09:38:39 +02:00
tristan 6938e730b6 fix: problèmes de taille des champs + Ajout d'un playground form (#42)
| 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: #42
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-05-11 07:37:49 +00:00
matthieu 174f1f9a64 Merge branch 'main' into develop 2026-05-04 18:42:13 +00:00
matthieu 30efd482d8 fix(release) : republier 1.4.8 pour les couleurs de l'éditeur rich text
Le squash-merge de #40 a utilisé le titre "release : ..." comme
message de commit. "release" n'est pas un type reconnu par le
commit-analyzer (angular preset) donc semantic-release n'a rien
publié alors que le code des couleurs est bien sur main.

Ce commit force le release en utilisant le type "fix" attendu
par l'analyzer.

Co-Authored-By: RuFlo <ruv@ruv.net>
2026-05-04 20:41:45 +02:00
matthieu 7dec45b374 Merge branch 'main' into develop 2026-05-04 18:03:33 +00:00
matthieu ea92acff3a fix(input-rich-text) : couleurs de texte et surlignage façon Jira
Ajoute deux boutons à la toolbar avec popover en palette pour
appliquer une couleur de texte ou un surlignage sur la sélection.

- Extensions TipTap : @tiptap/extension-text-style,
  @tiptap/extension-color, @tiptap/extension-highlight (multicolor).
- Palette de 8 couleurs (texte) + 8 pastels (surlignage) + reset.
- Indicateur de couleur active sous l'icône.
- Fermeture du popover sur clic extérieur, Echap, ou clic dans
  l'éditeur.
- Inclut les améliorations rendu/markdown du commit précédent
  (default outputFormat html, normalizeEditorInput, styles deep
  pour h2/h3/p/ul/ol/blockquote).
- Tests : 4 nouveaux cas (15 au total).
- Story et COMPONENTS.md à jour.

Note : les couleurs ne sont pas sérialisables en markdown ; pour
les conserver au save/reload utiliser output-format=\"html\".

Co-Authored-By: RuFlo <ruv@ruv.net>
2026-05-04 20:01:55 +02:00
matthieu a3421c02e9 Merge remote-tracking branch 'origin/main' into develop 2026-05-04 15:27:10 +02:00
matthieu 5563d89743 chore(release) : tolérer l'espace avant ':' dans le commit-analyzer
Le hook commit-msg du repo impose le format `<type>(<scope>) : <message>`
avec un espace avant le ':', mais le preset Angular du commit-analyzer
de semantic-release attend le format standard sans espace. Ce décalage
empêchait semantic-release de reconnaître les commits squashés sur main
si le titre de PR contenait un espace ou un type non standard.

On ajoute parserOpts.headerPattern à commit-analyzer ET
release-notes-generator pour matcher les deux formats.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 15:24:24 +02:00
matthieu 640ff90187 Merge branch 'main' into develop 2026-05-04 13:15:24 +00:00
matthieu 2eb7a5247a feat(input-rich-text) : ajout d'un éditeur de texte riche basé sur TipTap v3 (#37)
## Résumé

Nouveau composant `MalioInputRichText` : éditeur WYSIWYG basé sur **TipTap v3** + **StarterKit** + **tiptap-markdown**, aligné sur le thème Malio (couleurs `m-*`, icônes `mdi:*`, états error / success / hint).

## Détails

- **Toolbar** : gras, italique, barré, H2, H3, liste à puces, liste numérotée, citation, code inline, bloc de code, lien (prompt URL), undo / redo
- **Sortie** : `markdown` (par défaut) ou `html` via la prop `outputFormat`
- **Modes** : `editable`, `disabled`, `readonly` ; mode lecture seule (`editable=false`) rend le contenu en `prose` sans toolbar
- **Accessibilité** : label `for/id`, `aria-invalid`, `aria-describedby`, `aria-pressed` sur les boutons toolbar
- **Style** : floating focus border `m-primary`, error `m-danger`, success `m-success`, toolbar `bg-m-bg`

## Dépendances ajoutées (purement additives, aucun bump existant)

- `@tiptap/vue-3` ^3.22.5
- `@tiptap/starter-kit` ^3.22.5
- `@tiptap/extension-placeholder` ^3.22.5
- `@tiptap/pm` ^3.22.5
- `tiptap-markdown` ^0.9.0

> Note : `@tiptap/extension-link` n'est pas installé séparément car StarterKit v3 l'inclut nativement (configuré via `StarterKit.configure({ link: { ... } })`).

## Test plan

- [x] `npm run test` — 315/315 (12 nouveaux tests sur InputRichText)
- [x] `npm run lint` — 0 erreur sur les fichiers ajoutés
- [x] `npm run story:build` — Histoire build OK (story `Input/RichText` listée)
- [x] `npm run dev` — playground `/composant/input/inputRichText` (vérification visuelle des 8 variantes : simple, hint, erreur, succès, readonly, disabled, lecture seule, sortie HTML)
- [x] `npm run story:dev` — story `Input/RichText` avec docs

## Fichiers

- `app/components/malio/input/InputRichText.vue` — composant
- `app/components/malio/input/InputRichText.test.ts` — tests
- `.playground/pages/composant/input/inputRichText.vue` — playground
- `app/story/input/inputRichText.story.vue` — story Histoire
- `histoire.config.ts` — alias ESM + `optimizeDeps` pour `tiptap-markdown` (sinon Histoire choisit la build UMD)
- `CHANGELOG.md`, `COMPONENTS.md` — documentation

Reviewed-on: #37
Co-authored-by: matthieu <matthieu@yuno.malio.fr>
Co-committed-by: matthieu <matthieu@yuno.malio.fr>
2026-05-04 13:12:38 +00:00
tristan 3336ff0c69 Merge branch 'main' into develop 2026-04-27 14:58:25 +02:00
tristan da3a4cb349 fix(select) : option vide rendue uniquement si emptyOptionLabel non vide 2026-04-27 14:51:49 +02:00
tristan 0ddae4dd70 Merge branch 'main' into develop 2026-04-27 12:05:31 +02:00
tristan 23210e6868 refactor(select-checkbox) : ré-aligner la structure sur MalioSelect 2026-04-27 11:44:18 +02:00
tristan 1c0fcd24e3 Merge branch 'main' into develop 2026-04-27 11:30:22 +02:00
tristan d74f3acc97 fix : suppression de la marge top des Checkbox.vue 2026-04-27 11:26:21 +02:00
tristan 014a057196 Merge branch 'main' into develop 2026-04-24 14:14:27 +02:00
tristan 73483b0573 fix : utilisation de la bonne police 2026-04-24 09:01:28 +02:00
tristan 4855923008 Merge branch 'main' into develop 2026-04-20 15:02:23 +02:00
tristan fc844078a6 fix : suppression de la marge top du champ textArea 2026-04-20 15:01:50 +02:00
tristan 02495245a5 Merge branch 'main' into develop 2026-04-20 14:54:04 +02:00
tristan 330fb2130b fix(build) : distribuer tailwind.config.ts + paths absolus + pagination datatable
- Ajoute tailwind.config.ts aux files du package pour qu'il soit inclus dans le tarball npm
- Convertit les paths content en absolus (via fileURLToPath) pour que Tailwind scanne les composants du layer depuis node_modules côté consommateur
- Aligne la hauteur des boutons de pagination du DataTable (h-10) sur le Select
- Ajuste --m-radius à 6px

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 14:53:20 +02:00
tristan 5acefc1d59 Merge branch 'main' into develop
# Conflicts:
#	CHANGELOG.md
#	COMPONENTS.md
2026-04-17 14:30:39 +02:00
tristan e77bf49146 [#MUI-27] Création d'un composant sélection de site (#29)
Composant MalioSiteSelector : bande horizontale pour choisir un site
(usine ou lieu) parmi une liste. Tuiles flex proportionnelles, couleur
du site sélectionné partagée par toutes les tuiles (opacité 1 / 0.4).
Expose update:modelValue (id) + change (objet site complet) pour
faciliter les appels API côté consommateur.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

| 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: #29
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-04-17 12:28:44 +00:00
tristan f59f866354 Merge branch 'main' into develop 2026-04-16 09:06:04 +02:00
tristan 660c3787fd [#MUI-22] Création d'un composant datatable (#27)
| Numéro du ticket | Titre du ticket |
|------------------|-----------------|
|                  |                 |

## Description de la PR

## Modification du .env

## Check list

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

Reviewed-on: #27
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-04-16 07:00:59 +00:00
tristan e9741ff38d Merge branch 'main' into develop 2026-04-07 14:31:29 +02:00
tristan 32608c8f71 fix : suppression du doublon du composant Checkbox 2026-04-07 14:30:06 +02:00
tristan e1965db04e Merge remote-tracking branch 'origin/main' into develop 2026-04-07 10:14:51 +02:00
tristan 0ad344bab9 fix : style des inputs + hint/success/error 2026-04-07 10:02:11 +02:00
tristan 96719be78d Merge branch 'main' into develop
# Conflicts:
#	COMPONENTS.md
2026-03-26 08:57:02 +01:00
tristan b90baec571 fix : livraison + COMPONENTS.md 2026-03-26 08:54:49 +01:00
tristan 384f86a3b3 Merge remote-tracking branch 'origin/main' into develop
# Conflicts:
#	CHANGELOG.md
2026-03-26 08:39:11 +01:00
tristan e8ddf4e083 [#MUI-24] Fix composant Select (#22)
| 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: #22
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-03-26 07:33:20 +00:00
tristan 7ee64289a8 fix : drawer animation 2026-03-25 08:38:36 +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
9 changed files with 241 additions and 11 deletions
@@ -47,6 +47,31 @@
</p> </p>
</div> </div>
<div class="rounded-lg border-2 border-m-primary p-4 md:col-span-2">
<h2 class="mb-1 text-xl font-bold">allowCreate + BAN (test MUI-48)</h2>
<p class="mb-3 text-sm text-m-muted">
Tapez au moins 3 caractères → suggestions de la Base Adresse Nationale.
<strong>Repro :</strong> sélectionnez une adresse dans la liste, puis
<kbd>Ctrl</kbd>+<kbd>A</kbd> et <kbd>Ctrl</kbd>+<kbd>V</kbd> pour coller une autre valeur
par-dessus. La valeur collée doit rester (le champ ne doit ni se vider, ni faire redescendre le label).
</p>
<MalioInputAutocomplete
v-model="banValue"
label="Adresse"
:options="banOptions"
:loading="banLoading"
:min-search-length="3"
allow-create
icon-name="mdi:map-marker-outline"
icon-position="left"
@search="onSearchBan"
@create="onCreateBan"
/>
<p class="mt-2 text-sm text-m-muted">
v-model : <code>{{ banValue ?? 'null' }}</code>
</p>
</div>
<div class="rounded-lg border p-4"> <div class="rounded-lg border p-4">
<h2 class="mb-4 text-xl font-bold">Avec création (allowCreate)</h2> <h2 class="mb-4 text-xl font-bold">Avec création (allowCreate)</h2>
<MalioInputAutocomplete <MalioInputAutocomplete
@@ -199,4 +224,40 @@ const onSearchApi = async (query: string) => {
const onSelectApi = (option: Option | null) => { const onSelectApi = (option: Option | null) => {
apiSelected.value = option apiSelected.value = option
} }
// allowCreate + BAN (test MUI-48) : recherche nationale sur la Base Adresse Nationale.
const banValue = ref<string | number | null>(null)
const banOptions = ref<Option[]>([])
const banLoading = ref(false)
let banFetchId = 0
const onSearchBan = async (query: string) => {
if (query.length < 3) {
banOptions.value = []
banLoading.value = false
return
}
const requestId = ++banFetchId
banLoading.value = true
try {
const params = new URLSearchParams({q: query, limit: '8'})
const response = await fetch(`https://api-adresse.data.gouv.fr/search/?${params.toString()}`)
const data = await response.json() as {features: {properties: {label: string}}[]}
if (requestId !== banFetchId) return
banOptions.value = data.features.map(f => ({
label: f.properties.label,
value: f.properties.label,
}))
} catch (err) {
if (requestId !== banFetchId) return
banOptions.value = []
console.error('Erreur lors du chargement des adresses BAN', err)
} finally {
if (requestId === banFetchId) banLoading.value = false
}
}
const onCreateBan = (value: string) => {
console.log('BAN valeur libre créée :', value)
}
</script> </script>
@@ -10,6 +10,18 @@
<template #logo-collapsed> <template #logo-collapsed>
<img src="/LOGO_MALIO_COLLAPSED.png" alt="Malio" /> <img src="/LOGO_MALIO_COLLAPSED.png" alt="Malio" />
</template> </template>
<template #footer>
<div class="flex items-center gap-3">
<Icon name="mdi:account-circle" size="32" class="text-m-primary" />
<div class="leading-tight">
<p class="text-[14px] font-semibold text-m-text">Tristan</p>
<p class="text-[12px] text-m-muted">Administrateur</p>
</div>
</div>
</template>
<template #footer-collapsed>
<Icon name="mdi:account-circle" size="28" class="mx-auto block text-m-primary" />
</template>
</MalioSidebar> </MalioSidebar>
<MalioSidebar <MalioSidebar
@@ -22,6 +34,18 @@
<template #logo-collapsed> <template #logo-collapsed>
<img src="/LOGO_MALIO_COLLAPSED.png" alt="Malio" /> <img src="/LOGO_MALIO_COLLAPSED.png" alt="Malio" />
</template> </template>
<template #footer>
<button
type="button"
class="flex w-full items-center gap-2 text-[15px] text-m-text hover:text-m-primary"
>
<Icon name="mdi:logout" size="20" />
Déconnexion
</button>
</template>
<template #footer-collapsed>
<Icon name="mdi:logout" size="20" class="mx-auto block text-m-text" />
</template>
</MalioSidebar> </MalioSidebar>
</div> </div>
</template> </template>
+3
View File
@@ -57,6 +57,7 @@ Liste des évolutions de la librairie Malio layer UI
* [#MUI-44] MalioDate / MalioDateTime : event `update:rawValue` (string) exposant la saisie brute sur un canal séparé pour la validation back-autoritative — saisie invalide (non parsable ou hors `min`/`max`) → texte trimmé tel que tapé, saisie valide/vide + clear + sélection au calendrier → `''`. `modelValue` reste `string` ISO `| null` (la saisie invalide n'y transite jamais) ; le parent construit son payload via `valid ? modelValue : rawValue`. * [#MUI-44] MalioDate / MalioDateTime : event `update:rawValue` (string) exposant la saisie brute sur un canal séparé pour la validation back-autoritative — saisie invalide (non parsable ou hors `min`/`max`) → texte trimmé tel que tapé, saisie valide/vide + clear + sélection au calendrier → `''`. `modelValue` reste `string` ISO `| null` (la saisie invalide n'y transite jamais) ; le parent construit son payload via `valid ? modelValue : rawValue`.
* [#MUI-45] MalioDate : prop `markedDates` (`Record<"YYYY-MM-DD", 'success' | 'danger'>`) appliquant un fond tokenisé par jour dans la grille (générique, fourni par le consommateur ; précédence sélection/`today` > variante marquée > défaut) + event `month-change` (`{ month: 0-11, year }`) émis à l'ouverture du popover et à chaque navigation de mois. Sert l'écran *Heures* de SIRH (jours validés en vert, chargement du mois visible à la volée). * [#MUI-45] MalioDate : prop `markedDates` (`Record<"YYYY-MM-DD", 'success' | 'danger'>`) appliquant un fond tokenisé par jour dans la grille (générique, fourni par le consommateur ; précédence sélection/`today` > variante marquée > défaut) + event `month-change` (`{ month: 0-11, year }`) émis à l'ouverture du popover et à chaque navigation de mois. Sert l'écran *Heures* de SIRH (jours validés en vert, chargement du mois visible à la volée).
* Calendrier (Date/DateRange/DateTime/DateWeek) : sélecteur d'année (3ᵉ niveau de navigation — jours → mois → années) et grisage des mois et années hors `min`/`max`. * Calendrier (Date/DateRange/DateTime/DateWeek) : sélecteur d'année (3ᵉ niveau de navigation — jours → mois → années) et grisage des mois et années hors `min`/`max`.
* MalioSidebar : slots `footer` / `footer-collapsed` pour ajouter un contenu en bas de la sidebar (profil, déconnexion, version…). Toujours collé en bas (la nav `flex-1` le pousse), reste visible quand la liste de liens scrolle ; bordure haute `m-primary` en mode déplié, à l'image du bloc logo.
### Changed ### Changed
* Cohérence du mode **`disabled`** sur toute la famille formulaire (calqué sur InputText : texte + label grisés, `cursor-not-allowed`, aucune affordance interactive). Concrètement, quand `disabled` : le **bouton « + »** d'ajout disparaît (InputPhone, InputEmail), l'**œil** de révélation disparaît (InputPassword), le **chevron** disparaît (Select, SelectCheckbox, InputAutocomplete), la **croix d'effacement** reste masquée (date, upload, time), le **label** passe en `text-m-muted` (Select, SelectCheckbox, famille Date via CalendarField, TimePicker), et les **tags** du SelectCheckbox + la valeur du Select passent en gris. (InputText, InputAmount, InputNumber, InputTextArea, InputRichText, Checkbox, RadioButton, InputUpload étaient déjà conformes.) * Cohérence du mode **`disabled`** sur toute la famille formulaire (calqué sur InputText : texte + label grisés, `cursor-not-allowed`, aucune affordance interactive). Concrètement, quand `disabled` : le **bouton « + »** d'ajout disparaît (InputPhone, InputEmail), l'**œil** de révélation disparaît (InputPassword), le **chevron** disparaît (Select, SelectCheckbox, InputAutocomplete), la **croix d'effacement** reste masquée (date, upload, time), le **label** passe en `text-m-muted` (Select, SelectCheckbox, famille Date via CalendarField, TimePicker), et les **tags** du SelectCheckbox + la valeur du Select passent en gris. (InputText, InputAmount, InputNumber, InputTextArea, InputRichText, Checkbox, RadioButton, InputUpload étaient déjà conformes.)
@@ -71,6 +72,8 @@ Liste des évolutions de la librairie Malio layer UI
* [#MUI-42] Button / ButtonIcon : l'anneau de focus passe du halo `ring-2 ring-m-primary/50` à l'anneau standard `.m-focus-ring` (outline plein, offset 2px), pour l'homogénéité avec les autres composants. * [#MUI-42] Button / ButtonIcon : l'anneau de focus passe du halo `ring-2 ring-m-primary/50` à l'anneau standard `.m-focus-ring` (outline plein, offset 2px), pour l'homogénéité avec les autres composants.
### Fixed ### Fixed
* [#MUI-48] InputAutocomplete : après avoir **sélectionné une suggestion** dans la liste, un **collage qui remplace tout** (Ctrl+A puis Ctrl+V, sans re-cliquer dans le champ) **vidait le champ** au lieu de prendre la valeur collée (label qui redescend, dropdown « Tapez pour rechercher »). Cause : `onSelect` repassait `isFocused` à `false` alors que l'input gardait le focus DOM (option cliquée en `mousedown.prevent`) ; le `watch` de synchronisation, non protégé, remettait `inputValue` à `''`. `onInput` resynchronise désormais `isFocused`. La sélection à la souris n'était pas affectée (re-clic réalignant l'état).
* [#MUI-47] Sidebar : la **bande de ~4px en haut/bas d'un lien** (padding du `<li>` qui porte le fond de hover) était survolée mais **non cliquable**. Le padding vertical passe du `<li>` à l'`<a>` (`py-1`), si bien que toute la zone survolée devient cliquable — sans changement visuel. Les côtés n'étaient pas affectés (`<a>` en `block`, pas de padding horizontal sur le `<li>`).
* Sidebar : le **lien actif** reste actif sur les **sous-routes** (match par préfixe via `useRoute().path` au lieu de l'`active-class` de NuxtLink qui dépendait de l'imbrication des routes) — ex. `/supplier` reste surligné sur `/supplier/1/edit`. Nouvelle option `exact: true` par item pour forcer le match strict. * Sidebar : le **lien actif** reste actif sur les **sous-routes** (match par préfixe via `useRoute().path` au lieu de l'`active-class` de NuxtLink qui dépendait de l'imbrication des routes) — ex. `/supplier` reste surligné sur `/supplier/1/edit`. Nouvelle option `exact: true` par item pour forcer le match strict.
* Famille Date (CalendarField) : le **clic sur le picto calendrier** ouvre désormais le popover (le `<Icon>` en overlay absolu interceptait le clic sans le traiter, et ne le laissait pas retomber sur l'input). Couvre Date, DateTime, DateRange, DateWeek. La croix d'effacement conserve son comportement (efface sans ouvrir). * Famille Date (CalendarField) : le **clic sur le picto calendrier** ouvre désormais le popover (le `<Icon>` en overlay absolu interceptait le clic sans le traiter, et ne le laissait pas retomber sur l'input). Couvre Date, DateTime, DateRange, DateWeek. La croix d'effacement conserve son comportement (efface sans ouvrir).
* Famille Date editable (MalioDate, MalioDateTime) : la saisie clavier est désormais **bornée par champ** sur le premier **et** le second chiffre (jour `01-31`, mois `01-12`, heure `00-23`, minute `00-59`) — une valeur hors plage (`99/99/9999`, un jour `33`, un mois `19`…) ne peut plus être tapée (auparavant saisissable puis rejetée a posteriori par la validation). Les impossibilités calendaires fines (`31/02`, 29/02 non bissextile, hors `min`/`max`) restent captées par la validation. Implémenté via `buildBoundedMask(template)` (CalendarField) : un `preProcess` maska valide chaque champ progressivement (un chiffre n'est accepté que s'il reste une complétion valide dans la plage) ; il distingue le mois des minutes (même lettre `M`) selon la présence d'heures dans le gabarit. * Famille Date editable (MalioDate, MalioDateTime) : la saisie clavier est désormais **bornée par champ** sur le premier **et** le second chiffre (jour `01-31`, mois `01-12`, heure `00-23`, minute `00-59`) — une valeur hors plage (`99/99/9999`, un jour `33`, un mois `19`…) ne peut plus être tapée (auparavant saisissable puis rejetée a posteriori par la validation). Les impossibilités calendaires fines (`31/02`, 29/02 non bissextile, hors `min`/`max`) restent captées par la validation. Implémenté via `buildBoundedMask(template)` (CalendarField) : un `preProcess` maska valide chaque champ progressivement (un chiffre n'est accepté que s'il reste une complétion valide dans la plage) ; il distingue le mois des minutes (même lettre `M`) selon la présence d'heures dans le gabarit.
+5 -1
View File
@@ -893,12 +893,16 @@ Barre latérale de navigation rétractable.
**Lien actif :** un lien est marqué actif (texte `m-primary` + semi-bold) quand la route courante **est ce lien ou une de ses sous-routes** (match par préfixe) — ex. `/supplier` reste actif sur `/supplier/1/edit`. Mettre `exact: true` sur l'item force le match strict (actif uniquement sur la route exacte). Indépendant de l'imbrication des routes côté consommateur. **Lien actif :** un lien est marqué actif (texte `m-primary` + semi-bold) quand la route courante **est ce lien ou une de ses sous-routes** (match par préfixe) — ex. `/supplier` reste actif sur `/supplier/1/edit`. Mettre `exact: true` sur l'item force le match strict (actif uniquement sur la route exacte). Indépendant de l'imbrication des routes côté consommateur.
**Events :** `update:modelValue(value: boolean)` **Events :** `update:modelValue(value: boolean)`
**Slots :** `logo` (sidebar ouverte), `logo-collapsed` (sidebar fermée) **Slots :** `logo` (sidebar ouverte), `logo-collapsed` (sidebar fermée), `footer` (bas, sidebar ouverte), `footer-collapsed` (bas, sidebar fermée)
Le footer est **toujours collé en bas** : la nav occupe l'espace restant (`flex-1`) et pousse le footer vers le bas, qui reste visible même quand la liste de liens scrolle.
```vue ```vue
<MalioSidebar v-model="isOpen" :sections="menuSections"> <MalioSidebar v-model="isOpen" :sections="menuSections">
<template #logo><img src="/logo.png" /></template> <template #logo><img src="/logo.png" /></template>
<template #logo-collapsed><img src="/logo-small.png" /></template> <template #logo-collapsed><img src="/logo-small.png" /></template>
<template #footer><UserProfile /></template>
<template #footer-collapsed><Icon name="mdi:account" /></template>
</MalioSidebar> </MalioSidebar>
``` ```
@@ -1,6 +1,6 @@
import {describe, expect, it, vi} from 'vitest' import {describe, expect, it, vi} from 'vitest'
import {mount} from '@vue/test-utils' import {mount} from '@vue/test-utils'
import type {DefineComponent} from 'vue' import {defineComponent, nextTick, ref, type DefineComponent} from 'vue'
import {Icon as IconifyIcon} from '@iconify/vue' import {Icon as IconifyIcon} from '@iconify/vue'
import InputAutocomplete from './InputAutocomplete.vue' import InputAutocomplete from './InputAutocomplete.vue'
@@ -569,4 +569,40 @@ describe('MalioInputAutocomplete', () => {
expect(msg.exists()).toBe(true) expect(msg.exists()).toBe(true)
expect(msg.classes()).not.toContain('min-h-[1rem]') expect(msg.classes()).not.toContain('min-h-[1rem]')
}) })
// MUI-48 : après avoir sélectionné une option dans la liste, le champ garde le focus DOM
// mais isFocused interne passe à false (clic option en mousedown.prevent). Un collage qui
// remplace tout (Ctrl+A puis Ctrl+V) déclenche update:modelValue(null) ; le watch ne doit
// PAS vider la valeur collée. Régression : le champ se vidait au lieu de prendre le texte collé.
it('MUI-48 : un collage après sélection dans la liste remplace la valeur (ne la vide pas)', async () => {
const Harness = defineComponent({
components: {InputAutocomplete},
setup() {
const val = ref<string | number | null>(null)
const opts = ref([{label: '10 Rue de la Paix', value: '10 Rue de la Paix'}])
return {val, opts}
},
template: '<InputAutocomplete v-model="val" :options="opts" allow-create />',
})
const wrapper = mount(Harness, {
attachTo: document.body,
global: {stubs: {IconifyIcon: {template: '<span data-test="icon" v-bind="$attrs" />'}}},
})
const input = wrapper.get('input')
// saisie puis sélection d'une suggestion (commit, focus DOM conservé)
await input.trigger('focus')
await input.setValue('10')
await wrapper.findAll('[data-test="option"]')[0].trigger('click')
await nextTick()
expect(input.element.value).toBe('10 Rue de la Paix')
// Ctrl+A puis Ctrl+V : input toujours focalisé DOM, aucun nouvel évènement focus
await input.setValue('25 Avenue Victor Hugo')
await nextTick()
expect(input.element.value).toBe('25 Avenue Victor Hugo')
wrapper.unmount()
})
}) })
@@ -393,6 +393,11 @@ const scheduleSearch = () => {
const onInput = (event: Event) => { const onInput = (event: Event) => {
const target = event.target as HTMLInputElement const target = event.target as HTMLInputElement
// Un évènement input prouve que le champ est en cours d'édition : on resynchronise
// isFocused, qu'une sélection précédente (onSelect) a pu passer à false tout en gardant
// le focus DOM (clic option en mousedown.prevent). Sans ça, le watch ci-dessous remettrait
// inputValue à '' au collage et la valeur collée serait perdue (MUI-48).
isFocused.value = true
inputValue.value = target.value inputValue.value = target.value
if (!isOpen.value) isOpen.value = true if (!isOpen.value) isOpen.value = true
activeIndex.value = -1 activeIndex.value = -1
+49 -7
View File
@@ -107,19 +107,24 @@ describe('MalioSidebar', () => {
expect(links[2].attributes('href')).toBe('/fournisseurs') expect(links[2].attributes('href')).toBe('/fournisseurs')
}) })
it('hover : fond + couleur + semi-bold tous portés par le <li> (texte non figé sur le <a>)', () => { it('hover : fond + couleur + semi-bold portés par le <li>', () => {
const wrapper = mountComponent({sections}) const wrapper = mountComponent({sections})
const li = wrapper.find('li') const li = wrapper.find('li')
expect(li.classes()).toContain('hover:bg-m-primary/10') expect(li.classes()).toContain('hover:bg-m-primary/10')
expect(li.classes()).toContain('hover:text-m-primary') expect(li.classes()).toContain('hover:text-m-primary')
expect(li.classes()).toContain('hover:font-semibold') expect(li.classes()).toContain('hover:font-semibold')
expect(li.classes()).toContain('text-black') expect(li.classes()).toContain('text-black')
expect(li.classes()).toContain('pt-1') })
expect(li.classes()).toContain('pb-1')
// Le <a> ne fige PAS sa couleur (sinon le texte resterait noir sur les bandes it('zone cliquable : le padding vertical est sur le <a>, pas sur le <li> (pas de bande morte au survol)', () => {
// pt-1/pb-1 hors du <a> alors que le fond du <li> est bleu). const wrapper = mountComponent({sections})
expect(wrapper.find('a').classes()).not.toContain('text-black') // Le padding vertical doit appartenir à la cible de clic (<a>) pour que toute
expect(wrapper.find('a').classes()).not.toContain('hover:text-m-primary') // la bande survolée soit cliquable — sinon pt-1/pb-1 sur le <li> crée une
// bande colorée mais non cliquable en haut et en bas du lien.
const li = wrapper.find('li')
expect(li.classes()).not.toContain('pt-1')
expect(li.classes()).not.toContain('pb-1')
expect(wrapper.find('a').classes()).toContain('py-1')
}) })
it('actif : route exacte → lien en primary + semi-bold, sans fond', () => { it('actif : route exacte → lien en primary + semi-bold, sans fond', () => {
@@ -239,6 +244,43 @@ describe('MalioSidebar', () => {
expect(wrapper.find('img[alt="M"]').exists()).toBe(true) expect(wrapper.find('img[alt="M"]').exists()).toBe(true)
}) })
it('renders footer slot when expanded', () => {
const wrapper = mountComponent({sections}, {
footer: '<a href="/logout">Déconnexion</a>',
})
expect(wrapper.find('a[href="/logout"]').exists()).toBe(true)
expect(wrapper.text()).toContain('Déconnexion')
})
it('renders footer-collapsed slot when collapsed', async () => {
const wrapper = mountComponent({sections}, {
'footer-collapsed': '<span>FC</span>',
})
await wrapper.find('button').trigger('click')
expect(wrapper.text()).toContain('FC')
})
it('footer is rendered after the nav (pushed to the bottom)', () => {
const wrapper = mountComponent({sections}, {
footer: '<span class="ft">Footer</span>',
})
const children = wrapper.find('aside').element.children
const navIndex = Array.from(children).findIndex(el => el.tagName === 'NAV')
const footerEl = wrapper.find('.ft').element
const footerWrapperIndex = Array.from(children).findIndex(el => el.contains(footerEl))
expect(footerWrapperIndex).toBeGreaterThan(navIndex)
})
it('does not render a footer container when no footer slot is provided', () => {
const wrapper = mountComponent({sections})
// Seuls le bloc logo et le <nav> sont des conteneurs (+ le bouton toggle).
// Aucun div de footer ne doit apparaître après le <nav>.
const children = Array.from(wrapper.find('aside').element.children)
const navIndex = children.findIndex(el => el.tagName === 'NAV')
const after = children.slice(navIndex + 1)
expect(after.some(el => el.tagName === 'DIV')).toBe(false)
})
it('uses custom id when provided', () => { it('uses custom id when provided', () => {
const wrapper = mountComponent({sections, id: 'my-sidebar'}) const wrapper = mountComponent({sections, id: 'my-sidebar'})
expect(wrapper.find('aside').attributes('id')).toBe('my-sidebar') expect(wrapper.find('aside').attributes('id')).toBe('my-sidebar')
+16 -2
View File
@@ -49,14 +49,14 @@
<li <li
v-for="item in section.items" v-for="item in section.items"
:key="item.to" :key="item.to"
:class="collapsed ? '' : 'text-black hover:bg-m-primary/10 hover:font-semibold hover:text-m-primary pt-1 pb-1'" :class="collapsed ? '' : 'text-black hover:bg-m-primary/10 hover:font-semibold hover:text-m-primary'"
> >
<NuxtLink <NuxtLink
:to="item.to" :to="item.to"
active-class="!text-m-primary font-semibold" active-class="!text-m-primary font-semibold"
:class="twMerge( :class="twMerge(
'block truncate text-[15px] leading-[150%]', 'block truncate text-[15px] leading-[150%]',
collapsed ? 'px-3 text-center' : 'pl-[32px]', collapsed ? 'px-3 text-center' : 'pl-[32px] py-1',
isActive(item) ? '!text-m-primary font-semibold' : '', isActive(item) ? '!text-m-primary font-semibold' : '',
)" )"
> >
@@ -67,6 +67,20 @@
</div> </div>
</nav> </nav>
<div
v-if="$slots.footer || $slots['footer-collapsed']"
:class="['px-[20px] py-[14px]', collapsed ? '' : 'mx-[10px] border-t-2 border-m-primary']"
>
<slot
v-if="collapsed"
name="footer-collapsed"
/>
<slot
v-else
name="footer"
/>
</div>
<button <button
type="button" type="button"
:aria-label="collapsed ? 'Déplier le menu' : 'Plier le menu'" :aria-label="collapsed ? 'Déplier le menu' : 'Plier le menu'"
+41
View File
@@ -43,6 +43,37 @@
</div> </div>
</div> </div>
</Variant> </Variant>
<Variant title="Avec footer (collé en bas)">
<div class="flex h-[600px] border rounded-lg overflow-hidden">
<MalioSidebar
v-model="collapsed3"
:sections="sectionsLong"
>
<template #logo>
<span class="text-2xl font-bold text-m-primary">Malio</span>
</template>
<template #logo-collapsed>
<span class="text-2xl font-bold text-m-primary">M</span>
</template>
<template #footer>
<div class="leading-tight">
<p class="text-[14px] font-semibold text-m-text">Tristan</p>
<p class="text-[12px] text-m-muted">Administrateur</p>
</div>
</template>
<template #footer-collapsed>
<span class="block text-center text-[14px] font-bold text-m-primary">T</span>
</template>
</MalioSidebar>
<div class="flex-1 p-6 bg-white">
<p class="text-m-muted">
Le footer reste collé en bas même quand la nav scrolle.
</p>
</div>
</div>
</Variant>
</Story> </Story>
</template> </template>
@@ -94,6 +125,15 @@ entre les deux états.
- Contenu affiché en haut quand la sidebar est pliée. - Contenu affiché en haut quand la sidebar est pliée.
### footer
- Contenu affiché en bas quand la sidebar est dépliée (profil, déconnexion, version).
- Toujours collé en bas : la nav occupe l'espace restant (`flex-1`) et pousse le footer.
### footer-collapsed
- Contenu affiché en bas quand la sidebar est pliée.
------------------------------------------------------------------------ ------------------------------------------------------------------------
## Comportement ## Comportement
@@ -127,6 +167,7 @@ import MalioSidebar from '../../components/malio/sidebar/Sidebar.vue'
const collapsed1 = ref(false) const collapsed1 = ref(false)
const collapsed2 = ref(false) const collapsed2 = ref(false)
const collapsed3 = ref(false)
const sectionsShort = [ const sectionsShort = [
{ {