diff --git a/cypress/integration/messages/datePicker.spec.ts b/cypress/integration/messages/datePicker.spec.ts index d1f97232..41d51bba 100644 --- a/cypress/integration/messages/datePicker.spec.ts +++ b/cypress/integration/messages/datePicker.spec.ts @@ -1,6 +1,7 @@ /// -import * as moment from "moment" +import * as moment from "moment"; +import { formatDate, transformNamedDate } from "../../../src/plugins/date-picker/utils"; describe("Date Picker", () => { beforeEach(() => { @@ -44,10 +45,70 @@ describe("Date Picker", () => { cy .contains("foobar012b3").click(); - // Our default locale for english is "en-US" - const formattedDate = moment().format("MM/DD/YYYY"); + // Our default locale for english is "en-US" + const formattedDate = moment().format("MM/DD/YYYY"); cy.get(".regular-message.user").contains(formattedDate); }) }) + it("should format all dates with Intl the way Moment did", () => { + + const date = new Date(); + + const newFormatedDateAu = formatDate(date, false, 'en-AU'); + const newFormatedDateGb = formatDate(date, false, 'en-GB'); + const newFormatedDateCa = formatDate(date, false, 'en-CA'); + const newFormatedDateUs = formatDate(date, false, 'en'); + + const oldFormatedDateAu = moment(date).locale('en-AU').format('L'); + const oldFormatedDateGb = moment(date).locale('en-GB').format('L'); + const oldFormatedDateCa = moment(date).locale('en-CA').format('L'); + const oldFormatedDateUs = moment(date).locale('en').format('L'); + + const newFormatedDateTimeAu = formatDate(date, true, 'en-AU'); + const newFormatedDateTimeGb = formatDate(date, true, 'en-GB'); + const newFormatedDateTimeCa = formatDate(date, true, 'en-CA'); + const newFormatedDateTimeUs = formatDate(date, true, 'en'); + + const oldFormatedDateTimeAu = moment(date).locale('en-AU').format('L LT'); + const oldFormatedDateTimeGb = moment(date).locale('en-GB').format('L LT'); + const oldFormatedDateTimeCa = moment(date).locale('en-CA').format('L LT'); + const oldFormatedDateTimeUs = moment(date).locale('en').format('L LT'); + + expect(newFormatedDateAu, "Date format shouldn't change for en-AU").to.be.equal(oldFormatedDateAu); + expect(newFormatedDateGb, "Date format shouldn't change for en-GB").to.be.equal(oldFormatedDateGb); + expect(newFormatedDateCa, "Date format shouldn't change for en-CA").to.be.equal(oldFormatedDateCa); + expect(newFormatedDateUs, "Date format shouldn't change for en").to.be.equal(oldFormatedDateUs); + + expect(newFormatedDateTimeAu, "Date and time format shouldn't change for en-AU").to.be.equal(oldFormatedDateTimeAu); + expect(newFormatedDateTimeGb, "Date and time format shouldn't change for en-GB").to.be.equal(oldFormatedDateTimeGb); + expect(newFormatedDateTimeCa, "Date and time format shouldn't change for en-CA").to.be.equal(oldFormatedDateTimeCa); + expect(newFormatedDateTimeUs, "Date and time format shouldn't change for en").to.be.equal(oldFormatedDateTimeUs); + }) + + it("should display all formatted dates with Intl the way Moment did", () => { + + const date = new Date(); + + const transformNamedDateOld = (namedDate: string) => { + switch (namedDate) { + case "today": + return moment().format('YYYY-MM-DD'); + + case "tomorrow": + return moment().add(1, 'days').format('YYYY-MM-DD'); + + case "yesterday": + return moment().add(-1, 'days').format('YYYY-MM-DD'); + } + + return namedDate; + }; + + expect(transformNamedDate(date.toString()), "Current time format shouldn't have changed").to.be.equal(transformNamedDateOld(date.toString())); + expect(transformNamedDate('today'), "Time format for 'today' shouldn't have changed").to.be.equal(transformNamedDateOld('today')); + expect(transformNamedDate('tomorrow'), "Time format for 'tomorrow' shouldn't have changed").to.be.equal(transformNamedDateOld('tomorrow')); + expect(transformNamedDate('yesterday'), "Time format for 'yesterday' shouldn't have changed").to.be.equal(transformNamedDateOld('yesterday')); + }) + }) \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 8b4e6ab4..fd687c56 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,11 +18,11 @@ "flatpickr": "4.6.3", "immutable": "^4.0.0-rc.12", "lodash": "^4.17.21", - "moment": "^2.24.0", "path-parse": "^1.0.7", "react": "^17.0.2", "react-animate-height": "^2.0.23", "react-dom": "^17.0.2", + "react-flatpickr": "^3.10.7", "react-player": "^2.9.0", "react-redux": "^7.2.6", "react-responsive-carousel": "^3.2.22", @@ -62,6 +62,7 @@ "es-check": "^6.1.1", "http-server": "^0.13.0", "idempotent-babel-polyfill": "^7.4.4", + "moment": "^2.29.1", "npm-run-all": "^4.1.5", "react-svg-loader": "^3.0.3", "redux-devtools-extension": "^2.13.8", @@ -8227,6 +8228,7 @@ "version": "2.29.1", "resolved": "https://cognigy.pkgs.visualstudio.com/_packaging/cognigy-feed/npm/registry/moment/-/moment-2.29.1.tgz", "integrity": "sha1-sr52n6MZQL6e7qZGnAdeNQBvo9M=", + "dev": true, "license": "MIT", "engines": { "node": "*" @@ -9308,6 +9310,19 @@ "integrity": "sha1-ZBqdqBtqYyDycOiXJPtFoLOeQ7s=", "license": "MIT" }, + "node_modules/react-flatpickr": { + "version": "3.10.7", + "resolved": "https://cognigy.pkgs.visualstudio.com/_packaging/cognigy-feed/npm/registry/react-flatpickr/-/react-flatpickr-3.10.7.tgz", + "integrity": "sha1-vKbaKhzPI7uISIHioXIwc/C/s5E=", + "license": "MIT", + "dependencies": { + "flatpickr": "^4.6.2", + "prop-types": "^15.5.10" + }, + "peerDependencies": { + "react": "^17.0.1" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://cognigy.pkgs.visualstudio.com/_packaging/cognigy-feed/npm/registry/react-is/-/react-is-16.13.1.tgz", @@ -18280,7 +18295,8 @@ "moment": { "version": "2.29.1", "resolved": "https://cognigy.pkgs.visualstudio.com/_packaging/cognigy-feed/npm/registry/moment/-/moment-2.29.1.tgz", - "integrity": "sha1-sr52n6MZQL6e7qZGnAdeNQBvo9M=" + "integrity": "sha1-sr52n6MZQL6e7qZGnAdeNQBvo9M=", + "dev": true }, "ms": { "version": "2.1.2", @@ -19006,6 +19022,15 @@ "resolved": "https://cognigy.pkgs.visualstudio.com/_packaging/cognigy-feed/npm/registry/react-fast-compare/-/react-fast-compare-3.2.0.tgz", "integrity": "sha1-ZBqdqBtqYyDycOiXJPtFoLOeQ7s=" }, + "react-flatpickr": { + "version": "3.10.7", + "resolved": "https://cognigy.pkgs.visualstudio.com/_packaging/cognigy-feed/npm/registry/react-flatpickr/-/react-flatpickr-3.10.7.tgz", + "integrity": "sha1-vKbaKhzPI7uISIHioXIwc/C/s5E=", + "requires": { + "flatpickr": "^4.6.2", + "prop-types": "^15.5.10" + } + }, "react-is": { "version": "16.13.1", "resolved": "https://cognigy.pkgs.visualstudio.com/_packaging/cognigy-feed/npm/registry/react-is/-/react-is-16.13.1.tgz", diff --git a/package.json b/package.json index 7232bfeb..8558230b 100644 --- a/package.json +++ b/package.json @@ -43,11 +43,11 @@ "flatpickr": "4.6.3", "immutable": "^4.0.0-rc.12", "lodash": "^4.17.21", - "moment": "^2.24.0", "path-parse": "^1.0.7", "react": "^17.0.2", "react-animate-height": "^2.0.23", "react-dom": "^17.0.2", + "react-flatpickr": "^3.10.7", "react-player": "^2.9.0", "react-redux": "^7.2.6", "react-responsive-carousel": "^3.2.22", @@ -87,6 +87,7 @@ "es-check": "^6.1.1", "http-server": "^0.13.0", "idempotent-babel-polyfill": "^7.4.4", + "moment": "^2.29.1", "npm-run-all": "^4.1.5", "react-svg-loader": "^3.0.3", "redux-devtools-extension": "^2.13.8", @@ -105,4 +106,4 @@ "whatwg-fetch": "^3.6.2", "zlib": "^1.0.5" } -} \ No newline at end of file +} diff --git a/src/plugins/date-picker/components/react-flatpickr/Readme.md b/src/plugins/date-picker/components/react-flatpickr/Readme.md deleted file mode 100644 index 7b5e6b43..00000000 --- a/src/plugins/date-picker/components/react-flatpickr/Readme.md +++ /dev/null @@ -1,60 +0,0 @@ - -[![NPM version][npm-img]][npm-url] -[![License][license-img]][license-url] -[![Dependency status][david-img]][david-url] - -# react-flatpickr - -[Flatpickr](https://github.com/chmln/flatpickr) for React. - -## Usage - -```jsx -import 'flatpickr/dist/themes/material_green.css' - -import Flatpickr from 'react-flatpickr' -import { Component } from 'react' - -class App extends Component { - constructor() { - super(); - - this.state = { - date: new Date() - }; - } - - render() { - const { date } = this.state; - return ( - { this.setState({date}) }} /> - ) - } -} -``` -* `flatpickr options`: you can pass all `flatpickr parameters` to `props.options` -* All flatpickr [hooks][hooks] can be passed as a react prop, or to `props.options` - -```jsx - -``` - -### Themes -Please import themes directly from the `flatpickr` dependency. In most cases, you should just be able to `import 'flatpickr/dist/themes/theme.css'`, but in some cases npm or yarn may install `flatpickr` in `node_modules/react-flatpickr/node_modules/flatpickr`. If that happens, removing your `node_modules` dir and reinstalling should put flatpickr in the root `node_modules` dir, or you can import from `react-flatpickr/node_modules/flatpickr` manually. - -## License -MIT - -[npm-img]: https://img.shields.io/npm/v/react-flatpickr.svg?style=flat-square -[npm-url]: https://npmjs.org/package/react-flatpickr -[travis-img]: https://img.shields.io/travis/coderhaoxin/react-flatpickr.svg?style=flat-square -[travis-url]: https://travis-ci.org/coderhaoxin/react-flatpickr -[codecov-img]: https://img.shields.io/codecov/c/github/coderhaoxin/react-flatpickr.svg?style=flat-square -[codecov-url]: https://codecov.io/github/coderhaoxin/react-flatpickr?branch=master -[license-img]: https://img.shields.io/badge/license-MIT-green.svg?style=flat-square -[license-url]: http://opensource.org/licenses/MIT -[david-img]: https://img.shields.io/david/coderhaoxin/react-flatpickr.svg?style=flat-square -[david-url]: https://david-dm.org/coderhaoxin/react-flatpickr -[hooks]: https://chmln.github.io/flatpickr/events/#hooks diff --git a/src/plugins/date-picker/components/react-flatpickr/build/index.js b/src/plugins/date-picker/components/react-flatpickr/build/index.js deleted file mode 100644 index b3c6bdbe..00000000 --- a/src/plugins/date-picker/components/react-flatpickr/build/index.js +++ /dev/null @@ -1,168 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -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; }; - -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; }; }(); - -var _react = require('react'); - -var _react2 = _interopRequireDefault(_react); - -var _propTypes = require('prop-types'); - -var _propTypes2 = _interopRequireDefault(_propTypes); - -var _flatpickr = require('flatpickr'); - -var _flatpickr2 = _interopRequireDefault(_flatpickr); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -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; } - -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; } - -var hooks = ['onChange', 'onOpen', 'onClose', 'onMonthChange', 'onYearChange', 'onReady', 'onValueUpdate', 'onDayCreate']; -var hookPropType = _propTypes2.default.oneOfType([_propTypes2.default.func, _propTypes2.default.arrayOf(_propTypes2.default.func)]); - -var DateTimePicker = function (_Component) { - _inherits(DateTimePicker, _Component); - - function DateTimePicker() { - _classCallCheck(this, DateTimePicker); - - return _possibleConstructorReturn(this, (DateTimePicker.__proto__ || Object.getPrototypeOf(DateTimePicker)).apply(this, arguments)); - } - - _createClass(DateTimePicker, [{ - key: 'componentWillReceiveProps', - value: function componentWillReceiveProps(props) { - var _this2 = this; - - var options = props.options; - - var prevOptions = this.props.options; - - // Add prop hooks to options - hooks.forEach(function (hook) { - if (props.hasOwnProperty(hook)) { - options[hook] = props[hook]; - } - // Add prev ones too so we can compare against them later - if (_this2.props.hasOwnProperty(hook)) { - prevOptions[hook] = _this2.props[hook]; - } - }); - - var optionsKeys = Object.getOwnPropertyNames(options); - - for (var index = optionsKeys.length - 1; index >= 0; index--) { - var key = optionsKeys[index]; - var value = options[key]; - - if (value !== prevOptions[key]) { - // Hook handlers must be set as an array - if (hooks.indexOf(key) !== -1 && !Array.isArray(value)) { - value = [value]; - } - - this.flatpickr.set(key, value); - } - } - - if (props.hasOwnProperty('value') && props.value !== this.props.value) { - this.flatpickr.setDate(props.value, false); - } - } - }, { - key: 'componentDidMount', - value: function componentDidMount() { - var _this3 = this; - - var options = _extends({ - onClose: function onClose() { - _this3.node.blur && _this3.node.blur(); - } - }, this.props.options); - - // Add prop hooks to options - hooks.forEach(function (hook) { - if (_this3.props[hook]) { - options[hook] = _this3.props[hook]; - } - }); - - this.flatpickr = new _flatpickr2.default(this.node, options); - - if (this.props.hasOwnProperty('value')) { - this.flatpickr.setDate(this.props.value, false); - } - } - }, { - key: 'componentWillUnmount', - value: function componentWillUnmount() { - this.flatpickr.destroy(); - } - }, { - key: 'render', - value: function render() { - var _this4 = this; - - // eslint-disable-next-line no-unused-vars - var _props = this.props, - options = _props.options, - defaultValue = _props.defaultValue, - value = _props.value, - children = _props.children, - props = _objectWithoutProperties(_props, ['options', 'defaultValue', 'value', 'children']); - - // Don't pass hooks to dom node - - - hooks.forEach(function (hook) { - delete props[hook]; - }); - - return options.wrap ? _react2.default.createElement( - 'div', - _extends({}, props, { ref: function ref(node) { - _this4.node = node; - } }), - children - ) : _react2.default.createElement('input', _extends({}, props, { defaultValue: defaultValue, - ref: function ref(node) { - _this4.node = node; - } })); - } - }]); - - return DateTimePicker; -}(_react.Component); - -DateTimePicker.propTypes = { - defaultValue: _propTypes2.default.string, - options: _propTypes2.default.object, - onChange: hookPropType, - onOpen: hookPropType, - onClose: hookPropType, - onMonthChange: hookPropType, - onYearChange: hookPropType, - onReady: hookPropType, - onValueUpdate: hookPropType, - onDayCreate: hookPropType, - value: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.array, _propTypes2.default.object, _propTypes2.default.number]), - children: _propTypes2.default.node, - className: _propTypes2.default.string -}; -DateTimePicker.defaultProps = { - options: {} -}; -exports.default = DateTimePicker; \ No newline at end of file diff --git a/src/plugins/date-picker/components/react-flatpickr/lib/index.js b/src/plugins/date-picker/components/react-flatpickr/lib/index.js deleted file mode 100644 index ebec978f..00000000 --- a/src/plugins/date-picker/components/react-flatpickr/lib/index.js +++ /dev/null @@ -1,131 +0,0 @@ - -import React, { Component } from 'react' -import PropTypes from 'prop-types' -import Flatpickr from 'flatpickr' - -const hooks = [ - 'onChange', - 'onOpen', - 'onClose', - 'onMonthChange', - 'onYearChange', - 'onReady', - 'onValueUpdate', - 'onDayCreate' -] -const hookPropType = PropTypes.oneOfType([ - PropTypes.func, - PropTypes.arrayOf(PropTypes.func) -]) - -class DateTimePicker extends Component { - static propTypes = { - defaultValue: PropTypes.string, - options: PropTypes.object, - onChange: hookPropType, - onOpen: hookPropType, - onClose: hookPropType, - onMonthChange: hookPropType, - onYearChange: hookPropType, - onReady: hookPropType, - onValueUpdate: hookPropType, - onDayCreate: hookPropType, - value: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.array, - PropTypes.object, - PropTypes.number - ]), - children: PropTypes.node, - className: PropTypes.string - } - - static defaultProps = { - options: {} - } - - componentWillReceiveProps(props) { - const { options } = props - const prevOptions = this.props.options - - // Add prop hooks to options - hooks.forEach(hook => { - if (props.hasOwnProperty(hook)) { - options[hook] = props[hook] - } - // Add prev ones too so we can compare against them later - if (this.props.hasOwnProperty(hook)) { - prevOptions[hook] = this.props[hook] - } - }) - - const optionsKeys = Object.getOwnPropertyNames(options) - - for (let index = optionsKeys.length - 1; index >= 0; index--) { - const key = optionsKeys[index] - let value = options[key] - - if (value !== prevOptions[key]) { - // Hook handlers must be set as an array - if (hooks.indexOf(key) !== -1 && !Array.isArray(value)) { - value = [value] - } - - this.flatpickr.set(key, value) - } - } - - if (props.hasOwnProperty('value') && props.value !== this.props.value) { - this.flatpickr.setDate(props.value, false) - } - } - - componentDidMount() { - const options = { - onClose: () => { - this.node.blur && this.node.blur() - }, - ...this.props.options - } - - // Add prop hooks to options - hooks.forEach(hook => { - if (this.props[hook]) { - options[hook] = this.props[hook] - } - }) - - this.flatpickr = new Flatpickr(this.node, options) - - if (this.props.hasOwnProperty('value')) { - this.flatpickr.setDate(this.props.value, false) - } - } - - componentWillUnmount() { - this.flatpickr.destroy() - } - - render() { - // eslint-disable-next-line no-unused-vars - const { options, defaultValue, value, children, ...props } = this.props - - // Don't pass hooks to dom node - hooks.forEach(hook => { - delete props[hook] - }) - - return options.wrap - ? ( -
{ this.node = node }}> - { children } -
- ) - : ( - { this.node = node }} /> - ) - } -} - -export default DateTimePicker diff --git a/src/plugins/date-picker/components/react-flatpickr/package.json b/src/plugins/date-picker/components/react-flatpickr/package.json deleted file mode 100644 index 4d9263fb..00000000 --- a/src/plugins/date-picker/components/react-flatpickr/package.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "name": "react-flatpickr", - "version": "3.7.1", - "description": "flatpickr for React", - "main": "build/index.js", - "scripts": { - "example": "npm run build && webpack --config example/webpack.js", - "dev": "echo 'Please open the example directory to view the example' && webpack-dev-server --config example/webpack.js", - "lint": "eslint lib && eslint example", - "build": "babel lib --out-dir build", - "prepublishOnly": "npm run build", - "test": "echo todo" - }, - "repository": "coderhaoxin/react-flatpickr", - "keywords": [ - "flatpickr", - "react" - ], - "files": [ - "build/", - "lib/" - ], - "author": "haoxin", - "license": "MIT", - "dependencies": { - "flatpickr": "^4.3.2", - "prop-types": "^15.5.10" - }, - "devDependencies": { - "babel-cli": "^6.22.2", - "babel-core": "^6.22.1", - "babel-eslint": "^8.0.1", - "babel-loader": "^7.1.2", - "babel-preset-es2015": "^6.22.0", - "babel-preset-react": "^6.22.0", - "babel-preset-stage-0": "^6.22.0", - "css-loader": "^0.28.7", - "eslint": "^4.8.0", - "eslint-config-ok": "github:haoxins/eslint-config", - "react": "^16.0.0", - "react-dom": "^16.0.0", - "style-loader": "^0.19.0", - "webpack": "^3.6.0", - "webpack-dev-server": "^2.8.2" - }, - "eslintConfig": { - "extends": [ - "ok" - ] - } -} diff --git a/src/plugins/date-picker/index.tsx b/src/plugins/date-picker/index.tsx index fc2aac44..78efb7da 100644 --- a/src/plugins/date-picker/index.tsx +++ b/src/plugins/date-picker/index.tsx @@ -1,333 +1,299 @@ -import * as React from "react"; -import "./style.css"; - -// Flatpickr Datepicker -import Flatpickr from './components/react-flatpickr'; -import './flatpickr.css'; +/* Node modules */ +import React, { FC, useState } from "react"; +import Flatpickr from "react-flatpickr"; -// languages -import l10n from './langHelper'; -import moment from 'moment'; - -import { MessageComponentProps, MessagePlugin, MessagePluginFactory } from "../../common/interfaces/message-plugin"; -import { createMessagePlugin, registerMessagePlugin } from "../helper"; +/* Custom modules */ +import l10n from "./langHelper"; +import { registerMessagePlugin } from "../helper"; import { IMessage } from "../../common/interfaces/message"; +import { + MessageComponentProps, + MessagePluginFactory, +} from "../../common/interfaces/message-plugin"; -const datePickerDaySelector = ".flatpickr-day.selected, .flatpickr-day.startRange, .flatpickr-day.endRange, .flatpickr-day.selected.inRange, .flatpickr-day.startRange.inRange, .flatpickr-day.endRange.inRange, .flatpickr-day.selected:focus, .flatpickr-day.startRange:focus, .flatpickr-day.endRange:focus, .flatpickr-day.selected:hover, .flatpickr-day.startRange:hover, .flatpickr-day.endRange:hover, .flatpickr-day.selected.prevMonthDay, .flatpickr-day.startRange.prevMonthDay, .flatpickr-day.endRange.prevMonthDay, .flatpickr-day.selected.nextMonthDay, .flatpickr-day.startRange.nextMonthDay, .flatpickr-day.endRange.nextMonthDay"; - -interface IState { - msg: string, -} +/* CSS */ +import "./style.css"; +import "./flatpickr.css"; +import { formatDate, transformNamedDate } from "./utils"; +const datePickerDaySelector = + ".flatpickr-day.selected, .flatpickr-day.startRange, .flatpickr-day.endRange, .flatpickr-day.selected.inRange, .flatpickr-day.startRange.inRange, .flatpickr-day.endRange.inRange, .flatpickr-day.selected:focus, .flatpickr-day.startRange:focus, .flatpickr-day.endRange:focus, .flatpickr-day.selected:hover, .flatpickr-day.startRange:hover, .flatpickr-day.endRange:hover, .flatpickr-day.selected.prevMonthDay, .flatpickr-day.startRange.prevMonthDay, .flatpickr-day.endRange.prevMonthDay, .flatpickr-day.selected.nextMonthDay, .flatpickr-day.startRange.nextMonthDay, .flatpickr-day.endRange.nextMonthDay"; /** * Transforms regional locales to flatpicks internal locale key */ const getFlatpickrLocaleId = (locale: string) => { - switch (locale) { - case 'us': - case 'gb': - case 'au': - case 'ca': - return 'en'; - } + switch (locale) { + case "us": + case "gb": + case "au": + case "ca": + return "en"; + } - return locale; -} - -/** - * Transforms regional locales to flatpicks internal locale key - */ -const getMomemtLocaleId = (locale: string) => { - switch (locale) { - case 'au': - return 'en-au'; - case 'ca': - return 'en-ca'; - case 'gb': - return 'en-gb'; - case 'us': - return 'en'; - } - - return locale; -} + return locale; +}; const datePickerPlugin: MessagePluginFactory = ({ styled }) => { - - - const DatePickerRoot = styled.div(({ theme }) => ({ - display: "flex", - flexDirection: "column", - flexGrow: 1, - [datePickerDaySelector]: { - background: theme.primaryGradient, - color: theme.primaryContrastColor, - } - })); - - const Button = styled.button(({ theme }) => ({ - backgroundColor: theme.greyColor, - color: theme.greyContrastColor, - - cursor: "pointer", - border: "none", - - height: 40, - - padding: `${theme.unitSize}px ${theme.unitSize * 2}px`, - borderRadius: theme.unitSize * 2, - })); - - const PrimaryButton = styled(Button)(({ theme }) => ({ - background: theme.primaryGradient, - color: theme.primaryContrastColor, - })); - - const OutlinedButton = styled(Button)(({ theme }) => ({ - backgroundColor: 'transparent', - border: `1px solid ${theme.primaryColor}`, - color: theme.primaryColor - })); - - const SubmitButton = styled(PrimaryButton)(({ theme }) => ({ - flexGrow: 2, - marginLeft: theme.unitSize * 2 - })); - - const CancelButton = styled(Button)(({ theme }) => ({ - flexGrow: 1 - })); - - const OpenDatepickerButton = styled(OutlinedButton)(({ theme }) => ({ - '&[disabled]': { - borderColor: theme.greyColor, - color: theme.greyColor, - cursor: 'default' - }, - '&:focus':{ - outline: 'none', - boxShadow: `0 0 3px 1px ${theme.primaryWeakColor}` - } - })); - - const Padding = styled.div(({ theme }) => ({ - paddingTop: theme.unitSize, - paddingBottom: theme.unitSize, - paddingLeft: theme.unitSize * 2, - paddingRight: theme.unitSize * 2 - })); - - const Header = styled(Padding)(({ theme }) => ({ - background: theme.primaryGradient, - color: theme.primaryContrastColor, - flexGrow: 1, - display: 'flex', - alignItems: 'center', - fontWeight: 'bolder', - boxShadow: theme.shadow, - zIndex: 2 - })); - - const Content = styled(Padding)(({ theme }) => ({ - display: 'flex', - justifyContent: 'center', - })) - - const Footer = styled(Padding)(({ theme }) => ({ - display: 'flex', - justifyContent: 'space-between', - alignItems: 'center', - backgroundColor: 'white', - boxShadow: theme.shadow, - })); - - const processedMessages: Set = new Set(); - - class DatePicker extends React.Component { - constructor(props) { - super(props); - this.state = { - msg: "", - }; - } - - handleSubmit = () => { - const { message } = this.props - - // close plugin if user didn't choose a date - if (this.state.msg.length > 0) { - if (message.source === 'bot') - processedMessages.add(message.traceId); - - setTimeout(() => { - this.props.onSendMessage(this.state.msg), { - _plugin: "date-picker", - date: this.state.msg, - } - }, 300); - } else { - this.props.onDismissFullscreen(); - } - - }; - - handleAbort = () => { - const { message } = this.props; - - this.props.onDismissFullscreen(); - } - - static isWeekendDate(date: string) { - const isoWeekday = moment(date).isoWeekday(); - - switch (isoWeekday) { - // 6 is saturday - case 6: - // 7 is sunday - case 7: - return true; - } - - return false; - } - - static transformNamedDate(namedDate: string) { - switch (namedDate) { - case "today": - return moment().format('YYYY-MM-DD'); - - case "tomorrow": - return moment().add(1, 'days').format('YYYY-MM-DD'); - - case "yesterday": - return moment().add(-1, 'days').format('YYYY-MM-DD'); - } - - return namedDate - } - - static getOptionsFromMessage(message: IMessage) { - const { data } = message.data._plugin; - - const dateFormat = data.dateFormat || 'YYYY-MM-DD'; - const defaultDate = DatePicker.transformNamedDate(data.defaultDate) - || DatePicker.transformNamedDate(data.minDate) - || undefined; - - const localeId = data.locale || 'us'; - const momentLocaleId = getMomemtLocaleId(localeId); - const flatpickrLocaleId = getFlatpickrLocaleId(localeId); - let locale = l10n[flatpickrLocaleId]; - const enableTime = !!data.enableTime; - const timeTemp = data.time_24hr ? 'H:i' : 'h:i'; //12-hour format without AM/PM - const timeWithSeconds = data.enableSeconds ? `${timeTemp}:S` : timeTemp; - const timeFormat = data.time_24hr ? timeWithSeconds :`${timeWithSeconds} K` //12-hour format with AM/PM - - if ( localeId === 'gb' ) locale = { ...locale, firstDayOfWeek: 1 }; - const options = { - defaultHour: data.defaultHour || 12, - defaultMinute: data.defaultMinute || 0, - enableSeconds: data.enableSeconds || false, - hourIncrement: data.hourIncrement || 1, - minuteIncrement: data.minuteIncrement || 5, - noCalendar: data.noCalendar || false, - weekNumbers: data.weekNumbers || false, - dateFormat: enableTime ? `${dateFormat} ${timeFormat}` : dateFormat, - defaultDate, - disable: [] as string[], - enable: [] as string[], - enableTime, - event: data.eventName, - inline: true, - locale, - maxDate: DatePicker.transformNamedDate(data.maxDate) || '', - minDate: DatePicker.transformNamedDate(data.minDate) || '', - mode: data.mode || 'single', - static: true, - time_24hr: data.time_24hr || false, - parseDate: dateString => moment(dateString).toDate(), - // if no custom formatting is defined, apply default formatting - formatDate: !data.dateFormat - ? date => moment(date).locale(momentLocaleId).format(enableTime ? 'L LT' : 'L') - : undefined - }; - - const mask: string[] = [...(data.enable_disable || [])] - // add special rule for weekends - .map(dateString => { - if (dateString === 'weekends') - return DatePicker.isWeekendDate - - return dateString; - }) - // resolve relative date names like today, tomorrow or yesterday - .map(DatePicker.transformNamedDate); - - if (!!data.wantDisable) { - // add date mask as blacklist - options.disable = mask; - } else if (mask.length > 0) { - - // add date mask as whitelist - options.enable = mask; - } - - return options; - } - - render() { - const { onSendMessage, message, config, attributes, isFullscreen, onSetFullscreen } = this.props; - - - let dateButtonText = message.data._plugin.data.openPickerButtonText || 'pick date'; - let cancelButtonText = message.data._plugin.data.cancelButtonText || 'cancel'; - let submitButtonText = message.data._plugin.data.submitButtonText || 'submit'; - - const options = DatePicker.getOptionsFromMessage(message); - - let datepickerWasOpen = false; - if (message.source === 'bot') { - datepickerWasOpen = processedMessages.has(message.traceId); - } - - if (!isFullscreen) { - if (datepickerWasOpen) { - return {dateButtonText} - } - - return {dateButtonText} - } - - return ( - -
-

{options.event}

-
- - { this.setState({ msg }) }} - options={ - options - } - /> - -
- {cancelButtonText} - {submitButtonText} -
-
- ); - } - } - - const plugin = { - match: "date-picker", - component: DatePicker - } - - return plugin; -} + const DatePickerRoot = styled.div(({ theme }) => ({ + display: "flex", + flexDirection: "column", + flexGrow: 1, + [datePickerDaySelector]: { + background: theme.primaryGradient, + color: theme.primaryContrastColor, + }, + })); + + const Button = styled.button(({ theme }) => ({ + backgroundColor: theme.greyColor, + color: theme.greyContrastColor, + + cursor: "pointer", + border: "none", + + height: 40, + + padding: `${theme.unitSize}px ${theme.unitSize * 2}px`, + borderRadius: theme.unitSize * 2, + })); + + const PrimaryButton = styled(Button)(({ theme }) => ({ + background: theme.primaryGradient, + color: theme.primaryContrastColor, + })); + + const OutlinedButton = styled(Button)(({ theme }) => ({ + backgroundColor: "transparent", + border: `1px solid ${theme.primaryColor}`, + color: theme.primaryColor, + })); + + const SubmitButton = styled(PrimaryButton)(({ theme }) => ({ + flexGrow: 2, + marginLeft: theme.unitSize * 2, + })); + + const CancelButton = styled(Button)(({ theme }) => ({ + flexGrow: 1, + })); + + const OpenDatepickerButton = styled(OutlinedButton)(({ theme }) => ({ + "&[disabled]": { + borderColor: theme.greyColor, + color: theme.greyColor, + cursor: "default", + }, + "&:focus": { + outline: "none", + boxShadow: `0 0 3px 1px ${theme.primaryWeakColor}`, + }, + })); + + const Padding = styled.div(({ theme }) => ({ + paddingTop: theme.unitSize, + paddingBottom: theme.unitSize, + paddingLeft: theme.unitSize * 2, + paddingRight: theme.unitSize * 2, + })); + + const Header = styled(Padding)(({ theme }) => ({ + background: theme.primaryGradient, + color: theme.primaryContrastColor, + flexGrow: 1, + display: "flex", + alignItems: "center", + fontWeight: "bolder", + boxShadow: theme.shadow, + zIndex: 2, + })); + + const Content = styled(Padding)(({ theme }) => ({ + display: "flex", + justifyContent: "center", + })); + + const Footer = styled(Padding)(({ theme }) => ({ + display: "flex", + justifyContent: "space-between", + alignItems: "center", + backgroundColor: "white", + boxShadow: theme.shadow, + })); + + const processedMessages: Set = new Set(); + + const DatePicker: FC = props => { + const { + onSendMessage, + message, + config, + attributes, + isFullscreen, + onSetFullscreen, + onDismissFullscreen, + } = props; + + const [msg, setMsg] = useState(""); + + const handleSubmit = () => { + // close plugin if user didn't choose a date + if (msg.length > 0) { + if (message.source === "bot") processedMessages.add(message.traceId); + + setTimeout(() => { + onSendMessage(msg), + { + _plugin: "date-picker", + date: msg, + }; + }, 300); + } else { + onDismissFullscreen && onDismissFullscreen(); + } + }; + + const handleAbort = () => { + onDismissFullscreen && onDismissFullscreen(); + }; + + const isWeekendDate = (date: string) => { + const isoWeekday = ((new Date(date).getDay() + 6) % 7) + 1; + + switch (isoWeekday) { + // 6 is saturday + case 6: + // 7 is sunday + case 7: + return true; + } + + return false; + }; + + const getOptionsFromMessage = (message: IMessage) => { + const { data } = message.data._plugin; + const dateFormat = data.dateFormat || "YYYY-MM-DD"; + const defaultDate = + transformNamedDate(data.defaultDate) || transformNamedDate(data.minDate) || ""; + + const localeId = data.locale || "us"; + const flatpickrLocaleId = getFlatpickrLocaleId(localeId); + let locale = l10n[flatpickrLocaleId]; + const enableTime = !!data.enableTime; + const timeTemp = data.time_24hr ? "H:i" : "h:i"; //12-hour format without AM/PM + const timeWithSeconds = data.enableSeconds ? `${timeTemp}:S` : timeTemp; + const timeFormat = data.time_24hr ? timeWithSeconds : `${timeWithSeconds} K`; //12-hour format with AM/PM + + if (localeId === "gb") locale = { ...locale, firstDayOfWeek: 1 }; + const options = { + defaultHour: data.defaultHour || 12, + defaultMinute: data.defaultMinute || 0, + enableSeconds: data.enableSeconds || false, + hourIncrement: data.hourIncrement || 1, + minuteIncrement: data.minuteIncrement || 5, + noCalendar: data.noCalendar || false, + weekNumbers: data.weekNumbers || false, + dateFormat: enableTime ? `${dateFormat} ${timeFormat}` : dateFormat, + defaultDate, + disable: [] as string[], + enable: [] as string[], + enableTime, + event: data.eventName, + inline: true, + locale, + maxDate: transformNamedDate(data.maxDate) || "", + minDate: transformNamedDate(data.minDate) || "", + mode: data.mode || "single", + static: true, + time_24hr: data.time_24hr || false, + parseDate: dateString => new Date(dateString), + // if no custom formatting is defined, apply default formatting. es-PA is MM/dd/yyyy + formatDate: !data.dateFormat + ? date => formatDate(date, enableTime, flatpickrLocaleId) + : undefined, + }; + + const mask: string[] = [...(data.enable_disable || [])] + // add special rule for weekends + .map(dateString => { + if (dateString === "weekends") return isWeekendDate; + + return dateString; + }) + // resolve relative date names like today, tomorrow or yesterday + .map(transformNamedDate); + + if (!!data.wantDisable) { + // add date mask as blacklist + options.disable = mask; + } else if (mask.length > 0) { + // add date mask as whitelist + options.enable = mask; + } + + return options; + }; + + const dateButtonText = message.data._plugin.data.openPickerButtonText || "pick date"; + const cancelButtonText = message.data._plugin.data.cancelButtonText || "cancel"; + const submitButtonText = message.data._plugin.data.submitButtonText || "submit"; + + const options = getOptionsFromMessage(message); + + let datepickerWasOpen = false; + if (message.source === "bot") { + datepickerWasOpen = processedMessages.has(message.traceId); + } + + if (!isFullscreen) { + if (datepickerWasOpen) { + return ( + + {dateButtonText} + + ); + } + + return ( + + {dateButtonText} + + ); + } + + return ( + +
+

{options.event}

+
+ + { + setMsg(msg); + }} + options={options} + /> + + +
+ ); + }; + + const plugin = { + match: "date-picker", + component: DatePicker, + }; + + return plugin; +}; registerMessagePlugin(datePickerPlugin); diff --git a/src/plugins/date-picker/utils.ts b/src/plugins/date-picker/utils.ts new file mode 100644 index 00000000..5e1147a0 --- /dev/null +++ b/src/plugins/date-picker/utils.ts @@ -0,0 +1,46 @@ +export const transformNamedDate = (namedDate: string) => { + switch (namedDate) { + case "today": + // fr-CA is one of the few locales with a day format of YYYY-MM-DD + return new Intl.DateTimeFormat("fr-CA").format(new Date()); + + case "tomorrow": + return new Intl.DateTimeFormat("fr-CA").format( + new Date().setDate(new Date().getDate() + 1), + ); + + case "yesterday": + return new Intl.DateTimeFormat("fr-CA").format( + new Date().setDate(new Date().getDate() - 1), + ); + } + + return namedDate; +}; + +export const formatDate = (date: Date, enableTime: boolean, locale: string) => { + return new Intl.DateTimeFormat( + locale, + enableTime + ? { + hour: "2-digit", + minute: "2-digit", + year: "numeric", + month: "2-digit", + day: "2-digit", + } + : { + year: "numeric", + month: "2-digit", + day: "2-digit", + }, + ) + .format(date) + .replace(",", "") + .replace("am", "AM") + .replace("a.m.", "AM") + .replace("a. m.", "AM") + .replace("pm", "PM") + .replace("p.m.", "PM") + .replace("p. m.", "PM"); +}; \ No newline at end of file