121 lines
4.1 KiB
Vue
121 lines
4.1 KiB
Vue
<script setup lang="ts">
|
|
import { type HTMLAttributes, type Ref, computed, toRef } from 'vue'
|
|
import { CalendarRoot, type CalendarRootEmits, type CalendarRootProps, toDate, useForwardPropsEmits } from 'radix-vue'
|
|
import { type DateValue, getLocalTimeZone, today } from '@internationalized/date'
|
|
import { CalendarCell, CalendarCellTrigger, CalendarGrid, CalendarGridBody, CalendarGridHead, CalendarGridRow, CalendarHeadCell, CalendarHeader, CalendarHeading } from '@/lib/registry/new-york/ui/calendar'
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from '@/lib/registry/new-york/ui/select'
|
|
import { cn } from '@/lib/utils'
|
|
|
|
const props = withDefaults(defineProps<CalendarRootProps & { class?: HTMLAttributes['class'] }>(), {
|
|
modelValue: undefined,
|
|
placeholder() {
|
|
return today(getLocalTimeZone())
|
|
},
|
|
weekdayFormat: 'short',
|
|
})
|
|
const emits = defineEmits<CalendarRootEmits>()
|
|
|
|
const delegatedProps = computed(() => {
|
|
const { class: _, placeholder: __, ...delegated } = props
|
|
|
|
return delegated
|
|
})
|
|
const placeholder = toRef(props.placeholder) as Ref<DateValue>
|
|
|
|
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
|
</script>
|
|
|
|
<template>
|
|
<CalendarRoot
|
|
|
|
v-slot="{ getMonths, getYears, formatter, grid, weekDays }"
|
|
v-model:placeholder="placeholder"
|
|
v-bind="forwarded"
|
|
:class="cn('rounded-md border p-3', props.class)"
|
|
>
|
|
<CalendarHeader>
|
|
<CalendarHeading class="flex w-full items-center justify-between gap-2">
|
|
<Select
|
|
:default-value="placeholder.month.toString()"
|
|
@update:model-value="(v) => {
|
|
if (!v || !placeholder) return;
|
|
if (Number(v) === placeholder?.month) return;
|
|
placeholder = placeholder.set({
|
|
month: Number(v),
|
|
})
|
|
}"
|
|
>
|
|
<SelectTrigger aria-label="Select month" class="w-[60%]">
|
|
<SelectValue placeholder="Select month" />
|
|
</SelectTrigger>
|
|
<SelectContent class="max-h-[200px]">
|
|
<SelectItem
|
|
v-for="month in getMonths"
|
|
:key="month.toString()" :value="month.month.toString()"
|
|
>
|
|
{{ formatter.custom(toDate(month), { month: 'long' }) }}
|
|
</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
|
|
<Select
|
|
:default-value="props.placeholder.year.toString()"
|
|
@update:model-value="(v) => {
|
|
if (!v || !placeholder) return;
|
|
if (Number(v) === placeholder?.year) return;
|
|
placeholder = placeholder.set({
|
|
year: Number(v),
|
|
})
|
|
}"
|
|
>
|
|
<SelectTrigger aria-label="Select year" class="w-[40%]">
|
|
<SelectValue placeholder="Select year" />
|
|
</SelectTrigger>
|
|
<SelectContent class="max-h-[200px]">
|
|
<SelectItem
|
|
v-for="yearValue in getYears({ startIndex: -10, endIndex: 10 })"
|
|
:key="yearValue.toString()" :value="yearValue.year.toString()"
|
|
>
|
|
{{ yearValue.year }}
|
|
</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</CalendarHeading>
|
|
</CalendarHeader>
|
|
|
|
<div class="flex flex-col space-y-4 pt-4 sm:flex-row sm:gap-x-4 sm:gap-y-0">
|
|
<CalendarGrid v-for="month in grid" :key="month.value.toString()">
|
|
<CalendarGridHead>
|
|
<CalendarGridRow class="mb-1 grid w-full grid-cols-7">
|
|
<CalendarHeadCell
|
|
v-for="day in weekDays" :key="day"
|
|
>
|
|
{{ day }}
|
|
</CalendarHeadCell>
|
|
</CalendarGridRow>
|
|
</CalendarGridHead>
|
|
<CalendarGridBody class="grid">
|
|
<CalendarGridRow v-for="(weekDates, index) in month.rows" :key="`weekDate-${index}`" class="grid grid-cols-7">
|
|
<CalendarCell
|
|
v-for="weekDate in weekDates"
|
|
:key="weekDate.toString()"
|
|
:date="weekDate"
|
|
>
|
|
<CalendarCellTrigger
|
|
:day="weekDate"
|
|
:month="month.value"
|
|
/>
|
|
</CalendarCell>
|
|
</CalendarGridRow>
|
|
</CalendarGridBody>
|
|
</CalendarGrid>
|
|
</div>
|
|
</CalendarRoot>
|
|
</template>
|