diff --git a/packages/travix-ui-kit/.eslintrc b/packages/travix-ui-kit/.eslintrc
index 1e6473a..c9c964b 100644
--- a/packages/travix-ui-kit/.eslintrc
+++ b/packages/travix-ui-kit/.eslintrc
@@ -31,6 +31,7 @@
"comma-dangle": ["error", "always-multiline"],
"import/no-extraneous-dependencies": ["error", {"devDependencies": true}],
"max-len": [2, {"code": 120, "comments": 120, "tabWidth": 4}],
+ "react/jsx-indent": [2, 2],
"react/react-in-jsx-scope": 0,
"no-else-return": 0
}
diff --git a/packages/travix-ui-kit/builder/index.js b/packages/travix-ui-kit/builder/index.js
index a2e0efb..b7f90cf 100755
--- a/packages/travix-ui-kit/builder/index.js
+++ b/packages/travix-ui-kit/builder/index.js
@@ -3,6 +3,7 @@
const builder = require('./builder');
const pkg = require('../package.json');
const program = require('commander');
+const util = require('util');
program
.version(pkg.version)
@@ -16,4 +17,4 @@ program.parse(process.argv);
builder(program)
.then(() => console.log('Done!'))
- .catch(console.error);
+ .catch(e => console.error(util.inspect(e, true, undefined, true)));
diff --git a/packages/travix-ui-kit/components/_helpers.js b/packages/travix-ui-kit/components/_helpers.js
index 6d1dbbb..694b371 100644
--- a/packages/travix-ui-kit/components/_helpers.js
+++ b/packages/travix-ui-kit/components/_helpers.js
@@ -22,8 +22,44 @@ function getDataAttributes(attributes = {}) {
}, {});
}
+/**
+ * @function leftPad
+ * @param {Number} value The value to be left padded
+ * @return {String} The value with a leading 0 if it's between 1 - 9
+ */
+function leftPad(value, maxLength = 2, leftPaddedBy = '0') {
+ const valueStringified = value.toString();
+ if (valueStringified.length >= maxLength) {
+ return valueStringified;
+ }
+
+ return leftPaddedBy.repeat(maxLength - valueStringified.length) + valueStringified;
+}
+
+/**
+ * Receives a date object and normalizes it to the proper hours, minutes,
+ * seconds and milliseconds.
+ *
+ * @method normalizeDate
+ * @param {Date} dateObject Date object to be normalized.
+ * @param {Number} hours Value to set the hours to. Defaults to 0
+ * @param {Number} minutes Value to set the minutes to. Defaults to 0
+ * @param {Number} seconds Value to set the seconds to. Defaults to 0
+ * @param {Number} milliseconds Value to set the milliseconds to. Defaults to 0
+ * @return {Date} The normalized date object.
+ */
+function normalizeDate(dateObject, hours = 0, minutes = 0, seconds = 0, milliseconds = 0) {
+ dateObject.setHours(hours);
+ dateObject.setMinutes(minutes);
+ dateObject.setSeconds(seconds);
+ dateObject.setMilliseconds(milliseconds);
+ return dateObject;
+}
+
// Exports
export default {
getClassNamesWithMods,
getDataAttributes,
+ leftPad,
+ normalizeDate,
};
diff --git a/packages/travix-ui-kit/components/calendar/calendar.js b/packages/travix-ui-kit/components/calendar/calendar.js
new file mode 100644
index 0000000..52e9fa0
--- /dev/null
+++ b/packages/travix-ui-kit/components/calendar/calendar.js
@@ -0,0 +1,314 @@
+import React, { Component, PropTypes } from 'react';
+import { getClassNamesWithMods, getDataAttributes, normalizeDate } from '../_helpers';
+import DaysPanel from './panels/days';
+import calendarConstants from './constants/calendar';
+
+const {
+ CALENDAR_MOVE_TO_NEXT,
+ CALENDAR_MOVE_TO_PREVIOUS,
+ CALENDAR_SELECTION_TYPE_RANGE,
+} = calendarConstants;
+
+
+/**
+ * Processes the given props and the existing state and returns
+ * a new state.
+ *
+ * @function processProps
+ * @param {Object} props Props to base the new state on.
+ * @param {Object} state (Existing) state to be based on for the existing values.
+ * @return {Object} New state to be set/used.
+ * @static
+ */
+function processProps(props) {
+ const { initialDates, maxDate, minDate, selectionType } = props;
+ const maxLimit = maxDate ? normalizeDate(new Date(maxDate), 23, 59, 59, 999) : null;
+
+ const renderDate = (initialDates && initialDates.length && initialDates[0]) ? new Date(initialDates[0]) : new Date();
+ normalizeDate(renderDate);
+
+ let minLimit = minDate ? normalizeDate(new Date(minDate)) : null;
+ let selectedDates = [null, null];
+
+ if (initialDates) {
+ selectedDates = selectedDates.map((item, idx) => {
+ if (!initialDates[idx]) {
+ return null;
+ }
+
+ return normalizeDate(new Date(initialDates[idx]));
+ });
+ }
+
+ /**
+ * If a minDate or a maxDate is set, let's check if any selectedDates are outside of the boundaries.
+ * If so, resets the selectedDates.
+ */
+ if (minLimit || maxLimit) {
+ const isAnyDateOutOfLimit = selectedDates.some(item => (
+ item && (
+ (minLimit && (minLimit.getTime() > item.getTime())) ||
+ (maxLimit && (maxLimit.getTime() < item.getTime()))
+ )
+ ));
+
+ if (isAnyDateOutOfLimit) {
+ selectedDates = [null, null];
+ console.warn(`A calendar instance contains a selectedDate outside of the minDate and maxDate boundaries`); // eslint-disable-line
+ }
+ }
+
+ /** If initialDates is defined and we have a start date, we want to set it as the minLimit */
+ if (selectedDates[0] && (selectionType === CALENDAR_SELECTION_TYPE_RANGE)) {
+ minLimit = selectedDates[0];
+ }
+
+ /** If the renderDate is not between any of the minLimit and/or maxDate, we need to redefine it. */
+ if (
+ (minLimit && (renderDate.getMonth() < minLimit.getMonth())) ||
+ (maxLimit && (renderDate.getMonth() > maxLimit.getMonth()))
+ ) {
+ renderDate.setMonth(minLimit.getMonth());
+ }
+
+ return {
+ maxLimit,
+ minLimit,
+ renderDate,
+ selectedDates,
+ };
+}
+
+export default class Calendar extends Component {
+ constructor(props) {
+ super();
+
+ this.moveToMonth = this.moveToMonth.bind(this);
+ this.state = processProps(props);
+ }
+
+ componentWillReceiveProps(newProps) {
+ const { initialDates, maxDate, minDate, selectionType } = newProps;
+
+ let propsChanged = (
+ (maxDate !== this.props.maxDate) ||
+ (minDate !== this.props.minDate) ||
+ (selectionType !== this.props.selectionType)
+ );
+
+ if (initialDates) {
+ if (this.props.initialDates) {
+ propsChanged = propsChanged || initialDates.some((item, idx) => item !== this.props.initialDates[idx]);
+ } else {
+ propsChanged = true;
+ }
+ }
+
+ if (propsChanged) {
+ this.setState(() => processProps(newProps));
+ }
+ }
+
+ /**
+ * Changes the renderDate of the calendar to the previous or next month.
+ * Also triggers the onNavPreviousMonth/onNavNextMonth when the state gets changed
+ * and passes the new date to it.
+ *
+ * @method moveToMonth
+ * @param {String} direction Defines to which month is the calendar moving (previous or next).
+ */
+ moveToMonth(direction) {
+ const { onNavNextMonth, onNavPreviousMonth } = this.props;
+
+ this.setState(({ renderDate }) => {
+ renderDate.setMonth(renderDate.getMonth() + (direction === CALENDAR_MOVE_TO_PREVIOUS ? -1 : 1));
+ return { renderDate };
+ }, () => {
+ if ((direction === CALENDAR_MOVE_TO_PREVIOUS) && onNavPreviousMonth) {
+ onNavPreviousMonth(this.state.renderDate);
+ } else if ((direction === CALENDAR_MOVE_TO_NEXT) && onNavNextMonth) {
+ onNavNextMonth(this.state.renderDate);
+ }
+ });
+ }
+
+ /**
+ * Handler for the day's selection. Passed to the DaysPanel -> DaysView.
+ * Also triggers the onSelectDay function (when passed) after the state is updated,
+ * passing the selectedDates array to it.
+ *
+ * @method onSelectDay
+ * @param {Date} dateSelected Date selected by the user.
+ */
+ onSelectDay(dateSelected) {
+ const { onSelectDay, selectionType, minDate } = this.props;
+
+ this.setState((prevState) => {
+ let { minLimit, renderDate, selectedDates } = prevState;
+
+ /**
+ * If the calendar's selectionType is 'normal', we always set the date selected
+ * to the first position of the selectedDates array.
+ * If the selectionType is 'range', we need to verify the following requirements:
+ *
+ * - If there's no start date selected, then the selected date becomes the start
+ * date and the minLimit becomes that same date. Prevents the range selection to the past.
+ *
+ * - If there's a start date already selected:
+ *
+ * - If there's no end date selected, then the selected date becomes the end date. Also
+ * if the start and end dates are the same, it will remove the minLimit as the layout renders
+ * them as a 'normal' selection.
+ *
+ * - If there's an end date selected and the user is clicking on the start date again, it
+ * clears the selections and the limits, resetting the range.
+ */
+ if (selectionType === CALENDAR_SELECTION_TYPE_RANGE) {
+ if (selectedDates[0]) {
+ if (!selectedDates[1]) {
+ selectedDates[1] = dateSelected;
+ if (selectedDates[0].toDateString() === selectedDates[1].toDateString()) {
+ minLimit = minDate ? normalizeDate(new Date(minDate)) : null;
+ }
+ } else {
+ selectedDates = [null, null];
+ minLimit = minDate ? normalizeDate(new Date(minDate)) : null;
+ }
+ } else {
+ selectedDates[0] = dateSelected;
+ minLimit = dateSelected;
+ selectedDates[1] = null;
+ }
+ } else {
+ selectedDates[0] = dateSelected;
+ }
+
+ /**
+ * If the user selects a day of the previous or next month, the rendered month switches to
+ * the one of the selected date.
+ */
+ if (dateSelected.getMonth() !== renderDate.getMonth()) {
+ renderDate = new Date(dateSelected.toDateString());
+ }
+
+ return {
+ minLimit,
+ renderDate,
+ selectedDates,
+ };
+ }, () => {
+ if (onSelectDay) {
+ onSelectDay(this.state.selectedDates);
+ }
+ });
+ }
+
+ render() {
+ const { dataAttrs = {}, isDaySelectableFn, locale, mods = [], navButtons, selectionType } = this.props;
+ const { maxLimit, minLimit, renderDate, selectedDates } = this.state;
+
+ const restProps = getDataAttributes(dataAttrs);
+ const className = getClassNamesWithMods('ui-calendar', mods);
+
+ return (
+
+ this.moveToMonth(CALENDAR_MOVE_TO_NEXT)}
+ onNavPreviousMonth={() => this.moveToMonth(CALENDAR_MOVE_TO_PREVIOUS)}
+ onSelectDay={dt => this.onSelectDay(dt)}
+ renderDate={renderDate}
+ selectedDates={selectedDates}
+ selectionType={selectionType}
+ />
+
+ );
+ }
+}
+
+Calendar.defaultProps = {
+ selectionType: 'normal',
+};
+
+Calendar.propTypes = {
+ /**
+ * Data attribute. You can use it to set up GTM key or any custom data-* attribute
+ */
+ dataAttrs: PropTypes.oneOfType([
+ PropTypes.bool,
+ PropTypes.object,
+ ]),
+
+ /**
+ * Optional. Initial value of the calendar. Defaults to the current date as per the locale.
+ */
+ initialDates: PropTypes.array,
+
+ /**
+ * Optional. Function to be triggered to evaluate if the date (passed as an argument)
+ * is selectable. Must return a boolean.
+ */
+ isDaySelectableFn: PropTypes.func,
+
+ /**
+ * Locale definitions, with the calendar's months and weekdays in the right language.
+ * Also contains the startWeekDay which defines in which week day starts the week.
+ */
+ locale: PropTypes.shape({
+ months: PropTypes.array,
+ weekDays: PropTypes.array,
+ startWeekDay: PropTypes.number,
+ }),
+
+ /**
+ * Sets the max date boundary. Defaults to `null`.
+ */
+ maxDate: PropTypes.string,
+
+ /**
+ * Sets the min date boundary. Defaults to `null`.
+ */
+ minDate: PropTypes.string,
+
+ /**
+ * You can provide set of custom modifications.
+ */
+ mods: PropTypes.arrayOf(PropTypes.string),
+
+ navButtons: PropTypes.shape({
+ days: PropTypes.shape({
+ next: PropTypes.shape({
+ ariaLabel: PropTypes.string,
+ displayValue: PropTypes.string,
+ }),
+ previous: PropTypes.shape({
+ ariaLabel: PropTypes.string,
+ displayValue: PropTypes.string,
+ }),
+ }),
+ }),
+
+ /**
+ * Function to be triggered when pressing the nav's "next" button.
+ */
+ onNavNextMonth: PropTypes.func,
+
+ /**
+ * Function to be triggered when pressing the nav's "previous" button.
+ */
+ onNavPreviousMonth: PropTypes.func,
+
+ /**
+ * Function to be triggered when selecting a day.
+ */
+ onSelectDay: PropTypes.func,
+
+ /**
+ * Optional. Type of date selection.
+ */
+ selectionType: PropTypes.oneOf(['normal', 'range']),
+};
diff --git a/packages/travix-ui-kit/components/calendar/calendar.md b/packages/travix-ui-kit/components/calendar/calendar.md
new file mode 100644
index 0000000..f816ae0
--- /dev/null
+++ b/packages/travix-ui-kit/components/calendar/calendar.md
@@ -0,0 +1,102 @@
+# Basic calendar with attributes set:
+
+
+ ((dt.getDay() > 0) && (dt.getDay() < 6))}
+ locale={{
+ months: [
+ { name: 'Janeiro', short: 'Jan' },
+ { name: 'Fevereiro', short: 'Fev' },
+ { name: 'Março', short: 'Mar' },
+ { name: 'Abril', short: 'Abr' },
+ { name: 'Maio', short: 'Mai' },
+ { name: 'Junho', short: 'Jun' },
+ { name: 'Julho', short: 'Jul' },
+ { name: 'Agosto', short: 'Ago' },
+ { name: 'Setembro', short: 'Set' },
+ { name: 'Outubro', short: 'Out' },
+ { name: 'Novembro', short: 'Nov' },
+ { name: 'Dezembro', short: 'Dez' },
+ ],
+ weekDays: [
+ { name: 'Domingo', short: 'Dom' },
+ { name: 'Segunda', short: 'Seg' },
+ { name: 'Terça', short: 'Ter' },
+ { name: 'Quarta', short: 'Qua' },
+ { name: 'Quinta', short: 'Qui' },
+ { name: 'Sexta', short: 'Sex' },
+ { name: 'Sábado', short: 'Sáb' },
+ ],
+ }}
+ minDate="2017-04-01"
+ maxDate="2017-06-01"
+ navButtons={{
+ days: {
+ next: {
+ displayValue: '›'
+ },
+ previous: {
+ displayValue: '‹'
+ },
+ }
+ }}
+ onSelectDay={dt => (document.getElementById('output_1').value = dt[0].toDateString())}
+ />
+
+
+
+
+---
+
+# Range calendar with attributes set:
+
+
+ ((dt.getDay() > 0) && (dt.getDay() < 6))}
+ locale={{
+ months: [
+ { name: 'Janeiro', short: 'Jan' },
+ { name: 'Fevereiro', short: 'Fev' },
+ { name: 'Março', short: 'Mar' },
+ { name: 'Abril', short: 'Abr' },
+ { name: 'Maio', short: 'Mai' },
+ { name: 'Junho', short: 'Jun' },
+ { name: 'Julho', short: 'Jul' },
+ { name: 'Agosto', short: 'Ago' },
+ { name: 'Setembro', short: 'Set' },
+ { name: 'Outubro', short: 'Out' },
+ { name: 'Novembro', short: 'Nov' },
+ { name: 'Dezembro', short: 'Dez' },
+ ],
+ weekDays: [
+ { name: 'Domingo', short: 'Dom' },
+ { name: 'Segunda', short: 'Seg' },
+ { name: 'Terça', short: 'Ter' },
+ { name: 'Quarta', short: 'Qua' },
+ { name: 'Quinta', short: 'Qui' },
+ { name: 'Sexta', short: 'Sex' },
+ { name: 'Sábado', short: 'Sáb' },
+ ],
+ }}
+ minDate="2017-04-01"
+ maxDate="2017-06-01"
+ navButtons={{
+ days: {
+ next: {
+ displayValue: '›'
+ },
+ previous: {
+ displayValue: '‹'
+ },
+ }
+ }}
+ onSelectDay={dt => {
+ document.getElementById('output_2').value = dt[0].toDateString() + ' / ' + dt[1].toDateString();
+ }}
+ selectionType="range"
+ />
+
+
+---
diff --git a/packages/travix-ui-kit/components/calendar/calendar.scss b/packages/travix-ui-kit/components/calendar/calendar.scss
new file mode 100644
index 0000000..6b0ea33
--- /dev/null
+++ b/packages/travix-ui-kit/components/calendar/calendar.scss
@@ -0,0 +1,175 @@
+.ui-calendar {
+ display: flex;
+ flex-flow: column wrap;
+ height: $tx-calendar-height;
+ width: $tx-calendar-width;
+}
+
+.ui-calendar-days-panel {
+ align-items: stretch;
+ display: flex;
+ flex-flow: column wrap;
+ flex: 1 1 100%;
+
+ &_hidden {
+ display: none;
+ }
+}
+
+.ui-calendar-days {
+ align-items: stretch;
+ display: flex;
+ flex-flow: column wrap;
+ flex: 1 1 100%;
+}
+
+.ui-calendar-days__nav {
+ align-items: stretch;
+ background: $tx-calendar-nav-background;
+ border-radius: $tx-calendar-nav-border-radius;
+ display: flex;
+ flex: 0 0 $tx-calendar-nav-height;
+ flex-flow: row nowrap;
+ width: 100%;
+}
+
+.ui-calendar-days__previous-month,
+.ui-calendar-days__next-month {
+ align-self: center;
+ background: $tx-calendar-nav-button-background-color-end;
+ background: linear-gradient(to bottom,$tx-calendar-nav-button-background-color-start 0,$tx-calendar-nav-button-background-color-end 100%);
+ border: none;
+ border-radius: $tx-calendar-nav-button-border-radius;
+ box-shadow: 0 2px $tx-calendar-nav-button-box-shadow-color;
+ cursor: pointer;
+ color: $tx-calendar-nav-button-color;
+ flex: 0 0 $tx-calendar-nav-button-width;
+ font-weight: $tx-calendar-nav-button-font-weight;
+ height: $tx-calendar-nav-button-height;
+ margin: $tx-calendar-nav-button-margin;
+
+ &[disabled] {
+ visibility: hidden;
+ }
+}
+
+.ui-calendar-days__rendered-month {
+ align-self: center;
+ color: $tx-calendar-nav-label-color;
+ flex: 1 1 100%;
+ font-family: sans-serif;
+ font-weight: $tx-calendar-nav-label-font-weight;
+ text-align: center;
+}
+
+.ui-calendar-days__weekdays {
+ align-items: center;
+ background-color: $tx-calendar-header-background-color;
+ display: flex;
+ flex: 0 0 $tx-calendar-header-height;
+ flex-flow: row nowrap;
+}
+
+.ui-calendar-days__weekday {
+ color: $tx-calendar-header-weekday-color;
+ display: flex;
+ flex: 1 1 14.285%;
+ font-family: sans-serif;
+ font-size: $tx-calendar-header-weekday-font-size;
+ font-weight: $tx-calendar-header-weekday-font-weight;
+ justify-content: center;
+ text-transform: lowercase;
+}
+
+.ui-calendar-days__options {
+ border: 1px solid $tx-calendar-options-border-color;
+ display: flex;
+ flex: 1 1 auto;
+ flex-flow: row wrap;
+}
+
+.ui-calendar-days-option {
+ background: $tx-calendar-options-option-background-color;
+ border: none;
+ border-bottom: 1px solid $tx-calendar-options-option-border-bottom;
+ border-right: 1px solid $tx-calendar-options-option-border-right;
+ color: $tx-calendar-options-option-color;
+ cursor: pointer;
+ flex: 1 1 14.285%;
+ font-size: $tx-calendar-options-option-font-size;
+ outline: none;
+
+ &:nth-of-type(7),
+ &:nth-of-type(14),
+ &:nth-of-type(21),
+ &:nth-of-type(28),
+ &:nth-of-type(35),
+ &:nth-of-type(42) {
+ border-right: none;
+ }
+
+ &:nth-of-type(36),
+ &:nth-of-type(37),
+ &:nth-of-type(38),
+ &:nth-of-type(39),
+ &:nth-of-type(40),
+ &:nth-of-type(41),
+ &:nth-of-type(42) {
+ border-bottom: none;
+ }
+
+ &[disabled] {
+ background: $tx-calendar-options-option-disabled-background-color;
+ color: $tx-calendar-options-option-disabled-color;
+ }
+
+ &.ui-calendar-days-option_selected,
+ &.ui-calendar-days-option_selected-start,
+ &.ui-calendar-days-option_selected-end,
+ &:not([disabled]):hover {
+ background: $tx-calendar-options-option-hover-background-color;
+ color: $tx-calendar-options-option-hover-color;
+ }
+
+ &.ui-calendar-days-option_selected-between {
+ background: $tx-calendar-options-option-range-between-background-color;
+ }
+
+ &:not([disabled]):focus {
+ z-index: 1;
+ }
+}
+
+.ui-calendar-days-option_selected-start,
+.ui-calendar-days-option_selected-end {
+ position: relative;
+
+ &:before,
+ &:after {
+ border: 18px solid $tx-calendar-options-option-range-after-border-color;
+ bottom: 0;
+ content: " ";
+ position: absolute;
+ top: 0;
+ }
+}
+
+.ui-calendar-days-option_selected-start:after {
+ border-left: 10px solid $tx-calendar-options-option-range-start-after-border-color;
+ border-right: none;
+ right: -10px;
+}
+
+.ui-calendar-days-option_selected-end:before {
+ border-left: none;
+ border-right: 10px solid $tx-calendar-options-option-range-end-after-border-color;
+ left: -10px;
+}
+
+.ui-calendar-days-option_previous-month {
+ background: $tx-calendar-options-option-previous-month-background-color;
+}
+
+.ui-calendar-days-option_next-month {
+ background: $tx-calendar-options-option-next-month-background-color;
+}
diff --git a/packages/travix-ui-kit/components/calendar/constants/calendar.js b/packages/travix-ui-kit/components/calendar/constants/calendar.js
new file mode 100644
index 0000000..b89bfc6
--- /dev/null
+++ b/packages/travix-ui-kit/components/calendar/constants/calendar.js
@@ -0,0 +1,8 @@
+/**
+ * This file contains constants used by the calendar component.
+ */
+module.exports.CALENDAR_MOVE_TO_NEXT = 'next';
+module.exports.CALENDAR_MOVE_TO_PREVIOUS = 'previous';
+
+module.exports.CALENDAR_SELECTION_TYPE_NORMAL = 'normal';
+module.exports.CALENDAR_SELECTION_TYPE_RANGE = 'range';
diff --git a/packages/travix-ui-kit/components/calendar/panels/days.js b/packages/travix-ui-kit/components/calendar/panels/days.js
new file mode 100644
index 0000000..cf6c438
--- /dev/null
+++ b/packages/travix-ui-kit/components/calendar/panels/days.js
@@ -0,0 +1,6 @@
+import React from 'react';
+import DaysView from '../views/days';
+
+export default function Days(props) {
+ return
;
+}
diff --git a/packages/travix-ui-kit/components/calendar/views/days.js b/packages/travix-ui-kit/components/calendar/views/days.js
new file mode 100644
index 0000000..02475c0
--- /dev/null
+++ b/packages/travix-ui-kit/components/calendar/views/days.js
@@ -0,0 +1,353 @@
+import React, { Component, PropTypes } from 'react';
+
+import { getClassNamesWithMods, getDataAttributes, leftPad } from '../../_helpers';
+import calendarConstants from '../constants/calendar';
+
+const { CALENDAR_SELECTION_TYPE_RANGE } = calendarConstants;
+
+class Days extends Component {
+ constructor(props) {
+ super(props);
+
+ this.getLocale = this.getLocale.bind(this);
+ this.isOptionEnabled = this.isOptionEnabled.bind(this);
+ this.renderDays = this.renderDays.bind(this);
+ this.renderNav = this.renderNav.bind(this);
+ this.renderWeekDays = this.renderWeekDays.bind(this);
+
+ this.locale = this.getLocale();
+ this.navButtons = this.getNavButtons();
+ }
+
+ /**
+ * Returns a merge between a default locale and a provided one via props.
+ *
+ * @method getLocale
+ * @return {Object} The final locale, which is stored in a property of the instance.
+ */
+ getLocale() {
+ const { locale } = this.props;
+
+ const defaultLocale = {
+ months: [
+ { name: 'January', short: 'Jan' },
+ { name: 'February', short: 'Feb' },
+ { name: 'March', short: 'Mar' },
+ { name: 'April', short: 'Apr' },
+ { name: 'May', short: 'May' },
+ { name: 'June', short: 'Jun' },
+ { name: 'July', short: 'Jul' },
+ { name: 'August', short: 'Aug' },
+ { name: 'September', short: 'Sep' },
+ { name: 'October', short: 'Oct' },
+ { name: 'November', short: 'Nov' },
+ { name: 'December', short: 'Dec' },
+ ],
+ startWeekDay: 1,
+ weekDays: [
+ { name: 'Sunday', short: 'Sun' },
+ { name: 'Monday', short: 'Mon' },
+ { name: 'Tuesday', short: 'Tue' },
+ { name: 'Wednesday', short: 'Wed' },
+ { name: 'Thursday', short: 'Thu' },
+ { name: 'Friday', short: 'Fri' },
+ { name: 'Saturday', short: 'Sat' },
+ ],
+ };
+
+ return locale ? Object.assign(defaultLocale, locale) : defaultLocale;
+ }
+
+ getNavButtons() {
+ const { navButtons } = this.props;
+
+ const defaultNavButtons = {
+ days: {
+ next: {
+ displayValue: '>',
+ },
+ previous: {
+ displayValue: '<',
+ },
+ },
+ };
+
+ return navButtons ? Object.assign(defaultNavButtons, navButtons) : defaultNavButtons;
+ }
+
+ /**
+ * If any of the boundaries is defined and the date being rendered is out of those boundaries
+ * or if there's a function to check if the day is selectable and it returns false,
+ * returns false.
+ *
+ * @method isOptionEnabled
+ * @param {Date} dateToBeRendered Date object to be verified.
+ * @return {Boolean} Returns true when enabled, false when disabled.
+ */
+ isOptionEnabled(dateToBeRendered) {
+ const { isDaySelectableFn, maxDate, minDate } = this.props;
+
+ if (
+ (maxDate && (maxDate.getTime() < dateToBeRendered.getTime())) ||
+ (minDate && (minDate.getTime() > dateToBeRendered.getTime())) ||
+ (isDaySelectableFn && !isDaySelectableFn(dateToBeRendered))
+ ) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Renders the options with the days in the view.
+ * It does so, by calculating the number of days needed from the previous month and,
+ * starting from that day, iterates 42 days (to fill in all the 'options' in the calendar).
+ * While doing so, it handles the classes that the option element must have. Depending on
+ * if it is a 'range' or 'normal' calendar will determine how the selection (when applicable)
+ * should be rendered.
+ * Also handles the special case where, in a 'range' calendar, if both start and end dates are
+ * equal, it displays as a 'normal' selection (with the 'selected' modifier).
+ *
+ * @method renderDays
+ * @return {HTMLElement}
+ */
+ renderDays() {
+ const { onSelectDay, renderDate, selectedDates, selectionType } = this.props;
+ const { months, startWeekDay, weekDays } = this.locale;
+
+ /** Calculates the amount of days that */
+ const currentDate = new Date(renderDate.getFullYear(), renderDate.getMonth(), 1, 0, 0, 0);
+ const firstWeekDayOfMonth = currentDate.getDay();
+ const numDaysOfPreviousMonth = firstWeekDayOfMonth < startWeekDay ? 6 : (firstWeekDayOfMonth - startWeekDay);
+
+ currentDate.setDate(currentDate.getDate() - numDaysOfPreviousMonth);
+
+ const options = [];
+ let counter = 0;
+
+ const limits = selectedDates.map(item => (item ? item.getTime() : null));
+ const selectedDatesAreEqual = (limits.length === 2) && (limits[0] === limits[1]);
+
+ do {
+ const mods = [];
+ const selectedDate = selectedDates && selectedDates.find((item) => {
+ return item && (item.toDateString() === currentDate.toDateString());
+ });
+
+ if (currentDate.getMonth() < renderDate.getMonth()) {
+ mods.push('previous-month');
+ } else if (currentDate.getMonth() > renderDate.getMonth()) {
+ mods.push('next-month');
+ }
+
+ if (selectedDate) {
+ if ((selectionType === CALENDAR_SELECTION_TYPE_RANGE) && !selectedDatesAreEqual) {
+ mods.push((selectedDates.indexOf(selectedDate) === 0) ? 'selected-start' : 'selected-end');
+ } else {
+ mods.push('selected');
+ }
+ } else if (limits[1] && (limits[0] <= currentDate.getTime()) && (limits[1] >= currentDate.getTime())) {
+ mods.push('selected-between');
+ }
+
+ const className = getClassNamesWithMods('ui-calendar-days-option', mods);
+
+ const dateInYYYYMMDD = [
+ currentDate.getFullYear(),
+ leftPad(currentDate.getMonth() + 1),
+ leftPad(currentDate.getDate()),
+ ].join('-');
+
+ const ariaLabel = [
+ `${weekDays[currentDate.getDay()].name},`,
+ currentDate.getDate(),
+ months[currentDate.getMonth()].name,
+ currentDate.getFullYear(),
+ ].join(' ');
+
+ const onClickHandler = onSelectDay.bind(null, new Date(currentDate.toDateString()));
+
+ options.push(
+ {currentDate.getDate()}
+ );
+ currentDate.setDate(currentDate.getDate() + 1);
+ counter += 1;
+ } while (counter < 42);
+
+ return {options}
;
+ }
+
+ /**
+ * Renders the navigation of the days' calendar.
+ *
+ * @method renderNav
+ * @return {HTMLElement}
+ */
+ renderNav() {
+ const { renderDate, minDate, maxDate, onNavPreviousMonth, onNavNextMonth } = this.props;
+ const locale = this.locale;
+ const { next, previous } = this.navButtons.days;
+
+ const nextMonth = renderDate.getMonth() === 11 ? 0 : (renderDate.getMonth() + 1);
+ const previousMonth = renderDate.getMonth() === 0 ? 11 : (renderDate.getMonth() - 1);
+
+ const isPreviousMonthDisabled = minDate && (minDate.getMonth() >= renderDate.getMonth());
+ const isNextMonthDisabled = maxDate && (maxDate.getMonth() <= renderDate.getMonth());
+
+ return (
+
+ {previous.displayValue}
+
+ {locale.months[renderDate.getMonth()].short} {renderDate.getFullYear()}
+
+ {next.displayValue}
+
+ );
+ }
+
+ /**
+ * Renders the week days header of the days' calendar.
+ *
+ * @method renderWeekDays
+ * @return {HTMLElement}
+ */
+ renderWeekDays() {
+ const { startWeekDay, weekDays } = this.locale;
+ return (
+
+ {weekDays.slice(startWeekDay).map((weekDay, idx) => {
+ return {weekDay.short}
;
+ })}
+ {startWeekDay > 0 ? {weekDays[0].short}
: null}
+
+ );
+ }
+
+ render() {
+ const { dataAttrs, mods } = this.props;
+ const className = getClassNamesWithMods('ui-calendar-days', mods);
+ const restProps = getDataAttributes(dataAttrs);
+
+ return (
+
+ {this.renderNav()}
+ {this.renderWeekDays()}
+ {this.renderDays()}
+
+ );
+ }
+}
+
+Days.defaultProps = {
+ dataAttrs: {},
+ hide: false,
+ maxDate: null,
+ minDate: null,
+ mods: [],
+};
+
+Days.propTypes = {
+ /**
+ * Data attribute. You can use it to set up GTM key or any custom data-* attribute
+ */
+ dataAttrs: PropTypes.oneOfType([
+ PropTypes.bool,
+ PropTypes.object,
+ ]),
+
+ /**
+ * Optional. Function to be triggered to evaluate if the date (passed as an argument)
+ * is selectable. Must return a boolean.
+ */
+ isDaySelectableFn: PropTypes.func,
+
+ /**
+ * Locale definitions, with the calendar's months and weekdays in the right language.
+ * Also contains the startWeekDay which defines in which week day starts the week.
+ */
+ locale: PropTypes.shape({
+ months: PropTypes.array,
+ weekDays: PropTypes.array,
+ startWeekDay: PropTypes.number,
+ }),
+
+ /**
+ * You can provide set of custom modifications.
+ */
+ mods: PropTypes.arrayOf(PropTypes.string),
+
+ /**
+ * Sets the max date boundary. Defaults to `null`.
+ */
+ maxDate: PropTypes.objectOf(Date),
+
+ /**
+ * Sets the min date boundary. Defaults to `null`.
+ */
+ minDate: PropTypes.objectOf(Date),
+
+ navButtons: PropTypes.shape({
+ days: PropTypes.shape({
+ next: PropTypes.shape({
+ ariaLabel: PropTypes.string,
+ displayValue: PropTypes.string,
+ }),
+ previous: PropTypes.shape({
+ ariaLabel: PropTypes.string,
+ displayValue: PropTypes.string,
+ }),
+ }),
+ }),
+
+ /**
+ * Function to be triggered when pressing the nav's "next" button.
+ */
+ onNavNextMonth: PropTypes.func,
+
+ /**
+ * Function to be triggered when pressing the nav's "previous" button.
+ */
+ onNavPreviousMonth: PropTypes.func,
+
+ /**
+ * Function to be triggered when selecting a day.
+ */
+ onSelectDay: PropTypes.func,
+
+ /**
+ * Date to be rendered in the view
+ */
+ renderDate: PropTypes.objectOf(Date).isRequired,
+
+ /**
+ * Date that is selected (might not be the one rendered).
+ */
+ selectedDates: PropTypes.arrayOf(PropTypes.objectOf(Date)),
+
+ /**
+ * Optional. Type of date selection.
+ */
+ selectionType: PropTypes.oneOf(['normal', 'range']),
+};
+
+export default Days;
diff --git a/packages/travix-ui-kit/components/index.js b/packages/travix-ui-kit/components/index.js
index 74fd3ad..da216b2 100644
--- a/packages/travix-ui-kit/components/index.js
+++ b/packages/travix-ui-kit/components/index.js
@@ -1,4 +1,5 @@
import Button from './button/button';
+import Calendar from './calendar/calendar';
import List from './list/list';
import Modal from './modal/modal';
import Price from './price/price';
@@ -7,6 +8,7 @@ import Spinner from './spinner/spinner';
export default {
Button,
+ Calendar,
List,
Modal,
Price,
diff --git a/packages/travix-ui-kit/components/index.scss b/packages/travix-ui-kit/components/index.scss
index 2b37c08..586bd71 100644
--- a/packages/travix-ui-kit/components/index.scss
+++ b/packages/travix-ui-kit/components/index.scss
@@ -1,4 +1,5 @@
@import "./button/button.scss";
+@import "./calendar/calendar.scss";
@import "./global/global.scss";
@import "./list/list.scss";
@import "./modal/modal.scss";
diff --git a/packages/travix-ui-kit/package.json b/packages/travix-ui-kit/package.json
index 1b0375c..eaa1c7e 100644
--- a/packages/travix-ui-kit/package.json
+++ b/packages/travix-ui-kit/package.json
@@ -10,8 +10,8 @@
"prebuild:watch": "babel --copy-files ./components --out-dir lib --ignore *.scss,*.md -w &",
"styleguide-server": "styleguidist server",
"styleguide-build": "styleguidist build",
- "test": "jest -c ./tests/jest.config.json",
- "update-snapshots": "jest -c ./tests/jest.config.json -u",
+ "test": "TZ=utc jest -c ./tests/jest.config.json",
+ "update-snapshots": "TZ=utc jest -c ./tests/jest.config.json -u",
"cov": "jest -c ./tests/jest.config.json --coverage --no-cache",
"coverage:coveralls": "cat ./coverage/lcov.info | coveralls",
"lint": "eslint --color '{components,tests,utils,scripts}/**/*.js'",
@@ -78,9 +78,10 @@
"eslint-config-travix": "^2.2.0",
"eslint-plugin-babel": "^4.0.0",
"eslint-plugin-import": "^2.2.0",
- "eslint-plugin-react": "^6.6.0",
+ "eslint-plugin-react": "^6.10.2",
"jest": "^19.0.0",
"jest-cli": "^19.0.1",
+ "jest-serializer-enzyme": "^1.0.0",
"react-addons-test-utils": "^0.14.8",
"react-styleguidist": "^4.6.2",
"webpack-hot-middleware": "^2.15.0"
diff --git a/packages/travix-ui-kit/tests/jest.config.json b/packages/travix-ui-kit/tests/jest.config.json
index e3f0811..fa95bca 100644
--- a/packages/travix-ui-kit/tests/jest.config.json
+++ b/packages/travix-ui-kit/tests/jest.config.json
@@ -2,6 +2,7 @@
"collectCoverage": true,
"collectCoverageFrom": [
"builder/**/*.js",
+ "!builder/index.js",
"!builder/webpack.config.js",
"components/**/*.js",
"!components/index.js"
@@ -10,7 +11,11 @@
"js",
"json"
],
+ "snapshotSerializers": [
+ "/node_modules/jest-serializer-enzyme"
+ ],
"transform": {
".*": "/node_modules/babel-jest"
- }
+ },
+ "watchman": false
}
diff --git a/packages/travix-ui-kit/tests/unit/calendar/__snapshots__/calendar.normal.spec.js.snap b/packages/travix-ui-kit/tests/unit/calendar/__snapshots__/calendar.normal.spec.js.snap
new file mode 100644
index 0000000..ec8b193
--- /dev/null
+++ b/packages/travix-ui-kit/tests/unit/calendar/__snapshots__/calendar.normal.spec.js.snap
@@ -0,0 +1,10704 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Calendar (normal mode) #render() should apply the new initialDates on Calendar even when changed in runtime by the parent component 1`] = `
+
+
+
+
+
+
+
+
+
+
+ <
+
+
+ Mar
+
+ 2017
+
+
+ >
+
+
+
+
+ Mon
+
+
+ Tue
+
+
+ Wed
+
+
+ Thu
+
+
+ Fri
+
+
+ Sat
+
+
+ Sun
+
+
+
+
+ 27
+
+
+ 28
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 4
+
+
+ 5
+
+
+ 6
+
+
+ 7
+
+
+ 8
+
+
+ 9
+
+
+ 10
+
+
+ 11
+
+
+ 12
+
+
+ 13
+
+
+ 14
+
+
+ 15
+
+
+ 16
+
+
+ 17
+
+
+ 18
+
+
+ 19
+
+
+ 20
+
+
+ 21
+
+
+ 22
+
+
+ 23
+
+
+ 24
+
+
+ 25
+
+
+ 26
+
+
+ 27
+
+
+ 28
+
+
+ 29
+
+
+ 30
+
+
+ 31
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 4
+
+
+ 5
+
+
+ 6
+
+
+ 7
+
+
+ 8
+
+
+ 9
+
+
+
+
+
+
+
+
+
+ Change date
+
+
+ Change date
+
+
+
+`;
+
+exports[`Calendar (normal mode) #render() should apply the new minDate on Calendar even when changed by the parent component 1`] = `
+
+
+
+
+
+
+
+
+
+
+ <
+
+
+ Mar
+
+ 2017
+
+
+ >
+
+
+
+
+ Mon
+
+
+ Tue
+
+
+ Wed
+
+
+ Thu
+
+
+ Fri
+
+
+ Sat
+
+
+ Sun
+
+
+
+
+ 27
+
+
+ 28
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 4
+
+
+ 5
+
+
+ 6
+
+
+ 7
+
+
+ 8
+
+
+ 9
+
+
+ 10
+
+
+ 11
+
+
+ 12
+
+
+ 13
+
+
+ 14
+
+
+ 15
+
+
+ 16
+
+
+ 17
+
+
+ 18
+
+
+ 19
+
+
+ 20
+
+
+ 21
+
+
+ 22
+
+
+ 23
+
+
+ 24
+
+
+ 25
+
+
+ 26
+
+
+ 27
+
+
+ 28
+
+
+ 29
+
+
+ 30
+
+
+ 31
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 4
+
+
+ 5
+
+
+ 6
+
+
+ 7
+
+
+ 8
+
+
+ 9
+
+
+
+
+
+
+
+
+
+ Change date
+
+
+ Change date
+
+
+
+`;
+
+exports[`Calendar (normal mode) #render() should merge the locale definition with the default one 1`] = `
+
+
+
+
+
+
+
+
+ <
+
+
+ Oct
+
+ 2017
+
+
+ >
+
+
+
+
+ Seg
+
+
+ Ter
+
+
+ Qua
+
+
+ Qui
+
+
+ Sex
+
+
+ Sáb
+
+
+ Dom
+
+
+
+
+ 25
+
+
+ 26
+
+
+ 27
+
+
+ 28
+
+
+ 29
+
+
+ 30
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 4
+
+
+ 5
+
+
+ 6
+
+
+ 7
+
+
+ 8
+
+
+ 9
+
+
+ 10
+
+
+ 11
+
+
+ 12
+
+
+ 13
+
+
+ 14
+
+
+ 15
+
+
+ 16
+
+
+ 17
+
+
+ 18
+
+
+ 19
+
+
+ 20
+
+
+ 21
+
+
+ 22
+
+
+ 23
+
+
+ 24
+
+
+ 25
+
+
+ 26
+
+
+ 27
+
+
+ 28
+
+
+ 29
+
+
+ 30
+
+
+ 31
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 4
+
+
+ 5
+
+
+
+
+
+
+
+
+`;
+
+exports[`Calendar (normal mode) #render() should merge the locale definition with the default one 2`] = `
+
+
+
+
+
+
+
+
+ <
+
+
+ Mar
+
+ 2017
+
+
+ >
+
+
+
+
+ Sun
+
+
+ Mon
+
+
+ Tue
+
+
+ Wed
+
+
+ Thu
+
+
+ Fri
+
+
+ Sat
+
+
+
+
+ 26
+
+
+ 27
+
+
+ 28
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 4
+
+
+ 5
+
+
+ 6
+
+
+ 7
+
+
+ 8
+
+
+ 9
+
+
+ 10
+
+
+ 11
+
+
+ 12
+
+
+ 13
+
+
+ 14
+
+
+ 15
+
+
+ 16
+
+
+ 17
+
+
+ 18
+
+
+ 19
+
+
+ 20
+
+
+ 21
+
+
+ 22
+
+
+ 23
+
+
+ 24
+
+
+ 25
+
+
+ 26
+
+
+ 27
+
+
+ 28
+
+
+ 29
+
+
+ 30
+
+
+ 31
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 4
+
+
+ 5
+
+
+ 6
+
+
+ 7
+
+
+ 8
+
+
+
+
+
+
+
+
+`;
+
+exports[`Calendar (normal mode) #render() should only be able to select a date that fits the isDaySelectableFn condition, when set 1`] = `
+
+
+
+
+
+
+
+
+ <
+
+
+ Mar
+
+ 2017
+
+
+ >
+
+
+
+
+ Mon
+
+
+ Tue
+
+
+ Wed
+
+
+ Thu
+
+
+ Fri
+
+
+ Sat
+
+
+ Sun
+
+
+
+
+ 27
+
+
+ 28
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 4
+
+
+ 5
+
+
+ 6
+
+
+ 7
+
+
+ 8
+
+
+ 9
+
+
+ 10
+
+
+ 11
+
+
+ 12
+
+
+ 13
+
+
+ 14
+
+
+ 15
+
+
+ 16
+
+
+ 17
+
+
+ 18
+
+
+ 19
+
+
+ 20
+
+
+ 21
+
+
+ 22
+
+
+ 23
+
+
+ 24
+
+
+ 25
+
+
+ 26
+
+
+ 27
+
+
+ 28
+
+
+ 29
+
+
+ 30
+
+
+ 31
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 4
+
+
+ 5
+
+
+ 6
+
+
+ 7
+
+
+ 8
+
+
+ 9
+
+
+
+
+
+
+
+
+`;
+
+exports[`Calendar (normal mode) #render() should only change the renderDate and do nothing else if nav callbacks are not defined 1`] = `
+
+
+
+
+
+
+
+
+ <
+
+
+ Mar
+
+ 2017
+
+
+ >
+
+
+
+
+ Mon
+
+
+ Tue
+
+
+ Wed
+
+
+ Thu
+
+
+ Fri
+
+
+ Sat
+
+
+ Sun
+
+
+
+
+ 27
+
+
+ 28
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 4
+
+
+ 5
+
+
+ 6
+
+
+ 7
+
+
+ 8
+
+
+ 9
+
+
+ 10
+
+
+ 11
+
+
+ 12
+
+
+ 13
+
+
+ 14
+
+
+ 15
+
+
+ 16
+
+
+ 17
+
+
+ 18
+
+
+ 19
+
+
+ 20
+
+
+ 21
+
+
+ 22
+
+
+ 23
+
+
+ 24
+
+
+ 25
+
+
+ 26
+
+
+ 27
+
+
+ 28
+
+
+ 29
+
+
+ 30
+
+
+ 31
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 4
+
+
+ 5
+
+
+ 6
+
+
+ 7
+
+
+ 8
+
+
+ 9
+
+
+
+
+
+
+
+
+`;
+
+exports[`Calendar (normal mode) #render() should only change the selectedDate and do nothing else if selection callbacks are not defined 1`] = `
+
+
+
+
+
+
+
+
+ <
+
+
+ Mar
+
+ 2017
+
+
+ >
+
+
+
+
+ Mon
+
+
+ Tue
+
+
+ Wed
+
+
+ Thu
+
+
+ Fri
+
+
+ Sat
+
+
+ Sun
+
+
+
+
+ 27
+
+
+ 28
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 4
+
+
+ 5
+
+
+ 6
+
+
+ 7
+
+
+ 8
+
+
+ 9
+
+
+ 10
+
+
+ 11
+
+
+ 12
+
+
+ 13
+
+
+ 14
+
+
+ 15
+
+
+ 16
+
+
+ 17
+
+
+ 18
+
+
+ 19
+
+
+ 20
+
+
+ 21
+
+
+ 22
+
+
+ 23
+
+
+ 24
+
+
+ 25
+
+
+ 26
+
+
+ 27
+
+
+ 28
+
+
+ 29
+
+
+ 30
+
+
+ 31
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 4
+
+
+ 5
+
+
+ 6
+
+
+ 7
+
+
+ 8
+
+
+ 9
+
+
+
+
+
+
+
+
+`;
+
+exports[`Calendar (normal mode) #render() should render the Calendar and re-render on change of the props 1`] = `
+
+
+
+
+
+
+
+
+
+
+ <
+
+
+ Mar
+
+ 2017
+
+
+ >
+
+
+
+
+ Mon
+
+
+ Tue
+
+
+ Wed
+
+
+ Thu
+
+
+ Fri
+
+
+ Sat
+
+
+ Sun
+
+
+
+
+ 27
+
+
+ 28
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 4
+
+
+ 5
+
+
+ 6
+
+
+ 7
+
+
+ 8
+
+
+ 9
+
+
+ 10
+
+
+ 11
+
+
+ 12
+
+
+ 13
+
+
+ 14
+
+
+ 15
+
+
+ 16
+
+
+ 17
+
+
+ 18
+
+
+ 19
+
+
+ 20
+
+
+ 21
+
+
+ 22
+
+
+ 23
+
+
+ 24
+
+
+ 25
+
+
+ 26
+
+
+ 27
+
+
+ 28
+
+
+ 29
+
+
+ 30
+
+
+ 31
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 4
+
+
+ 5
+
+
+ 6
+
+
+ 7
+
+
+ 8
+
+
+ 9
+
+
+
+
+
+
+
+
+
+ Change date
+
+
+ Change date
+
+
+
+`;
+
+exports[`Calendar (normal mode) #render() should render the calendar with selectionType as normal, initialized in the current date 1`] = `
+
+
+
+
+
+
+
+
+ <
+
+
+ Mar
+
+ 2017
+
+
+ >
+
+
+
+
+ Mon
+
+
+ Tue
+
+
+ Wed
+
+
+ Thu
+
+
+ Fri
+
+
+ Sat
+
+
+ Sun
+
+
+
+
+ 27
+
+
+ 28
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 4
+
+
+ 5
+
+
+ 6
+
+
+ 7
+
+
+ 8
+
+
+ 9
+
+
+ 10
+
+
+ 11
+
+
+ 12
+
+
+ 13
+
+
+ 14
+
+
+ 15
+
+
+ 16
+
+
+ 17
+
+
+ 18
+
+
+ 19
+
+
+ 20
+
+
+ 21
+
+
+ 22
+
+
+ 23
+
+
+ 24
+
+
+ 25
+
+
+ 26
+
+
+ 27
+
+
+ 28
+
+
+ 29
+
+
+ 30
+
+
+ 31
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 4
+
+
+ 5
+
+
+ 6
+
+
+ 7
+
+
+ 8
+
+
+ 9
+
+
+
+
+
+
+
+
+`;
+
+exports[`Calendar (normal mode) #render() should reset selectedDates, when at least one of the initialDates are outside min/max limit 1`] = `
+
+
+
+
+
+
+
+
+ <
+
+
+ Mar
+
+ 2017
+
+
+ >
+
+
+
+
+ Mon
+
+
+ Tue
+
+
+ Wed
+
+
+ Thu
+
+
+ Fri
+
+
+ Sat
+
+
+ Sun
+
+
+
+
+ 27
+
+
+ 28
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 4
+
+
+ 5
+
+
+ 6
+
+
+ 7
+
+
+ 8
+
+
+ 9
+
+
+ 10
+
+
+ 11
+
+
+ 12
+
+
+ 13
+
+
+ 14
+
+
+ 15
+
+
+ 16
+
+
+ 17
+
+
+ 18
+
+
+ 19
+
+
+ 20
+
+
+ 21
+
+
+ 22
+
+
+ 23
+
+
+ 24
+
+
+ 25
+
+
+ 26
+
+
+ 27
+
+
+ 28
+
+
+ 29
+
+
+ 30
+
+
+ 31
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 4
+
+
+ 5
+
+
+ 6
+
+
+ 7
+
+
+ 8
+
+
+ 9
+
+
+
+
+
+
+
+
+`;
+
+exports[`Calendar (normal mode) #render() should set maxLimit, with a given "maxDate" attribute 1`] = `
+
+
+
+
+
+
+
+
+ <
+
+
+ Mar
+
+ 2017
+
+
+ >
+
+
+
+
+ Mon
+
+
+ Tue
+
+
+ Wed
+
+
+ Thu
+
+
+ Fri
+
+
+ Sat
+
+
+ Sun
+
+
+
+
+ 27
+
+
+ 28
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 4
+
+
+ 5
+
+
+ 6
+
+
+ 7
+
+
+ 8
+
+
+ 9
+
+
+ 10
+
+
+ 11
+
+
+ 12
+
+
+ 13
+
+
+ 14
+
+
+ 15
+
+
+ 16
+
+
+ 17
+
+
+ 18
+
+
+ 19
+
+
+ 20
+
+
+ 21
+
+
+ 22
+
+
+ 23
+
+
+ 24
+
+
+ 25
+
+
+ 26
+
+
+ 27
+
+
+ 28
+
+
+ 29
+
+
+ 30
+
+
+ 31
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 4
+
+
+ 5
+
+
+ 6
+
+
+ 7
+
+
+ 8
+
+
+ 9
+
+
+
+
+
+
+
+
+`;
+
+exports[`Calendar (normal mode) #render() should set minLimit, with a given "minDate" attribute 1`] = `
+
+
+
+
+
+
+
+
+ <
+
+
+ Mar
+
+ 2017
+
+
+ >
+
+
+
+
+ Mon
+
+
+ Tue
+
+
+ Wed
+
+
+ Thu
+
+
+ Fri
+
+
+ Sat
+
+
+ Sun
+
+
+
+
+ 27
+
+
+ 28
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 4
+
+
+ 5
+
+
+ 6
+
+
+ 7
+
+
+ 8
+
+
+ 9
+
+
+ 10
+
+
+ 11
+
+
+ 12
+
+
+ 13
+
+
+ 14
+
+
+ 15
+
+
+ 16
+
+
+ 17
+
+
+ 18
+
+
+ 19
+
+
+ 20
+
+
+ 21
+
+
+ 22
+
+
+ 23
+
+
+ 24
+
+
+ 25
+
+
+ 26
+
+
+ 27
+
+
+ 28
+
+
+ 29
+
+
+ 30
+
+
+ 31
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 4
+
+
+ 5
+
+
+ 6
+
+
+ 7
+
+
+ 8
+
+
+ 9
+
+
+
+
+
+
+
+
+`;
+
+exports[`Calendar (normal mode) #render() should set renderDate and not minLimit, with a given "initalDates" attribute 1`] = `
+
+
+
+
+
+
+
+
+ <
+
+
+ Mar
+
+ 2017
+
+
+ >
+
+
+
+
+ Mon
+
+
+ Tue
+
+
+ Wed
+
+
+ Thu
+
+
+ Fri
+
+
+ Sat
+
+
+ Sun
+
+
+
+
+ 27
+
+
+ 28
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 4
+
+
+ 5
+
+
+ 6
+
+
+ 7
+
+
+ 8
+
+
+ 9
+
+
+ 10
+
+
+ 11
+
+
+ 12
+
+
+ 13
+
+
+ 14
+
+
+ 15
+
+
+ 16
+
+
+ 17
+
+
+ 18
+
+
+ 19
+
+
+ 20
+
+
+ 21
+
+
+ 22
+
+
+ 23
+
+
+ 24
+
+
+ 25
+
+
+ 26
+
+
+ 27
+
+
+ 28
+
+
+ 29
+
+
+ 30
+
+
+ 31
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 4
+
+
+ 5
+
+
+ 6
+
+
+ 7
+
+
+ 8
+
+
+ 9
+
+
+
+
+
+
+
+
+`;
+
+exports[`Calendar (normal mode) #render() should set renderDate to next/previous months when the next/previous btns are pressed 1`] = `
+
+
+
+
+
+
+
+
+ <
+
+
+ Mar
+
+ 2017
+
+
+ >
+
+
+
+
+ Mon
+
+
+ Tue
+
+
+ Wed
+
+
+ Thu
+
+
+ Fri
+
+
+ Sat
+
+
+ Sun
+
+
+
+
+ 27
+
+
+ 28
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 4
+
+
+ 5
+
+
+ 6
+
+
+ 7
+
+
+ 8
+
+
+ 9
+
+
+ 10
+
+
+ 11
+
+
+ 12
+
+
+ 13
+
+
+ 14
+
+
+ 15
+
+
+ 16
+
+
+ 17
+
+
+ 18
+
+
+ 19
+
+
+ 20
+
+
+ 21
+
+
+ 22
+
+
+ 23
+
+
+ 24
+
+
+ 25
+
+
+ 26
+
+
+ 27
+
+
+ 28
+
+
+ 29
+
+
+ 30
+
+
+ 31
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 4
+
+
+ 5
+
+
+ 6
+
+
+ 7
+
+
+ 8
+
+
+ 9
+
+
+
+
+
+
+
+
+`;
+
+exports[`Calendar (normal mode) #render() should set renderDate to the month of the date pressed when different from current one 1`] = `
+
+
+
+
+
+
+
+
+ <
+
+
+ Mar
+
+ 2017
+
+
+ >
+
+
+
+
+ Mon
+
+
+ Tue
+
+
+ Wed
+
+
+ Thu
+
+
+ Fri
+
+
+ Sat
+
+
+ Sun
+
+
+
+
+ 27
+
+
+ 28
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 4
+
+
+ 5
+
+
+ 6
+
+
+ 7
+
+
+ 8
+
+
+ 9
+
+
+ 10
+
+
+ 11
+
+
+ 12
+
+
+ 13
+
+
+ 14
+
+
+ 15
+
+
+ 16
+
+
+ 17
+
+
+ 18
+
+
+ 19
+
+
+ 20
+
+
+ 21
+
+
+ 22
+
+
+ 23
+
+
+ 24
+
+
+ 25
+
+
+ 26
+
+
+ 27
+
+
+ 28
+
+
+ 29
+
+
+ 30
+
+
+ 31
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 4
+
+
+ 5
+
+
+ 6
+
+
+ 7
+
+
+ 8
+
+
+ 9
+
+
+
+
+
+
+
+
+`;
+
+exports[`Calendar (normal mode) #render() should set selectedDate to the date of the button pressed 1`] = `
+
+
+
+
+
+
+
+
+ <
+
+
+ Mar
+
+ 2017
+
+
+ >
+
+
+
+
+ Mon
+
+
+ Tue
+
+
+ Wed
+
+
+ Thu
+
+
+ Fri
+
+
+ Sat
+
+
+ Sun
+
+
+
+
+ 27
+
+
+ 28
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 4
+
+
+ 5
+
+
+ 6
+
+
+ 7
+
+
+ 8
+
+
+ 9
+
+
+ 10
+
+
+ 11
+
+
+ 12
+
+
+ 13
+
+
+ 14
+
+
+ 15
+
+
+ 16
+
+
+ 17
+
+
+ 18
+
+
+ 19
+
+
+ 20
+
+
+ 21
+
+
+ 22
+
+
+ 23
+
+
+ 24
+
+
+ 25
+
+
+ 26
+
+
+ 27
+
+
+ 28
+
+
+ 29
+
+
+ 30
+
+
+ 31
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 4
+
+
+ 5
+
+
+ 6
+
+
+ 7
+
+
+ 8
+
+
+ 9
+
+
+
+
+
+
+
+
+`;
+
+exports[`Calendar (normal mode) #render() should set the navButtons display values as defined on props, using default aria-labels 1`] = `
+
+
+
+
+
+
+
+
+ ‹
+
+
+ Mar
+
+ 2017
+
+
+ ›
+
+
+
+
+ Mon
+
+
+ Tue
+
+
+ Wed
+
+
+ Thu
+
+
+ Fri
+
+
+ Sat
+
+
+ Sun
+
+
+
+
+ 27
+
+
+ 28
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 4
+
+
+ 5
+
+
+ 6
+
+
+ 7
+
+
+ 8
+
+
+ 9
+
+
+ 10
+
+
+ 11
+
+
+ 12
+
+
+ 13
+
+
+ 14
+
+
+ 15
+
+
+ 16
+
+
+ 17
+
+
+ 18
+
+
+ 19
+
+
+ 20
+
+
+ 21
+
+
+ 22
+
+
+ 23
+
+
+ 24
+
+
+ 25
+
+
+ 26
+
+
+ 27
+
+
+ 28
+
+
+ 29
+
+
+ 30
+
+
+ 31
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 4
+
+
+ 5
+
+
+ 6
+
+
+ 7
+
+
+ 8
+
+
+ 9
+
+
+
+
+
+
+
+
+`;
+
+exports[`Calendar (normal mode) #render() should use be possible to navigate between months 1`] = `
+
+
+
+
+
+
+
+
+ <
+
+
+ Jan
+
+ 2017
+
+
+ >
+
+
+
+
+ Mon
+
+
+ Tue
+
+
+ Wed
+
+
+ Thu
+
+
+ Fri
+
+
+ Sat
+
+
+ Sun
+
+
+
+
+ 26
+
+
+ 27
+
+
+ 28
+
+
+ 29
+
+
+ 30
+
+
+ 31
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 4
+
+
+ 5
+
+
+ 6
+
+
+ 7
+
+
+ 8
+
+
+ 9
+
+
+ 10
+
+
+ 11
+
+
+ 12
+
+
+ 13
+
+
+ 14
+
+
+ 15
+
+
+ 16
+
+
+ 17
+
+
+ 18
+
+
+ 19
+
+
+ 20
+
+
+ 21
+
+
+ 22
+
+
+ 23
+
+
+ 24
+
+
+ 25
+
+
+ 26
+
+
+ 27
+
+
+ 28
+
+
+ 29
+
+
+ 30
+
+
+ 31
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 4
+
+
+ 5
+
+
+
+
+
+
+
+
+`;
+
+exports[`Calendar (normal mode) #render() should use the aria-labels defined on the navButtons attribute 1`] = `
+
+
+
+
+
+
+
+
+ ‹
+
+
+ Mar
+
+ 2017
+
+
+ ›
+
+
+
+
+ Mon
+
+
+ Tue
+
+
+ Wed
+
+
+ Thu
+
+
+ Fri
+
+
+ Sat
+
+
+ Sun
+
+
+
+
+ 27
+
+
+ 28
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 4
+
+
+ 5
+
+
+ 6
+
+
+ 7
+
+
+ 8
+
+
+ 9
+
+
+ 10
+
+
+ 11
+
+
+ 12
+
+
+ 13
+
+
+ 14
+
+
+ 15
+
+
+ 16
+
+
+ 17
+
+
+ 18
+
+
+ 19
+
+
+ 20
+
+
+ 21
+
+
+ 22
+
+
+ 23
+
+
+ 24
+
+
+ 25
+
+
+ 26
+
+
+ 27
+
+
+ 28
+
+
+ 29
+
+
+ 30
+
+
+ 31
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 4
+
+
+ 5
+
+
+ 6
+
+
+ 7
+
+
+ 8
+
+
+ 9
+
+
+
+
+
+
+
+
+`;
diff --git a/packages/travix-ui-kit/tests/unit/calendar/__snapshots__/calendar.range.spec.js.snap b/packages/travix-ui-kit/tests/unit/calendar/__snapshots__/calendar.range.spec.js.snap
new file mode 100644
index 0000000..1b679fa
--- /dev/null
+++ b/packages/travix-ui-kit/tests/unit/calendar/__snapshots__/calendar.range.spec.js.snap
@@ -0,0 +1,1644 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Calendar (range mode) #render() should render the calendar with selectionType as range, initialized in the current date 1`] = `
+
+
+
+
+
+
+
+
+ <
+
+
+ Mar
+
+ 2017
+
+
+ >
+
+
+
+
+ Mon
+
+
+ Tue
+
+
+ Wed
+
+
+ Thu
+
+
+ Fri
+
+
+ Sat
+
+
+ Sun
+
+
+
+
+ 27
+
+
+ 28
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 4
+
+
+ 5
+
+
+ 6
+
+
+ 7
+
+
+ 8
+
+
+ 9
+
+
+ 10
+
+
+ 11
+
+
+ 12
+
+
+ 13
+
+
+ 14
+
+
+ 15
+
+
+ 16
+
+
+ 17
+
+
+ 18
+
+
+ 19
+
+
+ 20
+
+
+ 21
+
+
+ 22
+
+
+ 23
+
+
+ 24
+
+
+ 25
+
+
+ 26
+
+
+ 27
+
+
+ 28
+
+
+ 29
+
+
+ 30
+
+
+ 31
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 4
+
+
+ 5
+
+
+ 6
+
+
+ 7
+
+
+ 8
+
+
+ 9
+
+
+
+
+
+
+
+
+`;
+
+exports[`Calendar (range mode) #render() should set the minLimit and dates properly and reset them at 3rd click 1`] = `
+
+
+
+
+
+
+
+
+ <
+
+
+ Mar
+
+ 2017
+
+
+ >
+
+
+
+
+ Mon
+
+
+ Tue
+
+
+ Wed
+
+
+ Thu
+
+
+ Fri
+
+
+ Sat
+
+
+ Sun
+
+
+
+
+ 27
+
+
+ 28
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 4
+
+
+ 5
+
+
+ 6
+
+
+ 7
+
+
+ 8
+
+
+ 9
+
+
+ 10
+
+
+ 11
+
+
+ 12
+
+
+ 13
+
+
+ 14
+
+
+ 15
+
+
+ 16
+
+
+ 17
+
+
+ 18
+
+
+ 19
+
+
+ 20
+
+
+ 21
+
+
+ 22
+
+
+ 23
+
+
+ 24
+
+
+ 25
+
+
+ 26
+
+
+ 27
+
+
+ 28
+
+
+ 29
+
+
+ 30
+
+
+ 31
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 4
+
+
+ 5
+
+
+ 6
+
+
+ 7
+
+
+ 8
+
+
+ 9
+
+
+
+
+
+
+
+
+`;
+
+exports[`Calendar (range mode) #render() should set the minLimit to the same date as the first initialDates when provided 1`] = `
+
+
+
+
+
+
+
+
+ <
+
+
+ Mar
+
+ 2017
+
+
+ >
+
+
+
+
+ Mon
+
+
+ Tue
+
+
+ Wed
+
+
+ Thu
+
+
+ Fri
+
+
+ Sat
+
+
+ Sun
+
+
+
+
+ 27
+
+
+ 28
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 4
+
+
+ 5
+
+
+ 6
+
+
+ 7
+
+
+ 8
+
+
+ 9
+
+
+ 10
+
+
+ 11
+
+
+ 12
+
+
+ 13
+
+
+ 14
+
+
+ 15
+
+
+ 16
+
+
+ 17
+
+
+ 18
+
+
+ 19
+
+
+ 20
+
+
+ 21
+
+
+ 22
+
+
+ 23
+
+
+ 24
+
+
+ 25
+
+
+ 26
+
+
+ 27
+
+
+ 28
+
+
+ 29
+
+
+ 30
+
+
+ 31
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ 4
+
+
+ 5
+
+
+ 6
+
+
+ 7
+
+
+ 8
+
+
+ 9
+
+
+
+
+
+
+
+
+`;
diff --git a/packages/travix-ui-kit/tests/unit/calendar/calendar.normal.spec.js b/packages/travix-ui-kit/tests/unit/calendar/calendar.normal.spec.js
new file mode 100644
index 0000000..23ff46d
--- /dev/null
+++ b/packages/travix-ui-kit/tests/unit/calendar/calendar.normal.spec.js
@@ -0,0 +1,406 @@
+import { mount } from 'enzyme';
+import React from 'react';
+import Calendar from '../../../components/calendar/calendar';
+import CalendarWrapper from './calendarWrapper.mock';
+import { normalizeDate } from '../../../components/_helpers';
+
+describe('Calendar (normal mode)', () => {
+ describe('#render()', () => {
+ it('should render the calendar with selectionType as normal, initialized in the current date', () => {
+ const todayDate = normalizeDate(new Date());
+ const wrapper = mount(
+
+ );
+
+ expect(wrapper).toMatchSnapshot();
+ expect(wrapper.props()).toEqual({
+ selectionType: 'normal',
+ });
+ expect(wrapper.state()).toEqual({
+ maxLimit: null,
+ minLimit: null,
+ renderDate: todayDate,
+ selectedDates: [null, null],
+ });
+ });
+
+ it('should set renderDate and not minLimit, with a given "initalDates" attribute', () => {
+ const initialDate = '2017-03-20';
+ const initialDateObject = normalizeDate(new Date(initialDate));
+
+ const wrapper = mount(
+
+ );
+
+ expect(wrapper).toMatchSnapshot();
+ expect(wrapper.props()).toEqual({
+ initialDates: [initialDate],
+ selectionType: 'normal',
+ });
+ expect(wrapper.state()).toEqual({
+ maxLimit: null,
+ minLimit: null,
+ renderDate: initialDateObject,
+ selectedDates: [initialDateObject, null],
+ });
+ });
+
+ it('should set maxLimit, with a given "maxDate" attribute', () => {
+ const maxDate = '2017-03-20';
+ const todayDate = normalizeDate(new Date());
+ const maxDateObject = normalizeDate(new Date(maxDate), 23, 59, 59, 999);
+
+ const wrapper = mount(
+
+ );
+
+ expect(wrapper).toMatchSnapshot();
+ expect(wrapper.props()).toEqual({
+ maxDate,
+ selectionType: 'normal',
+ });
+ expect(wrapper.state()).toEqual({
+ maxLimit: maxDateObject,
+ minLimit: null,
+ renderDate: todayDate,
+ selectedDates: [null, null],
+ });
+ });
+
+ it('should set minLimit, with a given "minDate" attribute', () => {
+ const minDate = '2017-03-20';
+ const todayDate = normalizeDate(new Date());
+ const minDateObject = normalizeDate(new Date(minDate));
+
+ const wrapper = mount(
+
+ );
+
+ expect(wrapper).toMatchSnapshot();
+ expect(wrapper.props()).toEqual({
+ minDate,
+ selectionType: 'normal',
+ });
+ expect(wrapper.state()).toEqual({
+ maxLimit: null,
+ minLimit: minDateObject,
+ renderDate: todayDate,
+ selectedDates: [null, null],
+ });
+ });
+
+ it('should reset selectedDates, when at least one of the initialDates are outside min/max limit', () => {
+ const initialDates = ['2017-03-23', '2017-03-29'];
+ const initialDatesObjects = initialDates.map(dateStr => normalizeDate(new Date(dateStr)));
+ const maxDate = '2017-03-25';
+ const maxDateObject = normalizeDate(new Date(maxDate), 23, 59, 59, 999);
+ const minDate = '2017-03-20';
+ const minDateObject = normalizeDate(new Date(minDate));
+
+
+ const wrapper = mount(
+
+ );
+
+ expect(wrapper).toMatchSnapshot();
+ expect(wrapper.props()).toEqual({
+ initialDates,
+ maxDate,
+ minDate,
+ selectionType: 'normal',
+ });
+ expect(wrapper.state()).toEqual({
+ maxLimit: maxDateObject,
+ minLimit: minDateObject,
+ renderDate: initialDatesObjects[0],
+ selectedDates: [null, null],
+ });
+ });
+
+ it('should set renderDate to next/previous months when the next/previous btns are pressed', () => {
+ const initialDate = '2017-03-05';
+ const initialDateObject = normalizeDate(new Date(initialDate));
+ const nextMonthDateObj = normalizeDate(new Date(initialDate));
+
+ const nextMock = jest.fn();
+ const previousMock = jest.fn();
+
+ const wrapper = mount(
+
+ );
+ expect(wrapper).toMatchSnapshot();
+
+ /** Clicks to go next month */
+ wrapper.find('.ui-calendar-days__next-month').simulate('click');
+
+ nextMonthDateObj.setMonth(nextMonthDateObj.getMonth() + 1);
+
+ expect(wrapper.state().renderDate).toEqual(nextMonthDateObj);
+ expect(nextMock.mock.calls.length).toEqual(1);
+ expect(nextMock.mock.calls[0][0]).toEqual(wrapper.state().renderDate);
+
+ /** Clicks to go next month */
+ wrapper.find('.ui-calendar-days__previous-month').simulate('click');
+
+ expect(wrapper.state().renderDate).toEqual(initialDateObject);
+ expect(previousMock.mock.calls.length).toEqual(1);
+ expect(previousMock.mock.calls[0][0]).toEqual(wrapper.state().renderDate);
+ });
+
+ it('should set selectedDate to the date of the button pressed', () => {
+ const initialDate = '2017-03-05';
+
+ const wrapper = mount(
+
+ );
+ expect(wrapper).toMatchSnapshot();
+
+ /** Clicks to go select the day */
+ wrapper.find(`[data-date="2017-03-25"]`).simulate('click');
+
+ expect(wrapper.state().selectedDates[0].getDate()).toEqual(25);
+ });
+
+ it('should set renderDate to the month of the date pressed when different from current one', () => {
+ const initialDate = '2017-03-05';
+ const selectDayMock = jest.fn();
+
+ const wrapper = mount(
+
+ );
+ expect(wrapper).toMatchSnapshot();
+
+ /** Clicks to go select the day */
+ expect(wrapper.state().renderDate.getMonth()).toEqual(2);
+
+ wrapper.find(`[data-date="2017-04-01"]`).simulate('click');
+
+ expect(wrapper.state().selectedDates[0].getMonth()).toEqual(3);
+ expect(wrapper.state().renderDate.getMonth()).toEqual(3);
+ expect(selectDayMock.mock.calls.length).toEqual(1);
+ expect(selectDayMock.mock.calls[0][0]).toEqual(wrapper.state().selectedDates);
+ });
+
+ it('should only change the renderDate and do nothing else if nav callbacks are not defined', () => {
+ const initialDate = '2017-03-05';
+ const initialDateObject = normalizeDate(new Date(initialDate));
+ const nextMonthDateObj = normalizeDate(new Date(initialDate));
+
+ const wrapper = mount(
+
+ );
+ expect(wrapper).toMatchSnapshot();
+
+ /** Clicks to go next month */
+ wrapper.find('.ui-calendar-days__next-month').simulate('click');
+
+ nextMonthDateObj.setMonth(nextMonthDateObj.getMonth() + 1);
+
+ expect(wrapper.state().renderDate).toEqual(nextMonthDateObj);
+
+ /** Clicks to go next month */
+ wrapper.find('.ui-calendar-days__previous-month').simulate('click');
+
+ expect(wrapper.state().renderDate).toEqual(initialDateObject);
+ });
+
+ it('should only change the selectedDate and do nothing else if selection callbacks are not defined', () => {
+ const initialDate = '2017-03-05';
+ const selectedDate = '2017-03-20';
+ const expectedSelectedDate = normalizeDate(new Date(selectedDate));
+
+ const wrapper = mount(
+
+ );
+ expect(wrapper).toMatchSnapshot();
+
+ /** Clicks to go next month */
+ wrapper.find(`[data-date="${selectedDate}"]`).simulate('click');
+
+
+ expect(wrapper.state().selectedDates).toEqual([expectedSelectedDate, null]);
+ });
+
+ it('should only be able to select a date that fits the isDaySelectableFn condition, when set', () => {
+ const initialDate = '2017-03-05';
+ const nonSelectableDate = '2017-03-20';
+ const selectableDate = '2017-03-21';
+
+ const isDaySelectableFn = dt => dt.getDate() === 21;
+ const onSelectDayMock = jest.fn();
+
+ const initialDateObject = normalizeDate(new Date(initialDate));
+ const selectableDateObject = normalizeDate(new Date(selectableDate));
+
+ const wrapper = mount(
+
+ );
+ expect(wrapper).toMatchSnapshot();
+
+ expect(wrapper.state().selectedDates).toEqual([initialDateObject, null]);
+
+ wrapper.find(`[data-date="${nonSelectableDate}"]`).simulate('click');
+ expect(wrapper.state().selectedDates).toEqual([initialDateObject, null]);
+
+ wrapper.find(`[data-date="${selectableDate}"]`).simulate('click');
+ expect(wrapper.state().selectedDates).toEqual([selectableDateObject, null]);
+
+ expect(onSelectDayMock.mock.calls.length).toEqual(1);
+ expect(onSelectDayMock.mock.calls[0][0]).toBeInstanceOf(Array);
+ expect(onSelectDayMock.mock.calls[0][0][0]).toBeInstanceOf(Date);
+ expect(onSelectDayMock.mock.calls[0][0][1]).toEqual(null);
+ });
+
+ it('should merge the locale definition with the default one', () => {
+ const myLocale = {
+ weekDays: [
+ { name: 'Domingo', short: 'Dom' },
+ { name: 'Segunda', short: 'Seg' },
+ { name: 'Terça', short: 'Ter' },
+ { name: 'Quarta', short: 'Qua' },
+ { name: 'Quinta', short: 'Qui' },
+ { name: 'Sexta', short: 'Sex' },
+ { name: 'Sábado', short: 'Sáb' },
+ ],
+ };
+
+ const wrapper = mount(
+
+ );
+ expect(wrapper).toMatchSnapshot();
+
+ expect(wrapper.find('.ui-calendar-days__weekday').first().text()).toEqual('Seg');
+ });
+
+ it('should merge the locale definition with the default one', () => {
+ const myLocale = { startWeekDay: 0 };
+
+ const wrapper = mount(
+
+ );
+ expect(wrapper).toMatchSnapshot();
+
+ expect(wrapper.find('.ui-calendar-days__weekday').first().text()).toEqual('Sun');
+ });
+
+ it('should apply the new initialDates on Calendar even when changed in runtime by the parent component', () => {
+ const wrapper = mount(
+
+ );
+ expect(wrapper).toMatchSnapshot();
+
+ expect(wrapper.find('Calendar').props().initialDates[0]).toEqual('2017-03-03');
+
+ wrapper.find('#changeInitialDate').simulate('click');
+
+ expect(wrapper.find('Calendar').props().initialDates[0]).toEqual('2017-04-03');
+ });
+
+ it('should render the Calendar and re-render on change of the props', () => {
+ const wrapper = mount(
+
+ );
+ expect(wrapper).toMatchSnapshot();
+
+ wrapper.find('#changeInitialDate').simulate('click');
+
+ expect(wrapper.find('Calendar').props().initialDates[0]).toEqual('2017-04-03');
+
+ // Check what happens when the props are the same.
+ wrapper.find('#changeInitialDate').simulate('click');
+
+ expect(wrapper.find('Calendar').props().initialDates[0]).toEqual('2017-04-03');
+ });
+
+ it('should apply the new minDate on Calendar even when changed by the parent component', () => {
+ const wrapper = mount(
+
+ );
+ expect(wrapper).toMatchSnapshot();
+
+ expect(wrapper.find('Calendar').props().minDate).toEqual('2017-03-01');
+
+ wrapper.find('#changeMinDate').simulate('click');
+
+ expect(wrapper.find('Calendar').props().minDate).toEqual('2017-04-01');
+ });
+
+ it('should set the navButtons display values as defined on props, using default aria-labels', () => {
+ const navButtons = {
+ days: {
+ next: {
+ displayValue: '›',
+ },
+ previous: {
+ displayValue: '‹',
+ },
+ },
+ };
+
+ const wrapper = mount(
+
+ );
+ expect(wrapper).toMatchSnapshot();
+
+ const nextBtn = wrapper.find('.ui-calendar-days__next-month');
+ const previousBtn = wrapper.find('.ui-calendar-days__previous-month');
+ expect(nextBtn.text()).toEqual(navButtons.days.next.displayValue);
+ expect(previousBtn.text()).toEqual(navButtons.days.previous.displayValue);
+
+ expect(nextBtn.props()['aria-label']).toEqual('April');
+ expect(previousBtn.props()['aria-label']).toEqual('February');
+ });
+
+ it('should use the aria-labels defined on the navButtons attribute', () => {
+ const navButtons = {
+ days: {
+ next: {
+ ariaLabel: 'Next month',
+ displayValue: '›',
+ },
+ previous: {
+ ariaLabel: 'Previous month',
+ displayValue: '‹',
+ },
+ },
+ };
+
+ const wrapper = mount(
+
+ );
+ expect(wrapper).toMatchSnapshot();
+
+ const nextBtn = wrapper.find('.ui-calendar-days__next-month');
+ const previousBtn = wrapper.find('.ui-calendar-days__previous-month');
+ expect(nextBtn.text()).toEqual(navButtons.days.next.displayValue);
+ expect(previousBtn.text()).toEqual(navButtons.days.previous.displayValue);
+
+ expect(nextBtn.props()['aria-label']).toEqual(navButtons.days.next.ariaLabel);
+ expect(previousBtn.props()['aria-label']).toEqual(navButtons.days.previous.ariaLabel);
+ });
+
+ it('should use be possible to navigate between months', () => {
+ const wrapper = mount(
+
+ );
+ expect(wrapper).toMatchSnapshot();
+
+ const daysView = wrapper.find('Days').at(1);
+ const nextBtn = wrapper.find('.ui-calendar-days__next-month');
+ const previousBtn = wrapper.find('.ui-calendar-days__previous-month');
+
+ previousBtn.simulate('click');
+ expect(daysView.props().renderDate.getMonth()).toEqual(11);
+
+ nextBtn.simulate('click');
+ expect(daysView.props().renderDate.getMonth()).toEqual(0);
+
+ nextBtn.simulate('click');
+ expect(daysView.props().renderDate.getMonth()).toEqual(1);
+ });
+ });
+});
diff --git a/packages/travix-ui-kit/tests/unit/calendar/calendar.range.spec.js b/packages/travix-ui-kit/tests/unit/calendar/calendar.range.spec.js
new file mode 100644
index 0000000..6a18054
--- /dev/null
+++ b/packages/travix-ui-kit/tests/unit/calendar/calendar.range.spec.js
@@ -0,0 +1,151 @@
+import { mount } from 'enzyme';
+import React from 'react';
+import Calendar from '../../../components/calendar/calendar';
+import { normalizeDate } from '../../../components/_helpers';
+
+describe('Calendar (range mode)', () => {
+ describe('#render()', () => {
+ it('should render the calendar with selectionType as range, initialized in the current date', () => {
+ const todayDate = normalizeDate(new Date());
+
+ const wrapper = mount(
+
+ );
+
+ expect(wrapper).toMatchSnapshot();
+ expect(wrapper.props()).toEqual({
+ selectionType: 'range',
+ });
+ expect(wrapper.state()).toEqual({
+ maxLimit: null,
+ minLimit: null,
+ renderDate: todayDate,
+ selectedDates: [null, null],
+ });
+ });
+
+ it('should set the minLimit to the same date as the first initialDates when provided', () => {
+ const initialDate = '2017-03-25';
+ const initialDateObj = normalizeDate(new Date(initialDate));
+
+ const wrapper = mount(
+
+ );
+
+ expect(wrapper).toMatchSnapshot();
+ expect(wrapper.props()).toEqual({
+ initialDates: [initialDate],
+ selectionType: 'range',
+ });
+ expect(wrapper.state()).toEqual({
+ maxLimit: null,
+ minLimit: initialDateObj,
+ renderDate: initialDateObj,
+ selectedDates: [initialDateObj, null],
+ });
+ });
+
+ it('should set the minLimit and dates properly and reset them at 3rd click', () => {
+ const todayDate = normalizeDate(new Date());
+
+ const wrapper = mount(
+
+ );
+
+ expect(wrapper).toMatchSnapshot();
+ expect(wrapper.props()).toEqual({
+ selectionType: 'range',
+ });
+ expect(wrapper.state()).toEqual({
+ maxLimit: null,
+ minLimit: null,
+ renderDate: todayDate,
+ selectedDates: [null, null],
+ });
+
+ const expectedStartDate = normalizeDate(new Date('2017-03-25'));
+
+ const startRangeOption = wrapper.find('[data-date="2017-03-25"]');
+ startRangeOption.simulate('click');
+ expect(startRangeOption.props().className.includes('ui-calendar-days-option_selected-start')).toEqual(true);
+ expect(wrapper.state().minLimit).toEqual(expectedStartDate);
+ expect(wrapper.state().selectedDates[0]).toEqual(expectedStartDate);
+ expect(wrapper.find('[data-date="2017-03-24"]').props().disabled).toEqual(true);
+
+ const expectedEndDate = new Date('2017-03-29');
+ expectedEndDate.setHours(0);
+ expectedEndDate.setMinutes(0);
+ expectedEndDate.setSeconds(0);
+ expectedEndDate.setMilliseconds(0);
+
+ // Selects the end date on the 2nd click
+ const endRangeOption = wrapper.find('[data-date="2017-03-29"]');
+ endRangeOption.simulate('click');
+
+ const betweenRangeOption = wrapper.find('[data-date="2017-03-28"]');
+
+ expect(endRangeOption.props().className.includes('ui-calendar-days-option_selected-end')).toEqual(true);
+ expect(wrapper.state().selectedDates[1]).toEqual(expectedEndDate);
+ expect(betweenRangeOption.props().className.includes('ui-calendar-days-option_selected-between')).toEqual(true);
+
+ // On 3rd click resets the calendar, removing the selection
+ betweenRangeOption.simulate('click');
+ expect(wrapper.state().minLimit).toEqual(null);
+ expect(wrapper.state().selectedDates).toEqual([null, null]);
+ });
+
+ it('should render the selection as normal (not range) when start and end date are the same', () => {
+ const expectedEndDate = normalizeDate(new Date('2017-03-25'));
+ const expectedStartDate = normalizeDate(new Date('2017-03-25'));
+ const wrapper = mount(
+
+ );
+
+ const startRangeOption = wrapper.find('[data-date="2017-03-25"]');
+ startRangeOption.simulate('click');
+ expect(startRangeOption.props().className.includes('ui-calendar-days-option_selected-start')).toEqual(true);
+ expect(wrapper.state().minLimit).toEqual(expectedStartDate);
+ expect(wrapper.state().selectedDates[0]).toEqual(expectedStartDate);
+
+ // Selects the end date on the 2nd click
+ const endRangeOption = wrapper.find('[data-date="2017-03-25"]');
+ endRangeOption.simulate('click');
+
+ expect(endRangeOption.props().className.includes('ui-calendar-days-option_selected')).toEqual(true);
+ expect(wrapper.state().minLimit).toEqual(null);
+ expect(wrapper.state().selectedDates[1]).toEqual(expectedEndDate);
+ });
+
+ it('should put the minLimit back to the one passed on props, when resetting it', () => {
+ const expectedEndDate = normalizeDate(new Date('2017-03-25'));
+ const expectedStartDate = normalizeDate(new Date('2017-03-25'));
+ const minDate = '2017-03-05';
+ const expectedInitialMinLimit = normalizeDate(new Date(minDate));
+
+ const wrapper = mount(
+
+ );
+
+ expect(wrapper.state().minLimit).toEqual(expectedInitialMinLimit);
+
+ const startRangeOption = wrapper.find('[data-date="2017-03-25"]');
+ startRangeOption.simulate('click');
+ expect(startRangeOption.props().className.includes('ui-calendar-days-option_selected-start')).toEqual(true);
+ expect(wrapper.state().minLimit).toEqual(expectedStartDate);
+ expect(wrapper.state().selectedDates[0]).toEqual(expectedStartDate);
+
+ // Selects the end date on the 2nd click
+ const endRangeOption = wrapper.find('[data-date="2017-03-25"]');
+ endRangeOption.simulate('click');
+
+ expect(endRangeOption.props().className.includes('ui-calendar-days-option_selected')).toEqual(true);
+ expect(wrapper.state().minLimit).toEqual(expectedInitialMinLimit);
+ expect(wrapper.state().selectedDates[1]).toEqual(expectedEndDate);
+
+ // And resets the dates with the 3rd click
+ wrapper.find('[data-date="2017-03-26"]').simulate('click');
+ expect(wrapper.state().minLimit).toEqual(expectedInitialMinLimit);
+ expect(wrapper.state().selectedDates).toEqual([null, null]);
+ });
+ });
+});
diff --git a/packages/travix-ui-kit/tests/unit/calendar/calendarWrapper.mock.js b/packages/travix-ui-kit/tests/unit/calendar/calendarWrapper.mock.js
new file mode 100644
index 0000000..fb295b5
--- /dev/null
+++ b/packages/travix-ui-kit/tests/unit/calendar/calendarWrapper.mock.js
@@ -0,0 +1,44 @@
+const React = require('react');
+const Calendar = require('../../../components/calendar/calendar');
+
+export default class CalendarWrapper extends React.Component {
+ constructor(props) {
+ super();
+
+ this.changeInitialDates = this.changeInitialDates.bind(this);
+ this.changeMinDate = this.changeMinDate.bind(this);
+ this.state = {
+ initialDates: props.initialDates ? [].concat(props.initialDates) : undefined,
+ minDate: props.minDate,
+ };
+ }
+
+ changeInitialDates() {
+ this.setState((prevState) => {
+ prevState.initialDates = ['2017-04-03', null];
+ return prevState;
+ });
+ }
+
+ changeMinDate() {
+ this.setState((prevState) => {
+ prevState.minDate = '2017-04-01';
+ return prevState;
+ });
+ }
+
+ render() {
+ return (
+
+
+ Change date
+ Change date
+
+ );
+ }
+}
+
+CalendarWrapper.propTypes = {
+ initialDates: React.PropTypes.arrayOf(String),
+ minDate: React.PropTypes.string,
+};
diff --git a/packages/travix-ui-kit/themes/_default.yaml b/packages/travix-ui-kit/themes/_default.yaml
index b907de9..50b6590 100644
--- a/packages/travix-ui-kit/themes/_default.yaml
+++ b/packages/travix-ui-kit/themes/_default.yaml
@@ -23,8 +23,11 @@ generic:
accent-darker: &accent-darker '#0B4848'
accent-dark: &accent-dark '#177D7D'
accent: &accent '#2A9595'
+ accent-border: &accent-border '#E6BB00'
+ accent-bottom: &accent-bottom '#FFD05E'
accent-light: &accent-light '#46BCBC'
accent-lighter: &accent-lighter '#6EE1E1'
+ accent-top: &accent-top '#FFD97C'
primary-darker: &primary-darker '#162052'
primary-dark: &primary-dark '#283A8E'
@@ -86,6 +89,89 @@ button:
color: "transparent"
hover:
params: "0 0"
+calendar:
+ height: 300px
+ nav:
+ background: *primary-lighter
+ border-radius: '5px 5px 0 0'
+ button:
+ background-color-end: *accent-bottom
+ background-color-start: *accent-top
+ border-radius: 50%
+ box-shadow-color: *accent-border
+ color: *primary-lighter
+ font-weight: '700'
+ height: 27px
+ margin: '0 5px'
+ width: 27px
+ height: 41px
+ label:
+ color: *blank
+ font-weight: '500'
+ header:
+ background-color: *primary-lighter
+ height: 32px
+ weekday:
+ color: *secondary
+ font-size: 14px
+ font-weight: bold
+ options:
+ border-color: *secondary
+ option:
+ background-color: *blank
+ border-bottom: *secondary
+ border-right: *secondary
+ color: *primary-darker
+ disabled:
+ background-color: *secondary
+ color: *secondary-darkest
+ font-size: 14px
+ hover:
+ background-color: *active
+ color: *blank
+ next-month:
+ background-color: *secondary-light
+ previous-month:
+ background-color: *secondary-light
+ range:
+ after:
+ border-color: transparent
+ between:
+ background-color: *secondary-dark
+ end:
+ after:
+ border-color: *active
+ background-color: *secondary-dark
+ start:
+ after:
+ border-color: *active
+ background-color: *secondary-dark
+ width: 300px
+list:
+ bullets:
+ color: *active
+modal:
+ z-index: '100'
+ color:
+ dark: *primary-dark
+ secondary: *secondary
+ overlay:
+ background:
+ color: *primary-dark
+ opacity: '0.65'
+ container:
+ background:
+ color: *blank
+ box-shadow:
+ color: 'rgba(0,0,0,.3)'
+ content:
+ padding-left-right: '24px'
+ content-devider:
+ color: *secondary
+ close-button:
+ color: *text-dark
+ hover:
+ color: *active
price:
height:
underline: 6px
@@ -131,28 +217,3 @@ spinner:
lighter: *accent
dark: *primary
darker: *primary-lighter
-list:
- bullets:
- color: *active
-modal:
- z-index: '100'
- color:
- dark: *primary-dark
- secondary: *secondary
- overlay:
- background:
- color: *primary-dark
- opacity: '0.65'
- container:
- background:
- color: *blank
- box-shadow:
- color: 'rgba(0,0,0,.3)'
- content:
- padding-left-right: '24px'
- content-devider:
- color: *secondary
- close-button:
- color: *text-dark
- hover:
- color: *active