feat: Calendar component inherits the VCalendar slots (#285)
This commit is contained in:
parent
8e7bbe3a8d
commit
cb974f95e0
|
|
@ -506,6 +506,13 @@ export const Index = {
|
||||||
component: () => import('../src/lib/registry/default/example/RadioGroupForm.vue').then(m => m.default),
|
component: () => import('../src/lib/registry/default/example/RadioGroupForm.vue').then(m => m.default),
|
||||||
files: ['../src/lib/registry/default/example/RadioGroupForm.vue'],
|
files: ['../src/lib/registry/default/example/RadioGroupForm.vue'],
|
||||||
},
|
},
|
||||||
|
RangePickerWithSlot: {
|
||||||
|
name: 'RangePickerWithSlot',
|
||||||
|
type: 'components:example',
|
||||||
|
registryDependencies: ['utils', 'button', 'calendar', 'popover'],
|
||||||
|
component: () => import('../src/lib/registry/default/example/RangePickerWithSlot.vue').then(m => m.default),
|
||||||
|
files: ['../src/lib/registry/default/example/RangePickerWithSlot.vue'],
|
||||||
|
},
|
||||||
ScrollAreaDemo: {
|
ScrollAreaDemo: {
|
||||||
name: 'ScrollAreaDemo',
|
name: 'ScrollAreaDemo',
|
||||||
type: 'components:example',
|
type: 'components:example',
|
||||||
|
|
@ -1390,6 +1397,13 @@ export const Index = {
|
||||||
component: () => import('../src/lib/registry/new-york/example/RadioGroupForm.vue').then(m => m.default),
|
component: () => import('../src/lib/registry/new-york/example/RadioGroupForm.vue').then(m => m.default),
|
||||||
files: ['../src/lib/registry/new-york/example/RadioGroupForm.vue'],
|
files: ['../src/lib/registry/new-york/example/RadioGroupForm.vue'],
|
||||||
},
|
},
|
||||||
|
RangePickerWithSlot: {
|
||||||
|
name: 'RangePickerWithSlot',
|
||||||
|
type: 'components:example',
|
||||||
|
registryDependencies: ['utils', 'button', 'calendar', 'popover'],
|
||||||
|
component: () => import('../src/lib/registry/new-york/example/RangePickerWithSlot.vue').then(m => m.default),
|
||||||
|
files: ['../src/lib/registry/new-york/example/RangePickerWithSlot.vue'],
|
||||||
|
},
|
||||||
ScrollAreaDemo: {
|
ScrollAreaDemo: {
|
||||||
name: 'ScrollAreaDemo',
|
name: 'ScrollAreaDemo',
|
||||||
type: 'components:example',
|
type: 'components:example',
|
||||||
|
|
|
||||||
|
|
@ -56,5 +56,40 @@ import { Calendar } from '@/components/ui/calendar'
|
||||||
</template>
|
</template>
|
||||||
```
|
```
|
||||||
|
|
||||||
See the [VCalendar](https://vcalendar.io/getting-started/installation.html) documentation for more information.
|
The API is essentially the same, i.e. props and slots. See the [VCalendar](https://vcalendar.io/getting-started/installation.html) documentation for more information.
|
||||||
|
|
||||||
|
### Slots
|
||||||
|
|
||||||
|
The slots available are [those currently supported](https://github.com/nathanreyes/v-calendar/blob/v3.1.2/src/components/Calendar/CalendarSlot.vue#L16-L28) by VCalendar, namely :
|
||||||
|
|
||||||
|
- `day-content`
|
||||||
|
- `day-popover`
|
||||||
|
- `dp-footer`
|
||||||
|
- `footer`
|
||||||
|
- `header-title-wrapper`
|
||||||
|
- `header-title`
|
||||||
|
- `header-prev-button`
|
||||||
|
- `header-next-button`
|
||||||
|
- `nav`
|
||||||
|
- `nav-prev-button`
|
||||||
|
- `nav-next-button`
|
||||||
|
- `page`
|
||||||
|
- `time-header`
|
||||||
|
|
||||||
|
Example using the `day-content` slot:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Calendar } from '@/components/ui/calendar'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Calendar>
|
||||||
|
<template #day-content="{ day, dayProps, dayEvents }">
|
||||||
|
<div v-bind="dayProps" v-on="dayEvents">
|
||||||
|
{{ day.label }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Calendar>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
@ -72,6 +72,10 @@ const date = ref<Date>()
|
||||||
|
|
||||||
<ComponentPreview name="DatePickerWithPresets" />
|
<ComponentPreview name="DatePickerWithPresets" />
|
||||||
|
|
||||||
|
### With Slot
|
||||||
|
|
||||||
|
<ComponentPreview name="RangePickerWithSlot" />
|
||||||
|
|
||||||
### Form
|
### Form
|
||||||
|
|
||||||
<ComponentPreview name="DatePickerForm" />
|
<ComponentPreview name="DatePickerForm" />
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { addDays, format } from 'date-fns'
|
||||||
|
import { Calendar as CalendarIcon } from 'lucide-vue-next'
|
||||||
|
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { Button } from '@/lib/registry/default/ui/button'
|
||||||
|
import { Calendar } from '@/lib/registry/default/ui/calendar'
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from '@/lib/registry/default/ui/popover'
|
||||||
|
|
||||||
|
const date = ref({
|
||||||
|
start: new Date(2022, 0, 20),
|
||||||
|
end: addDays(new Date(2022, 0, 20), 20),
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div :class="cn('grid gap-2', $attrs.class ?? '')">
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger as-child>
|
||||||
|
<Button
|
||||||
|
id="date"
|
||||||
|
:variant="'outline'"
|
||||||
|
:class="cn(
|
||||||
|
'w-[300px] justify-start text-left font-normal',
|
||||||
|
!date && 'text-muted-foreground',
|
||||||
|
)"
|
||||||
|
>
|
||||||
|
<CalendarIcon class="mr-2 h-4 w-4" />
|
||||||
|
|
||||||
|
<span>
|
||||||
|
{{ date.start ? (
|
||||||
|
date.end ? `${format(date.start, 'LLL dd, y')} - ${format(date.end, 'LLL dd, y')}`
|
||||||
|
: format(date.start, 'LLL dd, y')
|
||||||
|
) : 'Pick a date' }}
|
||||||
|
</span>
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent class="w-auto p-0" align="start" :avoid-collisions="true">
|
||||||
|
<Calendar
|
||||||
|
v-model.range="date"
|
||||||
|
mode="date"
|
||||||
|
:columns="2"
|
||||||
|
>
|
||||||
|
<template #footer>
|
||||||
|
<div class="w-full px-3 pb-3">
|
||||||
|
Entry time
|
||||||
|
<Calendar
|
||||||
|
v-model="date.start"
|
||||||
|
mode="time"
|
||||||
|
hide-time-header
|
||||||
|
/>
|
||||||
|
Exit time
|
||||||
|
<Calendar
|
||||||
|
v-model="date.end"
|
||||||
|
mode="time"
|
||||||
|
hide-time-header
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Calendar>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -3,7 +3,8 @@ import { useVModel } from '@vueuse/core'
|
||||||
import type { Calendar } from 'v-calendar'
|
import type { Calendar } from 'v-calendar'
|
||||||
import { DatePicker } from 'v-calendar'
|
import { DatePicker } from 'v-calendar'
|
||||||
import { ChevronLeft, ChevronRight } from 'lucide-vue-next'
|
import { ChevronLeft, ChevronRight } from 'lucide-vue-next'
|
||||||
import { computed, nextTick, onMounted, ref } from 'vue'
|
import { computed, nextTick, onMounted, ref, useSlots } from 'vue'
|
||||||
|
import { isVCalendarSlot } from '.'
|
||||||
import { buttonVariants } from '@/lib/registry/default/ui/button'
|
import { buttonVariants } from '@/lib/registry/default/ui/button'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
|
@ -64,6 +65,16 @@ onMounted(async () => {
|
||||||
if (modelValue.value instanceof Date && calendarRef.value)
|
if (modelValue.value instanceof Date && calendarRef.value)
|
||||||
calendarRef.value.focusDate(modelValue.value)
|
calendarRef.value.focusDate(modelValue.value)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const $slots = useSlots()
|
||||||
|
const vCalendarSlots = computed(() => {
|
||||||
|
return Object.keys($slots)
|
||||||
|
.filter(name => isVCalendarSlot(name))
|
||||||
|
.reduce((obj: Record<string, any>, key: string) => {
|
||||||
|
obj[key] = $slots[key]
|
||||||
|
return obj
|
||||||
|
}, {})
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -86,7 +97,11 @@ onMounted(async () => {
|
||||||
trim-weeks
|
trim-weeks
|
||||||
:transition="'none'"
|
:transition="'none'"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
/>
|
>
|
||||||
|
<template v-for="(_, slot) of vCalendarSlots" #[slot]="scope">
|
||||||
|
<slot :name="slot" v-bind="scope" />
|
||||||
|
</template>
|
||||||
|
</DatePicker>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1 +1,22 @@
|
||||||
export { default as Calendar } from './Calendar.vue'
|
export { default as Calendar } from './Calendar.vue'
|
||||||
|
import type { CalendarSlotName } from 'v-calendar/dist/types/src/components/Calendar/CalendarSlot.vue.d.ts'
|
||||||
|
|
||||||
|
export function isVCalendarSlot(slotName: string): slotName is CalendarSlotName {
|
||||||
|
const validSlots: CalendarSlotName[] = [
|
||||||
|
'day-content',
|
||||||
|
'day-popover',
|
||||||
|
'dp-footer',
|
||||||
|
'footer',
|
||||||
|
'header-title-wrapper',
|
||||||
|
'header-title',
|
||||||
|
'header-prev-button',
|
||||||
|
'header-next-button',
|
||||||
|
'nav',
|
||||||
|
'nav-prev-button',
|
||||||
|
'nav-next-button',
|
||||||
|
'page',
|
||||||
|
'time-header',
|
||||||
|
]
|
||||||
|
|
||||||
|
return validSlots.includes(slotName as CalendarSlotName)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { addDays, format } from 'date-fns'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { CalendarIcon } from '@radix-icons/vue'
|
||||||
|
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||||
|
import { Calendar } from '@/lib/registry/new-york/ui/calendar'
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from '@/lib/registry/new-york/ui/popover'
|
||||||
|
|
||||||
|
const date = ref({
|
||||||
|
start: new Date(2022, 0, 20),
|
||||||
|
end: addDays(new Date(2022, 0, 20), 20),
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div :class="cn('grid gap-2', $attrs.class ?? '')">
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger as-child>
|
||||||
|
<Button
|
||||||
|
id="date"
|
||||||
|
:variant="'outline'"
|
||||||
|
:class="cn(
|
||||||
|
'w-[300px] justify-start text-left font-normal',
|
||||||
|
!date && 'text-muted-foreground',
|
||||||
|
)"
|
||||||
|
>
|
||||||
|
<CalendarIcon class="mr-2 h-4 w-4" />
|
||||||
|
|
||||||
|
<span>
|
||||||
|
{{ date.start ? (
|
||||||
|
date.end ? `${format(date.start, 'LLL dd, y')} - ${format(date.end, 'LLL dd, y')}`
|
||||||
|
: format(date.start, 'LLL dd, y')
|
||||||
|
) : 'Pick a date' }}
|
||||||
|
</span>
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent class="w-auto p-0" align="start" :avoid-collisions="true">
|
||||||
|
<Calendar
|
||||||
|
v-model.range="date"
|
||||||
|
mode="date"
|
||||||
|
:columns="2"
|
||||||
|
>
|
||||||
|
<template #footer>
|
||||||
|
<div class="flex w-full mt-6 border-t border-accent pt-4">
|
||||||
|
<div class="w-1/2">
|
||||||
|
<strong>Entry time</strong>
|
||||||
|
<Calendar
|
||||||
|
v-model="date.start"
|
||||||
|
mode="time"
|
||||||
|
hide-time-header
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="w-1/2">
|
||||||
|
<strong>Exit time</strong>
|
||||||
|
<Calendar
|
||||||
|
v-model="date.end"
|
||||||
|
mode="time"
|
||||||
|
hide-time-header
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Calendar>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -3,7 +3,8 @@ import { useVModel } from '@vueuse/core'
|
||||||
import type { Calendar } from 'v-calendar'
|
import type { Calendar } from 'v-calendar'
|
||||||
import { DatePicker } from 'v-calendar'
|
import { DatePicker } from 'v-calendar'
|
||||||
import { ChevronLeftIcon, ChevronRightIcon } from '@radix-icons/vue'
|
import { ChevronLeftIcon, ChevronRightIcon } from '@radix-icons/vue'
|
||||||
import { computed, nextTick, onMounted, ref } from 'vue'
|
import { computed, nextTick, onMounted, ref, useSlots } from 'vue'
|
||||||
|
import { isVCalendarSlot } from '.'
|
||||||
import { buttonVariants } from '@/lib/registry/new-york/ui/button'
|
import { buttonVariants } from '@/lib/registry/new-york/ui/button'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
|
@ -63,6 +64,16 @@ onMounted(async () => {
|
||||||
if (modelValue.value instanceof Date && calendarRef.value)
|
if (modelValue.value instanceof Date && calendarRef.value)
|
||||||
calendarRef.value.focusDate(modelValue.value)
|
calendarRef.value.focusDate(modelValue.value)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const $slots = useSlots()
|
||||||
|
const vCalendarSlots = computed(() => {
|
||||||
|
return Object.keys($slots)
|
||||||
|
.filter(name => isVCalendarSlot(name))
|
||||||
|
.reduce((obj: Record<string, any>, key: string) => {
|
||||||
|
obj[key] = $slots[key]
|
||||||
|
return obj
|
||||||
|
}, {})
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -85,7 +96,11 @@ onMounted(async () => {
|
||||||
trim-weeks
|
trim-weeks
|
||||||
:transition="'none'"
|
:transition="'none'"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
/>
|
>
|
||||||
|
<template v-for="(_, slot) of vCalendarSlots" #[slot]="scope">
|
||||||
|
<slot :name="slot" v-bind="scope" />
|
||||||
|
</template>
|
||||||
|
</DatePicker>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1 +1,22 @@
|
||||||
export { default as Calendar } from './Calendar.vue'
|
export { default as Calendar } from './Calendar.vue'
|
||||||
|
import type { CalendarSlotName } from 'v-calendar/dist/types/src/components/Calendar/CalendarSlot.vue.d.ts'
|
||||||
|
|
||||||
|
export function isVCalendarSlot(slotName: string): slotName is CalendarSlotName {
|
||||||
|
const validSlots: CalendarSlotName[] = [
|
||||||
|
'day-content',
|
||||||
|
'day-popover',
|
||||||
|
'dp-footer',
|
||||||
|
'footer',
|
||||||
|
'header-title-wrapper',
|
||||||
|
'header-title',
|
||||||
|
'header-prev-button',
|
||||||
|
'header-next-button',
|
||||||
|
'nav',
|
||||||
|
'nav-prev-button',
|
||||||
|
'nav-next-button',
|
||||||
|
'page',
|
||||||
|
'time-header',
|
||||||
|
]
|
||||||
|
|
||||||
|
return validSlots.includes(slotName as CalendarSlotName)
|
||||||
|
}
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user