├── .editorconfig
├── .eslintrc
├── .gitignore
├── .npmignore
├── .prettierrc
├── .travis.yml
├── README.md
├── babel.config.js
├── build
├── MonthPicker.js
├── WeekDay.js
├── YearPicker.js
├── index.js
└── locale.js
├── demo
├── css
│ └── demo.css
├── index.html
├── js
│ └── demo.js
├── jsx
│ └── demo.js
├── less
│ ├── calendar.less
│ └── demo.less
└── webpack.config.js
├── package-lock.json
├── package.json
├── src
├── MonthPicker.js
├── WeekDay.js
├── YearPicker.js
├── index.js
└── locale.js
└── test
├── datepicker.js
└── utils
├── jsdom.js
└── render.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@detools/eslint-config"
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | coverage
3 | npm-debug.log
4 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | demo
2 | test
3 | src
4 | coverage
5 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 100,
3 | "tabWidth": 2,
4 | "useTabs": false,
5 | "semi": false,
6 | "singleQuote": true,
7 | "trailingComma": "es5",
8 | "bracketSpacing": true,
9 | "jsxBracketSameLine": true,
10 | "arrowParens": "avoid"
11 | }
12 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: node_js
3 | node_js:
4 | - 10.15.3
5 | branches:
6 | only:
7 | - master
8 | script: npm run travis
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-date-range-picker
2 |
3 | DateRange Picker as React Component
4 |
5 | [](https://www.npmjs.com/package/react-date-range-picker)
6 | [](https://david-dm.org/isnifer/react-date-range-picker)
7 | [](https://travis-ci.org/isnifer/react-date-range-picker)
8 | [](https://coveralls.io/github/isnifer/react-date-range-picker?branch=master)
9 |
10 | ### Installation
11 |
12 | `npm i react-date-range-picker`
13 |
14 | ### Example
15 |
16 | ```js
17 | import React from 'react'
18 | import { render } from 'react-dom'
19 | import Datepicker from 'react-date-range-picker'
20 |
21 | class Demo extends React.Component {
22 | state = {
23 | date: new Date().toLocaleString(),
24 | }
25 |
26 | onClick = date => {
27 | this.setState({ date: date.toLocaleString() })
28 | }
29 |
30 | render() {
31 | return (
32 |
33 |
34 |
35 |
36 | )
37 | }
38 | }
39 |
40 | render( , document.getElementById('app'))
41 | ```
42 |
43 | 
44 |
45 | ## Demo is here
46 |
47 | https://isnifer.github.io/react-calendar/
48 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | const getRealPaths = plugin =>
2 | Array.isArray(plugin) ? [require.resolve(plugin[0]), ...plugin.slice(1)] : require.resolve(plugin)
3 |
4 | module.exports = {
5 | presets: [[require.resolve('@babel/preset-env'), { modules: false }]],
6 | plugins: [
7 | ['@babel/plugin-proposal-class-properties', { loose: false }],
8 | ['@babel/plugin-proposal-decorators', { legacy: true }],
9 | '@babel/plugin-proposal-export-default-from',
10 | '@babel/plugin-proposal-export-namespace-from',
11 | '@babel/plugin-proposal-object-rest-spread',
12 | '@babel/plugin-syntax-dynamic-import',
13 | '@babel/plugin-syntax-jsx',
14 | '@babel/plugin-transform-runtime',
15 | 'babel-plugin-dynamic-import-node',
16 | 'babel-plugin-lodash',
17 | ].map(getRealPaths),
18 | }
19 |
--------------------------------------------------------------------------------
/build/MonthPicker.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 |
7 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
8 |
9 | var _react = require('react');
10 |
11 | var _react2 = _interopRequireDefault(_react);
12 |
13 | var _classnames = require('classnames');
14 |
15 | var _classnames2 = _interopRequireDefault(_classnames);
16 |
17 | var _momentRange = require('moment-range');
18 |
19 | var _momentRange2 = _interopRequireDefault(_momentRange);
20 |
21 | var _locale = require('./locale');
22 |
23 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
24 |
25 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
26 |
27 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
28 |
29 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
30 |
31 | var TODAY_MONTH = new Date().getMonth();
32 |
33 | var MonthPicker = function (_Component) {
34 | _inherits(MonthPicker, _Component);
35 |
36 | function MonthPicker(props) {
37 | _classCallCheck(this, MonthPicker);
38 |
39 | var _this = _possibleConstructorReturn(this, (MonthPicker.__proto__ || Object.getPrototypeOf(MonthPicker)).call(this, props));
40 |
41 | _initialiseProps.call(_this);
42 |
43 | _this.state = {
44 | month: parseInt(props.currentMonth, 10) || TODAY_MONTH
45 | };
46 | return _this;
47 | }
48 |
49 | _createClass(MonthPicker, [{
50 | key: 'componentWillReceiveProps',
51 | value: function componentWillReceiveProps(_ref) {
52 | var month = _ref.month;
53 |
54 | if (month !== this.props.month) {
55 | this.setState({ month: month });
56 | }
57 | }
58 | }, {
59 | key: 'render',
60 | value: function render() {
61 | var _this2 = this;
62 |
63 | return _react2.default.createElement(
64 | 'div',
65 | { className: 'calendar__months' },
66 | _locale.MONTH_NAMES[this.props.locale].map(function (item, i) {
67 | var range = new _momentRange2.default(new Date(_this2.props.year, i, 1), new Date(_this2.props.year, i, 31));
68 | var disabled = !range.overlaps(_this2.props.range);
69 | return _react2.default.createElement(
70 | 'div',
71 | {
72 | className: (0, _classnames2.default)('calendar__month-item', { 'calendar__month-item_current': i === _this2.state.month }, { 'calendar__month-item_disabled': disabled }),
73 | onClick: _this2.onClick,
74 | id: 'month_' + i + '_' + disabled,
75 | key: i },
76 | item
77 | );
78 | })
79 | );
80 | }
81 | }]);
82 |
83 | return MonthPicker;
84 | }(_react.Component);
85 |
86 | var _initialiseProps = function _initialiseProps() {
87 | var _this3 = this;
88 |
89 | this.onClick = function (_ref2) {
90 | var target = _ref2.target;
91 |
92 | var props = target.id.split('_');
93 | if (props[2] === 'true') {
94 | return false;
95 | }
96 |
97 | var id = props[1];
98 | _this3.props.onClick(parseInt(id, 10));
99 | };
100 | };
101 |
102 | MonthPicker.propTypes = {
103 | locale: _react.PropTypes.string.isRequired,
104 | onClick: _react.PropTypes.func.isRequired,
105 | range: _react.PropTypes.instanceOf(_momentRange2.default).isRequired,
106 | year: _react.PropTypes.oneOfType([_react.PropTypes.string, _react.PropTypes.number]),
107 | currentMonth: _react.PropTypes.oneOfType([_react.PropTypes.string, _react.PropTypes.number])
108 | };
109 |
110 | exports.default = MonthPicker;
--------------------------------------------------------------------------------
/build/WeekDay.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 |
7 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
8 |
9 | var _react = require('react');
10 |
11 | var _react2 = _interopRequireDefault(_react);
12 |
13 | var _classnames = require('classnames');
14 |
15 | var _classnames2 = _interopRequireDefault(_classnames);
16 |
17 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
18 |
19 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
20 |
21 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
22 |
23 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
24 |
25 | var WeekDay = function (_React$Component) {
26 | _inherits(WeekDay, _React$Component);
27 |
28 | function WeekDay(props) {
29 | _classCallCheck(this, WeekDay);
30 |
31 | var _this = _possibleConstructorReturn(this, (WeekDay.__proto__ || Object.getPrototypeOf(WeekDay)).call(this, props));
32 |
33 | _this.inRange = _this.inRange.bind(_this);
34 | _this.onClick = _this.onClick.bind(_this);
35 | return _this;
36 | }
37 |
38 | _createClass(WeekDay, [{
39 | key: 'inRange',
40 | value: function inRange() {
41 | return this.props.range.contains(this.props.date);
42 | }
43 | }, {
44 | key: 'onClick',
45 | value: function onClick(e) {
46 | return this.inRange() ? this.props.onClick(this.props.date) : e.preventDefault();
47 | }
48 | }, {
49 | key: 'render',
50 | value: function render() {
51 | var inRange = this.inRange();
52 | var className = (0, _classnames2.default)('calendar__day', { calendar__day_available: inRange }, { calendar__day_disabled: !inRange }, { calendar__day_current: this.props.current });
53 | return _react2.default.createElement(
54 | 'td',
55 | { className: 'calendar__cell' },
56 | _react2.default.createElement(
57 | 'span',
58 | { className: className, onClick: this.onClick },
59 | this.props.date.getDate()
60 | )
61 | );
62 | }
63 | }]);
64 |
65 | return WeekDay;
66 | }(_react2.default.Component);
67 |
68 | WeekDay.propTypes = {
69 | date: _react2.default.PropTypes.instanceOf(Date).isRequired,
70 | range: _react2.default.PropTypes.object.isRequired,
71 | onClick: _react2.default.PropTypes.func.isRequired
72 | };
73 |
74 | exports.default = WeekDay;
--------------------------------------------------------------------------------
/build/YearPicker.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 |
7 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
8 |
9 | var _react = require('react');
10 |
11 | var _react2 = _interopRequireDefault(_react);
12 |
13 | var _classnames = require('classnames');
14 |
15 | var _classnames2 = _interopRequireDefault(_classnames);
16 |
17 | var _momentRange = require('moment-range');
18 |
19 | var _momentRange2 = _interopRequireDefault(_momentRange);
20 |
21 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
22 |
23 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
24 |
25 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
26 |
27 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
28 |
29 | var range = function range(currentYear) {
30 | var year = parseInt(currentYear, 10);
31 | var tmpArray = [];
32 |
33 | for (var i = currentYear - 5; i <= currentYear + 6; i++) {
34 | tmpArray = tmpArray.concat(i);
35 | }
36 |
37 | return tmpArray;
38 | };
39 |
40 | var TODAY_YEAR = new Date().getFullYear();
41 |
42 | var YearPicker = function (_Component) {
43 | _inherits(YearPicker, _Component);
44 |
45 | function YearPicker(props) {
46 | _classCallCheck(this, YearPicker);
47 |
48 | var _this = _possibleConstructorReturn(this, (YearPicker.__proto__ || Object.getPrototypeOf(YearPicker)).call(this, props));
49 |
50 | _initialiseProps.call(_this);
51 |
52 | _this.state = {
53 | year: parseInt(props.currentYear, 10) || TODAY_YEAR
54 | };
55 | return _this;
56 | }
57 |
58 | _createClass(YearPicker, [{
59 | key: 'componentWillReceiveProps',
60 | value: function componentWillReceiveProps(_ref) {
61 | var currentYear = _ref.currentYear;
62 |
63 | if (parseInt(currentYear, 10) !== this.props.year) {
64 | this.setState({ year: parseInt(currentYear, 10) });
65 | }
66 | }
67 | }, {
68 | key: 'render',
69 | value: function render() {
70 | var _this2 = this;
71 |
72 | return _react2.default.createElement(
73 | 'div',
74 | { className: 'calendar__years' },
75 | range(this.state.year).map(function (item, i) {
76 | var range = new _momentRange2.default(new Date(item, 0, 1), new Date(item, 11, 31));
77 | var disabled = !range.overlaps(_this2.props.range);
78 | return _react2.default.createElement(
79 | 'div',
80 | {
81 | className: (0, _classnames2.default)('calendar__year', { calendar__year_current: item === _this2.state.year }, { calendar__year_disabled: disabled }),
82 | onClick: _this2.onClick,
83 | id: 'year_' + i + '_' + disabled,
84 | key: item },
85 | item
86 | );
87 | })
88 | );
89 | }
90 | }]);
91 |
92 | return YearPicker;
93 | }(_react.Component);
94 |
95 | var _initialiseProps = function _initialiseProps() {
96 | var _this3 = this;
97 |
98 | this.onClick = function (_ref2) {
99 | var _ref2$target = _ref2.target,
100 | textContent = _ref2$target.textContent,
101 | id = _ref2$target.id;
102 |
103 | var props = id.split('_');
104 | if (props[2] === 'true') {
105 | return false;
106 | }
107 |
108 | _this3.props.onClick(parseInt(textContent, 10));
109 | };
110 | };
111 |
112 | YearPicker.propTypes = {
113 | onClick: _react.PropTypes.func.isRequired,
114 | range: _react2.default.PropTypes.instanceOf(_momentRange2.default).isRequired,
115 | currentYear: _react.PropTypes.oneOfType([_react.PropTypes.string, _react.PropTypes.number])
116 | };
117 |
118 | exports.default = YearPicker;
--------------------------------------------------------------------------------
/build/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 |
7 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
8 |
9 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
10 |
11 | var _react = require('react');
12 |
13 | var _react2 = _interopRequireDefault(_react);
14 |
15 | var _momentRange = require('moment-range');
16 |
17 | var _momentRange2 = _interopRequireDefault(_momentRange);
18 |
19 | var _calendar = require('calendar');
20 |
21 | var _classnames = require('classnames');
22 |
23 | var _classnames2 = _interopRequireDefault(_classnames);
24 |
25 | var _WeekDay = require('./WeekDay');
26 |
27 | var _WeekDay2 = _interopRequireDefault(_WeekDay);
28 |
29 | var _YearPicker = require('./YearPicker');
30 |
31 | var _YearPicker2 = _interopRequireDefault(_YearPicker);
32 |
33 | var _MonthPicker = require('./MonthPicker');
34 |
35 | var _MonthPicker2 = _interopRequireDefault(_MonthPicker);
36 |
37 | var _locale = require('./locale');
38 |
39 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
40 |
41 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
42 |
43 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
44 |
45 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
46 |
47 | /**
48 | * Reset time of Date to 00:00:00
49 | * @param {Date} date
50 | * @return {Date}
51 | */
52 | function resetDate(date) {
53 | return new Date(date.toString().replace(/\d{2}:\d{2}:\d{2}/, '00:00:00'));
54 | }
55 |
56 | var id = 1;
57 |
58 | var Datepicker = function (_Component) {
59 | _inherits(Datepicker, _Component);
60 |
61 | function Datepicker(props) {
62 | _classCallCheck(this, Datepicker);
63 |
64 | var _this = _possibleConstructorReturn(this, (Datepicker.__proto__ || Object.getPrototypeOf(Datepicker)).call(this, props));
65 |
66 | _this.setOuterDate = function (date) {
67 | _this.props.onClick(date, _this.props.name || 'date_' + _this.id);
68 | };
69 |
70 | _this.onYearNameClick = function () {
71 | _this.setState({
72 | dateVisible: false,
73 | monthVisible: false,
74 | yearVisible: true
75 | });
76 | };
77 |
78 | _this.onYearClick = function (year) {
79 | _this.restoreDateVisibility({ year: year });
80 | };
81 |
82 | _this.onMonthNameClick = function () {
83 | _this.setState({
84 | dateVisible: false,
85 | monthVisible: true,
86 | yearVisible: false
87 | });
88 | };
89 |
90 | _this.onMonthClick = function (month) {
91 | _this.restoreDateVisibility({ month: month });
92 | };
93 |
94 | _this.restoreDateVisibility = function (model) {
95 | var month = model.month || _this.state.month;
96 | var year = model.year || _this.state.year;
97 | var day = _this.state.date.getDate();
98 | var date = new Date(year, month, day, 0, 0, 0);
99 |
100 | _this.setState(_extends({
101 | date: date,
102 | dateVisible: true,
103 | monthVisible: false,
104 | yearVisible: false
105 | }, model), _this.setOuterDate.bind(_this, date));
106 | };
107 |
108 | _this.state = {
109 | date: null,
110 | month: null,
111 | range: null,
112 | year: null,
113 |
114 | // UI: Visibility levels
115 | dateVisible: true,
116 | monthVisible: false,
117 | yearVisible: false
118 | };
119 |
120 | _this.id = id++;
121 |
122 | _this.calendar = new _calendar.Calendar(props.locale === 'RU' ? 1 : 0);
123 |
124 | _this.onClick = _this.onClick.bind(_this);
125 | _this.changeMonth = _this.changeMonth.bind(_this);
126 | return _this;
127 | }
128 |
129 | // Setting up default state of calendar
130 |
131 |
132 | _createClass(Datepicker, [{
133 | key: 'componentWillMount',
134 | value: function componentWillMount() {
135 | var TODAY = new Date();
136 |
137 | // If we have defined 'initialDate' prop
138 | // we will use it also for define month and year.
139 | // If not it will be TODAY
140 | var initialDate = this.props.initialDate || TODAY;
141 | var initialRange = this.props.range || new _momentRange2.default(resetDate(this.props.minimumDate), resetDate(this.props.maximumDate));
142 | var state = {
143 | date: initialDate,
144 | month: initialDate.getMonth(),
145 | year: initialDate.getFullYear()
146 | };
147 |
148 | // Before set range to state we should check -
149 | // is range contains our initialDate.
150 | // If not, we will fire Error
151 | if (initialRange.contains(initialDate)) {
152 | state.range = initialRange;
153 | } else {
154 | throw new Error('Initial Range doesn\'t contains Initial Date');
155 | }
156 |
157 | this.setState(state);
158 | }
159 | }, {
160 | key: 'componentWillReceiveProps',
161 | value: function componentWillReceiveProps(nextProps) {
162 | var range = void 0;
163 |
164 | if (nextProps.range !== this.props.range) {
165 | range = nextProps.range;
166 | } else if (nextProps.minimumDate !== this.props.minimumDate || nextProps.maximumDate !== this.props.maximumDate) {
167 | range = new _momentRange2.default(resetDate(nextProps.minimumDate), resetDate(nextProps.maximumDate));
168 | }
169 |
170 | if (range) {
171 | this.setState({ range: range });
172 | }
173 | }
174 |
175 | /**
176 | * Change month handler
177 | * @param {Number} direction - "1" or "-1"
178 | */
179 |
180 | }, {
181 | key: 'changeMonth',
182 | value: function changeMonth(direction) {
183 | if (this.state.dateVisible) {
184 | var nextMonth = this.state.month + direction;
185 | var isMonthAvailable = nextMonth >= 0 && nextMonth <= 11;
186 |
187 | var model = void 0;
188 | if (!isMonthAvailable) {
189 | model = {
190 | month: direction === 1 ? 0 : 11,
191 | year: this.state.year + direction
192 | };
193 | } else {
194 | model = { month: this.state.month + direction };
195 | }
196 |
197 | this.setState(model);
198 | }
199 |
200 | if (this.state.yearVisible) {
201 | this.setState({ year: this.state.year + (direction + 1 ? 12 : -12) });
202 | }
203 | }
204 |
205 | /**
206 | * Calendar state setter
207 | * @param {Date} date - selected date
208 | */
209 |
210 | }, {
211 | key: 'onClick',
212 | value: function onClick(date) {
213 | if (date) {
214 | this.setState({ date: date }, this.setOuterDate.bind(this, date));
215 | }
216 | }
217 | }, {
218 | key: 'renderMonth',
219 | value: function renderMonth() {
220 | var _this2 = this;
221 |
222 | // Array of weeks, which contains arrays of days
223 | // [[Date, Date, ...], [Date, Date, ...], ...]
224 | var month = this.calendar.monthDates(this.state.year, this.state.month);
225 | return month.map(function (week, i) {
226 | return _react2.default.createElement(
227 | 'tr',
228 | { className: 'calendar__week', key: i },
229 | _this2.renderWeek(week)
230 | );
231 | });
232 | }
233 |
234 | /**
235 | * Render week
236 | * @param {Array} weekDays - array of instanceof Date
237 | * @return {Array} - array of Components
238 | */
239 |
240 | }, {
241 | key: 'renderWeek',
242 | value: function renderWeek(weekDays) {
243 | var _this3 = this;
244 |
245 | return weekDays.map(function (day, i) {
246 | var isCurrent = _this3.state.date.toDateString() === day.toDateString();
247 | return _react2.default.createElement(_WeekDay2.default, {
248 | key: i,
249 | date: day,
250 | range: _this3.state.range,
251 | current: isCurrent,
252 | onClick: _this3.onClick });
253 | });
254 | }
255 | }, {
256 | key: 'renderWeekdayNames',
257 | value: function renderWeekdayNames() {
258 | return _locale.WEEK_NAMES[this.props.locale].map(function (weekname, i) {
259 | return _react2.default.createElement(
260 | 'th',
261 | { className: 'calendar__weekday-name', key: i },
262 | weekname
263 | );
264 | });
265 | }
266 |
267 | // TODO: Fix direction
268 |
269 | }, {
270 | key: 'renderNavigation',
271 | value: function renderNavigation() {
272 | return _react2.default.createElement(
273 | 'span',
274 | { className: 'calendar__arrows' },
275 | _react2.default.createElement(
276 | 'span',
277 | {
278 | className: 'calendar__arrow calendar__arrow_left',
279 | onClick: this.changeMonth.bind(this, -1) },
280 | '\u2190'
281 | ),
282 | _react2.default.createElement(
283 | 'span',
284 | { className: 'calendar__month-name' },
285 | _react2.default.createElement(
286 | 'span',
287 | { className: 'calendar__head-month', onClick: this.onMonthNameClick },
288 | _locale.MONTH_NAMES[this.props.locale][this.state.month]
289 | ),
290 | ', ',
291 | _react2.default.createElement(
292 | 'span',
293 | { className: 'calendar__head-year', onClick: this.onYearNameClick },
294 | this.state.year
295 | )
296 | ),
297 | _react2.default.createElement(
298 | 'span',
299 | {
300 | className: 'calendar__arrow calendar__arrow_right',
301 | onClick: this.changeMonth.bind(this, 1) },
302 | '\u2192'
303 | )
304 | );
305 | }
306 | }, {
307 | key: 'render',
308 | value: function render() {
309 | return _react2.default.createElement(
310 | 'div',
311 | { className: 'calendar' },
312 | _react2.default.createElement(
313 | 'div',
314 | { className: 'calendar__head' },
315 | !this.props.disableNavigation && !this.props.outsideNavigation ? this.renderNavigation() : ''
316 | ),
317 | this.state.dateVisible && _react2.default.createElement(
318 | 'table',
319 | { className: 'calendar__month' },
320 | _react2.default.createElement(
321 | 'thead',
322 | null,
323 | _react2.default.createElement(
324 | 'tr',
325 | { className: 'calendar__week-names' },
326 | this.renderWeekdayNames()
327 | )
328 | ),
329 | _react2.default.createElement(
330 | 'tbody',
331 | null,
332 | this.renderMonth()
333 | )
334 | ),
335 | this.state.monthVisible && _react2.default.createElement(_MonthPicker2.default, {
336 | currentMonth: this.props.month,
337 | onClick: this.onMonthClick,
338 | locale: this.props.locale,
339 | range: this.state.range,
340 | year: this.state.year
341 | }),
342 | this.state.yearVisible && _react2.default.createElement(_YearPicker2.default, { currentYear: this.state.year, onClick: this.onYearClick, range: this.state.range })
343 | );
344 | }
345 | }]);
346 |
347 | return Datepicker;
348 | }(_react.Component);
349 |
350 | Datepicker.propTypes = {
351 | onClick: _react.PropTypes.func,
352 | range: _react.PropTypes.instanceOf(_momentRange2.default),
353 | disableNavigation: _react.PropTypes.bool,
354 | outsideNavigation: _react.PropTypes.bool,
355 | initialDate: _react.PropTypes.instanceOf(Date),
356 | locale: _react.PropTypes.string,
357 | minimumDate: _react.PropTypes.instanceOf(Date),
358 | maximumDate: _react.PropTypes.instanceOf(Date),
359 | name: _react.PropTypes.string
360 | };
361 |
362 | Datepicker.defaultProps = {
363 | // Handler which will be execute when click on day
364 | onClick: function onClick() {},
365 |
366 | // Instance of DateRange
367 | range: null,
368 |
369 | // If true, navigation will be hidden
370 | disableNavigation: false,
371 |
372 | // If true, navigation will be in root container
373 | outsideNavigation: false,
374 |
375 | // Available locales: RU, EN, DE, FR, IT, POR, ESP
376 | locale: 'EN',
377 |
378 | // Minimum available date
379 | minimumDate: new Date(1970, 0, 1),
380 |
381 | // Maximum available date
382 | maximumDate: new Date(2100, 0, 1)
383 | };
384 |
385 | exports.default = Datepicker;
--------------------------------------------------------------------------------
/build/locale.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | var MONTH_NAMES = exports.MONTH_NAMES = {
7 | RU: ['Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь', 'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь'],
8 | EN: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
9 | DE: ['Januari', 'Februari', 'March', 'April', 'Kan', 'June', 'Juli', 'Augustus', 'September', 'Oktober', 'November', 'December'],
10 | FR: ['Janvier', 'Février', 'Mars', 'Avril', 'Peut', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'],
11 | ITA: ['Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giu', 'Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre'],
12 | POR: ['Janeiro', 'Fevereiro', 'Março', 'April', 'Maio', 'Junho', 'Julho', 'Agosto', 'Setembro', 'Outubro', 'November', 'Dezembro'],
13 | ESP: ['Enero', 'Febrero', 'Marzo', 'Abril', 'Puede', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre']
14 | };
15 |
16 | var WEEK_NAMES = exports.WEEK_NAMES = {
17 | RU: ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'],
18 | EN: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
19 | DE: ['So.', 'Mo.', 'Di.', 'Mi.', 'Do.', 'Fr.', 'Sa.'],
20 | FR: ['dim.', 'lun.', 'mar.', 'mer.', 'jeu.', 'ven.', 'sam.'],
21 | IT: ['Dom', 'Lun', 'Mar', 'Mer', 'Gio', 'Ven', 'Sab'],
22 | POR: ['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb'],
23 | ESP: ['Dom.', 'Lun.', 'Mar.', 'Mié.', 'Jue.', 'Vie.', 'Sáb.']
24 | };
25 |
26 | var WEEK_NAMES_SHORT = exports.WEEK_NAMES_SHORT = {
27 | RU: ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'],
28 | EN: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'],
29 | DE: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'],
30 | FR: ['Di', 'Lu', 'Ma', 'Me', 'Je', 'Ve', 'Sa'],
31 | IT: ['D', 'L', 'Ma', 'Me', 'G', 'V', 'S'],
32 | POR: ['Dom', '2ª', '3ª', '4ª', '5ª', '6ª', 'Sáb'],
33 | ESP: ['Do', 'Lu', 'Ma', 'Mi', 'Ju', 'Vi', 'Sá']
34 | };
--------------------------------------------------------------------------------
/demo/css/demo.css:
--------------------------------------------------------------------------------
1 | * {
2 | -webkit-box-sizing: border-box;
3 | -moz-box-sizing: border-box;
4 | box-sizing: border-box;
5 | }
6 | body {
7 | margin: 0;
8 | padding: 0;
9 | font: normal 16px "Arial", Tahoma, Verdana;
10 | }
11 | .demo {
12 | padding: 50px 0 0 50px;
13 | }
14 | .demo__part {
15 | margin-bottom: 50px;
16 | }
17 | .demo__part:before,
18 | .demo__part:after {
19 | display: table;
20 | content: '';
21 | width: 100%;
22 | }
23 | .demo__inline {
24 | float: left;
25 | margin-right: 20px;
26 | }
27 | .demo__description {
28 | font-size: 18px;
29 | line-height: 1.33333333;
30 | color: #333;
31 | }
32 | .demo__ru,
33 | .demo__en {
34 | position: relative;
35 | }
36 | .demo__ru:before,
37 | .demo__en:before {
38 | content: 'RU';
39 | display: block;
40 | position: absolute;
41 | color: red;
42 | top: 3px;
43 | left: -18px;
44 | font-size: 10px;
45 | }
46 | .demo__ru {
47 | margin-bottom: 10px;
48 | }
49 | .demo__en {
50 | margin-bottom: 20px;
51 | }
52 | .demo__en:before {
53 | content: 'EN';
54 | }
55 | .demo__title {
56 | margin-bottom: 5px;
57 | }
58 | .demo__props {
59 | margin-top: 20px;
60 | }
61 | .demo__code {
62 | margin: 0;
63 | display: inline-block;
64 | padding: 16px;
65 | overflow: auto;
66 | font-size: 85%;
67 | line-height: 1.45;
68 | background-color: #f7f7f7;
69 | border-radius: 3px;
70 | }
71 | .demo__input {
72 | margin-bottom: 5px;
73 | width: 300px;
74 | height: 40px;
75 | font-size: 25px;
76 | padding: 0 10px;
77 | }
78 | .demo__fork {
79 | position: absolute;
80 | display: block;
81 | overflow: hidden;
82 | top: 0;
83 | right: 0;
84 | border: 0;
85 | }
86 | .calendar {
87 | width: 300px;
88 | position: relative;
89 | overflow: hidden;
90 | -webkit-user-select: none;
91 | -moz-user-select: none;
92 | user-select: none;
93 | }
94 | .calendar__head {
95 | background: transparent;
96 | text-align: center;
97 | height: 40px;
98 | background: #FFCCBC;
99 | }
100 | .calendar__head-month {
101 | cursor: pointer;
102 | display: inline-block;
103 | border-bottom: 1px dotted rgba(0, 0, 0, 0.3);
104 | }
105 | .calendar__head-year {
106 | cursor: pointer;
107 | display: inline-block;
108 | border-bottom: 1px dotted rgba(0, 0, 0, 0.3);
109 | }
110 | .calendar__arrows {
111 | display: flex;
112 | flex-flow: row nowrap;
113 | justify-content: space-between;
114 | align-items: center;
115 | height: 40px;
116 | }
117 | .calendar__arrow {
118 | width: 40px;
119 | height: 40px;
120 | color: #FF5722;
121 | cursor: pointer;
122 | display: flex;
123 | align-items: center;
124 | justify-content: center;
125 | }
126 | .calendar__arrow:hover {
127 | background: #FF5722;
128 | color: #FFCCBC;
129 | }
130 | .calendar__month {
131 | margin: 0;
132 | border-collapse: collapse;
133 | }
134 | .calendar__weekday-name {
135 | text-align: center;
136 | width: 43px;
137 | height: 40px;
138 | padding: 0;
139 | background: #607D8B;
140 | color: #CFD8DC;
141 | }
142 | .calendar__cell {
143 | width: 43px;
144 | height: 40px;
145 | padding: 0;
146 | background: #CFD8DC;
147 | }
148 | .calendar__day {
149 | width: 43px;
150 | height: 40px;
151 | display: block;
152 | line-height: 40px;
153 | text-align: center;
154 | background: #CFD8DC;
155 | color: #727272;
156 | -webkit-user-select: none;
157 | -moz-user-select: none;
158 | user-select: none;
159 | }
160 | .calendar__day_other-month {
161 | opacity: 0.5;
162 | }
163 | .calendar__day_available {
164 | background: #4CAF50;
165 | color: #fff;
166 | cursor: pointer;
167 | }
168 | .calendar__day_available:hover {
169 | background: #AFB42B;
170 | }
171 | .calendar__day_disabled {
172 | background: #455A64;
173 | color: #B6B6B6;
174 | cursor: not-allowed;
175 | }
176 | .calendar__day_current {
177 | background: #673AB7;
178 | }
179 | .calendar__day_current_available.calendar__day_current:hover {
180 | background: #512DA8;
181 | }
182 | .calendar__years,
183 | .calendar__months {
184 | display: flex;
185 | flex-flow: row wrap;
186 | }
187 | .calendar__year,
188 | .calendar__month-item {
189 | width: 25%;
190 | height: 80px;
191 | display: flex;
192 | justify-content: center;
193 | align-items: center;
194 | background: #4CAF50;
195 | color: #fff;
196 | cursor: pointer;
197 | -webkit-user-select: none;
198 | -moz-user-select: none;
199 | user-select: none;
200 | }
201 | .calendar__year:hover,
202 | .calendar__month-item:hover {
203 | background: #AFB42B;
204 | }
205 | .calendar__year_current,
206 | .calendar__month-item_current,
207 | .calendar__year_current:hover,
208 | .calendar__month-item_current:hover {
209 | background: #673AB7;
210 | }
211 | .calendar__year_disabled,
212 | .calendar__month-item_disabled,
213 | .calendar__year_disabled:hover,
214 | .calendar__month-item_disabled:hover {
215 | background: #455A64;
216 | color: #B6B6B6;
217 | cursor: not-allowed;
218 | pointer-events: none;
219 | }
220 | .calendar__month-item {
221 | font-size: 14px;
222 | }
223 |
--------------------------------------------------------------------------------
/demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | React DateRange Picker
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/demo/jsx/demo.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render } from 'react-dom'
3 | import DateRange from 'moment-range'
4 | import Datepicker from '../../src'
5 |
6 | function transformDate(date) {
7 | const day = date.getDate()
8 |
9 | let month = date.getMonth() + 1
10 | month = month < 10 ? `0${month}` : month
11 |
12 | return [day, month, date.getFullYear()].join('.')
13 | }
14 |
15 | const leftRangeDate = new Date(2012, 7, 20)
16 | const rightRangeDate = new Date(2016, 7, 27)
17 |
18 | class Demo extends React.Component {
19 | constructor(props) {
20 | super(props)
21 |
22 | this.state = {
23 | date_1: transformDate(new Date()),
24 | date_2: transformDate(new Date(2015, 7, 8)),
25 | date_3: transformDate(new Date(2015, 7, 12)),
26 | date_4: leftRangeDate,
27 | date_5: rightRangeDate,
28 | }
29 |
30 | this.onClick = this.onClick.bind(this)
31 | this.onClickTwo = this.onClickTwo.bind(this)
32 | this.onClickThree = this.onClickThree.bind(this)
33 | this.onClickFour = this.onClickFour.bind(this)
34 | this.onClickFive = this.onClickFive.bind(this)
35 | }
36 |
37 | onClick(date, name) {
38 | this.setState({ [name]: transformDate(date) })
39 | }
40 |
41 | onClickTwo(date, name) {
42 | this.setState({ [name]: transformDate(date) })
43 | }
44 |
45 | onClickThree(date, name) {
46 | this.setState({ [name]: transformDate(date) })
47 | }
48 |
49 | onClickFour(date, name) {
50 | this.setState({ [name]: date })
51 | }
52 |
53 | onClickFive(date, name) {
54 | this.setState({ [name]: date })
55 | }
56 |
57 | render() {
58 | return (
59 |
60 |
61 |
62 |
63 | Самый простой вариант календаря. Ему передан только callback,
64 | который в свою очередь будет возвращать дату календаря в качестве
65 | единственного аргумента
66 |
67 |
68 | Very simple calendar example. It got only callback, which will pass
69 | selected date as argument.
70 |
71 |
72 |
79 |
80 |
81 |
Source
82 |
{' '}
83 |
84 |
85 |
86 |
87 |
88 | Чуть более сложный вариант календаря. Компонент получил три новых атрибута.
89 | Атрибут range - instanceof `moment-range`. Период календаря,
90 | задает пределы доступности выбора дат в календаре.
91 | Атрибут initialDate - instanceof `Date`. Дата, которая будет выбрана в
92 | календаре
93 | по умолчанию. Важно! Эта дата, должна быть частью периода календаря.
94 | Атрибут locale - instanceof `String`. Несколько встроенных локалей на выбор:{' '}
95 |
96 | RU, EN, DE, FR, ITA, POR, ESP
97 |
98 |
99 | A little more complex version of the calendar. The component has received three new
100 | attribute.
101 | Attribute range - instanceof `moment-range`. Period of the Calendar,
102 | sets limits accessibility to select dates on the calendar.
103 | Attribute initialDate - instanceof `Date`. Date to be selected on the
104 | calendar default.
105 | Important! This date should be part of the period of the calendar.
106 | Attribute locale - instanceof `String`. Several built-in locales to choose
107 | from:
108 | RU, EN, DE, FR, ITA, POR, ESP.
109 |
110 |
111 |
112 |
119 |
125 |
126 |
Source
127 |
128 | {' '}
133 |
134 |
135 |
136 |
137 |
144 |
150 |
151 |
Source
152 |
153 | {' '}
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 | Этот же вариант позволяет вам связать два календаря между собой.
166 | Довольно популярный кейс, когда у пользователя есть выбор дат С и ПО.
167 | Все, что необходимо - это зафиксировать изначально крайние даты,
168 | в данном примере это константы leftRangeDate и rightRangeDate.
169 | Передать их в качестве левого ограничения для календаря справа и
170 | правого ограничения для календаря справа соотвественно.
171 | Демо ниже более наглядно.
172 |
173 |
174 | This option allows us to link the two calendars together.
175 | Quite a popular case when the user has a choice of dates FROM and TO.
176 | All that is needed is to fix the original Dates,
177 | in this example leftRangeDate and rightRangeDate are constants.
178 | Send them as the left limit for the calendar on the left and
179 | right limit for the calendar to the right respectively.
180 | The demo below more clearly.
181 |
182 |
183 |
184 |
190 |
197 |
198 |
Source
199 |
200 | {' \n' +
205 | ' '}
211 |
212 |
213 |
214 |
215 |
221 |
228 |
229 |
Source
230 |
231 | {' \n' +
236 | ' '}
242 |
243 |
244 |
245 |
246 |
247 |
251 |
252 |
253 | )
254 | }
255 | }
256 |
257 | render( , document.querySelector('.datepicker'))
258 |
--------------------------------------------------------------------------------
/demo/less/calendar.less:
--------------------------------------------------------------------------------
1 | .no-select() {
2 | -webkit-user-select: none;
3 | -moz-user-select: none;
4 | user-select: none;
5 | }
6 |
7 | .calendar {
8 | width: 300px;
9 | position: relative;
10 | overflow: hidden;
11 | .no-select();
12 |
13 | &__head {
14 | background: transparent;
15 | text-align: center;
16 | height: 40px;
17 | background: #FFCCBC;
18 | }
19 |
20 | &__head-month {
21 | cursor: pointer;
22 | display: inline-block;
23 | border-bottom: 1px dotted rgba(0,0,0, .3);
24 | }
25 |
26 | &__head-year {
27 | cursor: pointer;
28 | display: inline-block;
29 | border-bottom: 1px dotted rgba(0,0,0, .3);
30 | }
31 |
32 | &__arrows {
33 | display: flex;
34 | flex-flow: row nowrap;
35 | justify-content: space-between;
36 | align-items: center;
37 | height: 40px;
38 | }
39 |
40 | &__arrow {
41 | width: 40px;
42 | height: 40px;
43 | color: #FF5722;
44 | cursor: pointer;
45 | display: flex;
46 | align-items: center;
47 | justify-content: center;
48 |
49 | &:hover {
50 | background: #FF5722;
51 | color: #FFCCBC;
52 | }
53 | }
54 |
55 | &__month {
56 | margin: 0;
57 | border-collapse: collapse;
58 | }
59 |
60 | &__weekday-name {
61 | text-align: center;
62 | width: 43px;
63 | height: 40px;
64 | padding: 0;
65 | background: #607D8B;
66 | color: #CFD8DC;
67 | }
68 |
69 | &__cell {
70 | width: 43px;
71 | height: 40px;
72 | padding: 0;
73 | background: #CFD8DC;
74 | }
75 |
76 | &__day {
77 | width: 43px;
78 | height: 40px;
79 | display: block;
80 | line-height: 40px;
81 | text-align: center;
82 | background: #CFD8DC;
83 | color: #727272;
84 | .no-select();
85 |
86 | &_other-month {
87 | opacity: 0.5;
88 | }
89 |
90 | &_available {
91 | background: #4CAF50;
92 | color: #fff;
93 | cursor: pointer;
94 |
95 | &:hover {
96 | background: #AFB42B;
97 | }
98 | }
99 |
100 | &_disabled {
101 | background: #455A64;
102 | color: #B6B6B6;
103 | cursor: not-allowed;
104 | }
105 |
106 | &_current {
107 | background: #673AB7;
108 |
109 | &_available&:hover {
110 | background: #512DA8;
111 | }
112 | }
113 | }
114 |
115 | &__years,
116 | &__months {
117 | display: flex;
118 | flex-flow: row wrap;
119 | }
120 |
121 | &__year,
122 | &__month-item {
123 | width: 25%;
124 | height: 80px;
125 | display: flex;
126 | justify-content: center;
127 | align-items: center;
128 | background: #4CAF50;
129 | color: #fff;
130 | cursor: pointer;
131 | .no-select();
132 |
133 | &:hover {
134 | background: #AFB42B;
135 | }
136 |
137 | &_current,
138 | &_current:hover {
139 | background: #673AB7;
140 | }
141 |
142 | &_disabled,
143 | &_disabled:hover {
144 | background: #455A64;
145 | color: #B6B6B6;
146 | cursor: not-allowed;
147 | pointer-events: none;
148 | }
149 | }
150 |
151 | &__month-item {
152 | font-size: 14px;
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/demo/less/demo.less:
--------------------------------------------------------------------------------
1 | .clearfix() {
2 | &:before,
3 | &:after {
4 | display: table;
5 | content: '';
6 | width: 100%;
7 | }
8 | }
9 |
10 | * {
11 | -webkit-box-sizing: border-box;
12 | -moz-box-sizing: border-box;
13 | box-sizing: border-box;
14 | }
15 |
16 | body {
17 | margin: 0;
18 | padding: 0;
19 | font: normal 16px "Arial", Tahoma, Verdana;
20 | }
21 |
22 | .demo {
23 | padding: 50px 0 0 50px;
24 |
25 | &__part {
26 | margin-bottom: 50px;
27 | .clearfix();
28 | }
29 |
30 | &__inline {
31 | float: left;
32 | margin-right: 20px;
33 | }
34 |
35 | &__description {
36 | font-size: 18px;
37 | line-height: 24/18;
38 | color: #333;
39 | }
40 |
41 | &__ru,
42 | &__en {
43 | position: relative;
44 |
45 | &:before {
46 | content: 'RU';
47 | display: block;
48 | position: absolute;
49 | color: red;
50 | top: 3px;
51 | left: -18px;
52 | font-size: 10px;
53 | }
54 | }
55 |
56 | &__ru {
57 | margin-bottom: 10px;
58 | }
59 |
60 | &__en {
61 | margin-bottom: 20px;
62 |
63 | &:before {
64 | content: 'EN';
65 | }
66 | }
67 |
68 | &__title {
69 | margin-bottom: 5px;
70 | }
71 |
72 | &__props {
73 | margin-top: 20px;
74 | }
75 |
76 | &__code {
77 | margin: 0;
78 | display: inline-block;
79 | padding: 16px;
80 | overflow: auto;
81 | font-size: 85%;
82 | line-height: 1.45;
83 | background-color: #f7f7f7;
84 | border-radius: 3px;
85 | }
86 |
87 | &__input {
88 | margin-bottom: 5px;
89 | width: 300px;
90 | height: 40px;
91 | font-size: 25px;
92 | padding: 0 10px;
93 | }
94 |
95 | &__fork {
96 | position: absolute;
97 | display: block;
98 | overflow: hidden;
99 | top: 0;
100 | right: 0;
101 | border: 0;
102 | }
103 | }
104 |
105 | @import 'calendar';
106 |
--------------------------------------------------------------------------------
/demo/webpack.config.js:
--------------------------------------------------------------------------------
1 | import path from 'path'
2 | import CleanWebpackPlugin from 'clean-webpack-plugin'
3 | import MiniCssExtractPlugin from 'mini-css-extract-plugin'
4 |
5 | const DEV_SERVER_PORT = 3000
6 | const IS_PRODUCTION = process.env.NODE_ENV === 'production'
7 | const PATH_TO_SRC = path.resolve(__dirname, '..', 'src')
8 | const PATH_TO_DIST = path.resolve(__dirname, 'js')
9 | const PATH_TO_DEV_DIST = path.resolve(__dirname, 'public')
10 |
11 | const CLEAN_OPTIONS = {
12 | // Instead of this ugly hack — we will get "wwwroot is outside of the project root. Skipping..."
13 | root: path.resolve(PATH_TO_DIST, '..'),
14 | exclude: ['index.html', 'favicon.png'],
15 | verbose: true,
16 | dry: false,
17 | }
18 |
19 | const productionPlugins = [
20 | new CleanWebpackPlugin(PATH_TO_DIST, CLEAN_OPTIONS),
21 | new MiniCssExtractPlugin({
22 | filename: '[name].css',
23 | chunkFilename: '[id].css',
24 | }),
25 | ]
26 |
27 | export default {
28 | mode: 'development',
29 |
30 | entry: path.resolve(PATH_TO_SRC, 'index.js'),
31 |
32 | output: {
33 | path: !IS_PRODUCTION ? PATH_TO_DEV_DIST : PATH_TO_DIST,
34 | filename: 'bundle.js',
35 | chunkFilename: '[name].bundle.js',
36 | publicPath: '/',
37 | },
38 |
39 | resolve: {
40 | extensions: ['.js'],
41 | alias: {
42 | '@': PATH_TO_SRC,
43 | },
44 | },
45 |
46 | target: 'web',
47 |
48 | module: {
49 | rules: [
50 | {
51 | test: /\.js$/,
52 | loader: 'babel-loader',
53 | include: [PATH_TO_SRC],
54 | },
55 | {
56 | test: /\.less$/,
57 | use: [
58 | !IS_PRODUCTION ? 'style-loader' : MiniCssExtractPlugin.loader, // CommonJS => Style nodes
59 | 'css-loader', // CSS => CommonJS
60 | 'less-loader', // Less => CSS
61 | ],
62 | },
63 | {
64 | test: /\.(png|jp(e?)g|gif|svg|eot|ttf|woff|woff2)$/,
65 | use: [
66 | {
67 | loader: 'url-loader',
68 | options: {
69 | limit: 10000,
70 | },
71 | },
72 | ],
73 | },
74 | ],
75 | },
76 |
77 | optimization: {
78 | splitChunks: {
79 | chunks: 'all',
80 | },
81 | },
82 |
83 | plugins: IS_PRODUCTION ? productionPlugins : [],
84 |
85 | devServer: {
86 | port: DEV_SERVER_PORT,
87 | hotOnly: true,
88 | contentBase: [PATH_TO_DEV_DIST], // boolean | string | array, static file location
89 | compress: true, // enable gzip compression
90 | historyApiFallback: true, // true for index.html upon 404, object for multiple paths
91 | https: false, // true for self-signed, object for cert authority
92 | noInfo: false, // only errors & warns on hot reload
93 | },
94 | }
95 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@isnifer/react-calendar",
3 | "version": "0.5.2",
4 | "description": "React DateRange Picker",
5 | "keywords": "react, date, range, picker, component, calendar",
6 | "homepage": "https://isnifer.github.io/react-calendar/",
7 | "license": "MIT",
8 | "author": {
9 | "name": "Anton Kuznetsov",
10 | "email": "isnifer@gmail.com",
11 | "url": "https://github.com/isnifer"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "https://github.com/isnifer/react-calendar.git"
16 | },
17 | "dependencies": {
18 | "calendar": "^0.1.0",
19 | "classnames": "^2.2.5",
20 | "moment-range": "^4.0.2",
21 | "prop-types": "^15.7.2"
22 | },
23 | "devDependencies": {
24 | "@babel/core": "^7.3.4",
25 | "@babel/plugin-proposal-class-properties": "^7.3.4",
26 | "@babel/plugin-proposal-decorators": "^7.3.0",
27 | "@babel/plugin-proposal-export-default-from": "^7.2.0",
28 | "@babel/plugin-proposal-export-namespace-from": "^7.2.0",
29 | "@babel/plugin-proposal-object-rest-spread": "^7.3.4",
30 | "@babel/plugin-syntax-dynamic-import": "^7.2.0",
31 | "@babel/plugin-syntax-jsx": "^7.2.0",
32 | "@babel/plugin-transform-runtime": "^7.3.4",
33 | "@babel/preset-env": "^7.3.4",
34 | "@detools/eslint-config": "^1.2.0",
35 | "babel-loader": "^8.0.5",
36 | "babel-plugin-dynamic-import-node": "^2.2.0",
37 | "babel-plugin-lodash": "^3.3.4",
38 | "clean-webpack-plugin": "^2.0.1",
39 | "coveralls": "^3.0.3",
40 | "css-loader": "^2.1.1",
41 | "enzyme": "^3.9.0",
42 | "eslint": "^5.15.3",
43 | "jsdom": "^14.0.0",
44 | "less": "^3.9.0",
45 | "less-loader": "^4.1.0",
46 | "mini-css-extract-plugin": "^0.5.0",
47 | "moment": "^2.13.0",
48 | "nyc": "^13.3.0",
49 | "prettier": "^1.16.4",
50 | "react": "^16.8.4",
51 | "react-addons-test-utils": "^15.0.1",
52 | "react-dom": "^16.8.4",
53 | "rimraf": "^2.4.3",
54 | "style-loader": "^0.23.1",
55 | "tap-spec": "^5.0.0",
56 | "tape": "^4.10.1",
57 | "url-loader": "^1.1.2",
58 | "webpack": "^4.29.6",
59 | "webpack-cli": "^3.3.0",
60 | "webpack-dev-server": "^3.2.1"
61 | },
62 | "peerDependencies": {
63 | "react": "16.x",
64 | "react-dom": "16.x",
65 | "moment": "^2.13.0"
66 | },
67 | "main": "build/index.js",
68 | "scripts": {
69 | "build": "rimraf build && babel src --out-dir build",
70 | "watch": "babel src --out-dir build -w",
71 | "demo": "webpack",
72 | "demo-server": "webpack-dev-server",
73 | "test": "babel-node test/datepicker.js | tap-spec",
74 | "travis": "npm run build && npm run coveralls",
75 | "prepare": "npm run build",
76 | "coverage": "babel-node ./node_modules/.bin/nyc cover --dir coverage test/datepicker.js | tap-spec",
77 | "coveralls": "npm run coverage && coveralls < coverage/lcov.info && rimraf ./coverage"
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/MonthPicker.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import PropTypes from 'prop-types'
3 | import cn from 'classnames'
4 | import DateRange from 'moment-range'
5 | import { MONTH_NAMES } from './locale'
6 |
7 | const TODAY_MONTH = new Date().getMonth()
8 | class MonthPicker extends Component {
9 | state = {
10 | month: parseInt(props.currentMonth, 10) || TODAY_MONTH,
11 | }
12 |
13 | componentWillReceiveProps({ month }) {
14 | if (month !== this.props.month) {
15 | this.setState({ month })
16 | }
17 | }
18 |
19 | onClick = ({ target }) => {
20 | const props = target.id.split('_')
21 | if (props[2] === 'true') {
22 | return false
23 | }
24 |
25 | const id = props[1]
26 | this.props.onClick(parseInt(id, 10))
27 | }
28 |
29 | render() {
30 | return (
31 |
32 | {MONTH_NAMES[this.props.locale].map((item, i) => {
33 | const range = new DateRange(
34 | new Date(this.props.year, i, 1),
35 | new Date(this.props.year, i, 31)
36 | )
37 | const disabled = !range.overlaps(this.props.range)
38 | return (
39 |
48 | {item}
49 |
50 | )
51 | })}
52 |
53 | )
54 | }
55 | }
56 |
57 | MonthPicker.propTypes = {
58 | locale: PropTypes.string.isRequired,
59 | onClick: PropTypes.func.isRequired,
60 | range: PropTypes.instanceOf(DateRange).isRequired,
61 | year: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
62 | month: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
63 | currentMonth: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
64 | }
65 |
66 | export default MonthPicker
67 |
--------------------------------------------------------------------------------
/src/WeekDay.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import PropTypes from 'prop-types'
3 | import cn from 'classnames'
4 |
5 | class WeekDay extends Component {
6 | inRange = () => this.props.range.contains(this.props.date)
7 |
8 | onClick = event => (this.inRange() ? this.props.onClick(this.props.date) : event.preventDefault())
9 |
10 | render() {
11 | const inRange = this.inRange()
12 | const className = cn(
13 | 'calendar__day',
14 | { calendar__day_available: inRange },
15 | { calendar__day_disabled: !inRange },
16 | { calendar__day_current: this.props.current }
17 | )
18 | return (
19 |
20 |
21 | {this.props.date.getDate()}
22 |
23 |
24 | )
25 | }
26 | }
27 |
28 | WeekDay.propTypes = {
29 | date: PropTypes.instanceOf(Date).isRequired,
30 | range: PropTypes.object.isRequired,
31 | onClick: PropTypes.func.isRequired,
32 | }
33 |
34 | export default WeekDay
35 |
--------------------------------------------------------------------------------
/src/YearPicker.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import PropTypes from 'prop-types'
3 | import cn from 'classnames'
4 | import DateRange from 'moment-range'
5 |
6 | const range = currentYear => {
7 | const year = parseInt(currentYear, 10)
8 | let tmpArray = []
9 |
10 | for (let i = year - 5; i <= year + 6; i += 1) {
11 | tmpArray = tmpArray.concat(i)
12 | }
13 |
14 | return tmpArray
15 | }
16 |
17 | const TODAY_YEAR = new Date().getFullYear()
18 | class YearPicker extends Component {
19 | constructor(props) {
20 | super(props)
21 |
22 | this.state = {
23 | year: parseInt(props.currentYear, 10) || TODAY_YEAR,
24 | }
25 | }
26 |
27 | componentWillReceiveProps({ currentYear }) {
28 | if (parseInt(currentYear, 10) !== this.props.year) {
29 | this.setState({ year: parseInt(currentYear, 10) })
30 | }
31 | }
32 |
33 | onClick = ({ target: { textContent, id } }) => {
34 | const props = id.split('_')
35 | if (props[2] === 'true') {
36 | return false
37 | }
38 |
39 | this.props.onClick(parseInt(textContent, 10))
40 | }
41 |
42 | render() {
43 | return (
44 |
45 | {range(this.state.year).map((item, i) => {
46 | const range = new DateRange(new Date(item, 0, 1), new Date(item, 11, 31))
47 | const disabled = !range.overlaps(this.props.range)
48 | return (
49 |
58 | {item}
59 |
60 | )
61 | })}
62 |
63 | )
64 | }
65 | }
66 |
67 | YearPicker.propTypes = {
68 | onClick: PropTypes.func.isRequired,
69 | range: React.PropTypes.instanceOf(DateRange).isRequired,
70 | currentYear: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
71 | }
72 |
73 | export default YearPicker
74 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import PropTypes from 'prop-types'
3 | import DateRange from 'moment-range'
4 | import { Calendar } from 'calendar'
5 | import WeekDay from './WeekDay'
6 | import YearPicker from './YearPicker'
7 | import MonthPicker from './MonthPicker'
8 | import { MONTH_NAMES, WEEK_NAMES } from './locale'
9 |
10 | /**
11 | * Reset time of Date to 00:00:00
12 | * @param {Date} date
13 | * @return {Date}
14 | */
15 | function resetDate(date) {
16 | return new Date(date.toString().replace(/\d{2}:\d{2}:\d{2}/, '00:00:00'))
17 | }
18 |
19 | let id = 1
20 | class Datepicker extends Component {
21 | constructor(props) {
22 | super(props)
23 |
24 | this.state = {
25 | date: null,
26 | month: null,
27 | range: null,
28 | year: null,
29 |
30 | // UI: Visibility levels
31 | dateVisible: true,
32 | monthVisible: false,
33 | yearVisible: false,
34 | }
35 |
36 | id += 1
37 | this.id = id
38 |
39 | this.calendar = new Calendar(props.locale === 'RU' ? 1 : 0)
40 |
41 | this.onClick = this.onClick.bind(this)
42 | this.changeMonth = this.changeMonth.bind(this)
43 | }
44 |
45 | // Setting up default state of calendar
46 | componentWillMount() {
47 | const TODAY = new Date()
48 |
49 | // If we have defined 'initialDate' prop
50 | // we will use it also for define month and year.
51 | // If not it will be TODAY
52 | const initialDate = this.props.initialDate || TODAY
53 | const initialRange =
54 | this.props.range ||
55 | new DateRange(resetDate(this.props.minimumDate), resetDate(this.props.maximumDate))
56 | const state = {
57 | date: initialDate,
58 | month: initialDate.getMonth(),
59 | year: initialDate.getFullYear(),
60 | }
61 |
62 | // Before set range to state we should check -
63 | // is range contains our initialDate.
64 | // If not, we will fire Error
65 | if (initialRange.contains(initialDate)) {
66 | state.range = initialRange
67 | } else {
68 | throw new Error("Initial Range doesn't contains Initial Date")
69 | }
70 |
71 | this.setState(state)
72 | }
73 |
74 | componentWillReceiveProps(nextProps) {
75 | let range
76 |
77 | if (nextProps.range !== this.props.range) {
78 | range = nextProps.range
79 | } else if (
80 | nextProps.minimumDate !== this.props.minimumDate ||
81 | nextProps.maximumDate !== this.props.maximumDate
82 | ) {
83 | range = new DateRange(resetDate(nextProps.minimumDate), resetDate(nextProps.maximumDate))
84 | }
85 |
86 | if (range) {
87 | this.setState({ range })
88 | }
89 | }
90 |
91 | /**
92 | * Change month handler
93 | * @param {Number} direction - "1" or "-1"
94 | */
95 | changeMonth(direction) {
96 | if (this.state.dateVisible) {
97 | const nextMonth = this.state.month + direction
98 | const isMonthAvailable = nextMonth >= 0 && nextMonth <= 11
99 |
100 | let model
101 | if (!isMonthAvailable) {
102 | model = {
103 | month: direction === 1 ? 0 : 11,
104 | year: this.state.year + direction,
105 | }
106 | } else {
107 | model = { month: this.state.month + direction }
108 | }
109 |
110 | this.setState(model)
111 | }
112 |
113 | if (this.state.yearVisible) {
114 | this.setState({ year: this.state.year + (direction + 1 ? 12 : -12) })
115 | }
116 | }
117 |
118 | /**
119 | * Calendar state setter
120 | * @param {Date} date - selected date
121 | */
122 | onClick(date) {
123 | if (date) {
124 | this.setState({ date }, this.setOuterDate.bind(this, date))
125 | }
126 | }
127 |
128 | setOuterDate = date => {
129 | this.props.onClick(date, this.props.name || `date_${this.id}`)
130 | }
131 |
132 | onYearNameClick = () => {
133 | this.setState({
134 | dateVisible: false,
135 | monthVisible: false,
136 | yearVisible: true,
137 | })
138 | }
139 |
140 | onYearClick = year => {
141 | this.restoreDateVisibility({ year })
142 | }
143 |
144 | onMonthNameClick = () => {
145 | this.setState({
146 | dateVisible: false,
147 | monthVisible: true,
148 | yearVisible: false,
149 | })
150 | }
151 |
152 | onMonthClick = month => {
153 | this.restoreDateVisibility({ month })
154 | }
155 |
156 | restoreDateVisibility = model => {
157 | const month = model.month || this.state.month
158 | const year = model.year || this.state.year
159 | const day = this.state.date.getDate()
160 | const date = new Date(year, month, day, 0, 0, 0)
161 |
162 | this.setState(
163 | {
164 | date,
165 | dateVisible: true,
166 | monthVisible: false,
167 | yearVisible: false,
168 | ...model,
169 | },
170 | this.setOuterDate.bind(this, date)
171 | )
172 | }
173 |
174 | renderMonth() {
175 | // Array of weeks, which contains arrays of days
176 | // [[Date, Date, ...], [Date, Date, ...], ...]
177 | var month = this.calendar.monthDates(this.state.year, this.state.month)
178 | return month.map((week, i) => {
179 | return (
180 |
181 | {this.renderWeek(week)}
182 |
183 | )
184 | })
185 | }
186 |
187 | /**
188 | * Render week
189 | * @param {Array} weekDays - array of instanceof Date
190 | * @return {Array} - array of Components
191 | */
192 | renderWeek(weekDays) {
193 | return weekDays.map((day, i) => {
194 | var isCurrent = this.state.date.toDateString() === day.toDateString()
195 | return (
196 |
203 | )
204 | })
205 | }
206 |
207 | renderWeekdayNames() {
208 | return WEEK_NAMES[this.props.locale].map((weekname, i) => {
209 | return (
210 |
211 | {weekname}
212 |
213 | )
214 | })
215 | }
216 |
217 | // TODO: Fix direction
218 | renderNavigation() {
219 | return (
220 |
221 |
224 | ←
225 |
226 |
227 |
228 | {MONTH_NAMES[this.props.locale][this.state.month]}
229 |
230 | {', '}
231 |
232 | {this.state.year}
233 |
234 |
235 |
238 | →
239 |
240 |
241 | )
242 | }
243 |
244 | render() {
245 | return (
246 |
247 |
248 | {!this.props.disableNavigation && !this.props.outsideNavigation
249 | ? this.renderNavigation()
250 | : ''}
251 |
252 |
253 | {this.state.dateVisible && (
254 |
255 |
256 | {this.renderWeekdayNames()}
257 |
258 | {this.renderMonth()}
259 |
260 | )}
261 |
262 | {this.state.monthVisible && (
263 |
270 | )}
271 |
272 | {this.state.yearVisible && (
273 |
278 | )}
279 |
280 | )
281 | }
282 | }
283 |
284 | Datepicker.propTypes = {
285 | name: PropTypes.string.isRequired,
286 | initialDate: PropTypes.instanceOf(Date).isRequired,
287 | onClick: PropTypes.func,
288 | range: PropTypes.instanceOf(DateRange),
289 | disableNavigation: PropTypes.bool,
290 | outsideNavigation: PropTypes.bool,
291 | locale: PropTypes.string,
292 | minimumDate: PropTypes.instanceOf(Date),
293 | maximumDate: PropTypes.instanceOf(Date),
294 | }
295 |
296 | Datepicker.defaultProps = {
297 | // Handler which will be execute when click on day
298 | onClick: () => {},
299 |
300 | // Instance of DateRange
301 | range: null,
302 |
303 | // If true, navigation will be hidden
304 | disableNavigation: false,
305 |
306 | // If true, navigation will be in root container
307 | outsideNavigation: false,
308 |
309 | // Available locales: RU, EN, DE, FR, IT, POR, ESP
310 | locale: 'EN',
311 |
312 | // Minimum available date
313 | minimumDate: new Date(1970, 0, 1),
314 |
315 | // Maximum available date
316 | maximumDate: new Date(2100, 0, 1),
317 | }
318 |
319 | export default Datepicker
320 |
--------------------------------------------------------------------------------
/src/locale.js:
--------------------------------------------------------------------------------
1 | export const MONTH_NAMES = {
2 | RU: [
3 | 'Январь',
4 | 'Февраль',
5 | 'Март',
6 | 'Апрель',
7 | 'Май',
8 | 'Июнь',
9 | 'Июль',
10 | 'Август',
11 | 'Сентябрь',
12 | 'Октябрь',
13 | 'Ноябрь',
14 | 'Декабрь',
15 | ],
16 | EN: [
17 | 'January',
18 | 'February',
19 | 'March',
20 | 'April',
21 | 'May',
22 | 'June',
23 | 'July',
24 | 'August',
25 | 'September',
26 | 'October',
27 | 'November',
28 | 'December',
29 | ],
30 | DE: [
31 | 'Januari',
32 | 'Februari',
33 | 'March',
34 | 'April',
35 | 'Kan',
36 | 'June',
37 | 'Juli',
38 | 'Augustus',
39 | 'September',
40 | 'Oktober',
41 | 'November',
42 | 'December',
43 | ],
44 | FR: [
45 | 'Janvier',
46 | 'Février',
47 | 'Mars',
48 | 'Avril',
49 | 'Peut',
50 | 'Juin',
51 | 'Juillet',
52 | 'Août',
53 | 'Septembre',
54 | 'Octobre',
55 | 'Novembre',
56 | 'Décembre',
57 | ],
58 | ITA: [
59 | 'Gennaio',
60 | 'Febbraio',
61 | 'Marzo',
62 | 'Aprile',
63 | 'Maggio',
64 | 'Giu',
65 | 'Luglio',
66 | 'Agosto',
67 | 'Settembre',
68 | 'Ottobre',
69 | 'Novembre',
70 | 'Dicembre',
71 | ],
72 | POR: [
73 | 'Janeiro',
74 | 'Fevereiro',
75 | 'Março',
76 | 'April',
77 | 'Maio',
78 | 'Junho',
79 | 'Julho',
80 | 'Agosto',
81 | 'Setembro',
82 | 'Outubro',
83 | 'November',
84 | 'Dezembro',
85 | ],
86 | ESP: [
87 | 'Enero',
88 | 'Febrero',
89 | 'Marzo',
90 | 'Abril',
91 | 'Puede',
92 | 'Junio',
93 | 'Julio',
94 | 'Agosto',
95 | 'Septiembre',
96 | 'Octubre',
97 | 'Noviembre',
98 | 'Diciembre',
99 | ],
100 | }
101 |
102 | export const WEEK_NAMES = {
103 | RU: ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'],
104 | EN: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
105 | DE: ['So.', 'Mo.', 'Di.', 'Mi.', 'Do.', 'Fr.', 'Sa.'],
106 | FR: ['dim.', 'lun.', 'mar.', 'mer.', 'jeu.', 'ven.', 'sam.'],
107 | IT: ['Dom', 'Lun', 'Mar', 'Mer', 'Gio', 'Ven', 'Sab'],
108 | POR: ['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb'],
109 | ESP: ['Dom.', 'Lun.', 'Mar.', 'Mié.', 'Jue.', 'Vie.', 'Sáb.'],
110 | }
111 |
112 | export const WEEK_NAMES_SHORT = {
113 | RU: ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'],
114 | EN: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'],
115 | DE: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'],
116 | FR: ['Di', 'Lu', 'Ma', 'Me', 'Je', 'Ve', 'Sa'],
117 | IT: ['D', 'L', 'Ma', 'Me', 'G', 'V', 'S'],
118 | POR: ['Dom', '2ª', '3ª', '4ª', '5ª', '6ª', 'Sáb'],
119 | ESP: ['Do', 'Lu', 'Ma', 'Mi', 'Ju', 'Vi', 'Sá'],
120 | }
121 |
--------------------------------------------------------------------------------
/test/datepicker.js:
--------------------------------------------------------------------------------
1 | import './utils/jsdom'
2 | import test from 'tape'
3 | import React from 'react'
4 | import TestUtils from 'react-addons-test-utils'
5 | import DateRange from 'moment-range'
6 | import render from './utils/render'
7 | import Datepicker from '../build'
8 |
9 | const c = render(Datepicker)
10 |
11 | test('Calendar inited', assert => {
12 | const initialDate = new Date()
13 | const DOMComponent = TestUtils.renderIntoDocument( )
14 |
15 | assert.equal(c.type, 'div', 'Should be div')
16 | assert.equal(c.props.className, 'calendar', 'With className "calendar"')
17 |
18 | assert.equal(DOMComponent.state.date, initialDate, 'Date in state should equal initialDate')
19 |
20 | assert.end()
21 | })
22 |
23 | test('Should have 3 elements: head, month, placeholder for nav', assert => {
24 | const { children } = c.props
25 |
26 | assert.equal(children.length, 4, 'Count of children is 4')
27 |
28 | assert.equal(children[0].type, 'div', 'Head should be a div')
29 | assert.equal(
30 | children[0].props.className,
31 | 'calendar__head',
32 | 'And should have className calendar__head'
33 | )
34 |
35 | assert.equal(children[1].type, 'table', 'Month should be a table')
36 | assert.equal(
37 | children[1].props.className,
38 | 'calendar__month',
39 | 'And should have className calendar__month'
40 | )
41 |
42 | assert.false(children[2], 'Month Placeholder is false')
43 | assert.false(children[3], 'Year Placeholder is false')
44 |
45 | assert.end()
46 | })
47 |
48 | test('Test componentWillReceiveProps', assert => {
49 | const leftRangeDate = new Date(2015, 7, 4)
50 | const rightRangeDate = new Date(2015, 7, 27)
51 |
52 | const Parent = React.createClass({
53 | getInitialState() {
54 | return {
55 | date1: leftRangeDate,
56 | date2: rightRangeDate,
57 | }
58 | },
59 |
60 | render() {
61 | return (
62 |
63 | {
69 | this.setState({ date1: date })
70 | }}
71 | />
72 | {
78 | this.setState({ date2: date })
79 | }}
80 | />
81 |
82 |
87 |
92 |
93 | )
94 | },
95 | })
96 |
97 | const parent = TestUtils.renderIntoDocument( )
98 |
99 | const datePicker1 = parent.refs.datepicker1
100 | const datePicker2 = parent.refs.datepicker2
101 |
102 | const datePicker3 = parent.refs.datepicker3
103 | const datePicker4 = parent.refs.datepicker4
104 |
105 | let currentDay1 = TestUtils.findRenderedDOMComponentWithClass(
106 | datePicker1,
107 | 'calendar__day_current'
108 | )
109 | // TEMP HACK
110 | currentDay1 = currentDay1[Object.keys(currentDay1)[0]]._currentElement
111 |
112 | let currentDay2 = TestUtils.findRenderedDOMComponentWithClass(
113 | datePicker2,
114 | 'calendar__day_current'
115 | )
116 | // TEMP HACK
117 | currentDay2 = currentDay2[Object.keys(currentDay2)[0]]._currentElement
118 |
119 | assert.equal(currentDay1.props.children, 4, 'Initial date is 4 August in first calendar')
120 | assert.equal(currentDay2.props.children, 27, 'Initial date is 5 August in second calendar')
121 |
122 | // 4 August
123 | assert.equal(
124 | datePicker2.state.range.start.toString(),
125 | new DateRange(leftRangeDate, rightRangeDate).start.toString(),
126 | 'Should set start of range 4 August'
127 | )
128 |
129 | let nextAvialableDate = TestUtils.scryRenderedDOMComponentsWithClass(
130 | datePicker1,
131 | 'calendar__day_available'
132 | )[1]
133 | TestUtils.Simulate.click(nextAvialableDate)
134 | // TEMP HACK
135 | nextAvialableDate = nextAvialableDate[Object.keys(nextAvialableDate)[0]]._currentElement
136 |
137 | assert.equal(
138 | nextAvialableDate.props.children,
139 | parent.state.date1.getDate(),
140 | 'Should set next available date - 5 August'
141 | )
142 |
143 | // 5 August
144 | assert.equal(
145 | datePicker2.state.range.start.toString(),
146 | new DateRange(parent.state.date1, rightRangeDate).start.toString(),
147 | 'Should change start of range to 5 August'
148 | )
149 |
150 | assert.equal(
151 | datePicker4.state.range.toString(),
152 | new DateRange(parent.state.date1, rightRangeDate).toString(),
153 | 'New range for second calendar should start from August 5'
154 | )
155 |
156 | assert.end()
157 | })
158 |
159 | test('Date is part of Range', assert => {
160 | assert.doesNotThrow(
161 | () =>
162 | TestUtils.renderIntoDocument(
163 |
167 | ),
168 | null,
169 | "Shouldn't throws an exception"
170 | )
171 |
172 | assert.end()
173 | })
174 |
175 | test('Date is not part of Range', assert => {
176 | assert.throws(
177 | () =>
178 | TestUtils.renderIntoDocument(
179 |
183 | ),
184 | null,
185 | 'Should throws an exception'
186 | )
187 |
188 | assert.end()
189 | })
190 |
191 | test('Change Month Action', assert => {
192 | const currentMonth = 8
193 | const currentYear = 2015
194 | let c = TestUtils.renderIntoDocument(
195 |
196 | )
197 |
198 | let currentDay = TestUtils.scryRenderedDOMComponentsWithClass(c, 'calendar__day_current')[0]
199 | // TEMP HACK
200 | currentDay = currentDay[Object.keys(currentDay)[0]]._currentElement
201 |
202 | const prevMonthButtonInstance = TestUtils.scryRenderedDOMComponentsWithClass(
203 | c,
204 | 'calendar__arrow_left'
205 | )[0]
206 | // TEMP HACK
207 | const prevMonthButton =
208 | prevMonthButtonInstance[Object.keys(prevMonthButtonInstance)[0]]._currentElement
209 |
210 | const nextMonthButtonInstance = TestUtils.scryRenderedDOMComponentsWithClass(
211 | c,
212 | 'calendar__arrow_right'
213 | )[0]
214 | // TEMP HACK
215 | const nextMonthButton =
216 | nextMonthButtonInstance[Object.keys(nextMonthButtonInstance)[0]]._currentElement
217 |
218 | assert.equal(currentDay.props.children, 1, 'Should be first of september')
219 | assert.equal(prevMonthButton.props.children, '←', 'Should be left change month button')
220 | assert.equal(nextMonthButton.props.children, '→', 'Should be right change month button')
221 |
222 | assert.equal(c.state.month, currentMonth, 'Should be September')
223 |
224 | TestUtils.Simulate.click(prevMonthButtonInstance)
225 | assert.equal(c.state.month, currentMonth - 1, 'Should be August')
226 |
227 | TestUtils.Simulate.click(nextMonthButtonInstance)
228 | TestUtils.Simulate.click(nextMonthButtonInstance)
229 | assert.equal(c.state.month, currentMonth + 1, 'Should be October')
230 |
231 | TestUtils.Simulate.click(nextMonthButtonInstance)
232 | TestUtils.Simulate.click(nextMonthButtonInstance)
233 | TestUtils.Simulate.click(nextMonthButtonInstance)
234 | assert.equal(c.state.month, 0, 'Should be January')
235 | assert.equal(c.state.year, currentYear + 1, 'Should be 2016')
236 |
237 | assert.end()
238 | })
239 |
--------------------------------------------------------------------------------
/test/utils/jsdom.js:
--------------------------------------------------------------------------------
1 | import jsdom from 'jsdom';
2 |
3 | global.document = jsdom.jsdom('');
4 | global.window = document.defaultView;
5 | global.navigator = global.window.navigator;
6 |
--------------------------------------------------------------------------------
/test/utils/render.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TestUtils from 'react-addons-test-utils';
3 |
4 | export default function render (component, props, ...children) {
5 | const shallowRenderer = TestUtils.createRenderer();
6 |
7 | shallowRenderer.render(
8 | React.createElement(
9 | component,
10 | props,
11 | ...children
12 | )
13 | );
14 |
15 | return shallowRenderer.getRenderOutput();
16 | }
17 |
--------------------------------------------------------------------------------