Skip to content

Commit 22dfb1b

Browse files
maryamBaratiijohnleiderJ-Sek
authored
feat(VDatePicker): add events and event-color support (#20965)
Co-authored-by: John Leider <9064066+johnleider@users.noreply.github.com> Co-authored-by: J-Sek <J-Sek@users.noreply.github.com> fixes #19296
1 parent bc6f183 commit 22dfb1b

File tree

7 files changed

+214
-29
lines changed

7 files changed

+214
-29
lines changed

packages/api-generator/src/locale/en/VDatePicker.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"calendarIcon": "The icon shown in the header when in 'input' **input-mode**.",
55
"dayFormat": "Allows you to customize the format of the day string that appears in the date table. Called with date (ISO 8601 **date** string) arguments.",
66
"displayDate": "The date displayed in the picker header.",
7-
"eventColor": "Sets the color for event dot. It can be string (all events will have the same color) or `object` where attribute is the event date and value is boolean/color/array of colors for specified date or `function` taking date as a parameter and returning boolean/color/array of colors for that date.",
7+
"eventColor": "Sets the color for event dots. It can be string (all events will have the same color) or `object` where attribute is the event date and value is boolean/color/array of colors for specified date or `function` taking date as a parameter and returning boolean/color/array of colors for that date.",
88
"events": "Array of dates or object defining events or colors or function returning boolean/color/array of colors.",
99
"headerColor": "Allows you to set a different color for the header when used in conjunction with the `color` prop.",
1010
"hideHeader": "Hides the header.",

packages/docs/src/examples/v-date-picker/event-events.vue renamed to packages/docs/src/examples/v-date-picker/prop-events.vue

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,37 @@
11
<template>
2-
<v-row justify="space-between">
3-
<div>
4-
<div class="subheading">
5-
Defined by array
2+
<v-container>
3+
<v-row justify="space-between">
4+
<div>
5+
<div class="subheading mb-5 font-weight-medium">
6+
Defined by array
7+
</div>
8+
<v-date-picker
9+
v-model="date1"
10+
:events="arrayEvents"
11+
event-color="green lighten-1"
12+
></v-date-picker>
613
</div>
7-
<v-date-picker
8-
v-model="date1"
9-
:events="arrayEvents"
10-
event-color="green lighten-1"
11-
></v-date-picker>
12-
</div>
13-
<div>
14-
<div class="subheading">
15-
Defined by function
14+
<div>
15+
<div class="subheading mb-5 font-weight-medium">
16+
Defined by function
17+
</div>
18+
<v-date-picker
19+
v-model="date2"
20+
:event-color="date => date[9] % 2 ? 'red' : 'yellow'"
21+
:events="functionEvents"
22+
></v-date-picker>
1623
</div>
17-
<v-date-picker
18-
v-model="date2"
19-
:event-color="date => date[9] % 2 ? 'red' : 'yellow'"
20-
:events="functionEvents"
21-
></v-date-picker>
22-
</div>
23-
</v-row>
24+
</v-row>
25+
</v-container>
2426
</template>
2527

2628
<script setup>
2729
import { onMounted, ref } from 'vue'
2830
2931
const arrayEvents = ref(null)
30-
const date1 = ref((new Date(Date.now() - (new Date()).getTimezoneOffset() * 60000)).toISOString().substr(0, 10))
31-
const date2 = ref((new Date(Date.now() - (new Date()).getTimezoneOffset() * 60000)).toISOString().substr(0, 10))
32+
const date1 = ref((new Date(Date.now() - (new Date()).getTimezoneOffset() * 60000)))
33+
const date2 = ref((new Date(Date.now() - (new Date()).getTimezoneOffset() * 60000)))
34+
3235
function functionEvents (date) {
3336
const [, , day] = date.split('-')
3437
if ([12, 17, 28].includes(parseInt(day, 10))) return true
@@ -49,8 +52,8 @@
4952
export default {
5053
data: () => ({
5154
arrayEvents: null,
52-
date1: (new Date(Date.now() - (new Date()).getTimezoneOffset() * 60000)).toISOString().substr(0, 10),
53-
date2: (new Date(Date.now() - (new Date()).getTimezoneOffset() * 60000)).toISOString().substr(0, 10),
55+
date1: (new Date(Date.now() - (new Date()).getTimezoneOffset() * 60000)),
56+
date2: (new Date(Date.now() - (new Date()).getTimezoneOffset() * 60000)),
5457
}),
5558
5659
mounted () {

packages/docs/src/pages/en/components/date-pickers.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,12 @@ Specify allowed dates using objects or functions. When using objects, accepts a
8888

8989
<ExamplesExample file="v-date-picker/prop-allowed-dates" />
9090

91+
#### Date events
92+
93+
You can specify events using arrays, objects or functions. To change the default color of the event use **event-color** prop. Your **events** function or object can return an array of colors (material or css) in case you want to display multiple event indicators.
94+
95+
<ExamplesExample file="v-date-picker/prop-events" />
96+
9197
### Internationalization
9298

9399
Vuetify components can localize date formats by utilizing the [i18n](/features/internationalization) feature. This determines the appropriate locale for date display. When the default date adapter is in use, localization is managed automatically.

packages/vuetify/src/components/VDatePicker/VDatePickerMonth.sass

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,25 @@
4848

4949
.v-date-picker-month__day--hide-adjacent
5050
opacity: 0
51+
.v-date-picker-month__events
52+
height: $date-picker-event-size
53+
left: 0
54+
text-indent: 0
55+
position: absolute
56+
text-align: center
57+
white-space: pre
58+
width: 100%
59+
60+
> div
61+
height: $date-picker-event-size
62+
margin: $date-picker-event-margin
63+
width: $date-picker-event-size
64+
margin-bottom: -1px
65+
66+
.v-badge--dot .v-badge__badge
67+
border-radius: $date-picker-event-border-radius
68+
height: $date-picker-event-size
69+
width: $date-picker-event-size
70+
71+
.v-date-picker-month__day .v-date-picker-month__events
72+
bottom: $date-picker-event-date-bottom

packages/vuetify/src/components/VDatePicker/VDatePickerMonth.tsx

Lines changed: 71 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import './VDatePickerMonth.sass'
33

44
// Components
5+
import { VBadge } from '@/components/VBadge'
56
import { VBtn } from '@/components/VBtn'
67

78
// Composables
@@ -12,11 +13,19 @@ import { MaybeTransition } from '@/composables/transition'
1213

1314
// Utilities
1415
import { computed, ref, shallowRef, toRef, watch } from 'vue'
15-
import { genericComponent, omit, propsFactory, useRender } from '@/util'
16+
import { genericComponent, omit, propsFactory, useRender, wrapInArray } from '@/util'
1617

1718
// Types
1819
import type { PropType } from 'vue'
1920

21+
export type DatePickerEventColorValue = boolean | string | string[]
22+
23+
export type DatePickerEventColors = DatePickerEventColorValue |
24+
Record<string, DatePickerEventColorValue> | ((date: string) => DatePickerEventColorValue)
25+
26+
export type DatePickerEvents = string[] |
27+
((date: string) => DatePickerEventColorValue) | Record<string, DatePickerEventColorValue>
28+
2029
export type VDatePickerMonthSlots = {
2130
day: {
2231
props: {
@@ -40,7 +49,14 @@ export const makeVDatePickerMonthProps = propsFactory({
4049
type: String,
4150
default: 'picker-reverse-transition',
4251
},
43-
52+
events: {
53+
type: [Array, Function, Object] as PropType<DatePickerEvents | null>,
54+
default: () => null,
55+
},
56+
eventColor: {
57+
type: [Array, Function, Object, String] as PropType<DatePickerEventColors>,
58+
default: () => null,
59+
},
4460
...omit(makeCalendarProps(), ['displayValue']),
4561
}, 'VDatePickerMonth')
4662

@@ -148,7 +164,54 @@ export const VDatePickerMonth = genericComponent<VDatePickerMonthSlots>()({
148164
model.value = [value]
149165
}
150166
}
167+
function getEventColors (date: string): string[] {
168+
const { events, eventColor } = props
169+
let eventData: boolean | DatePickerEventColorValue
170+
let eventColors: (boolean | string)[] = []
171+
172+
if (Array.isArray(events)) {
173+
eventData = events.includes(date)
174+
} else if (events instanceof Function) {
175+
eventData = events(date) || false
176+
} else if (events) {
177+
eventData = events[date] || false
178+
} else {
179+
eventData = false
180+
}
151181

182+
if (!eventData) {
183+
return []
184+
} else if (eventData !== true) {
185+
eventColors = wrapInArray(eventData)
186+
} else if (typeof eventColor === 'string') {
187+
eventColors = [eventColor]
188+
} else if (typeof eventColor === 'function') {
189+
eventColors = wrapInArray(eventColor(date))
190+
} else if (Array.isArray(eventColor)) {
191+
eventColors = eventColor
192+
} else if (typeof eventColor === 'object' && eventColor !== null) {
193+
eventColors = wrapInArray(eventColor[date])
194+
}
195+
196+
// Fallback to default color if no color is found
197+
return !eventColors.length
198+
? ['surface-variant']
199+
: eventColors
200+
.filter(Boolean)
201+
.map((color: string | boolean) => typeof color === 'string' ? color : 'surface-variant')
202+
}
203+
204+
function genEvents (date: string): JSX.Element | null {
205+
const eventColors = getEventColors(date)
206+
207+
if (!eventColors.length) return null
208+
209+
return (
210+
<div class="v-date-picker-month__events">
211+
{ eventColors.map((color: string) => <VBadge dot color={ color } />) }
212+
</div>
213+
)
214+
}
152215
useRender(() => (
153216
<div
154217
class="v-date-picker-month"
@@ -193,7 +256,6 @@ export const VDatePickerMonth = genericComponent<VDatePickerMonthSlots>()({
193256
disabled: item.isDisabled,
194257
icon: true,
195258
ripple: false,
196-
text: item.localized,
197259
variant: item.isSelected ? 'flat' : item.isToday ? 'outlined' : 'text',
198260
'aria-label': getDateAriaLabel(item),
199261
'aria-current': item.isToday ? 'date' : undefined,
@@ -222,7 +284,12 @@ export const VDatePickerMonth = genericComponent<VDatePickerMonthSlots>()({
222284
data-v-date={ !item.isDisabled ? item.isoDate : undefined }
223285
>
224286
{ (props.showAdjacentMonths || !item.isAdjacent) && (
225-
slots.day?.(slotProps) ?? (<VBtn { ...slotProps.props } />)
287+
slots.day?.(slotProps) ?? (
288+
<VBtn { ...slotProps.props }>
289+
{ item.localized }
290+
{ genEvents(item.isoDate) }
291+
</VBtn>
292+
)
226293
)}
227294
</div>
228295
)
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Utilities
2+
import { render } from '@test'
3+
import { VDatePicker } from '../VDatePicker'
4+
5+
describe('VDatePicker events', () => {
6+
it('renders event markers when events is an array', async () => {
7+
render(() => (
8+
<VDatePicker
9+
type="month"
10+
month={ 3 }
11+
year={ 2022 }
12+
events={['2022-04-09']}
13+
eventColor="red"
14+
/>
15+
))
16+
17+
const eventElements = document.querySelectorAll('.v-badge')
18+
expect(eventElements.length).toBeGreaterThan(0)
19+
})
20+
21+
it('renders event markers when events is a function', async () => {
22+
render(() => (
23+
<VDatePicker
24+
month={ 3 }
25+
year={ 2022 }
26+
events={ (date: string) => date === '2022-04-09' }
27+
eventColor="red"
28+
/>
29+
))
30+
31+
// Query the DOM for the event markers
32+
const eventElements = document.querySelectorAll('.v-badge')
33+
expect(eventElements.length).toBeGreaterThan(0)
34+
})
35+
36+
it('renders event markers with colors defined by an object', async () => {
37+
render(() => (
38+
<VDatePicker
39+
month={ 3 }
40+
year={ 2022 }
41+
events={['2022-04-09', '2022-04-20']}
42+
eventColor={{ '2022-04-09': 'red', '2022-04-20': 'blue lighten-1' }}
43+
/>
44+
))
45+
46+
const eventElements = document.querySelectorAll('.v-badge')
47+
expect(eventElements.length).toBeGreaterThan(0)
48+
})
49+
50+
it('renders event markers with colors defined by a function', async () => {
51+
render(() => (
52+
<VDatePicker
53+
month={ 3 }
54+
year={ 2022 }
55+
events={['2022-04-09', '2022-04-20']}
56+
eventColor={ (date: string) => ({ '2022-04-09': 'red' }[date] || false) }
57+
/>
58+
))
59+
60+
const eventElements = document.querySelectorAll('.v-badge')
61+
expect(eventElements.length).toBeGreaterThan(0)
62+
})
63+
64+
it('uses default color when eventColor is not set', async () => {
65+
render(() => (
66+
<VDatePicker
67+
type="month"
68+
month={ 3 }
69+
year={ 2022 }
70+
events={{ '2022-04-09': true }}
71+
/>
72+
))
73+
74+
const eventElements = document.querySelectorAll('.v-badge')
75+
expect(eventElements.length).toBeGreaterThan(0)
76+
77+
const hasSurfaceVariant = Array.from(eventElements).some(el =>
78+
el.querySelector('.v-badge__badge')?.classList.contains('bg-surface-variant')
79+
)
80+
expect(hasSurfaceVariant).toBe(true)
81+
})
82+
})

packages/vuetify/src/components/VDatePicker/_variables.scss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,8 @@ $date-picker-months-height: 288px !default;
1616

1717
$date-picker-years-height: 288px !default;
1818
$date-picker-years-padding-inline: 32px !default;
19+
20+
$date-picker-event-size: 8px !default;
21+
$date-picker-event-border-radius: 4px !default;
22+
$date-picker-event-margin: 0 1px !default;
23+
$date-picker-event-date-bottom: 8px !default;

0 commit comments

Comments
 (0)