feat : MonthGrid mode plage (surlignage demi-barre + hover + data-range-role) (#MUI-33)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div data-test="month-grid">
|
<div
|
||||||
|
data-test="month-grid"
|
||||||
|
@mouseleave="emit('hover', null)"
|
||||||
|
>
|
||||||
<div class="grid grid-cols-[auto_repeat(7,minmax(0,1fr))]">
|
<div class="grid grid-cols-[auto_repeat(7,minmax(0,1fr))]">
|
||||||
<div class="mr-[12px] flex h-8 w-[35px] items-center justify-center text-[14px] font-medium opacity-[60%]">
|
<div class="mr-[12px] flex h-8 w-[35px] items-center justify-center text-[14px] font-medium opacity-[60%]">
|
||||||
S
|
S
|
||||||
@@ -33,15 +36,29 @@
|
|||||||
type="button"
|
type="button"
|
||||||
data-test="day"
|
data-test="day"
|
||||||
:data-iso="cell.isoDate"
|
:data-iso="cell.isoDate"
|
||||||
|
:data-range-role="roleOf(cell)"
|
||||||
:disabled="!inRange(cell.isoDate)"
|
:disabled="!inRange(cell.isoDate)"
|
||||||
:aria-label="ariaLabel(cell)"
|
:aria-label="ariaLabel(cell)"
|
||||||
:aria-disabled="!inRange(cell.isoDate)"
|
:aria-disabled="!inRange(cell.isoDate)"
|
||||||
class="flex h-[45px] w-full items-center justify-center"
|
class="relative flex h-[45px] w-full items-center justify-center"
|
||||||
:class="inRange(cell.isoDate) ? 'cursor-pointer' : 'cursor-not-allowed'"
|
:class="inRange(cell.isoDate) ? 'cursor-pointer' : 'cursor-not-allowed'"
|
||||||
@click="onSelect(cell.isoDate)"
|
@click="onSelect(cell.isoDate)"
|
||||||
|
@mouseenter="emit('hover', cell.isoDate)"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="flex h-10 w-10 items-center justify-center rounded-full text-sm font-medium transition-colors duration-100"
|
v-if="roleOf(cell) === 'in-range'"
|
||||||
|
class="absolute inset-0 bg-m-primary-light"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
v-else-if="roleOf(cell) === 'start'"
|
||||||
|
class="absolute inset-y-0 left-1/2 right-0 bg-m-primary-light"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
v-else-if="roleOf(cell) === 'end'"
|
||||||
|
class="absolute inset-y-0 left-0 right-1/2 bg-m-primary-light"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class="relative flex h-10 w-10 items-center justify-center rounded-full text-sm font-medium transition-colors duration-100"
|
||||||
:class="cellClass(cell)"
|
:class="cellClass(cell)"
|
||||||
>
|
>
|
||||||
{{ cell.day }}
|
{{ cell.day }}
|
||||||
@@ -53,9 +70,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {toRef} from 'vue'
|
import {computed, toRef} from 'vue'
|
||||||
import {useMonthMatrix, type DayCell} from '../composables/useMonthMatrix'
|
import {useMonthMatrix, type DayCell} from '../composables/useMonthMatrix'
|
||||||
import {isDateInRange} from '../composables/dateFormat'
|
import {isDateInRange} from '../composables/dateFormat'
|
||||||
|
import {dayRangeRole, resolveRangeBounds, type DayRangeRole} from '../composables/dateRange'
|
||||||
|
|
||||||
defineOptions({name: 'MalioDateMonthGrid'})
|
defineOptions({name: 'MalioDateMonthGrid'})
|
||||||
|
|
||||||
@@ -64,13 +82,26 @@ const props = withDefaults(
|
|||||||
month: number
|
month: number
|
||||||
year: number
|
year: number
|
||||||
selectedDate?: string | null
|
selectedDate?: string | null
|
||||||
|
rangeStart?: string | null
|
||||||
|
rangeEnd?: string | null
|
||||||
|
previewDate?: string | null
|
||||||
min?: string
|
min?: string
|
||||||
max?: string
|
max?: string
|
||||||
}>(),
|
}>(),
|
||||||
{selectedDate: null, min: undefined, max: undefined},
|
{
|
||||||
|
selectedDate: null,
|
||||||
|
rangeStart: undefined,
|
||||||
|
rangeEnd: undefined,
|
||||||
|
previewDate: undefined,
|
||||||
|
min: undefined,
|
||||||
|
max: undefined,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
const emit = defineEmits<{(e: 'select', iso: string): void}>()
|
const emit = defineEmits<{
|
||||||
|
(e: 'select', iso: string): void
|
||||||
|
(e: 'hover', iso: string | null): void
|
||||||
|
}>()
|
||||||
|
|
||||||
const dayLabels = ['Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam', 'Dim']
|
const dayLabels = ['Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam', 'Dim']
|
||||||
const monthsLong = ['janvier', 'février', 'mars', 'avril', 'mai', 'juin',
|
const monthsLong = ['janvier', 'février', 'mars', 'avril', 'mai', 'juin',
|
||||||
@@ -80,15 +111,28 @@ const {weeks} = useMonthMatrix(toRef(props, 'month'), toRef(props, 'year'))
|
|||||||
|
|
||||||
const inRange = (iso: string) => isDateInRange(iso, props.min, props.max)
|
const inRange = (iso: string) => isDateInRange(iso, props.min, props.max)
|
||||||
|
|
||||||
|
const isRangeMode = computed(() => props.rangeStart !== undefined)
|
||||||
|
const bounds = computed(() =>
|
||||||
|
isRangeMode.value
|
||||||
|
? resolveRangeBounds(props.rangeStart ?? null, props.rangeEnd ?? null, props.previewDate ?? null)
|
||||||
|
: null,
|
||||||
|
)
|
||||||
|
|
||||||
|
const roleOf = (cell: DayCell): DayRangeRole => {
|
||||||
|
if (isRangeMode.value) return dayRangeRole(cell.isoDate, bounds.value)
|
||||||
|
return props.selectedDate === cell.isoDate ? 'single' : 'none'
|
||||||
|
}
|
||||||
|
|
||||||
const ariaLabel = (cell: DayCell) => {
|
const ariaLabel = (cell: DayCell) => {
|
||||||
const [, m, d] = cell.isoDate.split('-')
|
const [, m, d] = cell.isoDate.split('-')
|
||||||
return `${Number(d)} ${monthsLong[Number(m) - 1]} ${cell.isoDate.slice(0, 4)}`
|
return `${Number(d)} ${monthsLong[Number(m) - 1]} ${cell.isoDate.slice(0, 4)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const cellClass = (cell: DayCell) => {
|
const cellClass = (cell: DayCell) => {
|
||||||
const selected = props.selectedDate === cell.isoDate
|
|
||||||
if (!inRange(cell.isoDate)) return 'text-m-muted/30'
|
if (!inRange(cell.isoDate)) return 'text-m-muted/30'
|
||||||
if (selected) return 'bg-m-primary text-white'
|
const role = roleOf(cell)
|
||||||
|
if (role === 'start' || role === 'end' || role === 'single') return 'bg-m-primary text-white'
|
||||||
|
if (role === 'in-range') return 'text-black'
|
||||||
const parts = ['hover:bg-m-primary/10']
|
const parts = ['hover:bg-m-primary/10']
|
||||||
if (cell.isToday) parts.push('border border-m-primary text-m-primary')
|
if (cell.isToday) parts.push('border border-m-primary text-m-primary')
|
||||||
else if (cell.isCurrentMonth) parts.push('text-black')
|
else if (cell.isCurrentMonth) parts.push('text-black')
|
||||||
|
|||||||
Reference in New Issue
Block a user