Skip to content

Commit

Permalink
feat: Calendar component inherits the VCalendar slots (#285)
Browse files Browse the repository at this point in the history
  • Loading branch information
valh1996 authored Jan 18, 2024
1 parent 8e7bbe3 commit cb974f9
Show file tree
Hide file tree
Showing 11 changed files with 276 additions and 9 deletions.
14 changes: 14 additions & 0 deletions apps/www/__registry__/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,13 @@ export const Index = {
component: () => import('../src/lib/registry/default/example/RadioGroupForm.vue').then(m => m.default),
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: {
name: 'ScrollAreaDemo',
type: 'components:example',
Expand Down Expand Up @@ -1390,6 +1397,13 @@ export const Index = {
component: () => import('../src/lib/registry/new-york/example/RadioGroupForm.vue').then(m => m.default),
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: {
name: 'ScrollAreaDemo',
type: 'components:example',
Expand Down
37 changes: 36 additions & 1 deletion apps/www/src/content/docs/components/calendar.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,40 @@ import { Calendar } from '@/components/ui/calendar'
</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>
```
4 changes: 4 additions & 0 deletions apps/www/src/content/docs/components/date-picker.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ const date = ref<Date>()

<ComponentPreview name="DatePickerWithPresets" />

### With Slot

<ComponentPreview name="RangePickerWithSlot" />

### Form

<ComponentPreview name="DatePickerForm" />
69 changes: 69 additions & 0 deletions apps/www/src/lib/registry/default/example/RangePickerWithSlot.vue
Original file line number Diff line number Diff line change
@@ -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>
19 changes: 17 additions & 2 deletions apps/www/src/lib/registry/default/ui/calendar/Calendar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { useVModel } from '@vueuse/core'
import type { Calendar } from 'v-calendar'
import { DatePicker } from 'v-calendar'
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 { cn } from '@/lib/utils'
Expand Down Expand Up @@ -64,6 +65,16 @@ onMounted(async () => {
if (modelValue.value instanceof Date && calendarRef.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>

<template>
Expand All @@ -86,7 +97,11 @@ onMounted(async () => {
trim-weeks
:transition="'none'"
:columns="columns"
/>
>
<template v-for="(_, slot) of vCalendarSlots" #[slot]="scope">
<slot :name="slot" v-bind="scope" />
</template>
</DatePicker>
</div>
</template>

Expand Down
21 changes: 21 additions & 0 deletions apps/www/src/lib/registry/default/ui/calendar/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,22 @@
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)
}
73 changes: 73 additions & 0 deletions apps/www/src/lib/registry/new-york/example/RangePickerWithSlot.vue
Original file line number Diff line number Diff line change
@@ -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>
19 changes: 17 additions & 2 deletions apps/www/src/lib/registry/new-york/ui/calendar/Calendar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { useVModel } from '@vueuse/core'
import type { Calendar } from 'v-calendar'
import { DatePicker } from 'v-calendar'
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 { cn } from '@/lib/utils'
Expand Down Expand Up @@ -63,6 +64,16 @@ onMounted(async () => {
if (modelValue.value instanceof Date && calendarRef.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>

<template>
Expand All @@ -85,7 +96,11 @@ onMounted(async () => {
trim-weeks
:transition="'none'"
:columns="columns"
/>
>
<template v-for="(_, slot) of vCalendarSlots" #[slot]="scope">
<slot :name="slot" v-bind="scope" />
</template>
</DatePicker>
</div>
</template>

Expand Down
21 changes: 21 additions & 0 deletions apps/www/src/lib/registry/new-york/ui/calendar/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,22 @@
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)
}
4 changes: 2 additions & 2 deletions apps/www/src/public/registry/styles/default/calendar.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions apps/www/src/public/registry/styles/new-york/calendar.json

Large diffs are not rendered by default.

0 comments on commit cb974f9

Please sign in to comment.