docs: multiple month range picker (#496)
This commit is contained in:
parent
f7e475f16a
commit
fbe14a20c1
|
|
@ -423,6 +423,13 @@ export const Index = {
|
|||
component: () => import("../src/lib/registry/default/example/DatePickerForm.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/default/example/DatePickerForm.vue"],
|
||||
},
|
||||
"DatePickerWithIndependentMonths": {
|
||||
name: "DatePickerWithIndependentMonths",
|
||||
type: "components:example",
|
||||
registryDependencies: ["range-calendar","button","popover","utils"],
|
||||
component: () => import("../src/lib/registry/default/example/DatePickerWithIndependentMonths.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/default/example/DatePickerWithIndependentMonths.vue"],
|
||||
},
|
||||
"DatePickerWithPresets": {
|
||||
name: "DatePickerWithPresets",
|
||||
type: "components:example",
|
||||
|
|
@ -1656,6 +1663,13 @@ export const Index = {
|
|||
component: () => import("../src/lib/registry/new-york/example/DatePickerForm.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/new-york/example/DatePickerForm.vue"],
|
||||
},
|
||||
"DatePickerWithIndependentMonths": {
|
||||
name: "DatePickerWithIndependentMonths",
|
||||
type: "components:example",
|
||||
registryDependencies: ["range-calendar","button","popover","utils"],
|
||||
component: () => import("../src/lib/registry/new-york/example/DatePickerWithIndependentMonths.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/new-york/example/DatePickerWithIndependentMonths.vue"],
|
||||
},
|
||||
"DatePickerWithPresets": {
|
||||
name: "DatePickerWithPresets",
|
||||
type: "components:example",
|
||||
|
|
|
|||
|
|
@ -29,6 +29,10 @@ See installations instructions for the [Popover](/docs/components/popover), [Cal
|
|||
|
||||
<ComponentPreview name="DatePickerWithRange" />
|
||||
|
||||
### Date Range Picker with Independent Months
|
||||
|
||||
<ComponentPreview name="DatePickerWithIndependentMonths" />
|
||||
|
||||
### With Presets
|
||||
|
||||
<ComponentPreview name="DatePickerWithPresets" />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,286 @@
|
|||
<script setup lang="ts">
|
||||
import { type Ref, ref, watch } from 'vue'
|
||||
|
||||
import {
|
||||
Calendar as CalendarIcon,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
} from 'lucide-vue-next'
|
||||
import {
|
||||
CalendarDate,
|
||||
type DateValue,
|
||||
isEqualMonth,
|
||||
} from '@internationalized/date'
|
||||
|
||||
import { type DateRange, RangeCalendarRoot, useDateFormatter } from 'radix-vue'
|
||||
import { type Grid, createMonth, toDate } from 'radix-vue/date'
|
||||
import {
|
||||
RangeCalendarCell,
|
||||
RangeCalendarCellTrigger,
|
||||
RangeCalendarGrid,
|
||||
RangeCalendarGridBody,
|
||||
RangeCalendarGridHead,
|
||||
RangeCalendarGridRow,
|
||||
RangeCalendarHeadCell,
|
||||
} from '@/lib/registry/default/ui/range-calendar'
|
||||
import { Button, buttonVariants } from '@/lib/registry/default/ui/button'
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from '@/lib/registry/default/ui/popover'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const value = ref({
|
||||
start: new CalendarDate(2022, 1, 20),
|
||||
end: new CalendarDate(2022, 1, 20).add({ days: 20 }),
|
||||
}) as Ref<DateRange>
|
||||
|
||||
const locale = ref('en-US')
|
||||
const formatter = useDateFormatter(locale.value)
|
||||
|
||||
const placeholder = ref(value.value.start) as Ref<DateValue>
|
||||
const secondMonthPlaceholder = ref(value.value.end) as Ref<DateValue>
|
||||
|
||||
const firstMonth = ref(
|
||||
createMonth({
|
||||
dateObj: placeholder.value,
|
||||
locale: locale.value,
|
||||
fixedWeeks: true,
|
||||
weekStartsOn: 0,
|
||||
}),
|
||||
) as Ref<Grid<DateValue>>
|
||||
const secondMonth = ref(
|
||||
createMonth({
|
||||
dateObj: secondMonthPlaceholder.value,
|
||||
locale: locale.value,
|
||||
fixedWeeks: true,
|
||||
weekStartsOn: 0,
|
||||
}),
|
||||
) as Ref<Grid<DateValue>>
|
||||
|
||||
function updateMonth(reference: 'first' | 'second', months: number) {
|
||||
if (reference === 'first') {
|
||||
placeholder.value = placeholder.value.add({ months })
|
||||
}
|
||||
else {
|
||||
secondMonthPlaceholder.value = secondMonthPlaceholder.value.add({
|
||||
months,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
watch(placeholder, (_placeholder) => {
|
||||
firstMonth.value = createMonth({
|
||||
dateObj: _placeholder,
|
||||
weekStartsOn: 0,
|
||||
fixedWeeks: false,
|
||||
locale: locale.value,
|
||||
})
|
||||
if (isEqualMonth(secondMonthPlaceholder.value, _placeholder)) {
|
||||
secondMonthPlaceholder.value = secondMonthPlaceholder.value.add({
|
||||
months: 1,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
watch(secondMonthPlaceholder, (_secondMonthPlaceholder) => {
|
||||
secondMonth.value = createMonth({
|
||||
dateObj: _secondMonthPlaceholder,
|
||||
weekStartsOn: 0,
|
||||
fixedWeeks: false,
|
||||
locale: locale.value,
|
||||
})
|
||||
if (isEqualMonth(_secondMonthPlaceholder, placeholder.value))
|
||||
placeholder.value = placeholder.value.subtract({ months: 1 })
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Popover>
|
||||
<PopoverTrigger as-child>
|
||||
<Button
|
||||
variant="outline"
|
||||
:class="
|
||||
cn(
|
||||
'w-[280px] justify-start text-left font-normal',
|
||||
!value && 'text-muted-foreground',
|
||||
)
|
||||
"
|
||||
>
|
||||
<CalendarIcon class="mr-2 h-4 w-4" />
|
||||
<template v-if="value.start">
|
||||
<template v-if="value.end">
|
||||
{{
|
||||
formatter.custom(toDate(value.start), {
|
||||
dateStyle: "medium",
|
||||
})
|
||||
}}
|
||||
-
|
||||
{{
|
||||
formatter.custom(toDate(value.end), {
|
||||
dateStyle: "medium",
|
||||
})
|
||||
}}
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
{{
|
||||
formatter.custom(toDate(value.start), {
|
||||
dateStyle: "medium",
|
||||
})
|
||||
}}
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
Pick a date
|
||||
</template>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="w-auto p-0">
|
||||
<RangeCalendarRoot v-slot="{ weekDays }" v-model="value" v-model:placeholder="placeholder" 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',
|
||||
)
|
||||
"
|
||||
@click="updateMonth('first', -1)"
|
||||
>
|
||||
<ChevronLeft class="h-4 w-4" />
|
||||
</button>
|
||||
<div
|
||||
:class="cn('text-sm font-medium')"
|
||||
>
|
||||
{{
|
||||
formatter.fullMonthAndYear(
|
||||
toDate(firstMonth.value),
|
||||
)
|
||||
}}
|
||||
</div>
|
||||
<button
|
||||
:class="
|
||||
cn(
|
||||
buttonVariants({ variant: 'outline' }),
|
||||
'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100',
|
||||
)
|
||||
"
|
||||
@click="updateMonth('first', 1)"
|
||||
>
|
||||
<ChevronRight class="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
<RangeCalendarGrid>
|
||||
<RangeCalendarGridHead>
|
||||
<RangeCalendarGridRow>
|
||||
<RangeCalendarHeadCell
|
||||
v-for="day in weekDays"
|
||||
:key="day"
|
||||
class="w-full"
|
||||
>
|
||||
{{ day }}
|
||||
</RangeCalendarHeadCell>
|
||||
</RangeCalendarGridRow>
|
||||
</RangeCalendarGridHead>
|
||||
<RangeCalendarGridBody>
|
||||
<RangeCalendarGridRow
|
||||
v-for="(
|
||||
weekDates, index
|
||||
) in firstMonth.rows"
|
||||
:key="`weekDate-${index}`"
|
||||
class="mt-2 w-full"
|
||||
>
|
||||
<RangeCalendarCell
|
||||
v-for="weekDate in weekDates"
|
||||
:key="weekDate.toString()"
|
||||
:date="weekDate"
|
||||
>
|
||||
<RangeCalendarCellTrigger
|
||||
:day="weekDate"
|
||||
:month="firstMonth.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',
|
||||
)
|
||||
"
|
||||
@click="updateMonth('second', 1)"
|
||||
>
|
||||
<ChevronRight class="h-4 w-4" />
|
||||
</button>
|
||||
<div
|
||||
:class="cn('text-sm font-medium')"
|
||||
>
|
||||
{{
|
||||
formatter.fullMonthAndYear(
|
||||
toDate(secondMonth.value),
|
||||
)
|
||||
}}
|
||||
</div>
|
||||
|
||||
<button
|
||||
:class="
|
||||
cn(
|
||||
buttonVariants({ variant: 'outline' }),
|
||||
'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100',
|
||||
)
|
||||
"
|
||||
@click="updateMonth('second', 1)"
|
||||
>
|
||||
<ChevronRight class="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
<RangeCalendarGrid>
|
||||
<RangeCalendarGridHead>
|
||||
<RangeCalendarGridRow>
|
||||
<RangeCalendarHeadCell
|
||||
v-for="day in weekDays"
|
||||
:key="day"
|
||||
class="w-full"
|
||||
>
|
||||
{{ day }}
|
||||
</RangeCalendarHeadCell>
|
||||
</RangeCalendarGridRow>
|
||||
</RangeCalendarGridHead>
|
||||
<RangeCalendarGridBody>
|
||||
<RangeCalendarGridRow
|
||||
v-for="(
|
||||
weekDates, index
|
||||
) in secondMonth.rows"
|
||||
:key="`weekDate-${index}`"
|
||||
class="mt-2 w-full"
|
||||
>
|
||||
<RangeCalendarCell
|
||||
v-for="weekDate in weekDates"
|
||||
:key="weekDate.toString()"
|
||||
:date="weekDate"
|
||||
>
|
||||
<RangeCalendarCellTrigger
|
||||
:day="weekDate"
|
||||
:month="secondMonth.value"
|
||||
/>
|
||||
</RangeCalendarCell>
|
||||
</RangeCalendarGridRow>
|
||||
</RangeCalendarGridBody>
|
||||
</RangeCalendarGrid>
|
||||
</div>
|
||||
</div>
|
||||
</RangeCalendarRoot>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,282 @@
|
|||
<script setup lang="ts">
|
||||
import { type Ref, ref, watch } from 'vue'
|
||||
|
||||
import {
|
||||
Calendar as CalendarIcon,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
} from 'lucide-vue-next'
|
||||
import {
|
||||
CalendarDate,
|
||||
type DateValue,
|
||||
isEqualMonth,
|
||||
} from '@internationalized/date'
|
||||
|
||||
import { type DateRange, RangeCalendarRoot, useDateFormatter } from 'radix-vue'
|
||||
import { type Grid, createMonth, toDate } from 'radix-vue/date'
|
||||
import {
|
||||
RangeCalendarCell,
|
||||
RangeCalendarCellTrigger,
|
||||
RangeCalendarGrid,
|
||||
RangeCalendarGridBody,
|
||||
RangeCalendarGridHead,
|
||||
RangeCalendarGridRow,
|
||||
RangeCalendarHeadCell,
|
||||
} from '@/lib/registry/new-york/ui/range-calendar'
|
||||
import { Button, buttonVariants } from '@/lib/registry/new-york/ui/button'
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from '@/lib/registry/new-york/ui/popover'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const value = ref({
|
||||
start: new CalendarDate(2022, 1, 20),
|
||||
end: new CalendarDate(2022, 1, 20).add({ days: 20 }),
|
||||
}) as Ref<DateRange>
|
||||
|
||||
const locale = ref('en-US')
|
||||
const formatter = useDateFormatter(locale.value)
|
||||
|
||||
const placeholder = ref(value.value.start) as Ref<DateValue>
|
||||
const secondMonthPlaceholder = ref(value.value.end) as Ref<DateValue>
|
||||
|
||||
const firstMonth = ref(
|
||||
createMonth({
|
||||
dateObj: placeholder.value,
|
||||
locale: locale.value,
|
||||
fixedWeeks: true,
|
||||
weekStartsOn: 0,
|
||||
}),
|
||||
) as Ref<Grid<DateValue>>
|
||||
const secondMonth = ref(
|
||||
createMonth({
|
||||
dateObj: secondMonthPlaceholder.value,
|
||||
locale: locale.value,
|
||||
fixedWeeks: true,
|
||||
weekStartsOn: 0,
|
||||
}),
|
||||
) as Ref<Grid<DateValue>>
|
||||
|
||||
function updateMonth(reference: 'first' | 'second', months: number) {
|
||||
if (reference === 'first') {
|
||||
placeholder.value = placeholder.value.add({ months })
|
||||
}
|
||||
else {
|
||||
secondMonthPlaceholder.value = secondMonthPlaceholder.value.add({
|
||||
months,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
watch(placeholder, (_placeholder) => {
|
||||
firstMonth.value = createMonth({
|
||||
dateObj: _placeholder,
|
||||
weekStartsOn: 0,
|
||||
fixedWeeks: false,
|
||||
locale: locale.value,
|
||||
})
|
||||
if (isEqualMonth(secondMonthPlaceholder.value, _placeholder)) {
|
||||
secondMonthPlaceholder.value = secondMonthPlaceholder.value.add({
|
||||
months: 1,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
watch(secondMonthPlaceholder, (_secondMonthPlaceholder) => {
|
||||
secondMonth.value = createMonth({
|
||||
dateObj: _secondMonthPlaceholder,
|
||||
weekStartsOn: 0,
|
||||
fixedWeeks: false,
|
||||
locale: locale.value,
|
||||
})
|
||||
if (isEqualMonth(_secondMonthPlaceholder, placeholder.value))
|
||||
placeholder.value = placeholder.value.subtract({ months: 1 })
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Popover>
|
||||
<PopoverTrigger as-child>
|
||||
<Button
|
||||
variant="outline"
|
||||
:class="
|
||||
cn(
|
||||
'w-[280px] justify-start text-left font-normal',
|
||||
!value && 'text-muted-foreground',
|
||||
)
|
||||
"
|
||||
>
|
||||
<CalendarIcon class="mr-2 h-4 w-4" />
|
||||
<template v-if="value.start">
|
||||
<template v-if="value.end">
|
||||
{{
|
||||
formatter.custom(toDate(value.start), {
|
||||
dateStyle: "medium",
|
||||
})
|
||||
}}
|
||||
-
|
||||
{{
|
||||
formatter.custom(toDate(value.end), {
|
||||
dateStyle: "medium",
|
||||
})
|
||||
}}
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
{{
|
||||
formatter.custom(toDate(value.start), {
|
||||
dateStyle: "medium",
|
||||
})
|
||||
}}
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
Pick a date
|
||||
</template>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="w-auto p-0">
|
||||
<RangeCalendarRoot v-slot="{ weekDays }" v-model="value" v-model:placeholder="placeholder" 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',
|
||||
)
|
||||
"
|
||||
@click="updateMonth('first', -1)"
|
||||
>
|
||||
<ChevronLeft class="h-4 w-4" />
|
||||
</button>
|
||||
<div :class="cn('text-sm font-medium')">
|
||||
{{
|
||||
formatter.fullMonthAndYear(
|
||||
toDate(firstMonth.value),
|
||||
)
|
||||
}}
|
||||
</div>
|
||||
<button
|
||||
:class="
|
||||
cn(
|
||||
buttonVariants({ variant: 'outline' }),
|
||||
'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100',
|
||||
)
|
||||
"
|
||||
@click="updateMonth('first', 1)"
|
||||
>
|
||||
<ChevronRight class="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
<RangeCalendarGrid>
|
||||
<RangeCalendarGridHead>
|
||||
<RangeCalendarGridRow>
|
||||
<RangeCalendarHeadCell
|
||||
v-for="day in weekDays"
|
||||
:key="day"
|
||||
class="w-full"
|
||||
>
|
||||
{{ day }}
|
||||
</RangeCalendarHeadCell>
|
||||
</RangeCalendarGridRow>
|
||||
</RangeCalendarGridHead>
|
||||
<RangeCalendarGridBody>
|
||||
<RangeCalendarGridRow
|
||||
v-for="(
|
||||
weekDates, index
|
||||
) in firstMonth.rows"
|
||||
:key="`weekDate-${index}`"
|
||||
class="mt-2 w-full"
|
||||
>
|
||||
<RangeCalendarCell
|
||||
v-for="weekDate in weekDates"
|
||||
:key="weekDate.toString()"
|
||||
:date="weekDate"
|
||||
>
|
||||
<RangeCalendarCellTrigger
|
||||
:day="weekDate"
|
||||
:month="firstMonth.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',
|
||||
)
|
||||
"
|
||||
@click="updateMonth('second', 1)"
|
||||
>
|
||||
<ChevronRight class="h-4 w-4" />
|
||||
</button>
|
||||
<div :class="cn('text-sm font-medium')">
|
||||
{{
|
||||
formatter.fullMonthAndYear(
|
||||
toDate(secondMonth.value),
|
||||
)
|
||||
}}
|
||||
</div>
|
||||
|
||||
<button
|
||||
:class="
|
||||
cn(
|
||||
buttonVariants({ variant: 'outline' }),
|
||||
'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100',
|
||||
)
|
||||
"
|
||||
@click="updateMonth('second', 1)"
|
||||
>
|
||||
<ChevronRight class="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
<RangeCalendarGrid>
|
||||
<RangeCalendarGridHead>
|
||||
<RangeCalendarGridRow>
|
||||
<RangeCalendarHeadCell
|
||||
v-for="day in weekDays"
|
||||
:key="day"
|
||||
class="w-full"
|
||||
>
|
||||
{{ day }}
|
||||
</RangeCalendarHeadCell>
|
||||
</RangeCalendarGridRow>
|
||||
</RangeCalendarGridHead>
|
||||
<RangeCalendarGridBody>
|
||||
<RangeCalendarGridRow
|
||||
v-for="(
|
||||
weekDates, index
|
||||
) in secondMonth.rows"
|
||||
:key="`weekDate-${index}`"
|
||||
class="mt-2 w-full"
|
||||
>
|
||||
<RangeCalendarCell
|
||||
v-for="weekDate in weekDates"
|
||||
:key="weekDate.toString()"
|
||||
:date="weekDate"
|
||||
>
|
||||
<RangeCalendarCellTrigger
|
||||
:day="weekDate"
|
||||
:month="secondMonth.value"
|
||||
/>
|
||||
</RangeCalendarCell>
|
||||
</RangeCalendarGridRow>
|
||||
</RangeCalendarGridBody>
|
||||
</RangeCalendarGrid>
|
||||
</div>
|
||||
</div>
|
||||
</RangeCalendarRoot>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</template>
|
||||
Loading…
Reference in New Issue
Block a user