feat : composant YearPicker avec bornage min/max (#date-year-picker)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,36 @@
|
|||||||
|
import {describe, expect, it} from 'vitest'
|
||||||
|
import {mount} from '@vue/test-utils'
|
||||||
|
import YearPicker from './YearPicker.vue'
|
||||||
|
|
||||||
|
const mountPicker = (props: {pageStart: number, selectedYear?: number, min?: string, max?: string}) =>
|
||||||
|
mount(YearPicker, {props})
|
||||||
|
|
||||||
|
describe('MalioDateYearPicker', () => {
|
||||||
|
it('renders 12 years from pageStart', () => {
|
||||||
|
const wrapper = mountPicker({pageStart: 2021})
|
||||||
|
const years = wrapper.findAll('[data-test="year"]')
|
||||||
|
expect(years).toHaveLength(12)
|
||||||
|
expect(years[0].attributes('data-year')).toBe('2021')
|
||||||
|
expect(years[11].attributes('data-year')).toBe('2032')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('emits select with the clicked year', async () => {
|
||||||
|
const wrapper = mountPicker({pageStart: 2021})
|
||||||
|
await wrapper.get('[data-test="year"][data-year="2026"]').trigger('click')
|
||||||
|
expect(wrapper.emitted('select')?.[0]).toEqual([2026])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('disables years outside [min, max] and does not emit', async () => {
|
||||||
|
const wrapper = mountPicker({pageStart: 2021, min: '2025-01-01', max: '2027-12-31'})
|
||||||
|
const out = wrapper.get('[data-test="year"][data-year="2024"]')
|
||||||
|
expect(out.attributes('disabled')).toBeDefined()
|
||||||
|
await out.trigger('click')
|
||||||
|
expect(wrapper.emitted('select')).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('highlights the selected year', () => {
|
||||||
|
const wrapper = mountPicker({pageStart: 2021, selectedYear: 2026})
|
||||||
|
const span = wrapper.get('[data-test="year"][data-year="2026"] span')
|
||||||
|
expect(span.classes()).toContain('bg-m-primary')
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
data-test="year-picker"
|
||||||
|
class="grid grid-cols-3 gap-3"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
v-for="year in years"
|
||||||
|
:key="year"
|
||||||
|
type="button"
|
||||||
|
data-test="year"
|
||||||
|
:data-year="year"
|
||||||
|
:disabled="!isYearInRange(year, min, max)"
|
||||||
|
:aria-disabled="!isYearInRange(year, min, max)"
|
||||||
|
class="flex h-[45px] w-full items-center justify-center"
|
||||||
|
:class="isYearInRange(year, min, max) ? 'cursor-pointer' : 'cursor-not-allowed'"
|
||||||
|
@click="emit('select', year)"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="flex h-[30px] w-full items-center justify-center rounded text-sm transition-colors duration-100"
|
||||||
|
:class="year === selectedYear
|
||||||
|
? 'bg-m-primary text-white'
|
||||||
|
: isYearInRange(year, min, max)
|
||||||
|
? 'text-black hover:bg-m-primary/10'
|
||||||
|
: 'text-m-muted/30'"
|
||||||
|
>
|
||||||
|
{{ year }}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {computed} from 'vue'
|
||||||
|
import {isYearInRange} from '../composables/dateFormat'
|
||||||
|
|
||||||
|
defineOptions({name: 'MalioDateYearPicker'})
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
pageStart: number
|
||||||
|
selectedYear?: number
|
||||||
|
min?: string
|
||||||
|
max?: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{(e: 'select', year: number): void}>()
|
||||||
|
|
||||||
|
const years = computed(() => Array.from({length: 12}, (_, i) => props.pageStart + i))
|
||||||
|
</script>
|
||||||
Reference in New Issue
Block a user