Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions packages/docs/src/examples/v-date-picker/misc-week-selection.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<template>
<v-container>
<v-row justify="space-around">
<div>
<v-date-picker
v-model="dates"
color="primary"
multiple="week"
title="Week selection"
show-adjacent-months
show-week
></v-date-picker>
<div class="mt-3 d-flex justify-center">
<v-btn :disabled="!dates.length" @click="dates = []">Clear</v-btn>
</div>
</div>
</v-row>
</v-container>
</template>

<script setup>
import { ref } from 'vue'

const dates = ref([])
</script>

<script>
export default {
data: () => ({
dates: [],
}),
}
</script>
6 changes: 6 additions & 0 deletions packages/docs/src/pages/en/components/date-pickers.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@ Specify allowed dates using objects or functions. When using objects, accepts a

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

#### Week selection

You can let users select entire week with a single click when **multiple** is set to `week`.

<ExamplesExample file="v-date-picker/misc-week-selection" />

### Internationalization

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.
Expand Down
18 changes: 15 additions & 3 deletions packages/vuetify/src/components/VDatePicker/VDatePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,14 @@ export const makeVDatePickerProps = propsFactory({
}),
...omit(makeVDatePickerMonthsProps(), ['modelValue']),
...omit(makeVDatePickerYearsProps(), ['modelValue']),
...makeVPickerProps({ title: '$vuetify.datePicker.title' }),
...makeVPickerProps(),

modelValue: null,
}, 'VDatePicker')

export const VDatePicker = genericComponent<new <
T,
Multiple extends boolean | 'range' | number | (string & {}) = false,
Multiple extends boolean | 'range' | 'week' | number | (string & {}) = false,
TModel = Multiple extends true | number | string
? T[]
: T,
Expand Down Expand Up @@ -163,6 +163,12 @@ export const VDatePicker = genericComponent<new <

const isReversing = shallowRef(false)
const header = computed(() => {
if (props.multiple === 'week' && model.value.length > 0) {
const week = adapter.getWeek(model.value[0])
const year = adapter.getYear(model.value[0])
return t('$vuetify.datePicker.weekSelected', week, year)
}

if (props.multiple && model.value.length > 1) {
return t('$vuetify.datePicker.itemsSelected', model.value.length)
}
Expand All @@ -171,6 +177,12 @@ export const VDatePicker = genericComponent<new <
? adapter.format(adapter.date(model.value[0]), 'normalDateWithWeekday')
: t(props.header)
})
const titleText = computed(() => {
const defaultTitle = props.multiple === 'week'
? '$vuetify.datePicker.weekTitle'
: '$vuetify.datePicker.title'
return t(props.title ?? defaultTitle)
})
const text = computed(() => {
let date = adapter.date()

Expand Down Expand Up @@ -360,7 +372,7 @@ export const VDatePicker = genericComponent<new <
v-slots={{
title: () => slots.title?.() ?? (
<div class="v-date-picker__title">
{ t(props.title) }
{ titleText.value }
</div>
),
header: () => slots.header ? (
Expand Down
51 changes: 51 additions & 0 deletions packages/vuetify/src/components/VDatePicker/VDatePickerMonth.sass
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
@use '../../styles/settings'
@use '../../styles/tools'
@use './variables' as *

Expand Down Expand Up @@ -48,3 +49,53 @@

.v-date-picker-month__day--hide-adjacent
opacity: 0

.v-date-picker-month--hover-week
.v-date-picker-month__day--adjacent
opacity: 1
> .v-btn
opacity: 0.5

.v-date-picker-month__weeks
.v-date-picker-month__day--adjacent
opacity: 0.5

.v-date-picker-month__week-background
position: absolute
content: ''
display: block
width: calc(var(--v-date-picker-days-in-week) * (#{$date-picker-month-day-size} + #{$date-picker-month-column-gap}) - #{$date-picker-month-column-gap})
height: $date-picker-month-day-size
transition: 0.3s opacity settings.$standard-easing
left: 0
top: 0
background-color: currentColor
border-radius: $date-picker-month-week-selection-border-radius
z-index: 0
pointer-events: none
opacity: 0

.v-date-picker-month__day--week-start
&:hover,
&:has(+ *:hover)
.v-date-picker-month__week-background
opacity: calc(min(1, calc(var(--v-date-picker-days-in-week) - 1)) * $date-picker-month-week-selection-background-opacity)
&:has(+ * + *:hover)
.v-date-picker-month__week-background
opacity: calc(min(1, calc(var(--v-date-picker-days-in-week) - 2)) * $date-picker-month-week-selection-background-opacity)
&:has(+ * + * + *:hover)
.v-date-picker-month__week-background
opacity: calc(min(1, calc(var(--v-date-picker-days-in-week) - 3)) * $date-picker-month-week-selection-background-opacity)
&:has(+ * + * + * + *:hover)
.v-date-picker-month__week-background
opacity: calc(min(1, calc(var(--v-date-picker-days-in-week) - 4)) * $date-picker-month-week-selection-background-opacity)
&:has(+ * + * + * + * + *:hover)
.v-date-picker-month__week-background
opacity: calc(min(1, calc(var(--v-date-picker-days-in-week) - 5)) * $date-picker-month-week-selection-background-opacity)
&:has(+ * + * + * + * + * + *:hover)
.v-date-picker-month__week-background
opacity: calc(min(1, calc(var(--v-date-picker-days-in-week) - 6)) * $date-picker-month-week-selection-background-opacity)

&:is(.v-date-picker-month__day--selected)
.v-date-picker-month__week-background
opacity: $date-picker-week-selection-opacity
30 changes: 27 additions & 3 deletions packages/vuetify/src/components/VDatePicker/VDatePickerMonth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import { VBtn } from '@/components/VBtn'

// Composables
import { makeCalendarProps, useCalendar } from '@/composables/calendar'
import { createDateRange, useDate } from '@/composables/date/date'
import { useBackgroundColor } from '@/composables/color'
import { createDateRange, createWeekRange, useDate } from '@/composables/date/date'
import { MaybeTransition } from '@/composables/transition'

// Utilities
Expand All @@ -29,7 +30,7 @@ export type VDatePickerMonthSlots = {
export const makeVDatePickerMonthProps = propsFactory({
color: String,
hideWeekdays: Boolean,
multiple: [Boolean, Number, String] as PropType<boolean | 'range' | number | (string & {})>,
multiple: [Boolean, Number, String] as PropType<boolean | 'range' | 'week' | number | (string & {})>,
showWeek: Boolean,
transition: {
type: String,
Expand Down Expand Up @@ -81,6 +82,8 @@ export const VDatePickerMonth = genericComponent<VDatePickerMonthSlots>()({
return model.value.length >= max
})

const { backgroundColorClasses, backgroundColorStyles } = useBackgroundColor(() => props.color)

watch(daysInMonth, (val, oldVal) => {
if (!oldVal) return

Expand Down Expand Up @@ -119,6 +122,10 @@ export const VDatePickerMonth = genericComponent<VDatePickerMonthSlots>()({
}
}

function onWeekClick (value: unknown) {
model.value = createWeekRange(adapter, value, props.weekdays.at(0), props.weekdays.length)
}

function onMultipleClick (value: unknown) {
const index = model.value.findIndex(selection => adapter.isSameDay(selection, value))

Expand All @@ -134,6 +141,8 @@ export const VDatePickerMonth = genericComponent<VDatePickerMonthSlots>()({
function onClick (value: unknown) {
if (props.multiple === 'range') {
onRangeClick(value)
} else if (props.multiple === 'week') {
onWeekClick(value)
} else if (props.multiple) {
onMultipleClick(value)
} else {
Expand All @@ -143,7 +152,12 @@ export const VDatePickerMonth = genericComponent<VDatePickerMonthSlots>()({

useRender(() => (
<div
class="v-date-picker-month"
class={[
'v-date-picker-month',
{
'v-date-picker-month--hover-week': props.multiple === 'week',
},
]}
style={{ '--v-date-picker-days-in-week': props.weekdays.length }}
>
{ props.showWeek && (
Expand Down Expand Up @@ -211,6 +225,16 @@ export const VDatePickerMonth = genericComponent<VDatePickerMonthSlots>()({
]}
data-v-date={ !item.isDisabled ? item.isoDate : undefined }
>
{ item.isWeekStart && props.multiple === 'week' && (
<div
key="week-background"
class={[
'v-date-picker-month__week-background',
backgroundColorClasses.value,
]}
style={ backgroundColorStyles.value }
></div>
)}
{ (props.showAdjacentMonths || !item.isAdjacent) && (
slots.day?.(slotProps) ?? (<VBtn { ...slotProps.props } />)
)}
Expand Down
8 changes: 8 additions & 0 deletions packages/vuetify/src/components/VDatePicker/_variables.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
@use 'sass:map';
@use '../../styles/settings';

$date-picker-width: 328px !default;
$date-picker-show-week-width: 368px !default;
$date-picker-controls-height: var(--v-date-picker-controls-height, 56px) !default;
Expand All @@ -11,6 +14,11 @@ $date-picker-month-day-size: 40px !default;
$date-picker-month-font-size: 0.875rem !default;
$date-picker-month-padding: 0 12px 8px !default;

$date-picker-month-week-selection-border-radius: map.get(settings.$rounded, 'pill') !default;
$date-picker-month-week-selection-background-opacity: 0.1 !default;

$date-picker-week-selection-opacity: .4 !default;

$date-picker-months-grid-gap: 0px 24px !default;
$date-picker-months-height: 288px !default;

Expand Down
7 changes: 7 additions & 0 deletions packages/vuetify/src/composables/date/date.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,13 @@ export function createDateRange (adapter: DateInstance, start: unknown, stop?: u
return datesInRange
}

export function createWeekRange (adapter: DateInstance, value: unknown, firstDayOfWeek?: string | number, weekLength = 7) {
const firstDay = firstDayOfWeek !== undefined ? Number(firstDayOfWeek) : undefined
const weekStart = adapter.startOfWeek(value, firstDay)
const weekEnd = adapter.addDays(weekStart, weekLength - 1)
return [weekStart, adapter.endOfDay(weekEnd)]
}

function createInstance (options: InternalDateOptions, locale: LocaleInstance) {
const instance = reactive(
typeof options.adapter === 'function'
Expand Down
7 changes: 5 additions & 2 deletions packages/vuetify/src/labs/VDateInput/VDateInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { makeVTextFieldProps, VTextField } from '@/components/VTextField/VTextFi

// Composables
import { useDate } from '@/composables/date'
import { createDateRange } from '@/composables/date/date'
import { createDateRange, createWeekRange } from '@/composables/date/date'
import { makeDateFormatProps, useDateFormat } from '@/composables/dateFormat'
import { makeDisplayProps, useDisplay } from '@/composables/display'
import { makeFocusProps } from '@/composables/focus'
Expand Down Expand Up @@ -118,7 +118,7 @@ export const VDateInput = genericComponent<VDateInputSlots>()({
return t('$vuetify.datePicker.itemsSelected', value.length)
}

if (props.multiple === 'range') {
if (props.multiple === 'range' || props.multiple === 'week') {
const start = value[0]
const end = value[value.length - 1]

Expand Down Expand Up @@ -217,6 +217,9 @@ export const VDateInput = genericComponent<VDateInputSlots>()({
if (props.multiple === 'range') {
const [start, stop] = parts.map(parseDate).toSorted((a, b) => adapter.isAfter(a, b) ? 1 : -1)
model.value = createDateRange(adapter, start, stop)
} else if (props.multiple === 'week') {
const [start] = parts.map(parseDate).toSorted((a, b) => adapter.isAfter(a, b) ? 1 : -1)
model.value = createWeekRange(adapter, start, props.firstDayOfWeek)
} else {
model.value = parts.map(parseDate)
}
Expand Down
2 changes: 2 additions & 0 deletions packages/vuetify/src/locale/af.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ export default {
input: {
placeholder: 'Voer datum in',
},
weekSelected: 'Week {0}, {1}',
weekTitle: 'Select week',
},
noDataText: 'Geen data is beskikbaar nie',
carousel: {
Expand Down
2 changes: 2 additions & 0 deletions packages/vuetify/src/locale/ar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ export default {
input: {
placeholder: 'Enter date',
},
weekSelected: 'Week {0}, {1}',
weekTitle: 'Select week',
},
noDataText: 'لا توجد بيانات',
carousel: {
Expand Down
2 changes: 2 additions & 0 deletions packages/vuetify/src/locale/az.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ export default {
input: {
placeholder: 'Tarixi daxil edin',
},
weekSelected: 'Week {0}, {1}',
weekTitle: 'Select week',
},
noDataText: 'Heç bir məlumat yoxdur.',
carousel: {
Expand Down
2 changes: 2 additions & 0 deletions packages/vuetify/src/locale/bg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ export default {
input: {
placeholder: 'Въведете дата',
},
weekSelected: 'Week {0}, {1}',
weekTitle: 'Select week',
},
noDataText: 'Няма налични данни',
carousel: {
Expand Down
2 changes: 2 additions & 0 deletions packages/vuetify/src/locale/ca.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ export default {
input: {
placeholder: 'Introdueix la data',
},
weekSelected: 'Week {0}, {1}',
weekTitle: 'Select week',
},
noDataText: 'Sense dades',
carousel: {
Expand Down
2 changes: 2 additions & 0 deletions packages/vuetify/src/locale/ckb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ export default {
input: {
placeholder: 'بەروار بنووسە',
},
weekSelected: 'Week {0}, {1}',
weekTitle: 'Select week',
},
noDataText: 'هیچ داتایەک بەردەست نیە',
carousel: {
Expand Down
2 changes: 2 additions & 0 deletions packages/vuetify/src/locale/cs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ export default {
input: {
placeholder: 'Zadejte datum',
},
weekSelected: 'Week {0}, {1}',
weekTitle: 'Select week',
},
noDataText: 'Nejsou dostupná žádná data',
carousel: {
Expand Down
2 changes: 2 additions & 0 deletions packages/vuetify/src/locale/da.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ export default {
input: {
placeholder: 'Indtast dato',
},
weekSelected: 'Week {0}, {1}',
weekTitle: 'Select week',
},
noDataText: 'Ingen data tilgængelig',
carousel: {
Expand Down
2 changes: 2 additions & 0 deletions packages/vuetify/src/locale/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ export default {
input: {
placeholder: 'Datum eingeben',
},
weekSelected: 'Week {0}, {1}',
weekTitle: 'Select week',
},
noDataText: 'Keine Daten vorhanden',
carousel: {
Expand Down
2 changes: 2 additions & 0 deletions packages/vuetify/src/locale/el.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ export default {
input: {
placeholder: 'Εισαγάγετε ημερομηνία',
},
weekSelected: 'Week {0}, {1}',
weekTitle: 'Select week',
},
noDataText: 'Χωρίς δεδομένα',
carousel: {
Expand Down
2 changes: 2 additions & 0 deletions packages/vuetify/src/locale/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ export default {
input: {
placeholder: 'Enter date',
},
weekSelected: 'Week {0}, {1}',
weekTitle: 'Select week',
},
noDataText: 'No data available',
carousel: {
Expand Down
Loading