feat(ui) : MalioDate — markedDates (statut par jour) + event month-change (#MUI-45)

MonthGrid : prop markedDates (Record<ISO, 'success'|'danger'>) appliquant un fond
tokenisé par jour (bg-m-success/15 / bg-m-danger/15). Précédence : sélection (primary)
> variante marquée ; today garde sa bordure ET reçoit le fond marqué.

CalendarField : emit month-change { month: 0-11, year } à l'ouverture du popover et
à chaque navigation de mois (watch sur isOpen + currentMonth/currentYear).

Date : expose markedDates (passée à MonthGrid via le slot) et réémet month-change.

Tests MonthGrid (variantes + précédence today/sélection) et Date (month-change à
l'ouverture/nav + passthrough markedDates). Doc COMPONENTS.md + CHANGELOG + story +
playground. Sert l'écran Heures de SIRH (jours validés en vert, chargement du mois visible).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-16 11:38:58 +02:00
parent cca15524f4
commit f2c1845ea1
9 changed files with 225 additions and 2 deletions
+43
View File
@@ -17,6 +17,7 @@ type DateProps = {
success?: string
min?: string
max?: string
markedDates?: Record<string, 'success' | 'danger'>
clearable?: boolean
editable?: boolean
invalidMessage?: string
@@ -109,6 +110,48 @@ describe('MalioDate', () => {
})
})
describe('month-change', () => {
it('émet month-change à l\'ouverture avec le mois courant', async () => {
const wrapper = mountDate()
await wrapper.get('[data-test="date-input"]').trigger('click')
expect(wrapper.emitted('month-change')?.at(-1)).toEqual([{month: 4, year: 2026}])
})
it('émet month-change sur le mois de la valeur à l\'ouverture', async () => {
const wrapper = mountDate({modelValue: '2025-12-25'})
await wrapper.get('[data-test="date-input"]').trigger('click')
expect(wrapper.emitted('month-change')?.at(-1)).toEqual([{month: 11, year: 2025}])
})
it('émet month-change à chaque navigation de mois', async () => {
const wrapper = mountDate()
await wrapper.get('[data-test="date-input"]').trigger('click')
await wrapper.get('[data-test="header-next"]').trigger('click')
expect(wrapper.emitted('month-change')?.at(-1)).toEqual([{month: 5, year: 2026}])
await wrapper.get('[data-test="header-prev"]').trigger('click')
await wrapper.get('[data-test="header-prev"]').trigger('click')
expect(wrapper.emitted('month-change')?.at(-1)).toEqual([{month: 3, year: 2026}])
})
it('ne ré-émet pas month-change après fermeture', async () => {
const wrapper = mountDate()
await wrapper.get('[data-test="date-input"]').trigger('click')
const countOpen = wrapper.emitted('month-change')?.length ?? 0
document.body.dispatchEvent(new MouseEvent('mousedown', {bubbles: true}))
await wrapper.vm.$nextTick()
expect(wrapper.emitted('month-change')?.length ?? 0).toBe(countOpen)
})
})
describe('markedDates', () => {
it('transmet markedDates à la grille (fond tokenisé)', async () => {
const wrapper = mountDate({markedDates: {'2026-05-20': 'success'}})
await wrapper.get('[data-test="date-input"]').trigger('click')
const pill = wrapper.get('[data-iso="2026-05-20"]').get('span.rounded-full')
expect(pill.classes()).toContain('bg-m-success/15')
})
})
describe('sélection', () => {
it('emits the ISO date and closes on day click', async () => {
const wrapper = mountDate()