├── .babelrc ├── .eslintrc ├── .gitignore ├── .npmignore ├── .stylintrc ├── .travis.yml ├── LICENSE ├── README.md ├── bower.json ├── dist ├── react-datepicker.css ├── react-datepicker.css.map └── react-datepicker.min.css ├── docs ├── 0b39d1e64af23fe5437238402d08496a.svg ├── 1dada8ea59a31dad3dbb76472a3de6ba.svg ├── 35071d00819547a959ef3450c129d77e.eot ├── 37f4597594857b017901209aae0a60e1.svg ├── 3eff5a4e9fb92ca96cbf2fa77649b8c1.ttf ├── 5ce48c51b8e3b5d666156ff739655a38.svg ├── 81436770636b45508203b3022075ae73.ttf ├── 8a53d21a4d9aa1aac2bf15093bd748c4.woff ├── af39e41be700d6148c61a6c1ffc84215.svg ├── bundle.js ├── bundle.js.map ├── d6c7e9d3e5adb7a5261c5ad9f7d3caaa.woff ├── d9c6d360d27eac625da0405245ec9f0d.eot └── index.html ├── examples ├── .stylintrc ├── DatePicker │ ├── Controlled.jsx │ ├── Dropdown.jsx │ ├── Selectable.jsx │ ├── Uncontrolled.jsx │ └── index.js ├── DateTimePicker │ ├── Controlled.jsx │ ├── Uncontrolled.jsx │ └── index.js ├── DateTimeRangePicker │ ├── Controlled.jsx │ ├── DateTimeRangePicker.jsx │ ├── DateTimeRangePicker.styl │ ├── Dropdown.jsx │ ├── DropdownRight.jsx │ ├── Uncontrolled.jsx │ └── index.js ├── Navbar.jsx ├── Navbar.styl ├── Section.jsx ├── Section.styl ├── index.html ├── index.jsx ├── index.styl └── webpack.config.js ├── package.json ├── scripts └── bowersync ├── src ├── DateInput │ ├── Calendar.jsx │ ├── index.jsx │ └── index.styl ├── DatePicker.jsx ├── TimeInput │ ├── Clock.jsx │ ├── index.jsx │ ├── index.styl │ └── lib │ │ ├── caret.js │ │ ├── get-base.js │ │ ├── get-group-id.js │ │ ├── get-groups.js │ │ ├── is-twelve-hour-time.js │ │ ├── replace-char-at.js │ │ ├── stringify.js │ │ ├── time-string-adder.js │ │ ├── toggle-24-hour.js │ │ ├── validate.js │ │ └── zero-pad.js ├── angle-left.svg ├── angle-right.svg ├── index.js ├── index.styl ├── react-datepicker.styl └── reset-context.styl ├── test └── index.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-0", "react"], 3 | "plugins": [ 4 | "transform-decorators-legacy" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "trendmicro", 3 | "parser": "babel-eslint", 4 | "env": { 5 | "browser": true, 6 | "node": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | package-lock.json 4 | /.nyc_output 5 | /coverage 6 | /lib 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | npm-debug.log 2 | /.nyc_output 3 | /coverage 4 | -------------------------------------------------------------------------------- /.stylintrc: -------------------------------------------------------------------------------- 1 | // 2 | // https://github.com/rossPatton/stylint 3 | // 4 | { 5 | "blocks": false, 6 | "brackets": "always", 7 | "colons": "always", 8 | "colors": false, 9 | "commaSpace": "always", 10 | "commentSpace": false, 11 | "cssLiteral": "never", 12 | "depthLimit": false, 13 | "duplicates": false, 14 | "efficient": "always", 15 | "extendPref": false, 16 | "globalDupe": false, 17 | "indentPref": false, 18 | "leadingZero": "never", 19 | "maxErrors": false, 20 | "maxWarnings": false, 21 | "mixed": false, 22 | "namingConvention": false, 23 | "namingConventionStrict": false, 24 | "none": "never", 25 | "noImportant": true, 26 | "parenSpace": false, 27 | "placeholders": "always", 28 | "prefixVarsWithDollar": "always", 29 | "quotePref": false, 30 | "semicolons": "always", 31 | "sortOrder": false, 32 | "stackedProperties": "never", 33 | "trailingWhitespace": "never", 34 | "universal": false, 35 | "valid": true, 36 | "zeroUnits": "never", 37 | "zIndexNormalize": false 38 | } 39 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: trusty 3 | group: edge 4 | 5 | language: node_js 6 | 7 | os: 8 | - linux 9 | 10 | node_js: 11 | - '8' 12 | - '6' 13 | 14 | before_install: 15 | - npm install -g npm 16 | - npm --version 17 | 18 | after_success: 19 | - npm run coveralls 20 | - npm run coverage-clean 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Trend Micro Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-datepicker [![build status](https://travis-ci.org/trendmicro-frontend/react-datepicker.svg?branch=master)](https://travis-ci.org/trendmicro-frontend/react-datepicker) [![Coverage Status](https://coveralls.io/repos/github/trendmicro-frontend/react-datepicker/badge.svg?branch=master)](https://coveralls.io/github/trendmicro-frontend/react-datepicker?branch=master) 2 | 3 | [![NPM](https://nodei.co/npm/@trendmicro/react-datepicker.png?downloads=true&stars=true)](https://nodei.co/npm/@trendmicro/react-datepicker/) 4 | 5 | React DatePicker 6 | 7 | [![image](https://user-images.githubusercontent.com/447801/29000301-239cd7d4-7a99-11e7-842d-59c70abe9ee1.png)](https://trendmicro-frontend.github.io/react-datepicker) 8 | 9 | Demo: https://trendmicro-frontend.github.io/react-datepicker 10 | 11 | ## Installation 12 | 13 | 1. Install the latest version of [react](https://github.com/facebook/react), [moment](https://github.com/moment/moment) and [react-datepicker](https://github.com/trendmicro-frontend/react-datepicker): 14 | 15 | ``` 16 | npm install --save react moment @trendmicro/react-datepicker 17 | ``` 18 | 19 | 2. At this point you can import `@trendmicro/react-datepicker` and its styles in your application as follows: 20 | 21 | ```js 22 | import DatePicker, { DateInput, TimeInput } from '@trendmicro/react-datepicker'; 23 | 24 | // Be sure to include styles at some point, probably during your bootstraping 25 | import '@trendmicro/react-datepicker/dist/react-datepicker.css'; 26 | ``` 27 | 28 | ## Usage 29 | 30 | ### DatePicker 31 | 32 | Initialize state in your React component: 33 | ```js 34 | state = { 35 | date: moment().format('YYYY-MM-DD') 36 | }; 37 | ``` 38 | 39 | #### Controlled 40 | 41 | ```js 42 | { 45 | this.setState(state => ({ date: date })); 46 | }} 47 | /> 48 | ``` 49 | 50 | #### Uncontrolled 51 | 52 | ```js 53 | { 56 | // Optional 57 | }} 58 | /> 59 | ``` 60 | 61 | ### DateInput 62 | 63 | Initialize state in your React component: 64 | ```js 65 | state = { 66 | // 2017-08-01 67 | value: moment().format('YYYY-MM-DD') 68 | }; 69 | ``` 70 | 71 | #### Controlled 72 | 73 | ```js 74 | { 77 | this.setState(state => ({ value: value })); 78 | }} 79 | /> 80 | ``` 81 | 82 | #### Uncontrolled 83 | 84 | ```js 85 | { 88 | // Optional 89 | }} 90 | /> 91 | ``` 92 | 93 | ### TimeInput 94 | 95 | Initialize state in your React component: 96 | ```js 97 | state = { 98 | // 08:00:00 99 | value: moment().format('hh:mm:ss') 100 | }; 101 | ``` 102 | 103 | #### Controlled 104 | 105 | ```js 106 | { 109 | this.setState(state => ({ value: value })); 110 | }} 111 | /> 112 | ``` 113 | 114 | #### Uncontrolled 115 | 116 | ```js 117 | { 120 | // Optional 121 | }} 122 | /> 123 | ``` 124 | 125 | ## Examples 126 | 127 | ### [DatePicker](https://github.com/trendmicro-frontend/react-datepicker/tree/master/examples/DatePicker) 128 | 129 | [![image](https://user-images.githubusercontent.com/447801/29000356-37ffdbd0-7a9a-11e7-96b2-66ba33c212d1.png)](https://trendmicro-frontend.github.io/react-datepicker) 130 | 131 | #### Sources 132 | * [Controlled](https://github.com/trendmicro-frontend/react-datepicker/tree/master/examples/DatePicker/Controlled.jsx) 133 | * [Uncontrolled](https://github.com/trendmicro-frontend/react-datepicker/tree/master/examples/DatePicker/Uncontrolled.jsx) 134 | * [Selectable](https://github.com/trendmicro-frontend/react-datepicker/tree/master/examples/DatePicker/Selectable.jsx) 135 | * [Dropdown](https://github.com/trendmicro-frontend/react-datepicker/tree/master/examples/DatePicker/Dropdown.jsx) 136 | 137 | ### [DateTimePicker](https://github.com/trendmicro-frontend/react-datepicker/tree/master/examples/DateTimePicker) 138 | 139 | [![image](https://user-images.githubusercontent.com/447801/29000301-239cd7d4-7a99-11e7-842d-59c70abe9ee1.png)](https://trendmicro-frontend.github.io/react-datepicker) 140 | 141 | #### Sources 142 | * [Controlled](https://github.com/trendmicro-frontend/react-datepicker/tree/master/examples/DateTimePicker/Controlled.jsx) 143 | * [Uncontrolled](https://github.com/trendmicro-frontend/react-datepicker/tree/master/examples/DateTimePicker/Uncontrolled.jsx) 144 | 145 | ### [DateTimeRangePicker](https://github.com/trendmicro-frontend/react-datepicker/tree/master/examples/DateTimeRangePicker) 146 | 147 | [![image](https://user-images.githubusercontent.com/447801/29000331-ec51c0f4-7a99-11e7-859f-381b3e336a8d.png)](https://trendmicro-frontend.github.io/react-datepicker) 148 | 149 | #### Sources 150 | * [Controlled](https://github.com/trendmicro-frontend/react-datepicker/tree/master/examples/DateTimeRangePicker/Controlled.jsx) 151 | * [Uncontrolled](https://github.com/trendmicro-frontend/react-datepicker/tree/master/examples/DateTimeRangePicker/Uncontrolled.jsx) 152 | * [Dropdown](https://github.com/trendmicro-frontend/react-datepicker/tree/master/examples/DateTimeRangePicker/Dropdown.jsx) 153 | - @trendmicro/react-dropdown@0.7.0 or above is required to use `Dropdown.MenuWrapper` 154 | 155 | ## API 156 | 157 | ### Properties 158 | 159 | #### DatePicker 160 | 161 | Name | Type | Default | Description 162 | :--- | :--- | :------ | :---------- 163 | locale | string | 'en' | 164 | date | object or string | null | 165 | defaultDate | object or string | null | 166 | minDate | object or string | null | The minimum selectable date. When set to null, there is no minimum. 167 | maxDate | object or string | null | The maximum selectable date. When set to null, there is no maximum. 168 | onSelect | function(date) | | Called when a date is selected. 169 | 170 | #### DateInput 171 | 172 | Name | Type | Default | Description 173 | :--- | :--- | :------ | :---------- 174 | value | object or string | null | 175 | defaultValue | object or string | null | 176 | minDate | object or string | null | The minimum date. When set to null, there is no minimum. 177 | maxDate | object or string | null | The maximum date. When set to null, there is no maximum. 178 | onChange | function(value) | | Called when the value changes. 179 | 180 | #### TimeInput 181 | 182 | Name | Type | Default | Description 183 | :--- | :--- | :------ | :---------- 184 | value | string | '00:00:00' | 185 | defaultValue | string | '00:00:00' | 186 | onChange | function(value) | | Called when the value changes. 187 | 188 | ## License 189 | 190 | MIT 191 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@trendmicro/react-datepicker", 3 | "description": "React DatePicker Component", 4 | "homepage": "https://github.com/trendmicro-frontend/react-datepicker", 5 | "snapshots": [], 6 | "keywords": [ 7 | "react", 8 | "date", 9 | "picker", 10 | "react-datepicker" 11 | ], 12 | "license": "MIT" 13 | } 14 | -------------------------------------------------------------------------------- /dist/react-datepicker.css: -------------------------------------------------------------------------------- 1 | /*! react-datepicker v1.0.0-alpha.7 | (c) 2020 Trend Micro Inc. | MIT | https://github.com/trendmicro-frontend/react-datepicker */ 2 | .datepicker---date-picker-container---1kNBY { 3 | -webkit-box-sizing: border-box; 4 | -moz-box-sizing: border-box; 5 | box-sizing: border-box; 6 | line-height: 20px; 7 | border: none; 8 | border-radius: 0; 9 | -webkit-box-shadow: none; 10 | box-shadow: none; 11 | padding: 0 5px; 12 | position: relative; 13 | display: inline-block; 14 | } 15 | .datepicker---date-picker-container---1kNBY *, 16 | .datepicker---date-picker-container---1kNBY *:before, 17 | .datepicker---date-picker-container---1kNBY *:after { 18 | -webkit-box-sizing: inherit; 19 | -moz-box-sizing: inherit; 20 | box-sizing: inherit; 21 | } 22 | .datepicker---date-picker-container---1kNBY .react-datepicker__header { 23 | text-align: center; 24 | background-color: #fff; 25 | border: none; 26 | position: relative; 27 | padding: 0; 28 | } 29 | .datepicker---date-picker-container---1kNBY .react-datepicker__month { 30 | margin: 0; 31 | text-align: center; 32 | } 33 | .datepicker---date-picker-container---1kNBY .react-datepicker__current-month { 34 | color: #222; 35 | font-weight: bold; 36 | font-size: 13px; 37 | height: 20px; 38 | margin: 8px 0; 39 | } 40 | .datepicker---date-picker-container---1kNBY .react-datepicker__navigation { 41 | background: none; 42 | line-height: 20px; 43 | text-align: center; 44 | cursor: pointer; 45 | position: absolute; 46 | top: 3px; 47 | padding: 5px; 48 | border: none; 49 | z-index: 1; 50 | outline: 0; 51 | width: 30px; 52 | height: 30px; 53 | background-color: transparent; 54 | background-position: center center; 55 | background-repeat: no-repeat; 56 | } 57 | .datepicker---date-picker-container---1kNBY .react-datepicker__navigation:hover { 58 | border-radius: 3px; 59 | background-color: #eee; 60 | } 61 | .datepicker---date-picker-container---1kNBY .react-datepicker__navigation--previous { 62 | left: 8px; 63 | background-image: url(data:image/svg+xml;base64,PCEtLSBHZW5lcmF0ZWQgYnkgVHJlbmQgTWljcm8gU3R5bGUgUG9ydGFsIC0tPg0KPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiB2aWV3Qm94PSIwIDAgMTYgMTYiPg0KICA8dGl0bGU+YW5nbGUtbGVmdDwvdGl0bGU+DQogIDxwYXRoIGZpbGw9InJnYigzNCwzNCwzNCkiIGQ9Ik05LjUwMSAxMi41MDZsLTQuNDk5LTQuNTA2IDQuNDg4LTQuNDk0IDEgMS0zLjQ5IDMuNDk0IDMuNTAxIDMuNTA2eiI+PC9wYXRoPg0KPC9zdmc+); 64 | } 65 | .datepicker---date-picker-container---1kNBY .react-datepicker__navigation--next { 66 | right: 8px; 67 | background-image: url(data:image/svg+xml;base64,PCEtLSBHZW5lcmF0ZWQgYnkgVHJlbmQgTWljcm8gU3R5bGUgUG9ydGFsIC0tPg0KPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiB2aWV3Qm94PSIwIDAgMTYgMTYiPg0KICA8dGl0bGU+YW5nbGUtcmlnaHQ8L3RpdGxlPg0KICA8cGF0aCBmaWxsPSJyZ2IoMzQsMzQsMzQpIiBkPSJNNi41MDEgMTIuNWwtMS0xIDMuNTAxLTMuNTA2LTMuNDktMy40OTQgMS0xIDQuNDg4IDQuNDk0eiI+PC9wYXRoPg0KPC9zdmc+); 68 | } 69 | .datepicker---date-picker-container---1kNBY .react-datepicker__day, 70 | .datepicker---date-picker-container---1kNBY .react-datepicker__day-name { 71 | color: #222; 72 | display: inline-block; 73 | text-align: center; 74 | width: 30px; 75 | line-height: 20px; 76 | border: 0; 77 | padding: 5px; 78 | margin: 2px; 79 | } 80 | .datepicker---date-picker-container---1kNBY .react-datepicker__day { 81 | cursor: pointer; 82 | font-size: 13px; 83 | } 84 | .datepicker---date-picker-container---1kNBY .react-datepicker__day:hover { 85 | background: #eee; 86 | cursor: pointer; 87 | border-radius: 50%; 88 | } 89 | .datepicker---date-picker-container---1kNBY .react-datepicker__day.react-datepicker__day--disabled, 90 | .datepicker---date-picker-container---1kNBY .react-datepicker__day:hover.react-datepicker__day--disabled { 91 | background: inherit; 92 | cursor: default; 93 | color: #bbb; 94 | } 95 | .datepicker---date-picker-container---1kNBY .react-datepicker__day-name { 96 | padding-top: 9px; 97 | font-size: 12px; 98 | font-weight: bold; 99 | } 100 | .datepicker---date-picker-container---1kNBY .react-datepicker__day-names { 101 | white-space: nowrap; 102 | } 103 | .datepicker---date-picker-container---1kNBY .react-datepicker__day--outside-month { 104 | color: #bbb; 105 | } 106 | .datepicker---date-picker-container---1kNBY .react-datepicker__day--today { 107 | color: #db3d44; 108 | } 109 | .datepicker---date-picker-container---1kNBY .react-datepicker__day--selected { 110 | color: #fff; 111 | font-weight: bold; 112 | background-color: #db3d44; 113 | border-radius: 50%; 114 | } 115 | .datepicker---date-picker-container---1kNBY .react-datepicker__day--selected:hover { 116 | background-color: #db3d44; 117 | } 118 | @media all and (-ms-high-contrast: none), (-ms-high-contrast: active) { 119 | .datepicker---date-input---1vOyn > input { 120 | height: 32px; 121 | } 122 | } 123 | .datepicker---date-input-container---2zD9B { 124 | -webkit-box-sizing: border-box; 125 | -moz-box-sizing: border-box; 126 | box-sizing: border-box; 127 | line-height: 20px; 128 | position: relative; 129 | } 130 | .datepicker---date-input-container---2zD9B *, 131 | .datepicker---date-input-container---2zD9B *:before, 132 | .datepicker---date-input-container---2zD9B *:after { 133 | -webkit-box-sizing: inherit; 134 | -moz-box-sizing: inherit; 135 | box-sizing: inherit; 136 | } 137 | .datepicker---date-input---1vOyn { 138 | width: 120px; 139 | } 140 | .datepicker---date-input---1vOyn > input { 141 | display: block; 142 | width: 100%; 143 | height: auto; 144 | line-height: inherit; 145 | padding: 5px 12px; 146 | padding-left: 30px; 147 | font-size: 13px; 148 | color: #222; 149 | border: 1px solid #ccc; 150 | border-radius: 3px; 151 | outline: none; 152 | } 153 | .datepicker---date-input---1vOyn > input:focus { 154 | border-color: #0096cc; 155 | } 156 | .datepicker---date-input-icon---1UeQu { 157 | position: absolute; 158 | left: 9px; 159 | top: 8px; 160 | color: #666; 161 | width: 14px; 162 | height: 14px; 163 | } 164 | @media all and (-ms-high-contrast: none), (-ms-high-contrast: active) { 165 | .datepicker---time-input---2ICRB > input { 166 | height: 32px; 167 | } 168 | } 169 | .datepicker---time-input-container---s4Ikh { 170 | -webkit-box-sizing: border-box; 171 | -moz-box-sizing: border-box; 172 | box-sizing: border-box; 173 | line-height: 20px; 174 | position: relative; 175 | } 176 | .datepicker---time-input-container---s4Ikh *, 177 | .datepicker---time-input-container---s4Ikh *:before, 178 | .datepicker---time-input-container---s4Ikh *:after { 179 | -webkit-box-sizing: inherit; 180 | -moz-box-sizing: inherit; 181 | box-sizing: inherit; 182 | } 183 | .datepicker---time-input---2ICRB { 184 | width: 120px; 185 | } 186 | .datepicker---time-input---2ICRB > input { 187 | display: block; 188 | width: 100%; 189 | height: auto; 190 | line-height: inherit; 191 | padding: 5px 12px; 192 | padding-left: 30px; 193 | font-size: 13px; 194 | color: #222; 195 | border: 1px solid #ccc; 196 | border-radius: 3px; 197 | outline: none; 198 | } 199 | .datepicker---time-input---2ICRB > input:focus { 200 | border-color: #0096cc; 201 | } 202 | .datepicker---time-input-icon---3TeZ8 { 203 | position: absolute; 204 | left: 9px; 205 | top: 8px; 206 | color: #666; 207 | width: 14px; 208 | height: 14px; 209 | } 210 | 211 | /*# sourceMappingURL=react-datepicker.css.map*/ -------------------------------------------------------------------------------- /dist/react-datepicker.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":[],"names":[],"mappings":"","file":"../dist/react-datepicker.css","sourceRoot":""} -------------------------------------------------------------------------------- /dist/react-datepicker.min.css: -------------------------------------------------------------------------------- 1 | /*! react-datepicker v1.0.0-alpha.7 | (c) 2020 Trend Micro Inc. | MIT | https://github.com/trendmicro-frontend/react-datepicker */.datepicker---date-picker-container---1kNBY{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;line-height:20px;border:none;border-radius:0;-webkit-box-shadow:none;box-shadow:none;padding:0 5px;position:relative;display:inline-block}.datepicker---date-picker-container---1kNBY *,.datepicker---date-picker-container---1kNBY :after,.datepicker---date-picker-container---1kNBY :before{-webkit-box-sizing:inherit;-moz-box-sizing:inherit;box-sizing:inherit}.datepicker---date-picker-container---1kNBY .react-datepicker__header{text-align:center;background-color:#fff;border:none;position:relative;padding:0}.datepicker---date-picker-container---1kNBY .react-datepicker__month{margin:0;text-align:center}.datepicker---date-picker-container---1kNBY .react-datepicker__current-month{color:#222;font-weight:700;font-size:13px;height:20px;margin:8px 0}.datepicker---date-picker-container---1kNBY .react-datepicker__navigation{background:0 0;line-height:20px;text-align:center;cursor:pointer;position:absolute;top:3px;padding:5px;border:none;z-index:1;outline:0;width:30px;height:30px;background-color:transparent;background-position:center center;background-repeat:no-repeat}.datepicker---date-picker-container---1kNBY .react-datepicker__navigation:hover{border-radius:3px;background-color:#eee}.datepicker---date-picker-container---1kNBY .react-datepicker__navigation--previous{left:8px;background-image:url(data:image/svg+xml;base64,PCEtLSBHZW5lcmF0ZWQgYnkgVHJlbmQgTWljcm8gU3R5bGUgUG9ydGFsIC0tPg0KPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiB2aWV3Qm94PSIwIDAgMTYgMTYiPg0KICA8dGl0bGU+YW5nbGUtbGVmdDwvdGl0bGU+DQogIDxwYXRoIGZpbGw9InJnYigzNCwzNCwzNCkiIGQ9Ik05LjUwMSAxMi41MDZsLTQuNDk5LTQuNTA2IDQuNDg4LTQuNDk0IDEgMS0zLjQ5IDMuNDk0IDMuNTAxIDMuNTA2eiI+PC9wYXRoPg0KPC9zdmc+)}.datepicker---date-picker-container---1kNBY .react-datepicker__navigation--next{right:8px;background-image:url(data:image/svg+xml;base64,PCEtLSBHZW5lcmF0ZWQgYnkgVHJlbmQgTWljcm8gU3R5bGUgUG9ydGFsIC0tPg0KPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiB2aWV3Qm94PSIwIDAgMTYgMTYiPg0KICA8dGl0bGU+YW5nbGUtcmlnaHQ8L3RpdGxlPg0KICA8cGF0aCBmaWxsPSJyZ2IoMzQsMzQsMzQpIiBkPSJNNi41MDEgMTIuNWwtMS0xIDMuNTAxLTMuNTA2LTMuNDktMy40OTQgMS0xIDQuNDg4IDQuNDk0eiI+PC9wYXRoPg0KPC9zdmc+)}.datepicker---date-picker-container---1kNBY .react-datepicker__day,.datepicker---date-picker-container---1kNBY .react-datepicker__day-name{color:#222;display:inline-block;text-align:center;width:30px;line-height:20px;border:0;padding:5px;margin:2px}.datepicker---date-picker-container---1kNBY .react-datepicker__day{cursor:pointer;font-size:13px}.datepicker---date-picker-container---1kNBY .react-datepicker__day:hover{background:#eee;cursor:pointer;border-radius:50%}.datepicker---date-picker-container---1kNBY .react-datepicker__day.react-datepicker__day--disabled,.datepicker---date-picker-container---1kNBY .react-datepicker__day:hover.react-datepicker__day--disabled{background:inherit;cursor:default;color:#bbb}.datepicker---date-picker-container---1kNBY .react-datepicker__day-name{padding-top:9px;font-size:12px;font-weight:700}.datepicker---date-picker-container---1kNBY .react-datepicker__day-names{white-space:nowrap}.datepicker---date-picker-container---1kNBY .react-datepicker__day--outside-month{color:#bbb}.datepicker---date-picker-container---1kNBY .react-datepicker__day--today{color:#db3d44}.datepicker---date-picker-container---1kNBY .react-datepicker__day--selected{color:#fff;font-weight:700;background-color:#db3d44;border-radius:50%}.datepicker---date-picker-container---1kNBY .react-datepicker__day--selected:hover{background-color:#db3d44}@media all and (-ms-high-contrast:none),(-ms-high-contrast:active){.datepicker---date-input---1vOyn>input{height:32px}}.datepicker---date-input-container---2zD9B{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;line-height:20px;position:relative}.datepicker---date-input-container---2zD9B *,.datepicker---date-input-container---2zD9B :after,.datepicker---date-input-container---2zD9B :before{-webkit-box-sizing:inherit;-moz-box-sizing:inherit;box-sizing:inherit}.datepicker---date-input---1vOyn{width:120px}.datepicker---date-input---1vOyn>input{display:block;width:100%;height:auto;line-height:inherit;padding:5px 12px;padding-left:30px;font-size:13px;color:#222;border:1px solid #ccc;border-radius:3px;outline:0}.datepicker---date-input---1vOyn>input:focus{border-color:#0096cc}.datepicker---date-input-icon---1UeQu{position:absolute;left:9px;top:8px;color:#666;width:14px;height:14px}@media all and (-ms-high-contrast:none),(-ms-high-contrast:active){.datepicker---time-input---2ICRB>input{height:32px}}.datepicker---time-input-container---s4Ikh{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;line-height:20px;position:relative}.datepicker---time-input-container---s4Ikh *,.datepicker---time-input-container---s4Ikh :after,.datepicker---time-input-container---s4Ikh :before{-webkit-box-sizing:inherit;-moz-box-sizing:inherit;box-sizing:inherit}.datepicker---time-input---2ICRB{width:120px}.datepicker---time-input---2ICRB>input{display:block;width:100%;height:auto;line-height:inherit;padding:5px 12px;padding-left:30px;font-size:13px;color:#222;border:1px solid #ccc;border-radius:3px;outline:0}.datepicker---time-input---2ICRB>input:focus{border-color:#0096cc}.datepicker---time-input-icon---3TeZ8{position:absolute;left:9px;top:8px;color:#666;width:14px;height:14px} -------------------------------------------------------------------------------- /docs/1dada8ea59a31dad3dbb76472a3de6ba.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | angle-left 4 | 5 | -------------------------------------------------------------------------------- /docs/35071d00819547a959ef3450c129d77e.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trendmicro-frontend/react-datepicker/f84757698c83b4c8219aa5672588b1f9ce4ff931/docs/35071d00819547a959ef3450c129d77e.eot -------------------------------------------------------------------------------- /docs/37f4597594857b017901209aae0a60e1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /docs/3eff5a4e9fb92ca96cbf2fa77649b8c1.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trendmicro-frontend/react-datepicker/f84757698c83b4c8219aa5672588b1f9ce4ff931/docs/3eff5a4e9fb92ca96cbf2fa77649b8c1.ttf -------------------------------------------------------------------------------- /docs/5ce48c51b8e3b5d666156ff739655a38.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | angle-right 4 | 5 | -------------------------------------------------------------------------------- /docs/81436770636b45508203b3022075ae73.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trendmicro-frontend/react-datepicker/f84757698c83b4c8219aa5672588b1f9ce4ff931/docs/81436770636b45508203b3022075ae73.ttf -------------------------------------------------------------------------------- /docs/8a53d21a4d9aa1aac2bf15093bd748c4.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trendmicro-frontend/react-datepicker/f84757698c83b4c8219aa5672588b1f9ce4ff931/docs/8a53d21a4d9aa1aac2bf15093bd748c4.woff -------------------------------------------------------------------------------- /docs/d6c7e9d3e5adb7a5261c5ad9f7d3caaa.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trendmicro-frontend/react-datepicker/f84757698c83b4c8219aa5672588b1f9ce4ff931/docs/d6c7e9d3e5adb7a5261c5ad9f7d3caaa.woff -------------------------------------------------------------------------------- /docs/d9c6d360d27eac625da0405245ec9f0d.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trendmicro-frontend/react-datepicker/f84757698c83b4c8219aa5672588b1f9ce4ff931/docs/d9c6d360d27eac625da0405245ec9f0d.eot -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | React DatePicker 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/.stylintrc: -------------------------------------------------------------------------------- 1 | // 2 | // https://github.com/rossPatton/stylint 3 | // 4 | { 5 | "blocks": false, 6 | "brackets": "always", 7 | "colons": "always", 8 | "colors": false, 9 | "commaSpace": "always", 10 | "commentSpace": false, 11 | "cssLiteral": "never", 12 | "depthLimit": false, 13 | "duplicates": false, 14 | "efficient": "always", 15 | "extendPref": false, 16 | "globalDupe": false, 17 | "indentPref": false, 18 | "leadingZero": "never", 19 | "maxErrors": false, 20 | "maxWarnings": false, 21 | "mixed": false, 22 | "namingConvention": false, 23 | "namingConventionStrict": false, 24 | "none": "never", 25 | "noImportant": false, 26 | "parenSpace": false, 27 | "placeholders": "always", 28 | "prefixVarsWithDollar": "always", 29 | "quotePref": false, 30 | "semicolons": "always", 31 | "sortOrder": false, 32 | "stackedProperties": "never", 33 | "trailingWhitespace": "never", 34 | "universal": false, 35 | "valid": true, 36 | "zeroUnits": "never", 37 | "zIndexNormalize": false 38 | } 39 | -------------------------------------------------------------------------------- /examples/DatePicker/Controlled.jsx: -------------------------------------------------------------------------------- 1 | import Anchor from '@trendmicro/react-anchor'; 2 | import moment from 'moment'; 3 | import PropTypes from 'prop-types'; 4 | import React, { PureComponent } from 'react'; 5 | import DatePicker from '../../src'; 6 | 7 | export default class extends PureComponent { 8 | static propTypes = { 9 | locale: PropTypes.string 10 | }; 11 | 12 | state = this.getInitialState(); 13 | 14 | getInitialState() { 15 | const now = moment(); 16 | 17 | return { 18 | date: moment(now).format('YYYY-MM-DD') 19 | }; 20 | } 21 | render() { 22 | const { locale } = this.props; 23 | const { date } = this.state; 24 | 25 | return ( 26 |
27 |

Controlled Component

28 |

Selected: {date}

29 | { 33 | this.setState(state => ({ date: date })); 34 | }} 35 | /> 36 |
37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/DatePicker/Dropdown.jsx: -------------------------------------------------------------------------------- 1 | import '@trendmicro/react-dropdown/dist/react-dropdown.css'; 2 | import Anchor from '@trendmicro/react-anchor'; 3 | import Dropdown from '@trendmicro/react-dropdown'; 4 | import moment from 'moment'; 5 | import PropTypes from 'prop-types'; 6 | import React, { PureComponent } from 'react'; 7 | import DatePicker, { DateInput } from '../../src'; 8 | 9 | export default class extends PureComponent { 10 | static propTypes = { 11 | locale: PropTypes.string 12 | }; 13 | 14 | state = this.getInitialState(); 15 | 16 | getInitialState() { 17 | const now = moment(); 18 | 19 | return { 20 | date: moment(now).format('YYYY-MM-DD') 21 | }; 22 | } 23 | render() { 24 | const { locale } = this.props; 25 | const { date } = this.state; 26 | 27 | return ( 28 |
29 |

Dropdown

30 |

Selected: {date}

31 | 32 | 37 | { 40 | this.setState(state => ({ date: value })); 41 | }} 42 | /> 43 | 44 | 45 | { 49 | this.setState(state => ({ date: date })); 50 | }} 51 | /> 52 | 53 | 54 |
55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /examples/DatePicker/Selectable.jsx: -------------------------------------------------------------------------------- 1 | import Anchor from '@trendmicro/react-anchor'; 2 | import moment from 'moment'; 3 | import PropTypes from 'prop-types'; 4 | import React, { PureComponent } from 'react'; 5 | import DatePicker from '../../src'; 6 | 7 | export default class extends PureComponent { 8 | static propTypes = { 9 | locale: PropTypes.string 10 | }; 11 | 12 | state = this.getInitialState(); 13 | 14 | getInitialState() { 15 | const now = moment(); 16 | 17 | return { 18 | date: moment(now).format('YYYY-MM-DD'), 19 | minDate: moment(now).subtract(7, 'days').format('YYYY-MM-DD'), 20 | maxDate: moment(now).add(7, 'days').format('YYYY-MM-DD') 21 | }; 22 | } 23 | render() { 24 | const { locale } = this.props; 25 | const { date, minDate, maxDate } = this.state; 26 | 27 | return ( 28 |
29 |

Selectable Date ({minDate} ~ {maxDate})

30 |

Selected: {date}

31 | { 37 | this.setState(state => ({ date: date })); 38 | }} 39 | /> 40 |
41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /examples/DatePicker/Uncontrolled.jsx: -------------------------------------------------------------------------------- 1 | import Anchor from '@trendmicro/react-anchor'; 2 | import moment from 'moment'; 3 | import PropTypes from 'prop-types'; 4 | import React, { PureComponent } from 'react'; 5 | import DatePicker from '../../src'; 6 | 7 | export default class extends PureComponent { 8 | static propTypes = { 9 | locale: PropTypes.string 10 | }; 11 | 12 | state = this.getInitialState(); 13 | 14 | getInitialState() { 15 | const now = moment(); 16 | 17 | return { 18 | date: moment(now).format('YYYY-MM-DD') 19 | }; 20 | } 21 | render() { 22 | const { locale } = this.props; 23 | const { date } = this.state; 24 | 25 | return ( 26 |
27 |

Uncontrolled Component

28 |

Selected: {date}

29 | { 33 | this.setState(state => ({ date: date })); 34 | }} 35 | /> 36 |
37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/DatePicker/index.js: -------------------------------------------------------------------------------- 1 | export Controlled from './Controlled'; 2 | export Uncontrolled from './Uncontrolled'; 3 | export Selectable from './Selectable'; 4 | export Dropdown from './Dropdown'; 5 | -------------------------------------------------------------------------------- /examples/DateTimePicker/Controlled.jsx: -------------------------------------------------------------------------------- 1 | import Anchor from '@trendmicro/react-anchor'; 2 | import moment from 'moment'; 3 | import PropTypes from 'prop-types'; 4 | import React, { PureComponent } from 'react'; 5 | import DatePicker, { TimeInput, DateInput } from '../../src'; 6 | 7 | export default class extends PureComponent { 8 | static propTypes = { 9 | locale: PropTypes.string 10 | }; 11 | 12 | state = this.getInitialState(); 13 | 14 | getInitialState() { 15 | const now = moment(); 16 | 17 | return { 18 | date: moment(now).format('YYYY-MM-DD'), 19 | time: moment(now).format('hh:mm:ss') 20 | }; 21 | } 22 | render() { 23 | const { locale } = this.props; 24 | const { date, time } = this.state; 25 | 26 | return ( 27 |
28 |

Controlled Component

29 |

Selected: {date} {time}

30 |
31 |
32 | { 35 | this.setState(state => ({ date: value })); 36 | }} 37 | /> 38 |
39 |
40 | { 43 | this.setState(state => ({ time: value })); 44 | }} 45 | /> 46 |
47 |
48 |
49 | { 53 | this.setState(state => ({ date: date })); 54 | }} 55 | /> 56 |
57 |
58 | ); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /examples/DateTimePicker/Uncontrolled.jsx: -------------------------------------------------------------------------------- 1 | import Anchor from '@trendmicro/react-anchor'; 2 | import moment from 'moment'; 3 | import PropTypes from 'prop-types'; 4 | import React, { PureComponent } from 'react'; 5 | import DatePicker, { TimeInput, DateInput } from '../../src'; 6 | 7 | export default class extends PureComponent { 8 | static propTypes = { 9 | locale: PropTypes.string 10 | }; 11 | 12 | state = this.getInitialState(); 13 | 14 | getInitialState() { 15 | const now = moment(); 16 | 17 | return { 18 | date: moment(now).format('YYYY-MM-DD'), 19 | time: moment(now).format('hh:mm:ss') 20 | }; 21 | } 22 | render() { 23 | const { locale } = this.props; 24 | const { date, time } = this.state; 25 | 26 | return ( 27 |
28 |

Uncontrolled Component

29 |

Selected: {date} {time}

30 |
31 |
32 | { 35 | this.setState(state => ({ date: value })); 36 | }} 37 | /> 38 |
39 |
40 | { 43 | this.setState(state => ({ time: value })); 44 | }} 45 | /> 46 |
47 |
48 |
49 | { 53 | this.setState(state => ({ date: date })); 54 | }} 55 | /> 56 |
57 |
58 | ); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /examples/DateTimePicker/index.js: -------------------------------------------------------------------------------- 1 | export Controlled from './Controlled'; 2 | export Uncontrolled from './Uncontrolled'; 3 | -------------------------------------------------------------------------------- /examples/DateTimeRangePicker/Controlled.jsx: -------------------------------------------------------------------------------- 1 | import Anchor from '@trendmicro/react-anchor'; 2 | import moment from 'moment'; 3 | import PropTypes from 'prop-types'; 4 | import React, { PureComponent } from 'react'; 5 | import DateTimeRangePicker from './DateTimeRangePicker'; 6 | 7 | const normalizeDateString = (dateString) => { 8 | let m = moment(dateString); 9 | if (!m.isValid()) { 10 | m = moment(); 11 | } 12 | return m.format('YYYY-MM-DD'); 13 | }; 14 | 15 | const normalizeTimeString = (timeString) => { 16 | let [hh = '00', mm = '00', ss = '00'] = timeString.split(':'); 17 | hh = Number(hh) || 0; 18 | mm = Number(mm) || 0; 19 | ss = Number(ss) || 0; 20 | hh = (hh < 0 || hh > 23) ? '00' : ('0' + hh).slice(-2); 21 | mm = (mm < 0 || mm > 59) ? '00' : ('0' + mm).slice(-2); 22 | ss = (ss < 0 || ss > 59) ? '00' : ('0' + ss).slice(-2); 23 | return `${hh}:${mm}:${ss}`; 24 | }; 25 | 26 | export default class extends PureComponent { 27 | static propTypes = { 28 | locale: PropTypes.string 29 | }; 30 | 31 | state = this.getInitialState(); 32 | 33 | changeStartDate = (date) => { 34 | if (!date) { 35 | return; 36 | } 37 | 38 | this.setState(state => { 39 | const startDate = normalizeDateString(date); 40 | const endDate = normalizeDateString(state.endDate); 41 | const startTime = normalizeTimeString(state.startTime); 42 | const endTime = normalizeTimeString(state.endTime); 43 | const isoStartDateTime = `${startDate}T${startTime}`; 44 | const isoEndDateTime = `${endDate}T${endTime}`; 45 | const isEndDateAfterMaxDate = state.maxDate && moment(endDate).isAfter(moment(state.maxDate).endOf('day')); 46 | const isSameOrAfterEnd = moment(isoStartDateTime).isSameOrAfter(isoEndDateTime); 47 | 48 | let nextEndDate = endDate; 49 | if (isEndDateAfterMaxDate) { 50 | nextEndDate = normalizeDateString(state.maxDate); 51 | } else if (isSameOrAfterEnd) { 52 | nextEndDate = startDate; 53 | } 54 | 55 | return { 56 | startDate: startDate, 57 | endDate: nextEndDate, 58 | startTime: startTime, 59 | endTime: isSameOrAfterEnd ? startTime : endTime 60 | }; 61 | }); 62 | }; 63 | 64 | changeEndDate = (date) => { 65 | if (!date) { 66 | return; 67 | } 68 | 69 | this.setState(state => { 70 | const startDate = normalizeDateString(state.startDate); 71 | const endDate = normalizeDateString(date); 72 | const startTime = normalizeTimeString(state.startTime); 73 | const endTime = normalizeTimeString(state.endTime); 74 | const isoStartDateTime = `${startDate}T${startTime}`; 75 | const isoEndDateTime = `${endDate}T${endTime}`; 76 | const isStartDateBeforeMinDate = state.minDate && moment(startDate).isBefore(moment(state.minDate).startOf('day')); 77 | const isSameOrBeforeStart = moment(isoEndDateTime).isSameOrBefore(isoStartDateTime); 78 | 79 | let nextStartDate = startDate; 80 | if (isStartDateBeforeMinDate) { 81 | nextStartDate = normalizeDateString(state.minDate); 82 | } else if (isSameOrBeforeStart) { 83 | nextStartDate = endDate; 84 | } 85 | 86 | return { 87 | startDate: nextStartDate, 88 | endDate: endDate, 89 | startTime: isSameOrBeforeStart ? endTime : startTime, 90 | endTime: endTime 91 | }; 92 | }); 93 | }; 94 | 95 | changeStartTime = (time = '00:00:00') => { 96 | this.setState(state => { 97 | const startDate = normalizeDateString(state.startDate); 98 | const endDate = normalizeDateString(state.endDate); 99 | const startTime = normalizeTimeString(time); 100 | const endTime = normalizeTimeString(state.endTime); 101 | const isoStartDateTime = `${startDate}T${startTime}`; 102 | const isoEndDateTime = `${endDate}T${endTime}`; 103 | const isSameOrAfterEnd = moment(isoStartDateTime).isSameOrAfter(isoEndDateTime); 104 | 105 | return { 106 | startTime: startTime, 107 | endTime: isSameOrAfterEnd ? startTime : endTime 108 | }; 109 | }); 110 | }; 111 | 112 | changeEndTime = (time = '00:00:00') => { 113 | this.setState(state => { 114 | const startDate = normalizeDateString(state.startDate); 115 | const endDate = normalizeDateString(state.endDate); 116 | const startTime = normalizeTimeString(state.startTime); 117 | const endTime = normalizeTimeString(time); 118 | const isoStartDateTime = `${startDate}T${startTime}`; 119 | const isoEndDateTime = `${endDate}T${endTime}`; 120 | const isSameOrBeforeStart = moment(isoEndDateTime).isSameOrBefore(isoStartDateTime); 121 | 122 | return { 123 | startTime: isSameOrBeforeStart ? endTime : startTime, 124 | endTime: endTime 125 | }; 126 | }); 127 | }; 128 | 129 | getInitialState() { 130 | const now = moment(); 131 | 132 | return { 133 | minDate: '2000-01-01', 134 | maxDate: moment(now).format('YYYY-MM-DD'), 135 | startDate: moment(now).format('YYYY-MM-DD'), 136 | startTime: moment(now).format('hh:mm:ss'), 137 | endDate: moment(now).add(7, 'days').format('YYYY-MM-DD'), 138 | endTime: moment(now).add(7, 'days').format('hh:mm:ss') 139 | }; 140 | } 141 | render() { 142 | const { locale } = this.props; 143 | const { minDate, maxDate, startDate, startTime, endDate, endTime } = this.state; 144 | 145 | return ( 146 |
147 |

Controlled Component

148 |

Note: This example will update invalid date/time range.

149 |
    150 |
  • Minimum: {minDate}
  • 151 |
  • Maximum: {maxDate}
  • 152 |
  • Range: {startDate} {startTime} ~ {endDate} {endTime}
  • 153 |
154 | 167 |
168 | ); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /examples/DateTimeRangePicker/DateTimeRangePicker.jsx: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React, { PureComponent } from 'react'; 3 | import { uncontrollable } from 'uncontrollable'; 4 | import DatePicker, { TimeInput, DateInput } from '../../src'; 5 | import styles from './DateTimeRangePicker.styl'; 6 | 7 | class DateTimeRangePicker extends PureComponent { 8 | static propTypes = { 9 | locale: PropTypes.string, 10 | minDate: PropTypes.oneOfType([ 11 | PropTypes.object, 12 | PropTypes.string 13 | ]), 14 | maxDate: PropTypes.oneOfType([ 15 | PropTypes.object, 16 | PropTypes.string 17 | ]), 18 | startDate: PropTypes.string, 19 | startTime: PropTypes.string, 20 | endDate: PropTypes.string, 21 | endTime: PropTypes.string, 22 | onChangeStartDate: PropTypes.func, 23 | onChangeStartTime: PropTypes.func, 24 | onChangeEndDate: PropTypes.func, 25 | onChangeEndTime: PropTypes.func 26 | }; 27 | 28 | render() { 29 | const { 30 | locale, 31 | minDate, 32 | maxDate, 33 | startDate, 34 | startTime, 35 | endDate, 36 | endTime, 37 | onChangeStartDate, 38 | onChangeStartTime, 39 | onChangeEndDate, 40 | onChangeEndTime 41 | } = this.props; 42 | 43 | return ( 44 |
45 |
46 |
47 | 53 |
54 |
55 | 59 |
60 |
~
61 |
62 | 68 |
69 |
70 | 74 |
75 |
76 |
77 |
78 | 85 |
86 |
87 | 94 |
95 |
96 |
97 | ); 98 | } 99 | } 100 | 101 | export default uncontrollable(DateTimeRangePicker, { 102 | // Define the pairs of prop/handlers you want to be uncontrollable 103 | startDate: 'onChangeStartDate', 104 | startTime: 'onChangeStartTime', 105 | endDate: 'onChangeEndDate', 106 | endTime: 'onChangeEndTime' 107 | }); 108 | -------------------------------------------------------------------------------- /examples/DateTimeRangePicker/DateTimeRangePicker.styl: -------------------------------------------------------------------------------- 1 | .date-picker-pane { 2 | display: inline-block; 3 | 4 | .date-picker-pane-header, 5 | .date-picker-pane-body, 6 | .date-picker-pane-footer { 7 | white-space: nowrap; 8 | } 9 | 10 | .date-picker-pane-header { 11 | .tilde { 12 | display: inline-block; 13 | line-height: 32px; 14 | text-align: center; 15 | width: 24px; 16 | vertical-align: top; 17 | } 18 | } 19 | 20 | .date-picker-pane-body { 21 | margin-top: 8px; 22 | 23 | .date-picker-pane-container { 24 | display: inline-block; 25 | } 26 | 27 | .date-picker-pane-container + .date-picker-pane-container { 28 | margin-left: 24px; 29 | } 30 | .date-picker-pane-container + .date-picker-pane-container:before { 31 | content: ' '; 32 | display: block; 33 | height: 260px; 34 | vertical-align: middle; 35 | margin-left: -12px; 36 | float: left; 37 | margin-right: 12px; 38 | margin-top: 16px; 39 | border-left: 1px solid #E6E6E6; 40 | } 41 | } 42 | 43 | .date-picker-pane-footer { 44 | margin-top: 12px; 45 | } 46 | } 47 | 48 | .input-icon-group { 49 | position: relative; 50 | display: inline-block; 51 | vertical-align: top; 52 | 53 | + .input-icon-group { 54 | margin-left: 8px; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /examples/DateTimeRangePicker/Dropdown.jsx: -------------------------------------------------------------------------------- 1 | import _isInteger from 'lodash/isInteger'; 2 | import moment from 'moment'; 3 | import PropTypes from 'prop-types'; 4 | import React, { PureComponent } from 'react'; 5 | import Anchor from '@trendmicro/react-anchor'; 6 | import { Button } from '@trendmicro/react-buttons'; 7 | import Dropdown, { MenuItem } from '@trendmicro/react-dropdown'; 8 | import DateTimeRangePicker from './DateTimeRangePicker'; 9 | 10 | const normalizeDateString = (dateString) => { 11 | let m = moment(dateString); 12 | if (!m.isValid()) { 13 | m = moment(); 14 | } 15 | return m.format('YYYY-MM-DD'); 16 | }; 17 | 18 | const normalizeTimeString = (timeString) => { 19 | let [hh = '00', mm = '00', ss = '00'] = timeString.split(':'); 20 | hh = Number(hh) || 0; 21 | mm = Number(mm) || 0; 22 | ss = Number(ss) || 0; 23 | hh = (hh < 0 || hh > 23) ? '00' : ('0' + hh).slice(-2); 24 | mm = (mm < 0 || mm > 59) ? '00' : ('0' + mm).slice(-2); 25 | ss = (ss < 0 || ss > 59) ? '00' : ('0' + ss).slice(-2); 26 | return `${hh}:${mm}:${ss}`; 27 | }; 28 | 29 | const mapPeriodToString = (period) => { 30 | if (period === 'custom') { 31 | return 'Custom range...'; 32 | } 33 | 34 | // Only days are supported (e.g. 1, 7, '1d', or '7d') 35 | if (_isInteger(period) || period.match(/^\d+d$/)) { 36 | const days = parseInt(period, 10); 37 | if (days === 1) { 38 | return 'Today'; 39 | } 40 | if (days > 1) { 41 | return `Last ${days} days`; 42 | } 43 | } 44 | 45 | return ''; 46 | }; 47 | 48 | class DateTimeRangePickerDropdown extends PureComponent { 49 | static propTypes = { 50 | locale: PropTypes.string, 51 | startDate: PropTypes.string, 52 | startTime: PropTypes.string, 53 | endDate: PropTypes.string, 54 | endTime: PropTypes.string, 55 | minDate: PropTypes.string, 56 | maxDate: PropTypes.string, 57 | periods: PropTypes.arrayOf( 58 | PropTypes.oneOfType([ 59 | PropTypes.number, 60 | PropTypes.string 61 | ]) 62 | ), 63 | period: PropTypes.oneOfType([ 64 | PropTypes.number, 65 | PropTypes.string 66 | ]), 67 | defaultPeriod: PropTypes.oneOfType([ 68 | PropTypes.number, 69 | PropTypes.string 70 | ]), 71 | onSelect: PropTypes.func 72 | }; 73 | static defaultProps = { 74 | periods: ['1d', '7d', '14d', '30d', '60d'], 75 | defaultPeriod: '7d' 76 | }; 77 | 78 | state = this.getInitialState(); 79 | 80 | changeStartDate = (date) => { 81 | if (!date) { 82 | return; 83 | } 84 | 85 | this.setState(state => { 86 | const startDate = normalizeDateString(date); 87 | const endDate = normalizeDateString(state.nextEndDate); 88 | const startTime = normalizeTimeString(state.nextStartTime); 89 | const endTime = normalizeTimeString(state.nextEndTime); 90 | const isoStartDateTime = `${startDate}T${startTime}`; 91 | const isoEndDateTime = `${endDate}T${endTime}`; 92 | const isEndDateAfterMaxDate = state.maxDate && moment(endDate).isAfter(moment(state.maxDate).endOf('day')); 93 | const isSameOrAfterEnd = moment(isoStartDateTime).isSameOrAfter(isoEndDateTime); 94 | 95 | let nextEndDate = endDate; 96 | if (isEndDateAfterMaxDate) { 97 | nextEndDate = normalizeDateString(state.maxDate); 98 | } else if (isSameOrAfterEnd) { 99 | nextEndDate = startDate; 100 | } 101 | 102 | return { 103 | nextStartDate: startDate, 104 | nextEndDate: nextEndDate, 105 | nextStartTime: startTime, 106 | nextEndTime: isSameOrAfterEnd ? startTime : endTime 107 | }; 108 | }); 109 | }; 110 | 111 | changeEndDate = (date) => { 112 | if (!date) { 113 | return; 114 | } 115 | 116 | this.setState(state => { 117 | const startDate = normalizeDateString(state.nextStartDate); 118 | const endDate = normalizeDateString(date); 119 | const startTime = normalizeTimeString(state.nextStartTime); 120 | const endTime = normalizeTimeString(state.nextEndTime); 121 | const isoStartDateTime = `${startDate}T${startTime}`; 122 | const isoEndDateTime = `${endDate}T${endTime}`; 123 | const isStartDateBeforeMinDate = state.minDate && moment(startDate).isBefore(moment(state.minDate).startOf('day')); 124 | const isSameOrBeforeStart = moment(isoEndDateTime).isSameOrBefore(isoStartDateTime); 125 | 126 | let nextStartDate = startDate; 127 | if (isStartDateBeforeMinDate) { 128 | nextStartDate = normalizeDateString(state.minDate); 129 | } else if (isSameOrBeforeStart) { 130 | nextStartDate = endDate; 131 | } 132 | 133 | return { 134 | nextStartDate: nextStartDate, 135 | nextEndDate: endDate, 136 | nextStartTime: isSameOrBeforeStart ? endTime : startTime, 137 | nextEndTime: endTime 138 | }; 139 | }); 140 | }; 141 | 142 | changeStartTime = (time = '00:00:00') => { 143 | this.setState(state => { 144 | const startDate = normalizeDateString(state.nextStartDate); 145 | const endDate = normalizeDateString(state.nextEndDate); 146 | const startTime = normalizeTimeString(time); 147 | const endTime = normalizeTimeString(state.nextEndTime); 148 | const isoStartDateTime = `${startDate}T${startTime}`; 149 | const isoEndDateTime = `${endDate}T${endTime}`; 150 | const isSameOrAfterEnd = moment(isoStartDateTime).isSameOrAfter(isoEndDateTime); 151 | 152 | return { 153 | nextStartTime: startTime, 154 | nextEndTime: isSameOrAfterEnd ? startTime : endTime 155 | }; 156 | }); 157 | }; 158 | 159 | changeEndTime = (time = '00:00:00') => { 160 | this.setState(state => { 161 | const startDate = normalizeDateString(state.nextStartDate); 162 | const endDate = normalizeDateString(state.nextEndDate); 163 | const startTime = normalizeTimeString(state.nextStartTime); 164 | const endTime = normalizeTimeString(time); 165 | const isoStartDateTime = `${startDate}T${startTime}`; 166 | const isoEndDateTime = `${endDate}T${endTime}`; 167 | const isSameOrBeforeStart = moment(isoEndDateTime).isSameOrBefore(isoStartDateTime); 168 | 169 | return { 170 | nextStartTime: isSameOrBeforeStart ? endTime : startTime, 171 | nextEndTime: endTime 172 | }; 173 | }); 174 | }; 175 | 176 | handleDropdownSelect = (eventKey) => { 177 | const period = eventKey; 178 | 179 | this.setState(state => ({ 180 | period: period, 181 | 182 | // Restore to previous state 183 | nextStartDate: state.startDate, 184 | nextStartTime: state.startTime, 185 | nextEndDate: state.endDate, 186 | nextEndTime: state.endTime 187 | }), () => { 188 | const { onSelect } = this.props; 189 | 190 | if (typeof onSelect !== 'function') { 191 | return; 192 | } 193 | 194 | if (period !== 'custom') { 195 | onSelect({ period }); 196 | } 197 | }); 198 | }; 199 | 200 | handleDropdownClose = () => { 201 | this.setState(state => ({ 202 | open: false, 203 | 204 | // Restore to previous state 205 | period: this.props.period !== undefined ? this.props.period : state.period, 206 | nextStartDate: state.startDate, 207 | nextStartTime: state.startTime, 208 | nextEndDate: state.endDate, 209 | nextEndTime: state.endTime 210 | })); 211 | }; 212 | 213 | handleDropdownToggle = (open) => { 214 | this.setState(state => { 215 | const { period } = state; 216 | return { 217 | open: open || period === 'custom' 218 | }; 219 | }); 220 | }; 221 | 222 | handleClickApplyForCustomRange = () => { 223 | this.setState(state => ({ 224 | open: false, 225 | 226 | // Apply specified range 227 | startDate: state.nextStartDate, 228 | startTime: state.nextStartTime, 229 | endDate: state.nextEndDate, 230 | endTime: state.nextEndTime 231 | }), () => { 232 | const { onSelect } = this.props; 233 | 234 | if (typeof onSelect !== 'function') { 235 | return; 236 | } 237 | 238 | const { period, startDate, startTime, endDate, endTime } = this.state; 239 | if (period === 'custom') { 240 | onSelect({ period, startDate, startTime, endDate, endTime }); 241 | } else { 242 | onSelect({ period }); 243 | } 244 | }); 245 | }; 246 | 247 | handleClickCancelForCustomRange = () => { 248 | this.setState(state => ({ 249 | open: false, 250 | 251 | // Restore to previous state 252 | nextStartDate: state.startDate, 253 | nextStartTime: state.startTime, 254 | nextEndDate: state.endDate, 255 | nextEndTime: state.endTime 256 | })); 257 | }; 258 | 259 | getInitialState() { 260 | const now = moment().seconds(0); 261 | const days = parseInt(this.props.defaultPeriod, 10) || parseInt(DateTimeRangePickerDropdown.defaultProps.defaultPeriod, 10) || 0; 262 | const startOfDay = moment(now).startOf('day'); 263 | const endOfDay = moment(now).endOf('day'); 264 | const { 265 | startDate = moment(startOfDay).subtract((days > 0) ? (days - 1) : 0, 'days').format('YYYY-MM-DD'), 266 | startTime = moment(startOfDay).subtract((days > 0) ? (days - 1) : 0, 'days').format('HH:mm:ss'), 267 | endDate = moment(endOfDay).format('YYYY-MM-DD'), 268 | endTime = moment(endOfDay).format('HH:mm:ss'), 269 | minDate = null, // Defaults to no minimum 270 | maxDate = null, // Defaults to no maximum 271 | } = this.props; 272 | 273 | return { 274 | minDate: minDate, 275 | maxDate: maxDate, 276 | 277 | // prev 278 | startDate: startDate, 279 | startTime: startTime, 280 | endDate: endDate, 281 | endTime: endTime, 282 | 283 | // next 284 | nextStartDate: startDate, 285 | nextStartTime: startTime, 286 | nextEndDate: endDate, 287 | nextEndTime: endTime, 288 | 289 | // Dropdown 290 | open: false, 291 | period: this.props.period 292 | }; 293 | } 294 | 295 | componentDidUpdate(prevProps, prevState) { 296 | const { period: prevPeriod } = prevProps; 297 | const { period: nextPeriod } = this.props; 298 | if (prevPeriod !== nextPeriod) { 299 | this.handleDropdownSelect(nextPeriod); 300 | } 301 | } 302 | 303 | render() { 304 | const { locale, periods } = this.props; 305 | const { 306 | minDate, maxDate, 307 | startDate, startTime, endDate, endTime, 308 | nextStartDate, nextStartTime, nextEndDate, nextEndTime 309 | } = this.state; 310 | const period = this.state.period !== undefined ? this.state.period : this.props.defaultPeriod; 311 | const showDateTimeRangePicker = this.state.open && (period === 'custom'); 312 | 313 | return ( 314 |
315 |

Dropdown

316 |

Selected: {mapPeriodToString(period)}

317 | {period === 'custom' && 318 |
    319 |
  • Minimum: {minDate}
  • 320 |
  • Maximum: {maxDate}
  • 321 |
  • Range: {startDate} {startTime} - {endDate} {endTime}
  • 322 |
323 | } 324 | 330 | 331 | {mapPeriodToString(period)} 332 | 333 | 338 | 339 | {periods.map(period => ( 340 | 341 | {mapPeriodToString(period)} 342 | 343 | ))} 344 | 345 | 346 | {mapPeriodToString('custom')} 347 | 348 | 349 | {showDateTimeRangePicker && 350 |
357 | 370 |
371 |
372 | 379 | 382 |
383 |
384 |
385 | } 386 |
387 |
388 |
389 | ); 390 | } 391 | } 392 | 393 | export default DateTimeRangePickerDropdown; 394 | -------------------------------------------------------------------------------- /examples/DateTimeRangePicker/DropdownRight.jsx: -------------------------------------------------------------------------------- 1 | import '@trendmicro/react-buttons/dist/react-buttons.css'; 2 | import '@trendmicro/react-dropdown/dist/react-dropdown.css'; 3 | import Anchor from '@trendmicro/react-anchor'; 4 | import { Button } from '@trendmicro/react-buttons'; 5 | import Dropdown, { MenuItem } from '@trendmicro/react-dropdown'; // @trendmicro/react-dropdown@0.7.0 or above is required 6 | import moment from 'moment'; 7 | import PropTypes from 'prop-types'; 8 | import React, { PureComponent } from 'react'; 9 | import DateTimeRangePicker from './DateTimeRangePicker'; 10 | 11 | const normalizeDateString = (dateString) => { 12 | let m = moment(dateString); 13 | if (!m.isValid()) { 14 | m = moment(); 15 | } 16 | return m.format('YYYY-MM-DD'); 17 | }; 18 | 19 | const normalizeTimeString = (timeString) => { 20 | let [hh = '00', mm = '00', ss = '00'] = timeString.split(':'); 21 | hh = Number(hh) || 0; 22 | mm = Number(mm) || 0; 23 | ss = Number(ss) || 0; 24 | hh = (hh < 0 || hh > 23) ? '00' : ('0' + hh).slice(-2); 25 | mm = (mm < 0 || mm > 59) ? '00' : ('0' + mm).slice(-2); 26 | ss = (ss < 0 || ss > 59) ? '00' : ('0' + ss).slice(-2); 27 | return `${hh}:${mm}:${ss}`; 28 | }; 29 | 30 | const mapPeriodToString = (period) => { 31 | return { 32 | '1d': 'Last 24 hours', 33 | '7d': 'Last 7 days', 34 | '30d': 'Last 30 days', 35 | '90d': 'Last 90 days', 36 | 'custom': 'Custom range' 37 | }[period]; 38 | }; 39 | 40 | export default class extends PureComponent { 41 | static propTypes = { 42 | locale: PropTypes.string 43 | }; 44 | 45 | state = this.getInitialState(); 46 | 47 | changeStartDate = (date) => { 48 | if (!date) { 49 | return; 50 | } 51 | const startDate = normalizeDateString(date); 52 | const endDate = normalizeDateString(this.state.nextEndDate); 53 | const startTime = normalizeTimeString(this.state.nextStartTime); 54 | const endTime = normalizeTimeString(this.state.nextEndTime); 55 | const isoStartDateTime = `${startDate}T${startTime}`; 56 | const isoEndDateTime = `${endDate}T${endTime}`; 57 | const isSameOrAfterEnd = moment(isoStartDateTime).isSameOrAfter(isoEndDateTime); 58 | 59 | this.setState({ 60 | nextStartDate: startDate, 61 | nextEndDate: isSameOrAfterEnd ? startDate : endDate, 62 | nextStartTime: startTime, 63 | nextEndTime: isSameOrAfterEnd ? startTime : endTime 64 | }); 65 | }; 66 | changeEndDate = (date) => { 67 | if (!date) { 68 | return; 69 | } 70 | const startDate = normalizeDateString(this.state.nextStartDate); 71 | const endDate = normalizeDateString(date); 72 | const startTime = normalizeTimeString(this.state.nextStartTime); 73 | const endTime = normalizeTimeString(this.state.nextEndTime); 74 | const isoStartDateTime = `${startDate}T${startTime}`; 75 | const isoEndDateTime = `${endDate}T${endTime}`; 76 | const isSameOrBeforeStart = moment(isoEndDateTime).isSameOrBefore(isoStartDateTime); 77 | 78 | this.setState({ 79 | nextStartDate: isSameOrBeforeStart ? endDate : startDate, 80 | nextEndDate: endDate, 81 | nextStartTime: isSameOrBeforeStart ? endTime : startTime, 82 | nextEndTime: endTime 83 | }); 84 | }; 85 | changeStartTime = (time = '00:00:00') => { 86 | const startDate = normalizeDateString(this.state.nextStartDate); 87 | const endDate = normalizeDateString(this.state.nextEndDate); 88 | const startTime = normalizeTimeString(time); 89 | const endTime = normalizeTimeString(this.state.nextEndTime); 90 | const isoStartDateTime = `${startDate}T${startTime}`; 91 | const isoEndDateTime = `${endDate}T${endTime}`; 92 | const isSameOrAfterEnd = moment(isoStartDateTime).isSameOrAfter(isoEndDateTime); 93 | 94 | this.setState({ 95 | nextStartTime: startTime, 96 | nextEndTime: isSameOrAfterEnd ? startTime : endTime 97 | }); 98 | }; 99 | changeEndTime = (time = '00:00:00') => { 100 | const startDate = normalizeDateString(this.state.nextStartDate); 101 | const endDate = normalizeDateString(this.state.nextEndDate); 102 | const startTime = normalizeTimeString(this.state.nextStartTime); 103 | const endTime = normalizeTimeString(time); 104 | const isoStartDateTime = `${startDate}T${startTime}`; 105 | const isoEndDateTime = `${endDate}T${endTime}`; 106 | const isSameOrBeforeStart = moment(isoEndDateTime).isSameOrBefore(isoStartDateTime); 107 | 108 | this.setState({ 109 | nextStartTime: isSameOrBeforeStart ? endTime : startTime, 110 | nextEndTime: endTime 111 | }); 112 | }; 113 | 114 | getInitialState() { 115 | const now = moment(); 116 | const startDate = moment(now).format('YYYY-MM-DD'); 117 | const startTime = moment(now).format('hh:mm:ss'); 118 | const endDate = moment(now).add(7, 'days').format('YYYY-MM-DD'); 119 | const endTime = moment(now).add(7, 'days').format('hh:mm:ss'); 120 | 121 | return { 122 | // prev 123 | startDate: startDate, 124 | startTime: startTime, 125 | endDate: endDate, 126 | endTime: endTime, 127 | 128 | // next 129 | nextStartDate: startDate, 130 | nextStartTime: startTime, 131 | nextEndDate: endDate, 132 | nextEndTime: endTime, 133 | 134 | // Dropdown 135 | open: false, 136 | period: '1d', 137 | showDateTimeRangePicker: false 138 | }; 139 | } 140 | render() { 141 | const { locale } = this.props; 142 | const { 143 | startDate, startTime, endDate, endTime, 144 | nextStartDate, nextStartTime, nextEndDate, nextEndTime, 145 | period 146 | } = this.state; 147 | 148 | return ( 149 |
150 |

Right Align Dropdown

151 | {period !== 'custom' && 152 |

Selected: {mapPeriodToString(period)}

153 | } 154 | {period === 'custom' && 155 |

Selected: {startDate} {startTime} - {endDate} {endTime}

156 | } 157 | { 162 | this.setState(state => ({ 163 | period: eventKey, 164 | showDateTimeRangePicker: eventKey === 'custom', 165 | nextStartDate: state.startDate, 166 | nextStartTime: state.startTime, 167 | nextEndDate: state.endDate, 168 | nextEndTime: state.endTime 169 | })); 170 | }} 171 | onClose={() => { 172 | this.setState(state => ({ 173 | open: false, 174 | showDateTimeRangePicker: false 175 | })); 176 | }} 177 | onToggle={open => { 178 | this.setState(state => { 179 | const { period } = state; 180 | return { 181 | open: open || period === 'custom' 182 | }; 183 | }); 184 | }} 185 | > 186 | 187 | {mapPeriodToString(period)} 188 | 189 | 190 | 191 | {mapPeriodToString('1d')} 192 | {mapPeriodToString('7d')} 193 | {mapPeriodToString('30d')} 194 | {mapPeriodToString('90d')} 195 | 196 | {mapPeriodToString('custom')} 197 | 198 | 199 | {(this.state.showDateTimeRangePicker || this.state.period === 'custom') && 200 |
207 | 218 |
219 | 237 | 247 |
248 |
249 | } 250 |
251 |
252 |
253 | ); 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /examples/DateTimeRangePicker/Uncontrolled.jsx: -------------------------------------------------------------------------------- 1 | import Anchor from '@trendmicro/react-anchor'; 2 | import moment from 'moment'; 3 | import PropTypes from 'prop-types'; 4 | import React, { PureComponent } from 'react'; 5 | import DateTimeRangePicker from './DateTimeRangePicker'; 6 | 7 | export default class extends PureComponent { 8 | static propTypes = { 9 | locale: PropTypes.string 10 | }; 11 | 12 | state = this.getInitialState(); 13 | 14 | getInitialState() { 15 | const now = moment(); 16 | 17 | return { 18 | minDate: '2000-01-01', 19 | maxDate: moment(now).format('YYYY-MM-DD'), 20 | startDate: moment(now).format('YYYY-MM-DD'), 21 | startTime: moment(now).format('hh:mm:ss'), 22 | endDate: moment(now).add(7, 'days').format('YYYY-MM-DD'), 23 | endTime: moment(now).add(7, 'days').format('hh:mm:ss') 24 | }; 25 | } 26 | 27 | render() { 28 | const { locale } = this.props; 29 | const { minDate, maxDate, startDate, startTime, endDate, endTime } = this.state; 30 | 31 | return ( 32 |
33 |

Uncontrolled Component

34 |

Note: This example will not update invalid date/time range.

35 |
    36 |
  • Minimum: {minDate}
  • 37 |
  • Maximum: {maxDate}
  • 38 |
  • Range: {startDate} {startTime} ~ {endDate} {endTime}
  • 39 |
40 | 49 |
50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /examples/DateTimeRangePicker/index.js: -------------------------------------------------------------------------------- 1 | export Controlled from './Controlled'; 2 | export Uncontrolled from './Uncontrolled'; 3 | export Dropdown from './Dropdown'; 4 | export DropdownRight from './DropdownRight'; 5 | -------------------------------------------------------------------------------- /examples/Navbar.jsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | import PropTypes from 'prop-types'; 3 | import React, { Component } from 'react'; 4 | import Anchor from '@trendmicro/react-anchor'; 5 | import { Button } from '@trendmicro/react-buttons'; 6 | import Dropdown, { MenuItem } from '@trendmicro/react-dropdown'; 7 | import styles from './Navbar.styl'; 8 | 9 | const locales = [ 10 | 'en', 11 | 'cs', 12 | 'de', 13 | 'es', 14 | 'fr', 15 | 'it', 16 | 'ja', 17 | 'pt-br', 18 | 'ru', 19 | 'zh-cn', 20 | 'zh-tw' 21 | ]; 22 | 23 | const mapLocaleToString = (locale) => { 24 | return { 25 | 'en': 'English', 26 | 'cs': 'Czech', 27 | 'de': 'German', 28 | 'es': 'Spanish', 29 | 'fr': 'French', 30 | 'it': 'Italian', 31 | 'ja': 'Japanese', 32 | 'pt-br': 'Portuguese', 33 | 'ru': 'Russian', 34 | 'zh-cn': 'Simplified Chinese', 35 | 'zh-tw': 'Traditional Chinese' 36 | }[locale]; 37 | }; 38 | 39 | export default class extends Component { 40 | static propTypes = { 41 | name: PropTypes.string, 42 | url: PropTypes.string, 43 | locale: PropTypes.string, 44 | changeLocale: PropTypes.func 45 | }; 46 | 47 | state = { 48 | collapseIn: false 49 | }; 50 | 51 | render() { 52 | const { name, url } = this.props; 53 | 54 | return ( 55 | 123 | ); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /examples/Navbar.styl: -------------------------------------------------------------------------------- 1 | .container-fluid { 2 | padding-right: 15px; 3 | padding-left: 15px; 4 | margin-right: auto; 5 | margin-left: auto; 6 | } 7 | .sr-only { 8 | position: absolute; 9 | width: 1px; 10 | height: 1px; 11 | padding: 0; 12 | margin: -1px; 13 | overflow: hidden; 14 | clip: rect(0, 0, 0, 0); 15 | border: 0; 16 | } 17 | .collapse { 18 | display: none; 19 | &.in { display: block; } 20 | tr&.in { display: table-row; } 21 | tbody&.in { display: table-row-group; } 22 | } 23 | .nav:before, 24 | .nav:after, 25 | .navbar:before, 26 | .navbar:after, 27 | .navbar-header:before, 28 | .navbar-header:after, 29 | .navbar-collapse:before, 30 | .navbar-collapse:after { 31 | display: table; 32 | content: ""; 33 | } 34 | .nav:after, 35 | .navbar:after, 36 | .navbar-header:after, 37 | .navbar-collapse:after { 38 | clear: both; 39 | } 40 | .nav { 41 | padding-left: 0; 42 | margin-bottom: 0; 43 | list-style: none; 44 | } 45 | .navbar { 46 | position: relative; 47 | min-height: 50px; 48 | border: 1px solid transparent; 49 | } 50 | @media (min-width: 768px) { 51 | .navbar { 52 | border-radius: 4px; 53 | } 54 | } 55 | @media (min-width: 768px) { 56 | .navbar-header { 57 | float: left; 58 | } 59 | } 60 | .navbar-collapse { 61 | padding-right: 15px; 62 | padding-left: 15px; 63 | overflow-x: visible; 64 | -webkit-overflow-scrolling: touch; 65 | border-top: 1px solid transparent; 66 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1); 67 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1); 68 | } 69 | .navbar-collapse.in { 70 | overflow-y: auto; 71 | } 72 | @media (min-width: 768px) { 73 | .navbar-collapse { 74 | width: auto; 75 | border-top: 0; 76 | -webkit-box-shadow: none; 77 | box-shadow: none; 78 | } 79 | .navbar-collapse.collapse { 80 | display: block !important; 81 | height: auto !important; 82 | padding-bottom: 0; 83 | overflow: visible !important; 84 | } 85 | .navbar-collapse.in { 86 | overflow-y: visible; 87 | } 88 | .navbar-fixed-top .navbar-collapse, 89 | .navbar-static-top .navbar-collapse, 90 | .navbar-fixed-bottom .navbar-collapse { 91 | padding-right: 0; 92 | padding-left: 0; 93 | } 94 | } 95 | .navbar-fixed-top .navbar-collapse, 96 | .navbar-fixed-bottom .navbar-collapse { 97 | max-height: 340px; 98 | } 99 | @media (max-device-width: 480px) and (orientation: landscape) { 100 | .navbar-fixed-top .navbar-collapse, 101 | .navbar-fixed-bottom .navbar-collapse { 102 | max-height: 200px; 103 | } 104 | } 105 | .container > .navbar-header, 106 | .container-fluid > .navbar-header, 107 | .container > .navbar-collapse, 108 | .container-fluid > .navbar-collapse { 109 | margin-right: -15px; 110 | margin-left: -15px; 111 | } 112 | @media (min-width: 768px) { 113 | .container > .navbar-header, 114 | .container-fluid > .navbar-header, 115 | .container > .navbar-collapse, 116 | .container-fluid > .navbar-collapse { 117 | margin-right: 0; 118 | margin-left: 0; 119 | } 120 | } 121 | .navbar-static-top { 122 | z-index: 1000; 123 | border-width: 0 0 1px; 124 | } 125 | @media (min-width: 768px) { 126 | .navbar-static-top { 127 | border-radius: 0; 128 | } 129 | } 130 | .navbar-fixed-top, 131 | .navbar-fixed-bottom { 132 | position: fixed; 133 | right: 0; 134 | left: 0; 135 | z-index: 1030; 136 | } 137 | @media (min-width: 768px) { 138 | .navbar-fixed-top, 139 | .navbar-fixed-bottom { 140 | border-radius: 0; 141 | } 142 | } 143 | .navbar-fixed-top { 144 | top: 0; 145 | border-width: 0 0 1px; 146 | } 147 | .navbar-fixed-bottom { 148 | bottom: 0; 149 | margin-bottom: 0; 150 | border-width: 1px 0 0; 151 | } 152 | .navbar-brand { 153 | float: left; 154 | height: 50px; 155 | padding: 15px 15px; 156 | font-size: 18px; 157 | line-height: 20px; 158 | } 159 | .navbar-brand, 160 | .navbar-brand:hover, 161 | .navbar-brand:focus { 162 | text-decoration: none; 163 | } 164 | .navbar-brand > img { 165 | display: block; 166 | } 167 | @media (min-width: 768px) { 168 | .navbar > .container .navbar-brand, 169 | .navbar > .container-fluid .navbar-brand { 170 | margin-left: -15px; 171 | } 172 | } 173 | .navbar-toggle { 174 | position: relative; 175 | float: right; 176 | padding: 9px 10px; 177 | margin-top: 8px; 178 | margin-right: 15px; 179 | margin-bottom: 8px; 180 | background-color: transparent; 181 | background-image: none; 182 | border: 1px solid transparent; 183 | border-radius: 4px; 184 | cursor: pointer; 185 | } 186 | .navbar-toggle:focus { 187 | outline: 0; 188 | } 189 | .navbar-toggle .icon-bar { 190 | display: block; 191 | width: 22px; 192 | height: 2px; 193 | border-radius: 1px; 194 | } 195 | .navbar-toggle .icon-bar + .icon-bar { 196 | margin-top: 4px; 197 | } 198 | @media (min-width: 768px) { 199 | .navbar-toggle { 200 | display: none; 201 | } 202 | } 203 | .navbar-nav { 204 | margin: 7.5px -15px; 205 | } 206 | .navbar-nav > li > a { 207 | padding-top: 10px; 208 | padding-bottom: 10px; 209 | line-height: 20px; 210 | } 211 | @media (max-width: 767px) { 212 | .navbar-nav .open .dropdown-menu { 213 | position: static; 214 | float: none; 215 | width: auto; 216 | margin-top: 0; 217 | background-color: transparent; 218 | border: 0; 219 | -webkit-box-shadow: none; 220 | box-shadow: none; 221 | } 222 | .navbar-nav .open .dropdown-menu > li > a, 223 | .navbar-nav .open .dropdown-menu .dropdown-header { 224 | padding: 5px 15px 5px 25px; 225 | } 226 | .navbar-nav .open .dropdown-menu > li > a { 227 | line-height: 20px; 228 | } 229 | .navbar-nav .open .dropdown-menu > li > a:hover, 230 | .navbar-nav .open .dropdown-menu > li > a:focus { 231 | background-image: none; 232 | } 233 | } 234 | @media (min-width: 768px) { 235 | .navbar-nav { 236 | float: left; 237 | margin: 0; 238 | } 239 | .navbar-nav > li { 240 | float: left; 241 | } 242 | .navbar-nav > li > a { 243 | padding-top: 15px; 244 | padding-bottom: 15px; 245 | } 246 | } 247 | .navbar-form { 248 | padding: 10px 15px; 249 | margin-top: 8px; 250 | margin-right: -15px; 251 | margin-bottom: 8px; 252 | margin-left: -15px; 253 | border-top: 1px solid transparent; 254 | border-bottom: 1px solid transparent; 255 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1); 256 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1); 257 | } 258 | @media (min-width: 768px) { 259 | .navbar-form .form-group { 260 | display: inline-block; 261 | margin-bottom: 0; 262 | vertical-align: middle; 263 | } 264 | .navbar-form .form-control { 265 | display: inline-block; 266 | width: auto; 267 | vertical-align: middle; 268 | } 269 | .navbar-form .form-control-static { 270 | display: inline-block; 271 | } 272 | .navbar-form .input-group { 273 | display: inline-table; 274 | vertical-align: middle; 275 | } 276 | .navbar-form .input-group .input-group-addon, 277 | .navbar-form .input-group .input-group-btn, 278 | .navbar-form .input-group .form-control { 279 | width: auto; 280 | } 281 | .navbar-form .input-group > .form-control { 282 | width: 100%; 283 | } 284 | .navbar-form .control-label { 285 | margin-bottom: 0; 286 | vertical-align: middle; 287 | } 288 | .navbar-form .radio, 289 | .navbar-form .checkbox { 290 | display: inline-block; 291 | margin-top: 0; 292 | margin-bottom: 0; 293 | vertical-align: middle; 294 | } 295 | .navbar-form .radio label, 296 | .navbar-form .checkbox label { 297 | padding-left: 0; 298 | } 299 | .navbar-form .radio input[type="radio"], 300 | .navbar-form .checkbox input[type="checkbox"] { 301 | position: relative; 302 | margin-left: 0; 303 | } 304 | .navbar-form .has-feedback .form-control-feedback { 305 | top: 0; 306 | } 307 | } 308 | @media (max-width: 767px) { 309 | .navbar-form .form-group { 310 | margin-bottom: 5px; 311 | } 312 | .navbar-form .form-group:last-child { 313 | margin-bottom: 0; 314 | } 315 | } 316 | @media (min-width: 768px) { 317 | .navbar-form { 318 | width: auto; 319 | padding-top: 0; 320 | padding-bottom: 0; 321 | margin-right: 0; 322 | margin-left: 0; 323 | border: 0; 324 | -webkit-box-shadow: none; 325 | box-shadow: none; 326 | } 327 | } 328 | .navbar-nav > li > .dropdown-menu { 329 | margin-top: 0; 330 | border-top-left-radius: 0; 331 | border-top-right-radius: 0; 332 | } 333 | .navbar-fixed-bottom .navbar-nav > li > .dropdown-menu { 334 | margin-bottom: 0; 335 | border-top-left-radius: 4px; 336 | border-top-right-radius: 4px; 337 | border-bottom-right-radius: 0; 338 | border-bottom-left-radius: 0; 339 | } 340 | .navbar-btn { 341 | margin-top: 8px; 342 | margin-bottom: 8px; 343 | } 344 | .navbar-btn.btn-sm { 345 | margin-top: 10px; 346 | margin-bottom: 10px; 347 | } 348 | .navbar-btn.btn-xs { 349 | margin-top: 14px; 350 | margin-bottom: 14px; 351 | } 352 | .navbar-text { 353 | margin-top: 15px; 354 | margin-bottom: 15px; 355 | } 356 | @media (min-width: 768px) { 357 | .navbar-text { 358 | float: left; 359 | margin-right: 15px; 360 | margin-left: 15px; 361 | } 362 | } 363 | @media (min-width: 768px) { 364 | .navbar-left { 365 | float: left !important; 366 | } 367 | .navbar-right { 368 | float: right !important; 369 | margin-right: -15px; 370 | } 371 | .navbar-right ~ .navbar-right { 372 | margin-right: 0; 373 | } 374 | } 375 | .navbar-default { 376 | background-color: #f8f8f8; 377 | border-color: #e7e7e7; 378 | } 379 | .navbar-default .navbar-brand { 380 | color: #777; 381 | } 382 | .navbar-default .navbar-brand:hover, 383 | .navbar-default .navbar-brand:focus { 384 | color: #5e5e5e; 385 | background-color: transparent; 386 | } 387 | .navbar-default .navbar-text { 388 | color: #777; 389 | } 390 | .navbar-default .navbar-nav > li > a { 391 | color: #777; 392 | } 393 | .navbar-default .navbar-nav > li > a:hover, 394 | .navbar-default .navbar-nav > li > a:focus { 395 | color: #333; 396 | background-color: transparent; 397 | } 398 | .navbar-default .navbar-nav > .active > a, 399 | .navbar-default .navbar-nav > .active > a:hover, 400 | .navbar-default .navbar-nav > .active > a:focus { 401 | color: #555; 402 | background-color: #e7e7e7; 403 | } 404 | .navbar-default .navbar-nav > .disabled > a, 405 | .navbar-default .navbar-nav > .disabled > a:hover, 406 | .navbar-default .navbar-nav > .disabled > a:focus { 407 | color: #ccc; 408 | background-color: transparent; 409 | } 410 | .navbar-default .navbar-toggle { 411 | border-color: #ddd; 412 | } 413 | .navbar-default .navbar-toggle:hover, 414 | .navbar-default .navbar-toggle:focus { 415 | background-color: #ddd; 416 | } 417 | .navbar-default .navbar-toggle .icon-bar { 418 | background-color: #888; 419 | } 420 | .navbar-default .navbar-collapse, 421 | .navbar-default .navbar-form { 422 | border-color: #e7e7e7; 423 | } 424 | .navbar-default .navbar-nav > .open > a, 425 | .navbar-default .navbar-nav > .open > a:hover, 426 | .navbar-default .navbar-nav > .open > a:focus { 427 | color: #555; 428 | background-color: #e7e7e7; 429 | } 430 | @media (max-width: 767px) { 431 | .navbar-default .navbar-nav .open .dropdown-menu > li > a { 432 | color: #777; 433 | } 434 | .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover, 435 | .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus { 436 | color: #333; 437 | background-color: transparent; 438 | } 439 | .navbar-default .navbar-nav .open .dropdown-menu > .active > a, 440 | .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover, 441 | .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus { 442 | color: #555; 443 | background-color: #e7e7e7; 444 | } 445 | .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a, 446 | .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover, 447 | .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus { 448 | color: #ccc; 449 | background-color: transparent; 450 | } 451 | } 452 | .navbar-default .navbar-link { 453 | color: #777; 454 | } 455 | .navbar-default .navbar-link:hover { 456 | color: #333; 457 | } 458 | .navbar-default .btn-link { 459 | color: #777; 460 | } 461 | .navbar-default .btn-link:hover, 462 | .navbar-default .btn-link:focus { 463 | color: #333; 464 | } 465 | -------------------------------------------------------------------------------- /examples/Section.jsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | import React from 'react'; 3 | import styles from './Section.styl'; 4 | 5 | export default (props) => ( 6 |
7 |
8 | {props.children} 9 |
10 |
11 | ); 12 | -------------------------------------------------------------------------------- /examples/Section.styl: -------------------------------------------------------------------------------- 1 | .section { 2 | background: #fff; 3 | border: 1px solid #d6d6d6; 4 | position: relative; 5 | z-index: 1; 6 | transition: height .3s ease; 7 | } 8 | .section:last-child { 9 | margin-bottom: 20px; 10 | } 11 | .section-content { 12 | padding: 0 16px 16px; 13 | position: absolute; 14 | top: 0; 15 | bottom: 0; 16 | left: 0; 17 | right: 0; 18 | } 19 | @media screen and (max-width: 1023px) { 20 | .section-content { 21 | position: static; 22 | height: 100%; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | React DatePicker 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/index.jsx: -------------------------------------------------------------------------------- 1 | import 'trendmicro-ui/dist/css/trendmicro-ui.css'; 2 | import qs from 'qs'; 3 | import React, { PureComponent } from 'react'; 4 | import ReactDOM from 'react-dom'; 5 | import toRenderProps from 'recompose/toRenderProps'; 6 | import withStateHandlers from 'recompose/withStateHandlers'; 7 | import Navbar from './Navbar'; 8 | import Section from './Section'; 9 | import * as DatePickerExample from './DatePicker'; 10 | import * as DateTimePickerExample from './DateTimePicker'; 11 | import * as DateTimeRangePickerExample from './DateTimeRangePicker'; 12 | 13 | const q = qs.parse(window.location.search, { ignoreQueryPrefix: true }); 14 | const locale = q.locale || 'en'; 15 | 16 | const Enhanced = toRenderProps(withStateHandlers( 17 | { // state 18 | period: '7d' 19 | }, 20 | { // handlers 21 | onSelect: () => ({ period }) => ({ 22 | period 23 | }) 24 | } 25 | )); 26 | 27 | class App extends PureComponent { 28 | state = { 29 | locale: locale 30 | }; 31 | 32 | changeLocale = (locale) => { 33 | window.location.search = `locale=${locale}`; 34 | }; 35 | 36 | render() { 37 | const name = 'React DatePicker'; 38 | const url = 'https://github.com/trendmicro-frontend/react-datepicker'; 39 | const { locale } = this.state; 40 | 41 | return ( 42 |
43 | 49 |
50 |
51 |
52 |
53 |

DatePicker

54 |
55 |
56 | 57 |
58 |
59 | 60 |
61 |
62 | 63 |
64 |
65 | 66 |
67 |
68 |
69 |
70 |
71 |
72 |

DateTimePicker

73 |
74 |
75 | 76 |
77 |
78 | 79 |
80 |
81 |
82 |
83 |
84 |
85 |

DateTimeRangePicker

86 |
87 |
88 | 89 |
90 |
91 | 92 |
93 |
94 | 95 | {({ locale, period, onSelect }) => ( 96 | 101 | )} 102 | 103 |
104 |
105 | 106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 | ); 114 | } 115 | } 116 | 117 | ReactDOM.render( 118 | , 119 | document.getElementById('container') 120 | ); 121 | -------------------------------------------------------------------------------- /examples/index.styl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trendmicro-frontend/react-datepicker/f84757698c83b4c8219aa5672588b1f9ce4ff931/examples/index.styl -------------------------------------------------------------------------------- /examples/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | const stylusLoader = require('stylus-loader'); 5 | const nib = require('nib'); 6 | 7 | module.exports = { 8 | devtool: 'source-map', 9 | entry: path.resolve(__dirname, 'index.jsx'), 10 | output: { 11 | path: path.join(__dirname, '../docs'), 12 | filename: 'bundle.js?[hash]' 13 | }, 14 | module: { 15 | rules: [ 16 | // http://survivejs.com/webpack_react/linting_in_webpack/ 17 | { 18 | test: /\.jsx?$/, 19 | loader: 'eslint-loader', 20 | enforce: 'pre', 21 | exclude: /(node_modules)/ 22 | }, 23 | { 24 | test: /\.styl$/, 25 | loader: 'stylint-loader', 26 | enforce: 'pre' 27 | }, 28 | { 29 | test: /\.jsx?$/, 30 | loader: 'babel-loader', 31 | exclude: /(node_modules|bower_components)/ 32 | }, 33 | { 34 | test: /\.styl$/, 35 | use: [ 36 | 'style-loader', 37 | 'css-loader?camelCase&modules&importLoaders=1&localIdentName=[local]---[hash:base64:5]', 38 | 'stylus-loader' 39 | ] 40 | }, 41 | { 42 | test: /\.css$/, 43 | loader: 'style-loader!css-loader' 44 | }, 45 | { 46 | test: /\.(png|jpg)$/, 47 | loader: 'url-loader', 48 | options: { 49 | limit: 8192 50 | } 51 | }, 52 | { 53 | test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, 54 | loader: 'url-loader', 55 | options: { 56 | limit: 10000, 57 | mimetype: 'application/font-woff' 58 | } 59 | }, 60 | { 61 | test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/, 62 | loader: 'file-loader' 63 | } 64 | ] 65 | }, 66 | plugins: [ 67 | new webpack.LoaderOptionsPlugin({ 68 | debug: true 69 | }), 70 | new webpack.NamedModulesPlugin(), 71 | new webpack.NoEmitOnErrorsPlugin(), 72 | new stylusLoader.OptionsPlugin({ 73 | default: { 74 | // nib - CSS3 extensions for Stylus 75 | use: [nib()], 76 | // no need to have a '@import "nib"' in the stylesheet 77 | import: ['~nib/lib/nib/index.styl'] 78 | } 79 | }), 80 | new HtmlWebpackPlugin({ 81 | filename: '../docs/index.html', 82 | template: 'index.html' 83 | }) 84 | ], 85 | resolve: { 86 | extensions: ['.js', '.json', '.jsx'] 87 | }, 88 | // https://webpack.github.io/docs/webpack-dev-server.html#additional-configuration-options 89 | devServer: { 90 | disableHostCheck: true, 91 | noInfo: false, 92 | lazy: false, 93 | // https://webpack.github.io/docs/node.js-api.html#compiler 94 | watchOptions: { 95 | poll: true, // use polling instead of native watchers 96 | ignored: /node_modules/ 97 | } 98 | } 99 | }; 100 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@trendmicro/react-datepicker", 3 | "version": "1.0.0-alpha.7", 4 | "description": "React DatePicker Component", 5 | "main": "lib/index.js", 6 | "files": [ 7 | "dist", 8 | "lib" 9 | ], 10 | "scripts": { 11 | "prepublish": "npm run eslint && npm test && npm run clean && npm run bowersync && npm run build && npm run build-examples", 12 | "bowersync": "./scripts/bowersync", 13 | "build": "webpack && npm run cleancss", 14 | "build-examples": "cd examples; webpack", 15 | "clean": "rm -f {lib,dist}/*", 16 | "cleancss": "cleancss -o dist/react-datepicker.min.css dist/react-datepicker.css", 17 | "demo": "http-server -p 8000 docs/", 18 | "eslint": "eslint --ext .js --ext .jsx examples src test", 19 | "test": "tap test/*.js --node-arg=--require --node-arg=babel-register --node-arg=--require --node-arg=babel-polyfill", 20 | "coveralls": "tap test/*.js --coverage --coverage-report=text-lcov --nyc-arg=--require --nyc-arg=babel-register --nyc-arg=--require --nyc-arg=babel-polyfill | coveralls", 21 | "dev": "cd examples; webpack-dev-server --hot --inline --host 0.0.0.0 --port 8000 --content-base ../docs" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/trendmicro-frontend/react-datepicker.git" 26 | }, 27 | "author": "Cheton Wu", 28 | "license": "MIT", 29 | "bugs": { 30 | "url": "https://github.com/trendmicro-frontend/react-datepicker/issues" 31 | }, 32 | "homepage": "https://github.com/trendmicro-frontend/react-datepicker", 33 | "keywords": [ 34 | "react", 35 | "date", 36 | "picker", 37 | "react-datepicker" 38 | ], 39 | "peerDependencies": { 40 | "moment": ">=2.8.0", 41 | "react": "^0.14.0 || >=15.0.0" 42 | }, 43 | "dependencies": { 44 | "classnames": "^2.2.5", 45 | "prop-types": "^15.6.0", 46 | "react-datepicker": "~1.5.0", 47 | "uncontrollable": "^7.0.0" 48 | }, 49 | "devDependencies": { 50 | "@trendmicro/react-anchor": "~0.5.6", 51 | "@trendmicro/react-buttons": "~1.3.1", 52 | "@trendmicro/react-dropdown": "~1.4.0", 53 | "babel-cli": "~6.26.0", 54 | "babel-core": "~6.26.0", 55 | "babel-eslint": "~8.2.2", 56 | "babel-loader": "~7.1.4", 57 | "babel-plugin-transform-decorators-legacy": "~1.3.4", 58 | "babel-preset-es2015": "~6.24.1", 59 | "babel-preset-react": "~6.24.1", 60 | "babel-preset-stage-0": "~6.24.1", 61 | "clean-css": "~4.1.11", 62 | "clean-css-cli": "~4.1.11", 63 | "coveralls": "~3.0.0", 64 | "css-loader": "~0.28.0", 65 | "eslint": "~4.18.2", 66 | "eslint-config-trendmicro": "~1.3.0", 67 | "eslint-loader": "~1.7.1", 68 | "eslint-plugin-import": "~2.9.0", 69 | "eslint-plugin-jsx-a11y": "~6.0.3", 70 | "eslint-plugin-react": "~7.7.0", 71 | "extract-text-webpack-plugin": "~3.0.0", 72 | "file-loader": "~0.11.1", 73 | "find-imports": "~0.5.2", 74 | "html-webpack-plugin": "~2.30.1", 75 | "http-server": "~0.11.1", 76 | "moment": "~2.21.0", 77 | "nib": "~1.1.2", 78 | "qs": "~6.5.1", 79 | "react": "^0.14.0 || >=15.0.0", 80 | "react-dom": "^0.14.0 || >=15.0.0", 81 | "recompose": "^0.30.0", 82 | "style-loader": "~0.18.2", 83 | "stylint": "~1.5.9", 84 | "stylint-loader": "~1.0.0", 85 | "stylus-loader": "~3.0.1", 86 | "tap": "~11.1.1", 87 | "trendmicro-ui": "~0.5.1", 88 | "url-loader": "~0.5.8", 89 | "webpack": "~3.4.1", 90 | "webpack-dev-server": "~2.6.1", 91 | "which": "~1.3.0" 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /scripts/bowersync: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | var pkg = require('../package.json'); 6 | var bower = require('../bower.json'); 7 | 8 | // Update bower.json 9 | Object.keys(bower).forEach((key) => { 10 | bower[key] = pkg[key] || bower[key]; 11 | }); 12 | bower.authors = pkg.contributors.map(author => { 13 | return { 14 | name: author.name, 15 | email: author.email, 16 | homepage: author.url 17 | }; 18 | }); 19 | 20 | var content = JSON.stringify(bower, null, 2); 21 | fs.writeFileSync(path.join(__dirname, '../bower.json'), content + '\n', 'utf8'); 22 | -------------------------------------------------------------------------------- /src/DateInput/Calendar.jsx: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React from 'react'; 3 | 4 | const Calendar = ({ width = 16, height = 16, ...props }) => ( 5 | 13 | 14 | 15 | ); 16 | 17 | Calendar.propTypes = { 18 | width: PropTypes.number, 19 | height: PropTypes.number 20 | }; 21 | 22 | export default Calendar; 23 | -------------------------------------------------------------------------------- /src/DateInput/index.jsx: -------------------------------------------------------------------------------- 1 | import cx from 'classnames'; 2 | import moment from 'moment'; 3 | import PropTypes from 'prop-types'; 4 | import React, { PureComponent } from 'react'; 5 | import { uncontrollable } from 'uncontrollable'; 6 | import Calendar from './Calendar'; 7 | import styles from './index.styl'; 8 | 9 | const KEYCODE_BACKSPACE = 8; 10 | const KEYCODE_TAB = 9; 11 | const KEYCODE_ESCAPE = 27; 12 | const KEYCODE_PAGE_UP = 33; 13 | const KEYCODE_PAGE_DOWN = 34; 14 | const KEYCODE_UP_ARROW = 38; 15 | const KEYCODE_DOWN_ARROW = 40; 16 | const KEYCODE_SPACE = 32; 17 | const KEYCODE_DELETE = 46; 18 | 19 | const SILHOUETTE = '0001-01-01'; 20 | 21 | const getGroups = (str) => { 22 | return str.split(/[\-\s+]/); 23 | }; 24 | 25 | const getGroupId = (index) => { 26 | if (index < 5) { 27 | return 0; 28 | } 29 | if (index < 8) { 30 | return 1; 31 | } 32 | return 2; 33 | }; 34 | 35 | const replaceCharAt = (string, index, replace) => { 36 | return string.substring(0, index) + replace + string.substring(index + 1); 37 | }; 38 | 39 | const isValidDate = (date) => { 40 | if (!date) { 41 | return false; 42 | } 43 | 44 | return moment(date).isValid(); 45 | }; 46 | 47 | class DateInput extends PureComponent { 48 | static propTypes = { 49 | value: PropTypes.string, 50 | 51 | // The minimum date. When set to null, there is no minimum. 52 | // Types supported: 53 | // * Date: A date object containing the minimum date. 54 | // * String: A date string in ISO 8601 format (i.e. YYYY-MM-DD). 55 | minDate: PropTypes.oneOfType([ 56 | PropTypes.object, 57 | PropTypes.string 58 | ]), 59 | 60 | // The maximum date. When set to null, there is no maximum. 61 | // Types supported: 62 | // * Date: A date object containing the maximum date. 63 | // * String: A date string in ISO 8601 format (i.e. YYYY-MM-DD). 64 | maxDate: PropTypes.oneOfType([ 65 | PropTypes.object, 66 | PropTypes.string 67 | ]), 68 | 69 | // Called when the value changes. 70 | onChange: PropTypes.func 71 | }; 72 | static defaultProps = { 73 | value: '0000-00-00' 74 | }; 75 | 76 | input = null; 77 | mounted = false; 78 | state = { 79 | focused: false, 80 | caretIndex: null 81 | }; 82 | 83 | handleDateOutOfRange = () => { 84 | if (typeof this.props.onChange !== 'function') { 85 | return; 86 | } 87 | 88 | const date = moment(this.props.value); 89 | 90 | if (isValidDate(this.props.minDate)) { 91 | const minDate = moment(this.props.minDate).startOf('day'); 92 | if (date.isBefore(minDate)) { 93 | this.props.onChange(minDate.format('YYYY-MM-DD')); 94 | } 95 | } 96 | 97 | if (isValidDate(this.props.maxDate)) { 98 | const maxDate = moment(this.props.maxDate).endOf('day'); 99 | if (date.isAfter(maxDate)) { 100 | this.props.onChange(maxDate.format('YYYY-MM-DD')); 101 | } 102 | } 103 | }; 104 | 105 | handleFocus = (event) => { 106 | if (this.mounted) { 107 | this.setState({ focused: true }); 108 | } 109 | }; 110 | 111 | handleBlur = (event) => { 112 | if (this.mounted) { 113 | this.setState({ caretIndex: null, focused: false }); 114 | this.handleDateOutOfRange(); 115 | } 116 | }; 117 | 118 | handleChange = (event) => { 119 | let value = this.props.value; 120 | let newValue = this.input.value; 121 | let diff = newValue.length - value.length; 122 | let end = this.input.selectionStart; 123 | let insertion; 124 | let start = end - Math.abs(diff); 125 | 126 | event.preventDefault(); 127 | 128 | if (diff > 0) { 129 | insertion = newValue.slice(end - diff, end); 130 | while (diff--) { 131 | const oldChar = value.charAt(start); 132 | const newChar = insertion.charAt(0); 133 | if (this.isSeparator(oldChar)) { 134 | if (this.isSeparator(newChar)) { 135 | insertion = insertion.slice(1); 136 | start++; 137 | } else { 138 | start++; 139 | diff++; 140 | end++; 141 | } 142 | } else { 143 | value = replaceCharAt(value, start, newChar); 144 | insertion = insertion.slice(1); 145 | start++; 146 | } 147 | } 148 | newValue = value; 149 | } else { 150 | if (newValue.charAt(start) === '-') { 151 | start++; 152 | } 153 | // apply default to selection 154 | let result = value; 155 | for (let i = start; i < end; i++) { 156 | result = replaceCharAt(result, i, newValue.charAt(i)); 157 | } 158 | newValue = result; 159 | } 160 | 161 | if (newValue.length > SILHOUETTE.length) { 162 | return; 163 | } 164 | 165 | const m = moment(newValue); 166 | if (m.isValid()) { 167 | if (newValue.charAt(end) === '-') { 168 | end++; 169 | } 170 | this.onChange(newValue, end); 171 | } else { 172 | const caretIndex = this.props.value.length - (newValue.length - end); 173 | if (this.mounted) { 174 | this.setState({ caretIndex: caretIndex }); 175 | } 176 | } 177 | }; 178 | 179 | handleKeyDown = (event) => { 180 | event.stopPropagation(); 181 | 182 | if (event.which === KEYCODE_BACKSPACE) { 183 | this.handleBackspace(event); 184 | return; 185 | } 186 | if (event.which === KEYCODE_TAB) { 187 | this.handleTab(event); 188 | return; 189 | } 190 | if (event.which === KEYCODE_ESCAPE) { 191 | this.handleEscape(event); 192 | return; 193 | } 194 | if (event.which === KEYCODE_SPACE || event.which === KEYCODE_DELETE) { 195 | this.handleForwardspace(event); 196 | return; 197 | } 198 | if (event.which === KEYCODE_PAGE_UP) { 199 | this.handlePageUp(event); 200 | return; 201 | } 202 | if (event.which === KEYCODE_PAGE_DOWN) { 203 | this.handlePageDown(event); 204 | return; 205 | } 206 | if (event.which === KEYCODE_UP_ARROW) { 207 | this.handleUpArrow(event); 208 | return; 209 | } 210 | if (event.which === KEYCODE_DOWN_ARROW) { 211 | this.handleDownArrow(event); 212 | return; 213 | } 214 | }; 215 | 216 | handleEscape = () => { 217 | if (this.mounted) { 218 | this.input.blur(); 219 | } 220 | }; 221 | 222 | handleTab = (event) => { 223 | const start = this.input.selectionStart; 224 | const value = this.props.value; 225 | const groups = getGroups(value); 226 | let groupId = getGroupId(start); 227 | if (event.shiftKey) { 228 | if (!groupId) { 229 | return; 230 | } 231 | groupId--; 232 | } else { 233 | if (groupId >= (groups.length - 1)) { 234 | return; 235 | } 236 | groupId++; 237 | } 238 | 239 | event.preventDefault(); 240 | 241 | let index = 0; // YYYY-MM-DD 242 | if (groupId === 1) { 243 | index = (4 + 1); 244 | } 245 | if (groupId === 2) { 246 | index = (4 + 1) + (2 + 1); 247 | } 248 | if (this.props.value.charAt(index) === ' ') { 249 | index++; 250 | } 251 | if (this.mounted) { 252 | this.setState({ caretIndex: index }); 253 | } 254 | }; 255 | 256 | handlePageUp = (event) => { 257 | event.preventDefault(); 258 | 259 | const m = moment(this.props.value); 260 | if (!m.isValid()) { 261 | return; 262 | } 263 | 264 | const value = m.subtract(1, 'months').format('YYYY-MM-DD'); 265 | const start = this.input.selectionStart; 266 | this.onChange(value, start); 267 | }; 268 | 269 | handlePageDown = (event) => { 270 | event.preventDefault(); 271 | 272 | const m = moment(this.props.value); 273 | if (!m.isValid()) { 274 | return; 275 | } 276 | 277 | const value = m.add(1, 'months').format('YYYY-MM-DD'); 278 | const start = this.input.selectionStart; 279 | this.onChange(value, start); 280 | }; 281 | 282 | handleUpArrow = (event) => { 283 | event.preventDefault(); 284 | 285 | const start = this.input.selectionStart; 286 | const groupId = getGroupId(start); 287 | const unit = { 288 | 0: 'years', 289 | 1: 'months', 290 | 2: 'days' 291 | }[groupId]; 292 | 293 | if (!unit) { 294 | return; 295 | } 296 | 297 | const m = moment(this.props.value); 298 | if (!m.isValid()) { 299 | return; 300 | } 301 | 302 | const value = m.add(1, unit).format('YYYY-MM-DD'); 303 | this.onChange(value, start); 304 | }; 305 | 306 | handleDownArrow = (event) => { 307 | event.preventDefault(); 308 | 309 | const start = this.input.selectionStart; 310 | const groupId = getGroupId(start); 311 | const unit = { 312 | 0: 'years', 313 | 1: 'months', 314 | 2: 'days' 315 | }[groupId]; 316 | 317 | if (!unit) { 318 | return; 319 | } 320 | 321 | const m = moment(this.props.value); 322 | if (!m.isValid()) { 323 | return; 324 | } 325 | 326 | const value = m.subtract(1, unit).format('YYYY-MM-DD'); 327 | this.onChange(value, start); 328 | }; 329 | 330 | handleBackspace = (event) => { 331 | event.preventDefault(); 332 | 333 | let value = this.props.value; 334 | let start = this.input.selectionStart; 335 | let end = this.input.selectionEnd; 336 | 337 | if (!start && !end) { 338 | return; 339 | } 340 | 341 | let diff = end - start; 342 | const silhouette = this.silhouette(); 343 | 344 | if (!diff) { 345 | if (value[start - 1] === '-') { 346 | start--; 347 | } 348 | value = replaceCharAt(value, start - 1, silhouette.charAt(start - 1)); 349 | start--; 350 | } else { 351 | while (diff--) { 352 | if (value[end - 1] !== '-') { 353 | value = replaceCharAt(value, end - 1, silhouette.charAt(end - 1)); 354 | } 355 | end--; 356 | } 357 | if (value.charAt(start - 1) === '-') { 358 | start--; 359 | } 360 | } 361 | 362 | this.onChange(value, start); 363 | }; 364 | 365 | handleForwardspace = (event) => { 366 | event.preventDefault(); 367 | 368 | let start = this.input.selectionStart; 369 | let value = this.props.value; 370 | let end = this.input.selectionEnd; 371 | 372 | if (start === end === (value.length - 1)) { 373 | return; 374 | } 375 | 376 | let diff = end - start; 377 | const silhouette = this.silhouette(); 378 | 379 | if (!diff) { 380 | if (value[start] === '-') { 381 | start++; 382 | } 383 | value = replaceCharAt(value, start, silhouette.charAt(start)); 384 | start++; 385 | } else { 386 | while (diff--) { 387 | if (value[end - 1] !== '-') { 388 | value = replaceCharAt(value, start, silhouette.charAt(start)); 389 | } 390 | start++; 391 | } 392 | } 393 | 394 | if (value.charAt(start) === '-') { 395 | start++; 396 | } 397 | 398 | this.onChange(value, start); 399 | }; 400 | 401 | isSeparator = (char) => { 402 | return /[:\s]/.test(char); 403 | }; 404 | 405 | onChange = (str, caretIndex) => { 406 | const m = moment(str); 407 | if (m.isValid()) { 408 | this.props.onChange && this.props.onChange(str); 409 | } 410 | if (this.mounted && typeof caretIndex === 'number') { 411 | this.setState({ caretIndex: caretIndex }); 412 | } 413 | }; 414 | 415 | silhouette = () => { 416 | return this.props.value.replace(/\d/g, (val, i) => { 417 | return SILHOUETTE.charAt(i); 418 | }); 419 | }; 420 | 421 | componentDidMount () { 422 | this.mounted = true; 423 | this.handleDateOutOfRange(); 424 | } 425 | componentWillUnmount () { 426 | this.mounted = false; 427 | } 428 | componentDidUpdate() { 429 | const index = this.state.caretIndex; 430 | if (index || index === 0) { 431 | const selectionStart = index; 432 | const selectionEnd = index; 433 | this.input.setSelectionRange(selectionStart, selectionEnd); 434 | } 435 | } 436 | render() { 437 | const icon = ( 438 | 439 | ); 440 | 441 | return ( 442 |
446 |
447 | { 449 | this.input = node; 450 | }} 451 | type="text" 452 | value={this.props.value} 453 | onChange={this.handleChange} 454 | onFocus={this.handleFocus} 455 | onBlur={this.handleBlur} 456 | onKeyDown={this.handleKeyDown} 457 | /> 458 |
459 | {icon} 460 |
461 | ); 462 | } 463 | } 464 | 465 | export default uncontrollable(DateInput, { 466 | // Define the pairs of prop/handlers you want to be uncontrollable 467 | value: 'onChange' 468 | }); 469 | -------------------------------------------------------------------------------- /src/DateInput/index.styl: -------------------------------------------------------------------------------- 1 | @import "../reset-context"; 2 | 3 | @media all and (-ms-high-contrast: none), (-ms-high-contrast: active) { 4 | .date-input > input { 5 | height: 32px; 6 | } 7 | } 8 | 9 | .date-input-container { 10 | reset-context(); 11 | position: relative; 12 | } 13 | 14 | .date-input { 15 | width: 120px; 16 | 17 | > input { 18 | display: block; 19 | width: 100%; 20 | height: auto; 21 | line-height: inherit; 22 | padding: 5px 12px; 23 | padding-left: 30px; 24 | font-size: 13px; 25 | color: #222222; 26 | border: 1px solid #ccc; 27 | border-radius: 3px; 28 | outline: none; 29 | 30 | &:focus { 31 | border-color: #0096cc; 32 | } 33 | } 34 | } 35 | 36 | .date-input-icon { 37 | position: absolute; 38 | left: 9px; 39 | top: 8px; 40 | color: #666; 41 | width: 14px; 42 | height: 14px; 43 | } 44 | -------------------------------------------------------------------------------- /src/DatePicker.jsx: -------------------------------------------------------------------------------- 1 | import cx from 'classnames'; 2 | import moment from 'moment'; 3 | import PropTypes from 'prop-types'; 4 | import React, { PureComponent } from 'react'; 5 | import DatePicker from 'react-datepicker'; 6 | import { uncontrollable } from 'uncontrollable'; 7 | import styles from './index.styl'; 8 | 9 | class DatePickerWrapper extends PureComponent { 10 | static propTypes = { 11 | locale: PropTypes.string, 12 | 13 | date: PropTypes.oneOfType([ 14 | PropTypes.object, 15 | PropTypes.string 16 | ]), 17 | 18 | // The minimum selectable date. When set to null, there is no minimum. 19 | // Types supported: 20 | // * Date: A date object containing the minimum date. 21 | // * String: A date string in ISO 8601 format (i.e. YYYY-MM-DD). 22 | minDate: PropTypes.oneOfType([ 23 | PropTypes.object, 24 | PropTypes.string 25 | ]), 26 | 27 | // The maximum selectable date. When set to null, there is no maximum. 28 | // Types supported: 29 | // * Date: A date object containing the maximum date. 30 | // * String: A date string in ISO 8601 format (i.e. YYYY-MM-DD). 31 | maxDate: PropTypes.oneOfType([ 32 | PropTypes.object, 33 | PropTypes.string 34 | ]), 35 | 36 | // Called when a date is selected. 37 | onSelect: PropTypes.func 38 | }; 39 | static defaultProps = { 40 | date: null, 41 | minDate: null, 42 | maxDate: null, 43 | onSelect: () => {} 44 | }; 45 | 46 | handleSelect = (selected) => { 47 | const date = moment(selected).format('YYYY-MM-DD'); 48 | this.props.onSelect && this.props.onSelect(date); 49 | }; 50 | 51 | render() { 52 | const { 53 | locale, 54 | date, 55 | minDate, 56 | maxDate, 57 | onSelect, // eslint-disable-line 58 | className, 59 | ...props 60 | } = this.props; 61 | 62 | return ( 63 | 74 | ); 75 | } 76 | } 77 | 78 | export default uncontrollable(DatePickerWrapper, { 79 | // Define the pairs of prop/handlers you want to be uncontrollable 80 | date: 'onSelect' 81 | }); 82 | -------------------------------------------------------------------------------- /src/TimeInput/Clock.jsx: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React from 'react'; 3 | 4 | const Clock = ({ width = 16, height = 16, ...props }) => ( 5 | 13 | 14 | 15 | ); 16 | 17 | Clock.propTypes = { 18 | width: PropTypes.number, 19 | height: PropTypes.number 20 | }; 21 | 22 | export default Clock; 23 | -------------------------------------------------------------------------------- /src/TimeInput/index.jsx: -------------------------------------------------------------------------------- 1 | import cx from 'classnames'; 2 | import PropTypes from 'prop-types'; 3 | import React, { PureComponent } from 'react'; 4 | import { uncontrollable } from 'uncontrollable'; 5 | import Clock from './Clock'; 6 | import isTwelveHourTime from './lib/is-twelve-hour-time'; 7 | import replaceCharAt from './lib/replace-char-at'; 8 | import getGroupId from './lib/get-group-id'; 9 | import getGroups from './lib/get-groups'; 10 | import adder from './lib/time-string-adder'; 11 | import caret from './lib/caret'; 12 | import validate from './lib/validate'; 13 | import styles from './index.styl'; 14 | 15 | const SILHOUETTE = '00:00:00:000 AM'; 16 | 17 | class TimeInput extends PureComponent { 18 | static propTypes = { 19 | className: PropTypes.string, 20 | value: PropTypes.string, 21 | onChange: PropTypes.func 22 | }; 23 | static defaultProps = { 24 | value: '00:00:00:000 AM' 25 | }; 26 | 27 | input = null; 28 | mounted = false; 29 | state = { 30 | focused: false, 31 | caretIndex: null 32 | }; 33 | 34 | handleFocus = (event) => { 35 | if (this.mounted) { 36 | this.setState({ focused: true }); 37 | } 38 | }; 39 | 40 | handleBlur = (event) => { 41 | if (this.mounted) { 42 | this.setState({ caretIndex: null, focused: false }); 43 | } 44 | }; 45 | 46 | handleChange = (event) => { 47 | let value = this.props.value; 48 | let newValue = this.input.value; 49 | let diff = newValue.length - value.length; 50 | let end = caret.start(this.input); 51 | let insertion; 52 | let start = end - Math.abs(diff); 53 | 54 | event.preventDefault(); 55 | 56 | if (diff > 0) { 57 | insertion = newValue.slice(end - diff, end); 58 | while (diff--) { 59 | const oldChar = value.charAt(start); 60 | const newChar = insertion.charAt(0); 61 | if (this.isSeparator(oldChar)) { 62 | if (this.isSeparator(newChar)) { 63 | insertion = insertion.slice(1); 64 | start++; 65 | } else { 66 | start++; 67 | diff++; 68 | end++; 69 | } 70 | } else { 71 | value = replaceCharAt(value, start, newChar); 72 | insertion = insertion.slice(1); 73 | start++; 74 | } 75 | } 76 | newValue = value; 77 | } else { 78 | if (newValue.charAt(start) === ':') { 79 | start++; 80 | } 81 | // apply default to selection 82 | let result = value; 83 | for (let i = start; i < end; i++) { 84 | result = replaceCharAt(result, i, newValue.charAt(i)); 85 | } 86 | newValue = result; 87 | } 88 | 89 | if (validate(newValue)) { 90 | if (newValue.charAt(end) === ':') { 91 | end++; 92 | } 93 | this.onChange(newValue, end); 94 | } else { 95 | const caretIndex = this.props.value.length - (newValue.length - end); 96 | if (this.mounted) { 97 | this.setState({ caretIndex: caretIndex }); 98 | } 99 | } 100 | }; 101 | 102 | handleKeyDown = (event) => { 103 | event.stopPropagation(); 104 | 105 | if (event.which === 9) { 106 | this.handleTab(event); 107 | return; 108 | } 109 | if (event.which === 38 || event.which === 40) { 110 | this.handleArrows(event); 111 | return; 112 | } 113 | if (event.which === 8) { 114 | this.handleBackspace(event); 115 | return; 116 | } 117 | if (event.which === 32 || event.which === 46) { 118 | this.handleForwardspace(event); 119 | return; 120 | } 121 | if (event.which === 27) { 122 | this.handleEscape(event); 123 | return; 124 | } 125 | }; 126 | 127 | handleEscape = () => { 128 | if (this.mounted) { 129 | this.input.blur(); 130 | } 131 | }; 132 | 133 | handleTab = (event) => { 134 | const start = caret.start(this.input); 135 | const value = this.props.value; 136 | const groups = getGroups(value); 137 | let groupId = getGroupId(start); 138 | if (event.shiftKey) { 139 | if (!groupId) { 140 | return; 141 | } 142 | groupId--; 143 | } else { 144 | if (groupId >= (groups.length - 1)) { 145 | return; 146 | } 147 | groupId++; 148 | } 149 | 150 | event.preventDefault(); 151 | 152 | let index = groupId * 3; 153 | if (this.props.value.charAt(index) === ' ') { 154 | index++; 155 | } 156 | if (this.mounted) { 157 | this.setState({ caretIndex: index }); 158 | } 159 | }; 160 | 161 | handleArrows = (event) => { 162 | event.preventDefault(); 163 | const start = caret.start(this.input); 164 | const amount = event.which === 38 ? 1 : -1; 165 | const value = adder(this.props.value, getGroupId(start), amount); 166 | this.onChange(value, start); 167 | }; 168 | 169 | handleBackspace = (event) => { 170 | event.preventDefault(); 171 | 172 | let start = caret.start(this.input); 173 | let value = this.props.value; 174 | let end = caret.end(this.input); 175 | 176 | if (!start && !end) { 177 | return; 178 | } 179 | 180 | let diff = end - start; 181 | const silhouette = this.silhouette(); 182 | 183 | if (!diff) { 184 | if (value[start - 1] === ':') { 185 | start--; 186 | } 187 | value = replaceCharAt(value, start - 1, silhouette.charAt(start - 1)); 188 | start--; 189 | } else { 190 | while (diff--) { 191 | if (value[end - 1] !== ':') { 192 | value = replaceCharAt(value, end - 1, silhouette.charAt(end - 1)); 193 | } 194 | end--; 195 | } 196 | if (value.charAt(start - 1) === ':') { 197 | start--; 198 | } 199 | } 200 | 201 | this.onChange(value, start); 202 | }; 203 | 204 | handleForwardspace = (event) => { 205 | event.preventDefault(); 206 | 207 | let start = caret.start(this.input); 208 | let value = this.props.value; 209 | let end = caret.end(this.input); 210 | 211 | if (start === end === (value.length - 1)) { 212 | return; 213 | } 214 | 215 | let diff = end - start; 216 | const silhouette = this.silhouette(); 217 | 218 | if (!diff) { 219 | if (value[start] === ':') { 220 | start++; 221 | } 222 | value = replaceCharAt(value, start, silhouette.charAt(start)); 223 | start++; 224 | } else { 225 | while (diff--) { 226 | if (value[end - 1] !== ':') { 227 | value = replaceCharAt(value, start, silhouette.charAt(start)); 228 | } 229 | start++; 230 | } 231 | } 232 | if (value.charAt(start) === ':') { 233 | start++; 234 | } 235 | 236 | this.onChange(value, start); 237 | }; 238 | 239 | isSeparator = (char) => { 240 | return /[:\s]/.test(char); 241 | }; 242 | 243 | format = (val) => { 244 | if (isTwelveHourTime(val)) { 245 | val = val.replace(/^00/, '12'); 246 | } 247 | return val.toUpperCase(); 248 | }; 249 | 250 | onChange = (str, caretIndex) => { 251 | if (this.props.onChange) { 252 | this.props.onChange(this.format(str)); 253 | } 254 | if (this.mounted && typeof caretIndex === 'number') { 255 | this.setState({ caretIndex: caretIndex }); 256 | } 257 | }; 258 | 259 | silhouette = () => { 260 | return this.props.value.replace(/\d/g, (val, i) => { 261 | return SILHOUETTE.charAt(i); 262 | }); 263 | }; 264 | 265 | componentDidMount () { 266 | this.mounted = true; 267 | } 268 | componentWillUnmount () { 269 | this.mounted = false; 270 | } 271 | componentDidUpdate() { 272 | const index = this.state.caretIndex; 273 | if (index || index === 0) { 274 | caret.set(this.input, index); 275 | } 276 | } 277 | render() { 278 | const value = this.format(this.props.value); 279 | const icon = ( 280 | 281 | ); 282 | 283 | return ( 284 |
288 |
289 | { 291 | this.input = node; 292 | }} 293 | type="text" 294 | value={value} 295 | onChange={this.handleChange} 296 | onFocus={this.handleFocus} 297 | onBlur={this.handleBlur} 298 | onKeyDown={this.handleKeyDown} 299 | /> 300 |
301 | {icon} 302 |
303 | ); 304 | } 305 | } 306 | 307 | export default uncontrollable(TimeInput, { 308 | // Define the pairs of prop/handlers you want to be uncontrollable 309 | value: 'onChange' 310 | }); 311 | -------------------------------------------------------------------------------- /src/TimeInput/index.styl: -------------------------------------------------------------------------------- 1 | @import "../reset-context"; 2 | 3 | @media all and (-ms-high-contrast: none), (-ms-high-contrast: active) { 4 | .time-input > input { 5 | height: 32px; 6 | } 7 | } 8 | 9 | .time-input-container { 10 | reset-context(); 11 | position: relative; 12 | } 13 | 14 | .time-input { 15 | width: 120px; 16 | 17 | > input { 18 | display: block; 19 | width: 100%; 20 | height: auto; 21 | line-height: inherit; 22 | padding: 5px 12px; 23 | padding-left: 30px; 24 | font-size: 13px; 25 | color: #222222; 26 | border: 1px solid #ccc; 27 | border-radius: 3px; 28 | outline: none; 29 | 30 | &:focus { 31 | border-color: #0096cc; 32 | } 33 | } 34 | } 35 | 36 | .time-input-icon { 37 | position: absolute; 38 | left: 9px; 39 | top: 8px; 40 | color: #666; 41 | width: 14px; 42 | height: 14px; 43 | } 44 | -------------------------------------------------------------------------------- /src/TimeInput/lib/caret.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | start: function(el) { 3 | return el.selectionStart; 4 | }, 5 | end: function(el) { 6 | return el.selectionEnd; 7 | }, 8 | set: function(el, start, end) { 9 | el.setSelectionRange(start, end || start); 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /src/TimeInput/lib/get-base.js: -------------------------------------------------------------------------------- 1 | module.exports = function getBase(groupId, twelveHourTime) { 2 | if (!groupId) { 3 | return twelveHourTime ? 12 : 24; 4 | } 5 | if (groupId < 3) { 6 | return 60; 7 | } 8 | return 1000; 9 | }; 10 | -------------------------------------------------------------------------------- /src/TimeInput/lib/get-group-id.js: -------------------------------------------------------------------------------- 1 | module.exports = function getGroupId(index) { 2 | if (index < 3) { 3 | return 0; 4 | } 5 | if (index < 6) { 6 | return 1; 7 | } 8 | if (index < 9) { 9 | return 2; 10 | } 11 | if (index < 13) { 12 | return 3; 13 | } 14 | return 4; 15 | }; 16 | -------------------------------------------------------------------------------- /src/TimeInput/lib/get-groups.js: -------------------------------------------------------------------------------- 1 | module.exports = function getGroups(str) { 2 | return str.split(/[:\s+]/); 3 | }; 4 | -------------------------------------------------------------------------------- /src/TimeInput/lib/is-twelve-hour-time.js: -------------------------------------------------------------------------------- 1 | module.exports = function isTwelveHourTime(groups) { 2 | return /[a-z]/i.test(groups[groups.length - 1]); 3 | }; 4 | -------------------------------------------------------------------------------- /src/TimeInput/lib/replace-char-at.js: -------------------------------------------------------------------------------- 1 | module.exports = function replaceCharAt(str, index, replacement) { 2 | str = str.split(''); 3 | str[index] = replacement; 4 | return str.join(''); 5 | }; 6 | -------------------------------------------------------------------------------- /src/TimeInput/lib/stringify.js: -------------------------------------------------------------------------------- 1 | import isTwelveHourTime from './is-twelve-hour-time'; 2 | 3 | module.exports = function stringify(groups) { 4 | if (isTwelveHourTime(groups)) { 5 | return groups.slice(0, -1).join(':') + ' ' + groups[groups.length - 1]; 6 | } 7 | return groups.join(':'); 8 | }; 9 | -------------------------------------------------------------------------------- /src/TimeInput/lib/time-string-adder.js: -------------------------------------------------------------------------------- 1 | import zeroPad from './zero-pad'; 2 | import getGroups from './get-groups'; 3 | import getBase from './get-base'; 4 | import stringify from './stringify'; 5 | import toggle24Hr from './toggle-24-hour'; 6 | import isTwelveHourTime from './is-twelve-hour-time'; 7 | 8 | const add = (groups, groupId, amount, twelveHourTime) => { 9 | const base = getBase(groupId, twelveHourTime); 10 | if (!groupId && groups[groupId] === '12' && twelveHourTime) { 11 | groups[groupId] = '00'; 12 | } 13 | const val = Number(groups[groupId]) + amount; 14 | groups = replace(groups, groupId, (val + base) % base); 15 | if (groupId && val >= base) { 16 | return add(groups, groupId - 1, 1, twelveHourTime); 17 | } 18 | if (groupId && val < 0) { 19 | return add(groups, groupId - 1, -1, twelveHourTime); 20 | } 21 | if (!groupId && twelveHourTime) { 22 | if (val >= base || val < 0) { 23 | toggle24Hr(groups); 24 | } 25 | if (groups[0] === '00') { 26 | groups[0] = '12'; 27 | } 28 | } 29 | 30 | return groups; 31 | }; 32 | 33 | const replace = (groups, groupId, amount) => { 34 | const digits = groups[groupId].length; 35 | groups[groupId] = zeroPad(String(amount), digits); 36 | return groups; 37 | }; 38 | 39 | module.exports = function adder(str, groupId, amount) { 40 | const groups = getGroups(str); 41 | const twelveHourTime = isTwelveHourTime(groups); 42 | if (twelveHourTime && (groupId === (groups.length - 1))) { 43 | return stringify(toggle24Hr(groups)); 44 | } 45 | return stringify(add(groups, groupId, amount, twelveHourTime)); 46 | }; 47 | -------------------------------------------------------------------------------- /src/TimeInput/lib/toggle-24-hour.js: -------------------------------------------------------------------------------- 1 | module.exports = function toggle24Hr(groups) { 2 | const m = groups[groups.length - 1].toUpperCase(); 3 | groups[groups.length - 1] = (m === 'AM') ? 'PM' : 'AM'; 4 | return groups; 5 | }; 6 | -------------------------------------------------------------------------------- /src/TimeInput/lib/validate.js: -------------------------------------------------------------------------------- 1 | module.exports = function validate(val) { 2 | return /^([0-9]|0[0-9]|1[0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9](:[0-9][0-9][0-9])?)?(\s+[ap]m)?$/i.test(val); 3 | }; 4 | -------------------------------------------------------------------------------- /src/TimeInput/lib/zero-pad.js: -------------------------------------------------------------------------------- 1 | module.exports = function zeroPad(val, digits) { 2 | while (val.length < digits) { 3 | val = '0' + val; 4 | } 5 | return val; 6 | }; 7 | -------------------------------------------------------------------------------- /src/angle-left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | angle-left 4 | 5 | -------------------------------------------------------------------------------- /src/angle-right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | angle-right 4 | 5 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import DatePicker from './DatePicker'; 2 | 3 | export DateInput from './DateInput'; 4 | export TimeInput from './TimeInput'; 5 | 6 | export default DatePicker; 7 | -------------------------------------------------------------------------------- /src/index.styl: -------------------------------------------------------------------------------- 1 | @import "reset-context"; 2 | 3 | .date-picker-container { 4 | reset-context(); 5 | 6 | border: none; 7 | border-radius: 0; 8 | box-shadow: none; 9 | padding: 0 5px; 10 | 11 | position: relative; 12 | display: inline-block; 13 | 14 | :global { 15 | @import "react-datepicker"; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/react-datepicker.styl: -------------------------------------------------------------------------------- 1 | .react-datepicker { 2 | } 3 | 4 | .react-datepicker__header { 5 | text-align: center; 6 | background-color: #fff; 7 | border: none; 8 | position: relative; 9 | padding: 0; 10 | } 11 | 12 | .react-datepicker__month { 13 | margin: 0; 14 | text-align: center; 15 | } 16 | 17 | .react-datepicker__current-month { 18 | color: #222; 19 | font-weight: bold; 20 | font-size: 13px; 21 | height: 20px; 22 | margin: 8px 0; 23 | } 24 | 25 | .react-datepicker__navigation { 26 | background: none; 27 | line-height: 20px; 28 | text-align: center; 29 | cursor: pointer; 30 | position: absolute; 31 | top: 3px; 32 | padding: 5px; 33 | border: none; 34 | z-index: 1; 35 | outline: 0; 36 | width: 30px; 37 | height: 30px; 38 | background-color: transparent; 39 | background-position: center center; 40 | background-repeat: no-repeat; 41 | 42 | &:hover { 43 | border-radius: 3px; 44 | background-color: #eee; 45 | } 46 | } 47 | 48 | .react-datepicker__navigation--previous { 49 | left: 8px; 50 | background-image: url("./angle-left.svg"); 51 | } 52 | 53 | .react-datepicker__navigation--next { 54 | right: 8px; 55 | background-image: url("./angle-right.svg"); 56 | } 57 | 58 | .react-datepicker__day, 59 | .react-datepicker__day-name { 60 | color: #222; 61 | display: inline-block; 62 | text-align: center; 63 | width: 30px; 64 | line-height: 20px; 65 | border: 0; 66 | padding: 5px; 67 | margin: 2px; 68 | } 69 | 70 | .react-datepicker__day { 71 | cursor: pointer; 72 | font-size: 13px; 73 | 74 | &:hover { 75 | background: #eee; 76 | cursor: pointer; 77 | border-radius: 50%; 78 | } 79 | 80 | &.react-datepicker__day--disabled, 81 | &:hover.react-datepicker__day--disabled { 82 | background: inherit; 83 | cursor: default; 84 | color: #bbb; 85 | } 86 | } 87 | 88 | .react-datepicker__day-name { 89 | padding-top: 9px; 90 | font-size: 12px; 91 | font-weight: bold; 92 | } 93 | 94 | .react-datepicker__day-names { 95 | white-space: nowrap; 96 | } 97 | 98 | .react-datepicker__day--outside-month { 99 | color: #bbb; 100 | } 101 | 102 | .react-datepicker__day--today { 103 | color: #db3d44; 104 | } 105 | 106 | .react-datepicker__day--selected { 107 | color: #fff; 108 | font-weight: bold; 109 | background-color: #db3d44; 110 | border-radius: 50%; 111 | 112 | &:hover { 113 | background-color: #db3d44; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/reset-context.styl: -------------------------------------------------------------------------------- 1 | reset-context() { 2 | // https://www.paulirish.com/2012/box-sizing-border-box-ftw/ 3 | box-sizing: border-box; 4 | *, *:before, *:after { 5 | box-sizing: inherit; 6 | } 7 | 8 | line-height: 20px; 9 | } 10 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | import { test } from 'tap'; 2 | 3 | test('noop', (t) => { 4 | t.end(); 5 | }); 6 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var pkg = require('./package.json'); 2 | var path = require('path'); 3 | var webpack = require('webpack'); 4 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 5 | var findImports = require('find-imports'); 6 | var stylusLoader = require('stylus-loader'); 7 | var nib = require('nib'); 8 | var publicname = pkg.name.replace(/^@\w+\//, ''); // Strip out "@trendmicro/" from package name 9 | var banner = [ 10 | publicname + ' v' + pkg.version, 11 | '(c) ' + new Date().getFullYear() + ' Trend Micro Inc.', 12 | pkg.license, 13 | pkg.homepage 14 | ].join(' | '); 15 | var localClassPrefix = publicname.replace(/^react-/, ''); // Strip out "react-" from publicname 16 | 17 | module.exports = { 18 | devtool: 'source-map', 19 | entry: path.resolve(__dirname, 'src/index.js'), 20 | output: { 21 | path: path.join(__dirname, 'lib'), 22 | filename: 'index.js', 23 | libraryTarget: 'commonjs2' 24 | }, 25 | externals: [] 26 | .concat(findImports(['src/**/*.{js,jsx}'], { flatten: true })) 27 | .concat(Object.keys(pkg.peerDependencies)) 28 | .concat(Object.keys(pkg.dependencies)), 29 | module: { 30 | rules: [ 31 | // http://survivejs.com/webpack_react/linting_in_webpack/ 32 | { 33 | test: /\.jsx?$/, 34 | loader: 'eslint-loader', 35 | enforce: 'pre', 36 | exclude: /node_modules/ 37 | }, 38 | { 39 | test: /\.styl$/, 40 | loader: 'stylint-loader', 41 | enforce: 'pre' 42 | }, 43 | { 44 | test: /\.jsx?$/, 45 | loader: 'babel-loader', 46 | exclude: /(node_modules|bower_components)/ 47 | }, 48 | { 49 | test: /\.styl$/, 50 | use: ExtractTextPlugin.extract({ 51 | fallback: 'style-loader', 52 | use: 'css-loader?camelCase&modules&importLoaders=1&localIdentName=' + localClassPrefix + '---[local]---[hash:base64:5]!stylus-loader' 53 | }) 54 | }, 55 | { 56 | test: /\.css$/, 57 | loader: 'style-loader!css-loader' 58 | }, 59 | { 60 | test: /\.(png|jpg|svg)$/, 61 | loader: 'url-loader' 62 | } 63 | ] 64 | }, 65 | plugins: [ 66 | new webpack.DefinePlugin({ 67 | 'process.env': { 68 | // This has effect on the react lib size 69 | NODE_ENV: JSON.stringify('production') 70 | } 71 | }), 72 | new webpack.NoEmitOnErrorsPlugin(), 73 | new stylusLoader.OptionsPlugin({ 74 | default: { 75 | // nib - CSS3 extensions for Stylus 76 | use: [nib()], 77 | // no need to have a '@import "nib"' in the stylesheet 78 | import: ['~nib/lib/nib/index.styl'] 79 | } 80 | }), 81 | new ExtractTextPlugin('../dist/' + publicname + '.css'), 82 | new webpack.BannerPlugin(banner) 83 | ], 84 | resolve: { 85 | extensions: ['.js', '.json', '.jsx'] 86 | } 87 | }; 88 | --------------------------------------------------------------------------------