-
I am trying to create a date range picker element for a form, I have tried using the regular date picker element as an example to go off of, but after some attempts I could not get it to work properly... Here's the code: <script setup lang="ts">
import { computed, type HTMLAttributes } from 'vue';
import { type DateValue, getLocalTimeZone, today } from '@internationalized/date';
import {
RangeCalendarRoot,
type RangeCalendarRootProps,
type RangeCalendarRootEmits,
useDateFormatter,
useForwardPropsEmits,
} from 'radix-vue';
import { toDate } from 'radix-vue/date';
import {
RangeCalendarCell,
RangeCalendarCellTrigger,
RangeCalendarGrid,
RangeCalendarGridBody,
RangeCalendarGridHead,
RangeCalendarGridRow,
RangeCalendarHeadCell,
} from '@/components/ui/range-calendar';
import { Button, buttonVariants } from '@/components/ui/button';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { cn } from '@/lib/utils';
import { ChevronLeftIcon, ChevronRightIcon, CalendarIcon } from '@radix-icons/vue';
import { useVModel } from '@vueuse/core';
const props = withDefaults(
defineProps<RangeCalendarRootProps & { class?: HTMLAttributes['class'] }>(),
{
modelValue: undefined,
weekdayFormat: 'short',
placeholder() {
return today(getLocalTimeZone());
},
},
);
const emits = defineEmits<RangeCalendarRootEmits>();
const delegatedProps = computed(() => {
const { class: _, placeholder: __, ...delegated } = props;
return delegated;
});
const placeholder = useVModel(props, 'modelValue', emits, {
passive: true,
});
const forwarded = useForwardPropsEmits(delegatedProps, emits);
const formatter = useDateFormatter('en');
</script>
<template>
<Popover>
<PopoverTrigger as-child>
<Button
variant="outline"
:class="
cn(
'w-[280px] justify-start text-left font-normal',
!placeholder && 'text-muted-foreground',
)
"
>
<CalendarIcon class="mr-2 h-4 w-4" />
<template v-if="placeholder">
<template v-if="placeholder.end && placeholder.start">
{{
formatter.custom(toDate(placeholder.start as DateValue), {
dateStyle: 'medium',
})
}}
-
{{
formatter.custom(toDate(placeholder.end as DateValue), {
dateStyle: 'medium',
})
}}
</template>
<template v-else>
{{
formatter.custom(toDate(placeholder.start as DateValue), {
dateStyle: 'medium',
})
}}
</template>
</template>
<template v-else> Pick a date </template>
</Button>
</PopoverTrigger>
<PopoverContent class="w-auto p-0">
<RangeCalendarRoot
v-slot="{ weekDays, grid }"
v-bind="forwarded"
class="p-3"
>
<div class="flex flex-col gap-y-4 mt-4 sm:flex-row sm:gap-x-4 sm:gap-y-0">
<div class="flex flex-col gap-4">
<div class="flex items-center justify-between">
<button
:class="
cn(
buttonVariants({ variant: 'outline' }),
'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100',
)
"
>
<ChevronLeftIcon class="h-4 w-4" />
</button>
<div class="text-sm font-medium">
{{ formatter.fullMonthAndYear(toDate(placeholder!.start as DateValue)) }}
</div>
<button
:class="
cn(
buttonVariants({ variant: 'outline' }),
'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100',
)
"
>
<ChevronRightIcon class="h-4 w-4" />
</button>
</div>
<RangeCalendarGrid v-for="month in grid" :key="month.value.toString()">
<RangeCalendarGridHead>
<RangeCalendarGridRow>
<RangeCalendarHeadCell v-for="day in weekDays" :key="day">{{
day
}}</RangeCalendarHeadCell>
</RangeCalendarGridRow>
</RangeCalendarGridHead>
<RangeCalendarGridBody>
<RangeCalendarGridRow
v-for="(weekDates, index) in month"
:key="`weekDate-${index}`"
class="mt-2 w-full"
>
<RangeCalendarCell
v-for="weekDate in weekDates"
:key="weekDate.toString()"
:date="weekDate"
>
<RangeCalendarCellTrigger :day="weekDate" :month="month.value" />
</RangeCalendarCell>
</RangeCalendarGridRow>
</RangeCalendarGridBody>
</RangeCalendarGrid>
</div>
<div class="flex flex-col gap-4">
<div class="flex items-center justify-between">
<button
:class="
cn(
buttonVariants({ variant: 'outline' }),
'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100',
)
"
>
<ChevronLeftIcon class="h-4 w-4" />
</button>
<div class="text-sm font-medium">
{{ formatter.fullMonthAndYear(toDate(placeholder!.end as DateValue)) }}
</div>
<button
:class="
cn(
buttonVariants({ variant: 'outline' }),
'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100',
)
"
>
<ChevronRightIcon class="h-4 w-4" />
</button>
</div>
<RangeCalendarGrid v-for="month in grid" :key="month.value.toString()">
<RangeCalendarGridHead>
<RangeCalendarGridRow>
<RangeCalendarHeadCell v-for="day in weekDays" :key="day">{{
day
}}</RangeCalendarHeadCell>
</RangeCalendarGridRow>
</RangeCalendarGridHead>
<RangeCalendarGridBody>
<RangeCalendarGridRow
v-for="(weekDates, index) in month"
:key="`weekDate-${index}`"
class="mt-2 w-full"
>
<RangeCalendarCell
v-for="weekDate in weekDates"
:key="weekDate.toString()"
:date="weekDate"
>
<RangeCalendarCellTrigger :day="weekDate" :month="month.value" />
</RangeCalendarCell>
</RangeCalendarGridRow>
</RangeCalendarGridBody>
</RangeCalendarGrid>
</div>
</div>
</RangeCalendarRoot>
</PopoverContent>
</Popover>
</template> |
Beta Was this translation helpful? Give feedback.
Answered by
JasonGH17
Sep 14, 2024
Replies: 1 comment
-
I was able to get it working with the regular single month view date range calendar, I also added a month and year picker for convenience. Just make sure to pass in a valid DateRange object to its v-model. <script setup lang="ts">
import { computed, type HTMLAttributes } from 'vue';
import { type DateValue, getLocalTimeZone, today } from '@internationalized/date';
import { createDecade, createYear } from 'radix-vue/date';
import {
RangeCalendarRoot,
type RangeCalendarRootProps,
type RangeCalendarRootEmits,
useDateFormatter,
useForwardPropsEmits,
} from 'radix-vue';
import { toDate } from 'radix-vue/date';
import {
RangeCalendarCell,
RangeCalendarCellTrigger,
RangeCalendarGrid,
RangeCalendarGridBody,
RangeCalendarGridHead,
RangeCalendarGridRow,
RangeCalendarHeadCell,
RangeCalendarHeader,
RangeCalendarPrevButton,
RangeCalendarHeading,
RangeCalendarNextButton,
} from '@/components/ui/range-calendar';
import {
Select,
SelectTrigger,
SelectValue,
SelectContent,
SelectItem,
} from '@/components/ui/select';
import { Button } from '@/components/ui/button';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { cn } from '@/lib/utils';
import { CalendarIcon } from '@radix-icons/vue';
import { useVModel } from '@vueuse/core';
const props = withDefaults(
defineProps<RangeCalendarRootProps & { class?: HTMLAttributes['class'] }>(),
{
modelValue: undefined,
weekdayFormat: 'short',
placeholder() {
return today(getLocalTimeZone());
},
},
);
const emits = defineEmits<RangeCalendarRootEmits>();
const delegatedProps = computed(() => {
const { class: _, placeholder: __, ...delegated } = props;
return delegated;
});
const placeholder = useVModel(props, 'modelValue', emits, {
passive: true,
});
const gridRange = props.placeholder.copy();
const forwarded = useForwardPropsEmits(delegatedProps, emits);
const formatter = useDateFormatter('en');
</script>
<template>
<Popover>
<PopoverTrigger as-child>
<Button
variant="outline"
:class="cn(' justify-start text-left font-normal', !placeholder && 'text-muted-foreground')"
>
<CalendarIcon class="mr-2 h-4 w-4" />
<template v-if="placeholder">
<template v-if="placeholder.start">
<template v-if="placeholder.end">
{{
formatter.custom(toDate(placeholder.start as DateValue), {
dateStyle: 'medium',
})
}}
-
{{
formatter.custom(toDate(placeholder.end as DateValue), {
dateStyle: 'medium',
})
}}
</template>
<template v-else>
{{
formatter.custom(toDate(placeholder.start as DateValue), {
dateStyle: 'medium',
})
}}
</template>
</template>
<template v-else> Pick the ending date </template>
</template>
<template v-else> Pick the starting date </template>
</Button>
</PopoverTrigger>
<PopoverContent class="w-auto p-0">
<RangeCalendarRoot
v-slot="{ grid, weekDays, date }"
:class="cn('p-3', props.class)"
v-bind="forwarded"
:placeholder="gridRange"
>
<RangeCalendarHeader>
<RangeCalendarHeading class="flex w-full items-center justify-between gap-2">
<Select
:default-value="gridRange.month.toString()"
@update:model-value="
(v: any) => {
if (!v || !gridRange) return;
if (Number(v) === gridRange.month) return;
gridRange = gridRange.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 createYear({ dateObj: date })"
: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: any) => {
if (!v || !gridRange) return;
if (Number(v) === gridRange.year) return;
gridRange = gridRange.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 createDecade({
dateObj: date,
startIndex: -100,
endIndex: -10,
})"
:key="yearValue.toString()"
:value="yearValue.year.toString()"
>
{{ yearValue.year }}
</SelectItem>
</SelectContent>
</Select>
</RangeCalendarHeading>
</RangeCalendarHeader>
<div class="flex flex-col gap-y-4 mt-4 sm:flex-row sm:gap-x-4 sm:gap-y-0">
<RangeCalendarGrid v-for="month in grid" :key="month.value.toString()">
<RangeCalendarGridHead>
<RangeCalendarGridRow>
<RangeCalendarHeadCell v-for="day in weekDays" :key="day">
{{ day }}
</RangeCalendarHeadCell>
</RangeCalendarGridRow>
</RangeCalendarGridHead>
<RangeCalendarGridBody>
<RangeCalendarGridRow
v-for="(weekDates, index) in month.rows"
:key="`weekDate-${index}`"
class="mt-2 w-full"
>
<RangeCalendarCell
v-for="weekDate in weekDates"
:key="weekDate.toString()"
:date="weekDate"
>
<RangeCalendarCellTrigger :day="weekDate" :month="month.value" />
</RangeCalendarCell>
</RangeCalendarGridRow>
</RangeCalendarGridBody>
</RangeCalendarGrid>
</div>
</RangeCalendarRoot>
</PopoverContent>
</Popover>
</template> |
Beta Was this translation helpful? Give feedback.
0 replies
Answer selected by
JasonGH17
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I was able to get it working with the regular single month view date range calendar, I also added a month and year picker for convenience.
Just make sure to pass in a valid DateRange object to its v-model.