# Datepicker
# Description
Component providing a classic datapicker with rotating qualities:
- Easy mode switching: single / band
- Range The mode is made "on one sheet"
- Mode of operation with weeks, months, quarterly periods and years
- Works in both unixtime and ISO formats
# Connection
<template>
<section>
<!-- unixtime -->
<Datepicker
:range="true"
:allowFrom="1405398400"
:allowTo="1705398400"
:yearsFuture="2"
:yearsPast="4"
panel="quarters"
@select="dateSelect"
/>
<!-- ISO -->
<Datepicker
:range="true"
allowFrom="2014-07-15T08:26:40+04:00"
allowTo="2024-01-16T12:46:40+03:00"
:yearsFuture="2"
:yearsPast="4"
panel="quarters"
:unixtime="false"
@select="dateSelect"
/>
</section>
</template>
<script>
export default {
methods: {
dateSelect(event) {
console.log(event);
},
},
};
</script>
# API
# Props
Name | Type | Description | Default |
---|---|---|---|
range | Boolean | Widget mode: range / single. Default: range. | true |
locale | String, | Locale. Currently, only the default Russian is supported. | 'ru' |
past | Boolean | Accessibility of the past. | true |
future | Boolean | Availability of the future. | true |
panel | String: 'days' , 'weeks' , 'months' , 'quarters' , 'years' | Set the default bar for the range. | null |
showControls | Boolean | Show control buttons (not used or customized). | false |
yearsCount | Number | A complex parameter that determines how many years in the future and the past simultaneously from the current date should be shown. | 2 |
yearsPast | Number | A parameter that determines how many years in the past from the current date to show. | null |
yearsFuture | Number | A parameter that determines how many years in the future from the current date to show. | null |
allowFrom | [Number, String] | The date in the past from which dates are available. | null |
allowTo | [Number, String] | The date in the future, the end of which dates are available. | null |
unixtime | Boolean | The format for receiving (allowFrom, allowTo) and displaying dates. Default: unixtime, can be ISO-8601. | true |
resetTitle | String | Reset button text. | null |
submitTitle | String | Submit button text. | null |
presets | Array | List of available presets (not used or customized). | ['today', 'yesterday', 'last7days', 'last30days', 'last90days', 'last365days', 'custom'] |
# Events
Name | Description |
---|---|
@select | The event is triggered when a range or date in a single is selected. |
@update | The event is triggered when the submit button is clicked. |
@reset | The event is triggered when the reset button is pressed. |
Return:
// With @reset: to === from === null, with @select and @update for a single: to === from
{
to: null,
from: null,
panel: 'quarters'
}
# Styles
$width = 400px
$range-color = rgba($colors.bird, $opacites.psy)
# Render
2021
Квартал 1
Январь
Февраль
Март
Квартал 2
Апрель
Май
Июнь
Квартал 3
Июль
Август
Сентябрь
Квартал 4
Октябрь
Ноябрь
Декабрь
# Source code
@/src/components/Datepicker/Datepicker.vue
<template>
<div class="datepicker">
<div class="panels-choices" v-if="availablePanels.length > 1">
<div
class="panels-button"
v-for="(panel, index) in availablePanels"
:key="`panel${index}`"
:class="{ 'is-current': panel === currentPanel }"
@click="currentPanel = panel"
>
{{ dictionnaries.panels[panel] }}
</div>
</div>
<div class="preset-ranges" v-if="isPresetPicker && presets.length > 1">
<div
class="preset"
v-for="(entry, index) in availablePresets"
:key="`preset${index}`"
>
<input type="radio" v-model="preset" :id="entry" :value="entry" />
<label :for="entry">
<span class="check" />
<span>{{ dictionnaries.presets[entry] }}</span>
</label>
</div>
</div>
<div
class="calendar"
:class="weekSelector ? 'calendar-week' : 'calendar-days'"
v-if="isDaysPicker"
>
<div class="calendar-header">
<div
class="calendar-previous-month calendar-arrow calendar-arrow-previous"
:aria-label="dictionnaries.previousMonth"
@click="changeMonth(1)"
>
<Icon name="left" />
</div>
<div class="calendar-month-name">
{{ currentMonthName }}
</div>
<div
class="calendar-previous-month calendar-arrow calendar-arrow-next"
:aria-label="dictionnaries.nextMonth"
@click="changeMonth(-1)"
>
<Icon name="right" />
</div>
</div>
<div class="calendar-days-name">
<div class="day" v-for="(day, index) in firstWeek" :key="`day${index}`">
<span>
{{ day.name }}
</span>
</div>
</div>
<div class="calendar-days">
<div
class="day"
v-for="day in monthDays"
:key="day.date | date('DDMMYYYY')"
:class="dayClasses(day)"
@click="selectDay(day.date)"
@mouseover="hoverizeDay(day.date)"
@mouseleave="hoverRange = []"
>
<span>{{ day.date | date('D') }}</span>
</div>
</div>
</div>
<div class="calendar" v-if="isMonthsPicker">
<div class="calendar-header">
<div
class="calendar-previous-month calendar-arrow calendar-arrow-previous"
:aria-label="dictionnaries.previousYear"
@click="changeYear(1)"
>
<Icon name="left" />
</div>
<div class="calendar-month-name">
{{ currentYearName }}
</div>
<div
class="calendar-previous-month calendar-arrow calendar-arrow-next"
:aria-label="dictionnaries.nextYear"
@click="changeYear(-1)"
>
<Icon name="right" />
</div>
</div>
<div class="calendar-months" v-if="isMonthsPanel">
<div
class="month"
v-for="month in yearMonths"
:key="month.date | date('DDMMYYYY')"
:class="monthClasses(month)"
@click="selectMonth(month)"
@mouseover="hoverizeMonth(month.date)"
@mouseleave="hoverRange = []"
>
<span>
{{ month.displayDate }}
</span>
</div>
</div>
<div class="calendar-quarters" v-if="isQuartersPanel">
<div
class="quarter"
v-for="(quarter, index) in yearQuarters"
:key="`quarter${index}`"
:class="quarterClasses(quarter)"
@click="selectQuarter(quarter)"
@mouseover="hoverizeQuarter(quarter.range.start, quarter.range.end)"
@mouseleave="hoverRange = []"
>
<div class="legend">{{ dictionnaries.quarter }} {{ ++index }}</div>
<div class="months">
<div
class="month"
v-for="(month, index) in quarter.months"
:key="`month${index}`"
>
<span>
{{ month.displayDate }}
</span>
</div>
</div>
</div>
</div>
</div>
<div class="calendar" v-if="isYearPicker">
<div class="calendar-years">
<div
class="year"
v-for="(year, index) in years"
:key="`year${index}`"
:class="yearClasses(year)"
@click="selectYear(year)"
@mouseover="hoverizeYear(year)"
@mouseleave="hoverRange = []"
>
<span>
{{ year.displayDate }}
</span>
</div>
</div>
</div>
<div class="datepicker-controls" v-if="showControls">
<div class="datepicker-button datepicker-reset" @click="reset">
{{ resetLegend }}
</div>
<div
class="datepicker-button datepicker-submit"
@click="update"
:class="{ 'is-disabled': !(values.from && values.to) }"
>
{{ submitLegend }}
</div>
</div>
</div>
</template>
<script>
import { dateFilter } from '../../../node_modules/vue-date-fns/src/index';
import {
addDays,
addMonths,
addWeeks,
addYears,
differenceInDays,
endOfDay,
endOfMonth,
endOfWeek,
endOfYear,
format,
isAfter,
isBefore,
isSameDay,
isSameMonth,
isValid,
isWithinRange,
parse,
startOfDay,
startOfMonth,
startOfWeek,
startOfYear,
subDays,
subMonths,
subWeeks,
subYears,
} from 'date-fns';
import Icon from '../Icon/Icon';
const LOCALES = {
ru: {
reset: 'Сбросить',
submit: 'Применить',
previousMonth: 'Предыдущий месяц',
nextMonth: 'Следующий месяц',
previousYear: 'Предыдущий год',
nextYear: 'Следующий год',
quarter: 'Квартал',
panels: {
days: 'Дни',
weeks: 'Недели',
months: 'Месяцы',
quarters: 'Кварталы',
years: 'Года',
day: 'День',
week: 'Неделя',
month: 'Месяц',
quarter: 'Квартал',
year: 'Год',
},
presets: {
custom: 'Пользовательский диапазон',
forever: 'С начала',
last7days: 'Последние 7 дней',
last30days: 'Последние 30 дней',
last90days: 'Последние 90 дней',
last365days: 'Последние 365 дней',
next365days: 'Следующие 365 дней',
next90days: 'Следующие 90 дней',
next30days: 'Следующие 30 дней',
next7days: 'Следующие 7 дней',
today: 'Сегодня',
tomorrow: 'Завтра',
yesterday: 'Вчера',
},
},
};
const locales = {
ru: require('date-fns/locale/ru'),
};
const RANGE_PANELS = ['days', 'weeks', 'months', 'quarters', 'years'];
const SINGLE_PANELS = ['day'];
const FULL_ISO_FORMAT = 'YYYY-MM-DDTHH:mm:ssZ';
const UNIX_FORMAT = 'X';
export default {
name: 'Datepicker',
filters: {
date: dateFilter,
},
components: {
Icon,
},
props: {
range: {
type: Boolean,
default: true,
},
locale: {
type: String,
default: 'ru',
},
past: {
type: Boolean,
default: true,
},
future: {
type: Boolean,
default: true,
},
showControls: {
type: Boolean,
default: false,
},
panel: {
type: [String, null],
default: null,
},
yearsCount: {
type: Number,
default: 2,
},
yearsPast: {
type: [Number, null],
default: null,
},
yearsFuture: {
type: [Number, null],
default: null,
},
allowFrom: {
type: [Number, String, null],
default: null,
},
allowTo: {
type: [Number, String, null],
default: null,
},
unixtime: {
type: Boolean,
default: true,
},
resetTitle: {
type: [String, null],
default: null,
},
submitTitle: {
type: [String, null],
default: null,
},
presets: {
type: Array,
default: () => [
'today',
'yesterday',
'last7days',
'last30days',
'last90days',
'last365days',
'custom',
],
},
},
data() {
return {
currentPanel: null,
current: null,
weekSelector: false,
isWeeksRangeOpen: false,
isMonthsRangeOpen: false,
isQuartersRangeOpen: false,
isYearsRangeOpen: false,
monthDays: [],
now: new Date().toISOString(),
start: null,
end: null,
values: {
from: null,
to: null,
},
hoverRange: [],
preset: 'custom',
aFrom: null,
aTo: null,
};
},
created() {
// Parse Inputs
Object.keys(this.values).forEach((value) => {
this.values[value] = isValid(parse(this[value])) ? this[value] : null;
});
// Display current month or "to" month
this.current = this.values.to ? this.values.to : this.now;
// Update Calendar
this.updateCalendar();
this.start = startOfYear(this.years[0].date);
this.end = endOfYear(this.years[this.years.length - 1].date);
// Set current panel
if (this.range && this.panel && RANGE_PANELS.includes(this.panel)) {
this.currentPanel = this.panel || this.availablePanels[0];
} else {
this.currentPanel = this.availablePanels[0];
}
if (!this.unixtime) {
this.aFrom = this.allowFrom;
this.aTo = this.allowTo;
} else {
this.aFrom = format(new Date(this.allowFrom * 1000), FULL_ISO_FORMAT);
this.aTo = format(new Date(this.allowTo * 1000), FULL_ISO_FORMAT);
}
},
computed: {
dictionnaries() {
return LOCALES[this.locale];
},
availablePanels() {
if (this.range) {
return RANGE_PANELS;
}
return SINGLE_PANELS;
},
availablePresets() {
return this.presets;
},
resetLegend() {
return this.resetTitle ? this.resetTitle : this.dictionnaries.reset;
},
submitLegend() {
return this.submitTitle ? this.submitTitle : this.dictionnaries.submit;
},
firstWeek() {
const days = this.monthDays.slice(0, 7);
const week = [];
for (const day of days) {
week.push({
name: format(day.date, 'dd', { locale: locales[this.locale] }),
});
}
return week;
},
yearMonths() {
const months = [];
let month = startOfYear(this.current);
while (months.length !== 12) {
const isMonthAllowed = this.isRangeAllowed([
startOfMonth(month),
endOfMonth(month),
]);
const str1 = format(month, 'MMMM', { locale: locales[this.locale] });
const str2 = str1[0].toUpperCase() + str1.slice(1);
months.push({
date: month,
selectable: isMonthAllowed,
displayDate: str2,
});
month = addMonths(month, 1);
}
return months;
},
yearQuarters() {
const quarters = [];
for (const [index] of this.yearMonths.entries()) {
if (index % 3 === 0) {
const isQuarterAllowed = this.isRangeAllowed([
startOfMonth(this.yearMonths[index].date),
endOfMonth(this.yearMonths[index + 2].date),
]);
quarters.push({
months: [
this.yearMonths[index],
this.yearMonths[index + 1],
this.yearMonths[index + 2],
],
selectable: isQuarterAllowed,
range: {
start: startOfDay(startOfMonth(this.yearMonths[index].date)),
end: endOfDay(endOfMonth(this.yearMonths[index + 2].date)),
},
});
}
}
return quarters;
},
years() {
const years = [];
const futureCount = this.yearsFuture ? this.yearsFuture : this.yearsCount;
const pastCount = this.yearsPast ? this.yearsPast : this.yearsCount;
let i = +this.future * futureCount + +this.past * pastCount + 1;
let start = this.future ? addYears(this.now, futureCount) : this.now;
while (i !== 0) {
const isYearAllowed = this.isRangeAllowed([
startOfYear(start),
endOfYear(start),
]);
years.push({
date: start,
selectable: isYearAllowed,
displayDate: format(start, 'YYYY', { locale: locales[this.locale] }),
});
start = subYears(start, 1);
i = i - 1;
}
return years.reverse();
},
currentMonthName() {
return format(this.current, 'MMMM YYYY', {
locale: locales[this.locale],
});
},
currentYearName() {
return format(this.current, 'YYYY', { locale: locales[this.locale] });
},
isPresetPicker() {
return this.currentPanel === 'days';
},
isDaysPicker() {
return (
this.currentPanel === 'days' ||
this.currentPanel === 'weeks' ||
this.currentPanel === 'week' ||
this.currentPanel === 'day'
);
},
isMonthsPicker() {
return (
this.currentPanel === 'months' ||
this.currentPanel === 'month' ||
this.currentPanel === 'quarter' ||
this.currentPanel === 'quarters'
);
},
isYearPicker() {
return this.currentPanel === 'year' || this.currentPanel === 'years';
},
isMonthsPanel() {
return this.currentPanel === 'months' || this.currentPanel === 'month';
},
isQuartersPanel() {
return (
this.currentPanel === 'quarter' || this.currentPanel === 'quarters'
);
},
formatValues() {
if (!this.unixtime) {
return {
to: format(endOfDay(this.values.to), FULL_ISO_FORMAT),
from: format(startOfDay(this.values.from), FULL_ISO_FORMAT),
};
}
return {
to: format(this.values.to, UNIX_FORMAT),
from: format(this.values.from, UNIX_FORMAT),
};
},
},
methods: {
reset() {
this.values = {
to: null,
from: null,
};
this.preset = null;
this.$emit('reset', { to: null, from: null });
},
update() {
if (!this.values.from || !this.values.to) {
return;
}
this.$emit('update', {
...this.formatValues,
panel: this.currentPanel,
});
},
changeMonth(diff) {
this.current = subMonths(this.current, diff);
this.updateCalendar();
},
changeYear(diff) {
this.current = subYears(this.current, diff);
this.updateCalendar();
},
selectDay(date) {
let range;
// Select weeks
if (this.weekSelector) {
if (!this.range) {
// Select week single
range = this.getAllowedDatesOfRange([
startOfWeek(date, { weekStartsOn: 1 }),
endOfWeek(date, { weekStartsOn: 1 }),
]);
this.values.from = range[0];
this.values.to = range[range.length - 1];
return;
} else {
// Select weeks range
this.isMonthsRangeOpen = false;
this.isQuartersRangeOpen = false;
this.isYearsRangeOpen = false;
if (!this.values.from && !this.values.to) {
range = this.getAllowedDatesOfRange([
startOfWeek(date, { weekStartsOn: 1 }),
endOfWeek(date, { weekStartsOn: 1 }),
]);
this.isWeeksRangeOpen = true;
} else {
if (isWithinRange(date, this.values.from, this.values.from)) {
// On selected weeks
range = this.getAllowedDatesOfRange([
startOfWeek(date, { weekStartsOn: 1 }),
endOfWeek(date, { weekStartsOn: 1 }),
]);
} else if (isBefore(date, this.values.from)) {
// Before selected weeks
range = this.getAllowedDatesOfRange([
startOfWeek(date, { weekStartsOn: 1 }),
endOfWeek(date, { weekStartsOn: 1 }),
]);
this.isWeeksRangeOpen = true;
} else {
// After selected weeks
if (this.isWeeksRangeOpen) {
range = this.getAllowedDatesOfRange([
this.values.from,
endOfWeek(date, { weekStartsOn: 1 }),
]);
} else {
range = this.getAllowedDatesOfRange([
startOfWeek(date, { weekStartsOn: 1 }),
endOfWeek(date, { weekStartsOn: 1 }),
]);
}
this.isWeeksRangeOpen = !this.isWeeksRangeOpen;
}
}
this.values.from = range[0];
this.values.to = range[range.length - 1];
this.preset = 'custom';
return;
}
}
// Select days
if (!this.range) {
// Select day single
this.values.from = startOfDay(date);
this.values.to = startOfDay(date);
return;
} else {
// Select days range
this.isWeeksRangeOpen = false;
this.isMonthsRangeOpen = false;
this.isQuartersRangeOpen = false;
this.isYearsRangeOpen = false;
if (
(this.values.from && this.values.to) ||
(!this.values.from && !this.values.to)
) {
this.values.from = date;
this.values.to = null;
} else if (this.values.from && !this.values.to) {
if (isBefore(date, this.values.from)) {
this.values.from = date;
} else {
this.values.to = date;
this.hoverRange = [];
}
}
this.preset = 'custom';
}
},
selectMonth(month) {
let range;
if (!this.range) {
// Select month single
range = this.getAllowedDatesOfRange([
startOfMonth(month.date),
endOfMonth(month.date),
]);
this.values.from = range[0];
this.values.to = range[range.length - 1];
this.current = this.values.to;
return;
} else {
// Select months range
this.isWeeksRangeOpen = false;
this.isQuartersRangeOpen = false;
this.isYearsRangeOpen = false;
if (
(!this.values.from && !this.values.to) ||
(this.values.from && !this.values.to)
) {
range = this.getAllowedDatesOfRange([
startOfMonth(month.date),
endOfMonth(month.date),
]);
this.current = this.values.to;
this.isMonthsRangeOpen = true;
} else {
if (isWithinRange(month.date, this.values.from, this.values.to)) {
// On selected months
if (!this.isMonthsRangeOpen) {
range = this.getAllowedDatesOfRange([
startOfMonth(month.date),
endOfMonth(month.date),
]);
} else {
range = this.getAllowedDatesOfRange([
this.values.from,
endOfMonth(month.date),
]);
}
this.isMonthsRangeOpen = !this.isMonthsRangeOpen;
} else if (isBefore(month.date, this.values.from)) {
// Before selected months
range = this.getAllowedDatesOfRange([
startOfMonth(month.date),
endOfMonth(month.date),
]);
this.isMonthsRangeOpen = true;
} else {
// After selected months
if (this.isMonthsRangeOpen) {
range = this.getAllowedDatesOfRange([
this.values.from,
endOfMonth(month.date),
]);
} else {
range = this.getAllowedDatesOfRange([
startOfMonth(month.date),
endOfMonth(month.date),
]);
}
this.isMonthsRangeOpen = !this.isMonthsRangeOpen;
}
}
this.values.from = range[0];
this.values.to = range[range.length - 1];
this.current = this.values.to;
this.preset = 'custom';
}
},
selectQuarter(quarter) {
let range;
if (!this.range) {
// Select quarter single
range = this.getAllowedDatesOfRange([
startOfDay(startOfMonth(quarter.range.start)),
endOfMonth(quarter.range.end),
]);
this.values.from = range[0];
this.values.to = range[range.length - 1];
this.current = this.values.to;
return;
} else {
// Select quarters range
this.isWeeksRangeOpen = false;
this.isMonthsRangeOpen = false;
this.isYearsRangeOpen = false;
if (
(!this.values.from && !this.values.to) ||
(this.values.from && !this.values.to)
) {
range = this.getAllowedDatesOfRange([
startOfDay(startOfMonth(quarter.range.start)),
endOfMonth(quarter.range.end),
]);
this.isQuartersRangeOpen = true;
} else {
if (
isWithinRange(quarter.range.start, this.values.from, this.values.to)
) {
// On selected quarters
if (!this.isQuartersRangeOpen) {
range = this.getAllowedDatesOfRange([
startOfDay(startOfMonth(quarter.range.start)),
endOfMonth(quarter.range.end),
]);
} else {
range = this.getAllowedDatesOfRange([
this.values.from,
endOfMonth(quarter.range.end),
]);
}
this.isQuartersRangeOpen = !this.isQuartersRangeOpen;
} else if (isBefore(quarter.range.start, this.values.from)) {
// Before selected quarters
range = this.getAllowedDatesOfRange([
startOfDay(startOfMonth(quarter.range.start)),
endOfMonth(quarter.range.end),
]);
this.isQuartersRangeOpen = true;
} else {
// After selected quarters
if (this.isQuartersRangeOpen) {
range = this.getAllowedDatesOfRange([
this.values.from,
endOfMonth(quarter.range.end),
]);
} else {
range = this.getAllowedDatesOfRange([
startOfDay(startOfMonth(quarter.range.start)),
endOfMonth(quarter.range.end),
]);
}
this.isQuartersRangeOpen = !this.isQuartersRangeOpen;
}
}
this.values.from = range[0];
this.values.to = range[range.length - 1];
this.current = this.values.to;
this.preset = 'custom';
}
},
selectYear(year) {
let range;
if (!this.range) {
// Select year single
range = this.getAllowedDatesOfRange([
startOfYear(year.date),
endOfYear(year.date),
]);
this.values.from = range[0];
this.values.to = range[range.length - 1];
this.current = this.values.to;
return;
} else {
// Select years range
this.isWeeksRangeOpen = false;
this.isMonthsRangeOpen = false;
this.isQuartersRangeOpen = false;
if (
(!this.values.from && !this.values.to) ||
(this.values.from && !this.values.to)
) {
range = this.getAllowedDatesOfRange([
startOfYear(year.date),
endOfYear(year.date),
]);
this.isYearsRangeOpen = true;
} else {
if (isWithinRange(year.date, this.values.from, this.values.to)) {
// On selected years
if (!this.isYearsRangeOpen) {
range = this.getAllowedDatesOfRange([
startOfYear(year.date),
endOfYear(year.date),
]);
} else {
range = this.getAllowedDatesOfRange([
this.values.from,
endOfYear(year.date),
]);
}
this.isYearsRangeOpen = !this.isYearsRangeOpen;
} else if (isBefore(startOfYear(year.date), this.values.from)) {
// Before selected years
range = this.getAllowedDatesOfRange([
startOfYear(year.date),
endOfYear(year.date),
]);
this.isYearsRangeOpen = true;
} else {
// After selected years
if (this.isYearsRangeOpen) {
range = this.getAllowedDatesOfRange([
this.values.from,
endOfYear(year.date),
]);
} else {
range = this.getAllowedDatesOfRange([
startOfYear(year.date),
endOfYear(year.date),
]);
}
this.isYearsRangeOpen = !this.isYearsRangeOpen;
}
}
this.values.from = range[0];
this.values.to = range[range.length - 1];
this.current = this.values.to;
this.preset = 'custom';
}
},
hoverizeDay(date) {
if (
!this.weekSelector &&
(!(this.values.from && !this.values.to) ||
isBefore(date, this.values.from))
) {
this.hoverRange = [];
return;
}
if (this.weekSelector) {
let availableRange = [];
if (this.range) {
if (
(!this.values.from && !this.values.to) ||
(this.values.from && !this.values.to)
) {
availableRange = this.getAllowedDatesOfRange([
startOfWeek(date, { weekStartsOn: 1 }),
endOfWeek(date, { weekStartsOn: 1 }),
]);
} else {
if (isBefore(date, this.values.from)) {
availableRange = this.getAllowedDatesOfRange([
startOfWeek(date, { weekStartsOn: 1 }),
endOfWeek(date, { weekStartsOn: 1 }),
]);
} else {
if (this.isWeeksRangeOpen) {
availableRange = this.getAllowedDatesOfRange([
startOfWeek(this.values.from, { weekStartsOn: 1 }),
endOfWeek(date, { weekStartsOn: 1 }),
]);
} else {
availableRange = this.getAllowedDatesOfRange([
startOfWeek(date, { weekStartsOn: 1 }),
endOfWeek(date, { weekStartsOn: 1 }),
]);
}
}
}
} else {
availableRange = this.getAllowedDatesOfRange([
startOfWeek(date, { weekStartsOn: 1 }),
endOfWeek(date, { weekStartsOn: 1 }),
]);
}
this.hoverRange = [
availableRange[0],
availableRange[availableRange.length - 1],
];
} else {
this.hoverRange = [this.values.from, date];
}
},
hoverizeMonth(date) {
if (
this.range &&
!isBefore(date, this.values.from) &&
this.isMonthsRangeOpen
) {
this.hoverRange = [startOfMonth(this.values.from), endOfMonth(date)];
return;
} else {
this.hoverRange = [];
}
},
hoverizeQuarter(start, end) {
if (
this.range &&
!isBefore(start, this.values.from) &&
this.isQuartersRangeOpen
) {
this.hoverRange = [this.values.from, end];
return;
} else {
this.hoverRange = [];
}
},
hoverizeYear(year) {
if (
this.range &&
!isBefore(startOfYear(year.date), this.values.from) &&
this.isYearsRangeOpen
) {
this.hoverRange = [this.values.from, endOfYear(year.date)];
return;
} else {
this.hoverRange = [];
}
},
updateCalendar() {
const days = [];
const lastDayOfMonth = endOfMonth(this.current);
const firstDayOfMonth = startOfMonth(this.current);
let nbDaysLastMonth = (+format(firstDayOfMonth, 'd') - 1) % 7;
nbDaysLastMonth = nbDaysLastMonth === -1 ? 6 : nbDaysLastMonth;
let day = subDays(firstDayOfMonth, nbDaysLastMonth);
while (isBefore(day, lastDayOfMonth) || days.length % 7 > 0) {
const isAllowedByFutureAndPast =
this.future && isAfter(day, this.now)
? true
: false || (this.past && isBefore(day, this.now))
? true
: false || isSameDay(day, this.now);
const isAllowedByAllowedProps = this.isDateAllowed(day);
days.push({
date: day,
selectable: isAllowedByFutureAndPast && isAllowedByAllowedProps,
currentMonth: isSameMonth(this.current, day),
});
day = addDays(day, 1);
}
this.monthDays = days;
},
dayClasses(day) {
const classes = [];
if (day.currentMonth) {
classes.push('is-current-month');
}
if (
this.values.from &&
this.values.to &&
isWithinRange(day.date, this.values.from, this.values.to)
) {
classes.push('is-selected');
}
if (!day.selectable) {
classes.push('is-disabled');
}
if (isSameDay(day.date, this.now)) {
classes.push('is-today');
}
if (
(!this.values.to && isSameDay(day.date, this.values.from)) ||
(this.values.to &&
!this.values.from &&
isSameDay(day.date, this.values.from) &&
isSameDay(day.date, this.values.to)) ||
(this.values.to &&
this.values.from &&
isSameDay(day.date, this.values.from))
) {
classes.push('is-first-range');
classes.push('is-edge-range');
}
if (this.values.to && isSameDay(day.date, this.values.to)) {
classes.push('is-last-range');
classes.push('is-edge-range');
}
if (
this.hoverRange.length === 2 &&
isWithinRange(day.date, this.hoverRange[0], this.hoverRange[1])
) {
classes.push('is-in-range');
}
return classes;
},
monthClasses(month) {
const classes = [];
if (!month.selectable) {
classes.push('is-disabled');
}
if (
this.values.from &&
this.values.to &&
this.isWithinRangeCustom(
startOfMonth(month.date),
endOfMonth(month.date),
this.values.from,
this.values.to,
)
) {
classes.push('is-selected');
}
if (
this.hoverRange.length === 2 &&
isWithinRange(month.date, this.hoverRange[0], this.hoverRange[1])
) {
classes.push('is-in-range');
}
return classes;
},
quarterClasses(quarter) {
const classes = [];
if (!quarter.selectable) {
classes.push('is-disabled');
}
if (
this.values.from &&
this.values.to &&
this.isWithinRangeCustom(
quarter.range.start,
quarter.range.end,
this.values.from,
this.values.to,
)
) {
classes.push('is-selected');
}
if (
this.hoverRange.length === 2 &&
isWithinRange(
quarter.months[1].date,
this.hoverRange[0],
this.hoverRange[1],
)
) {
classes.push('is-in-range');
}
return classes;
},
yearClasses(year) {
const classes = [];
if (!year.selectable) {
classes.push('is-disabled');
}
if (
this.values.from &&
this.values.to &&
this.isWithinRangeCustom(
startOfYear(year.date),
endOfYear(year.date),
this.values.from,
this.values.to,
)
) {
classes.push('is-selected');
}
if (
this.hoverRange.length === 2 &&
isWithinRange(year.date, this.hoverRange[0], this.hoverRange[1])
) {
classes.push('is-in-range');
}
return classes;
},
isDateAllowed(date) {
let isAllowed = true;
if (this.aFrom) {
isAllowed = isAllowed && !isBefore(date, parse(this.aFrom));
}
if (this.aTo) {
isAllowed = isAllowed && !isAfter(date, parse(this.aTo));
}
return isAllowed;
},
isWithinRangeCustom(from, to, start, end) {
return (
isWithinRange(from, start, end) ||
isWithinRange(to, start, end) ||
(isBefore(from, start) && isAfter(to, end))
);
},
isRangeAllowed([from, to]) {
let rangeFrom;
let rangeTo;
if (!this.past) {
rangeFrom = subDays(this.now, 1);
} else if (this.aFrom) {
rangeFrom = this.aFrom;
} else {
rangeFrom = this.start;
}
if (!this.future) {
rangeTo = addDays(this.now, 1);
} else if (this.aTo) {
rangeTo = this.aTo;
} else {
rangeFrom = this.end;
}
return (
isWithinRange(from, rangeFrom, rangeTo) ||
isWithinRange(to, rangeFrom, rangeTo) ||
(isBefore(from, rangeFrom) && isAfter(to, rangeTo))
);
},
getAllowedDatesOfRange([from, to]) {
const distance = differenceInDays(to, from);
return new Array(distance + 1)
.fill(null)
.map((_, index) => addDays(from, index))
.filter(this.isDateAllowed);
},
},
watch: {
currentPanel(panel) {
this.weekSelector = panel === 'week' || panel === 'weeks' ? true : false;
this.updateCalendar();
},
values: {
handler: function () {
if (this.values.from && this.values.to) {
this.$emit('select', {
...this.formatValues,
panel: this.currentPanel,
});
}
},
deep: true,
},
preset(preset) {
this.current = this.now;
this.updateCalendar();
switch (preset) {
case 'custom':
this.values = { from: null, to: null };
break;
case 'today':
this.values = { from: startOfDay(this.now), to: this.now };
break;
case 'yesterday':
this.values = {
from: startOfDay(subDays(this.now, 1)),
to: endOfDay(subDays(this.now, 1)),
};
break;
case 'tomorrow':
this.values = {
from: startOfDay(addDays(this.now, 1)),
to: endOfDay(addDays(this.now, 1)),
};
break;
case 'last7days':
this.values = {
from: startOfDay(subWeeks(this.now, 1)),
to: this.now,
};
break;
case 'next7days':
this.values = {
to: startOfDay(addWeeks(this.now, 1)),
from: this.now,
};
break;
case 'last30days':
this.values = {
from: startOfDay(subMonths(this.now, 1)),
to: this.now,
};
break;
case 'next30days':
this.values = {
to: startOfDay(addMonths(this.now, 1)),
from: this.now,
};
break;
case 'last90days':
this.values = {
from: startOfDay(subMonths(this.now, 3)),
to: this.now,
};
break;
case 'next90days':
this.values = {
to: startOfDay(addMonths(this.now, 3)),
from: this.now,
};
break;
case 'last365days':
this.values = {
from: startOfDay(subYears(this.now, 1)),
to: this.now,
};
break;
case 'next365days':
this.values = {
to: startOfDay(addYears(this.now, 1)),
from: this.now,
};
break;
}
},
},
};
</script>
<style lang="stylus" scoped>
@import "~/src/stylus/_stylebase.styl";
$width = 400px
$range-color = rgba($colors.bird, $opacites.psy)
.datepicker
text-align left
height auto
min-width $width
max-width $width
width $width
user-select none
border none
border-radius 0
& *
box-sizing border-box
.panels-choices
display grid
grid-template-columns repeat(5, minmax(max-content, max-content))
gap 5%
border none
border-radius 0
padding: $gutter / 2 $gutter / 4 0 28px
.panels-button
position relative
display inline-flex
align-items center
justify-content left
height 32px
padding 0 0 15px 0
border-radius 0
cursor pointer
background none
$text("alena")
&.is-current,
&:hover
color $colors.text
&.is-current:after
content ""
background $colors.primary
position absolute
bottom 0
left 0
right 0
border-radius $border-radiuses.dancing
width 100%
height 6px
// Кнопки Сбросить/Применить - не использовалось, поэтому не проработано полностью под стиль!
&-controls
display none
border-radius 0
margin-top 10px
display flex
align-items center
justify-content space-between
border none
border-top 1px solid $colors.sky
padding: 20px
.datepicker-button
height 36px
min-width 150px
padding 5px
display inline-flex
flex-grow 1
align-items center
justify-content center
border-radius 4px
font-size 12px
& + .datepicker-button
margin-left 10px
&:not(.is-disabled)
cursor pointer
&-submit
background-color $colors.primary
color $colors.stone
&.is-disabled
opacity $opacites.pop
cursor not-allowed
pointer-events none
// &-reset
// Не использовалось, поэтому не проработано под стиль!
// &-reset
.preset-ranges
padding 20px
display flex
display none
flex-wrap wrap
border-bottom 1px solid $colors.sky
.preset
width 50%
font-size 13px
height 20px
cursor pointer
position relative
margin 5px 0
input
position absolute
opacity 0
height 0
width 0
&:checked ~ label .check
background-color $colors.primary
&::after
background-color transparent
label
display inline-flex
align-items center
span + span
margin-left 10px
.check
display block
position relative
height 20px
width 20px
background-color $colors.secondary
border-radius $border-radiuses.shooting
&::after
content ""
position absolute
height 10px
width 10px
left 50%
top 50%
background-color $colors.stone
border-radius 100%
border 3px solid $colors.stone
transform translateX(-50%) translateY(-50%)
*
cursor: pointer
.calendar
color $colors.ball
background-color $colors.stone
&,
.calendar-days .day.is-edge-range,
.calendar-days .day.is-selected,
.calendar-months .month:not(.is-disabled),
.calendar-quarters .quarter .months .month,
.calendar-years .year:not(.is-disabled)
color $colors.text
.calendar-header
padding-top 10px
display flex
justify-content space-between
align-items center
.calendar-month-name
flex 1
text-align center
$text("katya")
.calendar-arrow
fill $colors.secondary
cursor pointer
&:hover
fill $color_text
// &.calendar-arrow-next
// &.calendar-arrow-previous
.calendar-months
padding-top 10px
display grid
gap 10px 10px
grid-template-columns 1fr 1fr 1fr
.month
height 50px
padding 10px
display inline-flex
align-items center
border 1px solid $colors.sky
border-radius: $border-radiuses.swimming
font-size 13px
&:hover
background-color $range-color
&.is-disabled
cursor not-allowed
opacity 0.33
pointer-events none
&.is-in-range
background-color $range-color
&.is-selected
background-color $colors.primary
&:not(.is-disabled)
cursor pointer
.calendar-quarters
padding-top 10px
.quarter
position relative
display block
margin 10px 0 8px 0
&:last-child
margin-bottom 0
.legend
position absolute
top 10px
padding-left: 21px
$text("natasha")
.months
height auto
display grid
gap 10px 10px
align-items center
grid-template-columns 1fr 1fr 1fr
padding: 29px 10px 7px 21px
&:hover
background-color: $range-color
.month
font-size 10px
text-align left
&.is-disabled
cursor not-allowed
pointer-events none
.months,
.legend
opacity $opacites.pop
&.is-in-range
.months
background-color $range-color
&.is-selected .months
background-color $colors.primary
&:not(.is-disabled) .months
cursor pointer
.calendar-years
padding-top 20px
display grid
gap 10px 10px
grid-template-columns 1fr 1fr
.year
height 50px
padding 10px 10px 10px 21px
display flex
align-items center
margin 10px 0
&:hover
background-color $range-color
&.is-disabled
cursor not-allowed
opacity $opacites.pop
pointer-events none
&.is-in-range
background-color $range-color
&.is-selected
background-color $colors.primary
&:not(.is-disabled)
cursor pointer
.calendar-months .month,
.calendar-quarters .quarter .months,
.calendar-years .year
border none
border-radius 0
justify-content left
padding-left 21px
$text("natasha")
.calendar-years .year
$text("katya")
.calendar-months,
.calendar-quarters,
.calendar-quarters .quarter,
.calendar-years .year
margin-top 0
.calendar-days-name,
.calendar-days
.day
width calc(100% / 7)
display inline-flex
align-items center
justify-content center
.calendar-days-name .day
color $colors.secondary !important
height 40px
$text("natasha")
.calendar-days .day
height 40px
&.is-today span
color $colors.primary
&:not(.is-current-month)
color rgba(0, 0, 0, 0.5)
&:not(.is-current-month)
color $colors.ball
&.is-disabled
cursor not-allowed
opacity $opacites.pop
pointer-events none
&.is-today
span
color $colors.secondary
font-weight bold
&.is-in-range
background-color $range-color
&.is-first-range
border-top-left-radius $border-radiuses.swimming
border-bottom-left-radius $border-radiuses.swimming
&.is-last-range
border-top-right-radius $border-radiuses.swimming
border-bottom-right-radius $border-radiuses.swimming
&.is-edge-range
background-color $colors.primary
&.is-selected
background-color $colors.primary
&:not(.is-disabled)
cursor pointer
&.calendar-days
.calendar-days .day
&:not(.is-edge-range):hover
background-color $range-color
</style>