├── .babelrc ├── .eslintrc ├── .gitignore ├── .hophoprc ├── .smooth-releaserc ├── .stylelintrc ├── CHANGELOG.md ├── LICENSE.txt ├── README.md ├── ci ├── pipeline.yml ├── test.sh └── test.yml ├── examples ├── DatePicker.example ├── DatePickerInput.example ├── build │ ├── bundle.js │ ├── bundle.js.gz │ ├── index.html │ └── style.2703567a00ec10a4a818.min.css ├── examples.js ├── index.html ├── webpack.base.babel.js ├── webpack.config.babel.js └── webpack.config.build.babel.js ├── generateReadme.js ├── index.d.ts ├── index.js ├── karma.conf.js ├── karma.js ├── package.json ├── src ├── DatePicker.js ├── DatePickerInput.js ├── Input.js ├── InvalidDate.js ├── Picker.js ├── PickerTop.js ├── README.md ├── Row.js ├── daypicker │ ├── DayPicker.js │ ├── DayPickerBody.js │ └── DayPickerTop.js ├── icons.scss ├── index.js ├── monthpicker │ ├── MonthPicker.js │ ├── MonthPickerBody.js │ └── MonthPickerTop.js ├── style.scss ├── utils │ ├── DateUtils.js │ ├── format.js │ ├── index.js │ ├── model.js │ ├── pure.js │ ├── skinnable.js │ └── valueLink.js └── yearpicker │ ├── YearPicker.js │ ├── YearPickerBody.js │ └── YearPickerTop.js ├── styles.js ├── test ├── .eslintrc ├── index.js └── tests │ └── DatePickerInput-test.js └── webpack.config.build.babel.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["buildo", { env: "react" }] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/scriptoni/lib/scripts/eslint/eslintrc.json", 3 | "rules": { 4 | "max-len": 0 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | npm-debug.log 4 | lib 5 | .eslintcache 6 | package-lock.json 7 | -------------------------------------------------------------------------------- /.hophoprc: -------------------------------------------------------------------------------- 1 | toggl: no 2 | branchPrefix: n 3 | branchSuffix: y 4 | -------------------------------------------------------------------------------- /.smooth-releaserc: -------------------------------------------------------------------------------- 1 | { 2 | "github": { 3 | "changelog": { 4 | "ignoredLabels": ["DX", "discarded", "discussion", "greenkeeper"] 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/scriptoni/lib/scripts/stylelint/stylelintrc.json" 3 | } 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | 4 | 5 | ## [v5.0.17](https://github.com/buildo/rc-datepicker/tree/v5.0.17) (2024-07-09) 6 | [Full Changelog](https://github.com/buildo/rc-datepicker/compare/v5.0.16...v5.0.17) 7 | 8 | #### New features: 9 | 10 | - Custom icon [#177](https://github.com/buildo/rc-datepicker/issues/177) 11 | 12 | ## [v5.0.16](https://github.com/buildo/rc-datepicker/tree/v5.0.16) (2020-04-27) 13 | [Full Changelog](https://github.com/buildo/rc-datepicker/compare/v5.0.14...v5.0.16) 14 | 15 | #### New features: 16 | 17 | - Style.scss imports a file that does not exist in recent versions of react-flexview [#167](https://github.com/buildo/rc-datepicker/issues/167) 18 | - Placeholder not showing [#162](https://github.com/buildo/rc-datepicker/issues/162) 19 | - Disable user input? [#149](https://github.com/buildo/rc-datepicker/issues/149) 20 | 21 | #### Fixes (bugs & defects): 22 | 23 | - fix onChange typings for DatePickerInput: it should accept a second argument "formatteDate: string" [#159](https://github.com/buildo/rc-datepicker/issues/159) 24 | 25 | ## [v5.0.14](https://github.com/buildo/rc-datepicker/tree/v5.0.14) (2019-04-23) 26 | [Full Changelog](https://github.com/buildo/rc-datepicker/compare/v5.0.13...v5.0.14) 27 | 28 | #### New features: 29 | 30 | - Allow already-working "placeholder" props in typings [#161](https://github.com/buildo/rc-datepicker/issues/161) 31 | - Logo Design for rc-datepicker [#154](https://github.com/buildo/rc-datepicker/issues/154) 32 | 33 | ## [v5.0.13](https://github.com/buildo/rc-datepicker/tree/v5.0.13) (2018-04-30) 34 | [Full Changelog](https://github.com/buildo/rc-datepicker/compare/v5.0.12...v5.0.13) 35 | 36 | #### Fixes (bugs & defects): 37 | 38 | - @types/react should not be a dependency [#146](https://github.com/buildo/rc-datepicker/issues/146) 39 | 40 | ## [v5.0.12](https://github.com/buildo/rc-datepicker/tree/v5.0.12) (2018-03-23) 41 | [Full Changelog](https://github.com/buildo/rc-datepicker/compare/v5.0.11...v5.0.12) 42 | 43 | #### Fixes (bugs & defects): 44 | 45 | - String refs are deprecated [#144](https://github.com/buildo/rc-datepicker/issues/144) 46 | 47 | ## [v5.0.11](https://github.com/buildo/rc-datepicker/tree/v5.0.11) (2018-02-26) 48 | [Full Changelog](https://github.com/buildo/rc-datepicker/compare/v5.0.10...v5.0.11) 49 | 50 | #### Fixes (bugs & defects): 51 | 52 | - Add react16 to list of peerDependencies [#142](https://github.com/buildo/rc-datepicker/issues/142) 53 | 54 | ## [v5.0.10](https://github.com/buildo/rc-datepicker/tree/v5.0.10) (2017-10-13) 55 | [Full Changelog](https://github.com/buildo/rc-datepicker/compare/v5.0.9...v5.0.10) 56 | 57 | #### Fixes (bugs & defects): 58 | 59 | - Typescript: `void` as state complains [#136](https://github.com/buildo/rc-datepicker/issues/136) 60 | 61 | ## [v5.0.9](https://github.com/buildo/rc-datepicker/tree/v5.0.9) (2017-08-28) 62 | [Full Changelog](https://github.com/buildo/rc-datepicker/compare/v5.0.8...v5.0.9) 63 | 64 | #### Fixes (bugs & defects): 65 | 66 | - replace t.ReactChildren with exported ReactChildren [#131](https://github.com/buildo/rc-datepicker/issues/131) 67 | 68 | ## [v5.0.8](https://github.com/buildo/rc-datepicker/tree/v5.0.8) (2017-06-20) 69 | [Full Changelog](https://github.com/buildo/rc-datepicker/compare/v5.0.7...v5.0.8) 70 | 71 | #### Fixes (bugs & defects): 72 | 73 | - [.d.ts] onChange first param should be a Date [#129](https://github.com/buildo/rc-datepicker/issues/129) 74 | - Ci is failing with Error: Missing description for prop 'startDate' in 'DatePickerInput'. [#126](https://github.com/buildo/rc-datepicker/issues/126) 75 | 76 | ## [v5.0.7](https://github.com/buildo/rc-datepicker/tree/v5.0.7) (2017-05-29) 77 | [Full Changelog](https://github.com/buildo/rc-datepicker/compare/v5.0.6...v5.0.7) 78 | 79 | #### New features: 80 | 81 | - backport scriptoni@0.6 [#124](https://github.com/buildo/rc-datepicker/issues/124) 82 | - Add the ability to specify the initial visible date [#122](https://github.com/buildo/rc-datepicker/issues/122) 83 | 84 | ## [v5.0.6](https://github.com/buildo/rc-datepicker/tree/v5.0.6) (2017-05-05) 85 | [Full Changelog](https://github.com/buildo/rc-datepicker/compare/v5.0.5...v5.0.6) 86 | 87 | #### New features: 88 | 89 | - typescript support [#105](https://github.com/buildo/rc-datepicker/issues/105) 90 | 91 | ## [v5.0.5](https://github.com/buildo/rc-datepicker/tree/v5.0.5) (2017-04-26) 92 | [Full Changelog](https://github.com/buildo/rc-datepicker/compare/v5.0.4...v5.0.5) 93 | 94 | #### Fixes (bugs & defects): 95 | 96 | - DatePickerInput is ignoring locale when initialized [#114](https://github.com/buildo/rc-datepicker/issues/114) 97 | - Can not find lib/style.css. [#111](https://github.com/buildo/rc-datepicker/issues/111) 98 | 99 | ## [v5.0.4](https://github.com/buildo/rc-datepicker/tree/v5.0.4) (2017-03-08) 100 | [Full Changelog](https://github.com/buildo/rc-datepicker/compare/v5.0.3...v5.0.4) 101 | 102 | #### New features: 103 | 104 | - DatePickerInput: add ability to open the popup on top [#112](https://github.com/buildo/rc-datepicker/issues/112) 105 | 106 | ## [v5.0.3](https://github.com/buildo/rc-datepicker/tree/v5.0.3) (2017-03-06) 107 | [Full Changelog](https://github.com/buildo/rc-datepicker/compare/v5.0.2...v5.0.3) 108 | 109 | ## [v5.0.2](https://github.com/buildo/rc-datepicker/tree/v5.0.2) (2017-03-01) 110 | [Full Changelog](https://github.com/buildo/rc-datepicker/compare/v5.0.1...v5.0.2) 111 | 112 | #### New features: 113 | 114 | - add "disabled" functionality to DatePickerInput [#109](https://github.com/buildo/rc-datepicker/issues/109) 115 | 116 | ## [v5.0.1](https://github.com/buildo/rc-datepicker/tree/v5.0.1) (2017-01-02) 117 | [Full Changelog](https://github.com/buildo/rc-datepicker/compare/v5.0.0...v5.0.1) 118 | 119 | #### Fixes (bugs & defects): 120 | 121 | - style.css is not fully compiled in lib folder [#102](https://github.com/buildo/rc-datepicker/issues/102) 122 | 123 | ## [v5.0.0](https://github.com/buildo/rc-datepicker/tree/v5.0.0) (2016-12-29) 124 | [Full Changelog](https://github.com/buildo/rc-datepicker/compare/v5.0.0-beta2...v5.0.0) 125 | 126 | #### New features: 127 | 128 | - [style] update style with new mockup [#98](https://github.com/buildo/rc-datepicker/issues/98) 129 | - [style] improve UI following styleguide [#81](https://github.com/buildo/rc-datepicker/issues/81) 130 | - React 15.x compatibility [#78](https://github.com/buildo/rc-datepicker/issues/78) 131 | - datepicker layout may break with different fonts [#68](https://github.com/buildo/rc-datepicker/issues/68) 132 | 133 | ## [v5.0.0-beta2](https://github.com/buildo/rc-datepicker/tree/v5.0.0-beta2) (2016-06-06) 134 | [Full Changelog](https://github.com/buildo/rc-datepicker/compare/v5.0.0-beta1...v5.0.0-beta2) 135 | 136 | #### New features: 137 | 138 | - Find an alternative to valueLink [#88](https://github.com/buildo/rc-datepicker/issues/88) 139 | 140 | ## [v5.0.0-beta1](https://github.com/buildo/rc-datepicker/tree/v5.0.0-beta1) (2016-05-30) 141 | [Full Changelog](https://github.com/buildo/rc-datepicker/compare/v4.0.0...v5.0.0-beta1) 142 | 143 | #### New features: 144 | 145 | - [style] improve picker style [#86](https://github.com/buildo/rc-datepicker/issues/86) 146 | - [style] improve button style [#82](https://github.com/buildo/rc-datepicker/issues/82) 147 | 148 | #### Fixes (bugs & defects): 149 | 150 | - [IE] datepicker returns InvalidDate at first click [#83](https://github.com/buildo/rc-datepicker/issues/83) 151 | 152 | ## [v4.0.0](https://github.com/buildo/rc-datepicker/tree/v4.0.0) (2016-04-21) 153 | [Full Changelog](https://github.com/buildo/rc-datepicker/compare/v3.1.2...v4.0.0) 154 | 155 | ## [v3.1.2](https://github.com/buildo/rc-datepicker/tree/v3.1.2) (2016-03-30) 156 | [Full Changelog](https://github.com/buildo/rc-datepicker/compare/v3.1.1...v3.1.2) 157 | 158 | #### Fixes (bugs & defects): 159 | 160 | - January calendar wrong with moment 2.11+ [#65](https://github.com/buildo/rc-datepicker/issues/65) 161 | 162 | ## [v3.1.1](https://github.com/buildo/rc-datepicker/tree/v3.1.1) (2016-03-23) 163 | [Full Changelog](https://github.com/buildo/rc-datepicker/compare/v3.0.2...v3.1.1) 164 | 165 | #### Fixes (bugs & defects): 166 | 167 | - 3.1.0 break change mode button [#75](https://github.com/buildo/rc-datepicker/issues/75) 168 | - closeOnClickOutside not triggered when clicking into another DatePickerInput [#69](https://github.com/buildo/rc-datepicker/issues/69) 169 | 170 | #### New features: 171 | 172 | - Update lodash to ^4 [#73](https://github.com/buildo/rc-datepicker/issues/73) 173 | - use Popover from buildo-react-components [#61](https://github.com/buildo/rc-datepicker/issues/61) 174 | - improve DatePicker and DatePickerInput API [#39](https://github.com/buildo/rc-datepicker/issues/39) 175 | - Drop Travis and Coveralls in favor of Bamboo and Codecov [#11](https://github.com/buildo/rc-datepicker/issues/11) 176 | 177 | ## [v3.0.2](https://github.com/buildo/rc-datepicker/tree/v3.0.2) (2016-01-27) 178 | [Full Changelog](https://github.com/buildo/rc-datepicker/compare/v3.0.1...v3.0.2) 179 | 180 | #### New features: 181 | 182 | - temporarily use moment@2.10.6 [#66](https://github.com/buildo/rc-datepicker/issues/66) 183 | 184 | ## [v3.0.1](https://github.com/buildo/rc-datepicker/tree/v3.0.1) (2015-12-22) 185 | [Full Changelog](https://github.com/buildo/rc-datepicker/compare/v3.0.0...v3.0.1) 186 | 187 | #### New features: 188 | 189 | - add clearable [#62](https://github.com/buildo/rc-datepicker/issues/62) 190 | - fix YearPickerTop propTypes [#60](https://github.com/buildo/rc-datepicker/issues/60) 191 | 192 | ## [v3.0.0](https://github.com/buildo/rc-datepicker/tree/v3.0.0) (2015-10-12) 193 | [Full Changelog](https://github.com/buildo/rc-datepicker/compare/v2.0.0...v3.0.0) 194 | 195 | #### Breaking: 196 | 197 | - separate format in displayFormat and returnFormat [#57](https://github.com/buildo/rc-datepicker/issues/57) 198 | 199 | #### New features: 200 | 201 | - handle undefined value [#55](https://github.com/buildo/rc-datepicker/issues/55) 202 | 203 | ## [v2.0.0](https://github.com/buildo/rc-datepicker/tree/v2.0.0) (2015-08-10) 204 | [Full Changelog](https://github.com/buildo/rc-datepicker/compare/v1.3.0...v2.0.0) 205 | 206 | #### Fixes (bugs & defects): 207 | 208 | - DatePickerInput does not close if same date is inserted [#48](https://github.com/buildo/rc-datepicker/issues/48) 209 | 210 | #### New features: 211 | 212 | - remove unused and unwanted initialDate in componentWillRecieveProps of DatePickerInput [#40](https://github.com/buildo/rc-datepicker/issues/40) 213 | - should we introduce t-comb for prop and type checking? [#38](https://github.com/buildo/rc-datepicker/issues/38) 214 | - safer import of lodash [#35](https://github.com/buildo/rc-datepicker/issues/35) 215 | - add support for valueLink [#13](https://github.com/buildo/rc-datepicker/issues/13) 216 | 217 | ## [v1.3.0](https://github.com/buildo/rc-datepicker/tree/v1.3.0) (2015-07-23) 218 | [Full Changelog](https://github.com/buildo/rc-datepicker/compare/v1.2.0...v1.3.0) 219 | 220 | #### New features: 221 | 222 | - add onShow and onHide callbacks to DatePickerInput [#36](https://github.com/buildo/rc-datepicker/issues/36) 223 | 224 | ## [v1.2.0](https://github.com/buildo/rc-datepicker/tree/v1.2.0) (2015-07-16) 225 | [Full Changelog](https://github.com/buildo/rc-datepicker/compare/v1.1.0...v1.2.0) 226 | 227 | #### Fixes (bugs & defects): 228 | 229 | - CI is failing due to lint error [#30](https://github.com/buildo/rc-datepicker/issues/30) 230 | - remove window.onclick listener in componentWillUnmount [#29](https://github.com/buildo/rc-datepicker/issues/29) 231 | 232 | #### New features: 233 | 234 | - in the last release (v1.1.0) there is a console.log [#28](https://github.com/buildo/rc-datepicker/issues/28) 235 | - Type in closeOnClickOutiside prop. [#25](https://github.com/buildo/rc-datepicker/issues/25) 236 | - Rename to "rc-datepicker"? [#16](https://github.com/buildo/rc-datepicker/issues/16) 237 | 238 | ## [v1.1.0](https://github.com/buildo/rc-datepicker/tree/v1.1.0) (2015-07-06) 239 | [Full Changelog](https://github.com/buildo/rc-datepicker/compare/v1.0.3...v1.1.0) 240 | 241 | #### New features: 242 | 243 | - Should log a warning if required locale is not available [#22](https://github.com/buildo/rc-datepicker/issues/22) 244 | - Add a screenshot in the README [#17](https://github.com/buildo/rc-datepicker/issues/17) 245 | - change repo name (remove "semantic") [#14](https://github.com/buildo/rc-datepicker/issues/14) 246 | - Add example project for quicker development [#7](https://github.com/buildo/rc-datepicker/issues/7) 247 | 248 | #### Fixes (bugs & defects): 249 | 250 | - the calendar doesn't close when you click outside [#20](https://github.com/buildo/rc-datepicker/issues/20) 251 | 252 | ## [v1.0.3](https://github.com/buildo/rc-datepicker/tree/v1.0.3) (2015-06-29) 253 | [Full Changelog](https://github.com/buildo/rc-datepicker/compare/v1.0.2...v1.0.3) 254 | 255 | ## [v1.0.2](https://github.com/buildo/rc-datepicker/tree/v1.0.2) (2015-06-29) 256 | [Full Changelog](https://github.com/buildo/rc-datepicker/compare/v1.0.0...v1.0.2) 257 | 258 | ## [v1.0.0](https://github.com/buildo/rc-datepicker/tree/v1.0.0) (2015-06-26) 259 | [Full Changelog](https://github.com/buildo/rc-datepicker/compare/v0.2.0...v1.0.0) 260 | 261 | #### New features: 262 | 263 | - New build system based on karma/webpack/babel [#8](https://github.com/buildo/rc-datepicker/issues/8) 264 | - Improve build system to produce minified version [#3](https://github.com/buildo/rc-datepicker/issues/3) 265 | 266 | ## [v0.2.0](https://github.com/buildo/rc-datepicker/tree/v0.2.0) (2015-04-22) 267 | [Full Changelog](https://github.com/buildo/rc-datepicker/compare/v0.1.4...v0.2.0) 268 | 269 | #### New features: 270 | 271 | - Add 'name' prop to DatePickerInput [#5](https://github.com/buildo/rc-datepicker/issues/5) 272 | 273 | ## [v0.1.4](https://github.com/buildo/rc-datepicker/tree/v0.1.4) (2015-04-22) 274 | [Full Changelog](https://github.com/buildo/rc-datepicker/compare/v0.1.3...v0.1.4) 275 | 276 | #### New features: 277 | 278 | - Present date picker on input field focus [#4](https://github.com/buildo/rc-datepicker/issues/4) 279 | 280 | ## [v0.1.3](https://github.com/buildo/rc-datepicker/tree/v0.1.3) (2015-04-20) 281 | [Full Changelog](https://github.com/buildo/rc-datepicker/compare/v0.1.2...v0.1.3) 282 | 283 | #### Fixes (bugs & defects): 284 | 285 | - When setting minDate the wrong bounds are set for 'year' mode [#1](https://github.com/buildo/rc-datepicker/issues/1) 286 | 287 | ## [v0.1.2](https://github.com/buildo/rc-datepicker/tree/v0.1.2) (2015-04-11) 288 | [Full Changelog](https://github.com/buildo/rc-datepicker/compare/v0.1.1...v0.1.2) 289 | 290 | ## [v0.1.1](https://github.com/buildo/rc-datepicker/tree/v0.1.1) (2015-04-10) 291 | [Full Changelog](https://github.com/buildo/rc-datepicker/compare/v0.1.0...v0.1.1) 292 | 293 | #### Fixes (bugs & defects): 294 | 295 | - dateString shouldn't be set if date is undefined [#2](https://github.com/buildo/rc-datepicker/issues/2) 296 | 297 | ## [v0.1.0](https://github.com/buildo/rc-datepicker/tree/v0.1.0) (2015-04-10) 298 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 buildo s.r.l.s. 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 | [![NPM](https://nodei.co/npm/rc-datepicker.png?downloads=true&downloadRank=true&stars=true)](https://nodei.co/npm/rc-datepicker/) 2 | 3 | # React Datepicker 4 | A decent and pretty date picker to be used with React 5 | 6 | 7 | ![image](https://cloud.githubusercontent.com/assets/4029499/21563371/023efd1a-ce82-11e6-862e-692c73f1370b.png) 8 | 9 | 10 | ```jsx 11 | import 'moment/locale/it.js'; 12 | import { DatePicker, DatePickerInput } from 'rc-datepicker'; 13 | 14 | const date = '2015-06-26' // or Date or Moment.js 15 | 16 | onChange = (jsDate, dateString) => { 17 | // ... 18 | } 19 | 20 | React.renderComponent( 21 |
22 | // this renders the full component (input and datepicker) 23 | 29 | 30 | // this renders only a fixed datepicker 31 | 32 |
, 33 | document.body 34 | ); 35 | ``` 36 | 37 | You can see a live demo [here](https://rawgit.com/buildo/react-semantic-datepicker/master/examples/build/index.html) 38 | 39 | or check the full examples [here](https://github.com/buildo/react-semantic-datepicker/tree/master/examples) 40 | 41 | **FOR WEBPACK USERS:** webpack by default imports every locale. Please take a look at [this question](http://stackoverflow.com/questions/25384360/how-to-prevent-moment-js-from-loading-locales-with-webpack) on Stack Overflow for possible solutions. 42 | 43 | ## Install 44 | ``` 45 | npm install --save rc-datepicker 46 | ``` 47 | The npm package is compiled in JavaScript 5 48 | 49 | ## Changelog 50 | See [CHANGELOG.md](https://github.com/buildo/rc-datepicker/blob/master/CHANGELOG.md) 51 | 52 | ## DatePickerInput API 53 | See [this readme](https://github.com/buildo/rc-datepicker/blob/master/src/README.md) 54 | 55 | ## Locales 56 | `DatePicker` and `DatePickerInput` use **Moment.js**, therefore they support any locale inside "moment/locale". 57 | 58 | To select a locale you need to require it **before** requiring the datepicker or moment anywhere in your app: this way it will be automatically selected as current locale. 59 | 60 | ```js 61 | import 'moment/locale/fr.js' // or 'rc-datepicker/node_modules/moment/locale/fr.js' if you don't have it in your node_modules folder 62 | 63 | import { DatePickerInput } from 'rc-datepicker'; 64 | ``` 65 | 66 | `DatePickerInput` will now use French locale by default. 67 | 68 | ### Switch between locales 69 | You can switch between locales by passing the prop "locale" to `DatePickerInput` or `DatePicker`. 70 | 71 | **WATCH OUT** this method requires the wanted locale to be already available in your bundle which is only true if you had already imported it or if you're using `moment-with-locales.min.js` 72 | 73 | ```jsx 74 | import 'moment/locale/fr.js' 75 | import 'moment/locale/es.js' 76 | 77 | 78 | ``` 79 | 80 | ## Style 81 | `DatePickerInput` and `DatePicker` come with their own default style. In order to use it you should import it in your project as follows: 82 | 83 | ```js 84 | import 'rc-datepicker/lib/style.css'; 85 | ``` 86 | 87 | ## Examples 88 | 89 | ```shell 90 | $ npm install 91 | $ npm start 92 | $ open http://localhost:8080 93 | ``` 94 | -------------------------------------------------------------------------------- /ci/pipeline.yml: -------------------------------------------------------------------------------- 1 | resource_types: 2 | - name: pull-request 3 | type: docker-image 4 | source: 5 | repository: teliaoss/github-pr-resource 6 | 7 | resources: 8 | - name: master 9 | type: git 10 | icon: github-circle 11 | source: 12 | uri: git@github.com:buildo/rc-datepicker 13 | branch: master 14 | private_key: ((private-key)) 15 | 16 | - name: pr 17 | type: pull-request 18 | source: 19 | repository: buildo/rc-datepicker 20 | access_token: ((github-token)) 21 | 22 | jobs: 23 | - name: pr-test 24 | plan: 25 | - get: rc-datepicker 26 | resource: pr 27 | trigger: true 28 | version: every 29 | - put: pr 30 | params: 31 | path: rc-datepicker 32 | status: pending 33 | context: concourse 34 | - do: 35 | - task: test 36 | file: rc-datepicker/ci/test.yml 37 | attempts: 2 38 | on_success: 39 | put: pr 40 | params: 41 | path: rc-datepicker 42 | status: success 43 | context: concourse 44 | on_failure: 45 | put: pr 46 | params: 47 | path: rc-datepicker 48 | status: failure 49 | context: concourse 50 | 51 | - name: test 52 | plan: 53 | - get: rc-datepicker 54 | resource: master 55 | trigger: true 56 | - do: 57 | - task: test 58 | file: rc-datepicker/ci/test.yml 59 | attempts: 2 60 | -------------------------------------------------------------------------------- /ci/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | npm i --no-package-lock 6 | npm run lint 7 | npm run lint-style 8 | npm run build 9 | -------------------------------------------------------------------------------- /ci/test.yml: -------------------------------------------------------------------------------- 1 | platform: linux 2 | 3 | image_resource: 4 | type: docker-image 5 | source: 6 | repository: node 7 | tag: 10 8 | 9 | inputs: 10 | - name: rc-datepicker 11 | 12 | caches: 13 | - path: rc-datepicker/node_modules 14 | 15 | run: 16 | path: ci/test.sh 17 | dir: rc-datepicker 18 | -------------------------------------------------------------------------------- /examples/DatePicker.example: -------------------------------------------------------------------------------- 1 | class Example extends React.Component { 2 | 3 | constructor(props) { 4 | super(props); 5 | const yesterday = new Date(); 6 | yesterday.setDate(yesterday.getDate() - 1); 7 | this.state = { 8 | yesterday, 9 | value: '2015-05-13' 10 | }; 11 | } 12 | 13 | resetState() { 14 | this.setState({ value: null }); 15 | } 16 | 17 | render() { 18 | return ( 19 |
20 |

jsDate = {String(this.state.value)}

21 | this.setState({value: jsDate})} 25 | locale='fr' 26 | /> 27 |
28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /examples/DatePickerInput.example: -------------------------------------------------------------------------------- 1 | class Example extends React.Component { 2 | 3 | constructor(props) { 4 | super(props); 5 | const yesterday = new Date(); 6 | yesterday.setDate(yesterday.getDate() - 1); 7 | this.state = { 8 | yesterday, 9 | value: null 10 | }; 11 | } 12 | 13 | resetState = () => this.setState({ value: null }) 14 | 15 | render() { 16 | return ( 17 |
18 | 19 |

jsDate = {String(this.state.value)}

20 |
21 | 32 |
33 |
34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /examples/build/bundle.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buildo/rc-datepicker/06c70ef8b96457e470f1b544e440cc3c32ecce1d/examples/build/bundle.js.gz -------------------------------------------------------------------------------- /examples/build/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | RC Datepicker 5 | 6 | 7 | 8 | 13 | 14 | 15 | 16 |
17 | 18 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /examples/build/style.2703567a00ec10a4a818.min.css: -------------------------------------------------------------------------------- 1 | .react-flex-view{box-sizing:"border-box";min-width:0;min-height:0;display:flexbox;display:-ms-flexbox;display:flex;-webkit-box-flex-direction:row;-moz-box-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-box-flex-wrap:nowrap;-moz-box-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap;-ms-flex-align:stretch;flex-align:stretch;-webkit-align-items:stretch;-moz-box-align-items:stretch;-ms-align-items:stretch;align-items:stretch}.react-flex-view.flex-column{-webkit-box-flex-direction:column;-moz-box-flex-direction:column;-ms-flex-direction:column;flex-direction:column}.react-flex-view.flex-wrap{-webkit-box-flex-wrap:wrap;-moz-box-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap}.react-flex-view.align-content-start{-ms-flex-align:start;flex-align:start;-webkit-align-items:flex-start;-moz-box-align-items:flex-start;-ms-align-items:flex-start;align-items:flex-start}.react-flex-view.align-content-center{-ms-flex-align:center;flex-align:center;-webkit-align-items:center;-moz-box-align-items:center;-ms-align-items:center;align-items:center}.react-flex-view.align-content-end{-ms-flex-align:end;flex-align:end;-webkit-align-items:flex-end;-moz-box-align-items:flex-end;-ms-align-items:flex-end;align-items:flex-end}.react-flex-view.justify-content-start{-ms-flex-pack:start;flex-pack:start;-webkit-justify-content:flex-start;-moz-box-justify-content:flex-start;-ms-justify-content:flex-start;justify-content:flex-start}.react-flex-view.justify-content-center{-ms-flex-pack:center;flex-pack:center;-webkit-justify-content:center;-moz-box-justify-content:center;-ms-justify-content:center;justify-content:center}.react-flex-view.justify-content-end{-ms-flex-pack:end;flex-pack:end;-webkit-justify-content:flex-end;-moz-box-justify-content:flex-end;-ms-justify-content:flex-end;justify-content:flex-end}@font-face{font-family:rc-datepicker;src:url("data:application/x-font-ttf;charset=utf-8;base64,AAEAAAALAIAAAwAwT1MvMg8SDIcAAAC8AAAAYGNtYXAAitFNAAABHAAAAGRnYXNwAAAAEAAAAYAAAAAIZ2x5ZmYIkl0AAAGIAAAC7GhlYWQLeirXAAAEdAAAADZoaGVhB3kDyQAABKwAAAAkaG10eBKTAOAAAATQAAAAIGxvY2ECQgFeAAAE8AAAABJtYXhwABgAfgAABQQAAAAgbmFtZUzHCYMAAAUkAAABznBvc3QAAwAAAAAG9AAAACAAAwLqAZAABQAAApkCzAAAAI8CmQLMAAAB6wAzAQkAAAAAAAAAAAAAAAAAAAABEAAAAAAAAAAAAAAAAAAAAABAAADwcwPA/8AAQAPAAEAAAAABAAAAAAAAAAAAAAAgAAAAAAADAAAAAwAAABwAAQADAAAAHAADAAEAAAAcAAQASAAAAA4ACAACAAYAAQAg8A3wVPBz//3//wAAAAAAIPAN8FPwc//9//8AAf/jD/cPsg+UAAMAAQAAAAAAAAAAAAAAAAAAAAEAAf//AA8AAQAAAAAAAAAAAAIAADc5AQAAAAABAAAAAAAAAAAAAgAANzkBAAAAAAEAAAAAAAAAAAACAAA3OQEAAAAAAQA/AD8C5gLmADwAACUUBg8BDgEjIiYvAQcOASMiJi8BLgE1NDY/AScuATU0Nj8BPgEzMhYfATc+ATMyFh8BHgEVFAYPARceARUC5gkHTggUCwsUCKioBxULChUHTggICAioqAgICAhOBxUKCxUHqKgIFAsLFAhOBwkJB6ioBwnDChUHTggICAioqAgICAhOBxUKCxUHqKgIFAsLFAhOBwkJB6ioBwkJB04IFAsLFAioqAcVCwAAAAEAYwAaAp0DnQAVAAAJAhYUDwEGIicBJjQ3ATYyHwEWFAcCnf7RAS8LC18KHgv+WAsLAagLHgpfCwsDC/7Q/tELHgpfCwsBqAoeCwGoCwtfCh4LAAEAPgAaAnkDnQAVAAAJAQYiLwEmNDcJASY0PwE2MhcBFhQHAnn+WAseC18KCgEw/tAKCl8LHgsBqAoKAcL+WAsLXwoeCwEvATALHgpfCwv+WAseCgAAAAAPAAD/twO3A7cAAwAIAAwAEQAVABoAHwAjACgAOAA8AEEARQBWAHsAADczNSMXMzUjFSczNSMXMzUjFSczNSMBMzUjFQMzNSMVATM1IyczNSMVAzU0JisBIgYdARQWOwEyNgEzNSMnMzUjFTsBNSM3NTQmKwEiBh0BFBY7ATI2NTcRFAYjISImNRE0NjsBNTQ2OwEyFh0BMzU0NjsBMhYdATMyFhVJpaXJt7fJpaXJt7fJpaUBpbe33Le3Abelpdu3t8kLCCQICwsIJAgLAaSlpdu3t9ulpRILByQICwsIJAcL3Cse/NseKyseSTYmJCY22zYmJCY2SR4rAKWlpaXJt7e3t9yk/belpQGlpKT+W6Ukt7cB7qQICwsIpAcLC/4ZtyWkpKRupAgLCwikBwsLByT9JR4rKx4C2x4sNiY2NiY2NiY2NiY2LB4AAAEAAAABAABplrQ9Xw889QALBAAAAAAA1FHzNgAAAADUUfM2AAD/twO3A7cAAAAIAAIAAAAAAAAAAQAAA8D/wAAABAAAAAAAA7cAAQAAAAAAAAAAAAAAAAAAAAgEAAAAAAAAAAAAAAACAAAAAyUAPwMAAGMCtwA+A7cAAAAAAAAACgAUAB4AegCkANABdgAAAAEAAAAIAHwADwAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAOAK4AAQAAAAAAAQANAAAAAQAAAAAAAgAHAJYAAQAAAAAAAwANAEgAAQAAAAAABAANAKsAAQAAAAAABQALACcAAQAAAAAABgANAG8AAQAAAAAACgAaANIAAwABBAkAAQAaAA0AAwABBAkAAgAOAJ0AAwABBAkAAwAaAFUAAwABBAkABAAaALgAAwABBAkABQAWADIAAwABBAkABgAaAHwAAwABBAkACgA0AOxyYy1kYXRlcGlja2VyAHIAYwAtAGQAYQB0AGUAcABpAGMAawBlAHJWZXJzaW9uIDEuMABWAGUAcgBzAGkAbwBuACAAMQAuADByYy1kYXRlcGlja2VyAHIAYwAtAGQAYQB0AGUAcABpAGMAawBlAHJyYy1kYXRlcGlja2VyAHIAYwAtAGQAYQB0AGUAcABpAGMAawBlAHJSZWd1bGFyAFIAZQBnAHUAbABhAHJyYy1kYXRlcGlja2VyAHIAYwAtAGQAYQB0AGUAcABpAGMAawBlAHJGb250IGdlbmVyYXRlZCBieSBJY29Nb29uLgBGAG8AbgB0ACAAZwBlAG4AZQByAGEAdABlAGQAIABiAHkAIABJAGMAbwBNAG8AbwBuAC4AAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA") format("truetype");font-weight:400;font-style:normal}.icon-rc-datepicker{font-family:rc-datepicker!important;speak:none;font-style:normal;font-weight:400;font-variant:normal;text-transform:none;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.icon-rc-datepicker_clear:before{content:"\F00D"}.icon-rc-datepicker_prev:before{content:"\F053"}.icon-rc-datepicker_next:before{content:"\F054"}.icon-rc-datepicker_calendar:before{content:"\F073"}.react-datepicker-component{position:relative;font-size:14px;font-weight:600}.react-datepicker-component.is-disabled{pointer-events:none;opacity:.5}.react-datepicker-component .react-datepicker{margin-left:5px;margin-top:5px;display:inherit}.react-datepicker-component .react-datepicker-input{position:relative;min-width:150px;height:36px;background:linear-gradient(#fff,#f2f4f7);border:1px solid #ced0da;border-radius:4px}.react-datepicker-component .react-datepicker-input.is-small{height:32px}.react-datepicker-component .react-datepicker-input input{width:100%;height:100%;background:transparent;border:none;box-sizing:border-box;padding-left:15px;padding-right:60px;font-size:14px;color:#354052;font-weight:600}.react-datepicker-component .react-datepicker-input input:focus{outline:none}.react-datepicker-component .react-datepicker-input input::-webkit-input-placeholder{color:#9098a7;font-weight:600}.react-datepicker-component .react-datepicker-input input:-moz-placeholder,.react-datepicker-component .react-datepicker-input input::-moz-placeholder{color:#9098a7;font-weight:600}.react-datepicker-component .react-datepicker-input input:-ms-input-placeholder{color:#9098a7;font-weight:600}.react-datepicker-component .react-datepicker-input .button-wrapper{position:absolute;right:0;top:0;height:100%}.react-datepicker-component .react-datepicker-input .button-wrapper .input-button{margin:0 10px;background:transparent;font-size:15px;border-radius:0 4px 4px 0;cursor:pointer;color:#b5c0ce}.react-datepicker-component .react-datepicker-input .button-wrapper .input-button:hover{background:transparent;color:#9098a7}.react-datepicker-component .react-datepicker-input .button-wrapper .clear-button{cursor:pointer;font-size:13px;color:#b5c0ce}.react-datepicker-component .react-datepicker-input .button-wrapper .clear-button:hover{color:#db242c}.react-datepicker-component .react-datepicker-input:hover{background:linear-gradient(#fff,#dfe3e8);border:1px solid #ced0da}.react-datepicker-component .react-datepicker-input:hover input{color:#354052}.react-datepicker-component .react-datepicker-input:hover input::-webkit-input-placeholder{color:#9098a7}.react-datepicker-component .react-datepicker-input:hover input:-moz-placeholder,.react-datepicker-component .react-datepicker-input:hover input::-moz-placeholder{color:#9098a7}.react-datepicker-component .react-datepicker-input:hover input:-ms-input-placeholder{color:#9098a7}.react-datepicker-component .react-datepicker-input:hover .button-wrapper .input-button{color:#9098a7}.react-datepicker-component .react-datepicker-input.is-open{background:linear-gradient(#fff,#dfe3e8);border:1px solid #2da1f8}.react-datepicker-component .react-datepicker-input.is-open input{color:#354052}.react-datepicker-component .react-datepicker-input.is-open input::-webkit-input-placeholder{color:#354052}.react-datepicker-component .react-datepicker-input.is-open input:-moz-placeholder,.react-datepicker-component .react-datepicker-input.is-open input::-moz-placeholder{color:#354052}.react-datepicker-component .react-datepicker-input.is-open input:-ms-input-placeholder{color:#354052}.react-datepicker-component .react-datepicker-input.is-open .button-wrapper .input-button{color:#b5c0ce}.react-datepicker-component .react-datepicker-input.has-value input{color:#354052}.react-datepicker{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;display:inline-block;font-size:14px;font-weight:600}.react-datepicker.floating{position:absolute;z-index:10000;box-shadow:1px 1px 5px 1px rgba(0,0,0,.1)}.react-datepicker.position-top{top:auto;bottom:100%;margin-bottom:5px}.react-datepicker.position-top .react-datepicker-container:after,.react-datepicker.position-top .react-datepicker-container:before{top:100%;left:50%;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none}.react-datepicker.position-top .react-datepicker-container:after{border-top-color:#d9dee3;border-width:5px;margin-left:-5px}.react-datepicker.position-top .react-datepicker-container:before{border-top-color:#d9dee3;border-width:6px;margin-left:-6px}.react-datepicker:not(.position-top) .react-datepicker-container .react-datepicker-top:after,.react-datepicker:not(.position-top) .react-datepicker-container .react-datepicker-top:before{bottom:100%;left:50%;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none}.react-datepicker:not(.position-top) .react-datepicker-container .react-datepicker-top:after{border-bottom-color:#2da1f8;border-width:5px;margin-left:-5px}.react-datepicker:not(.position-top) .react-datepicker-container .react-datepicker-top:before{border-bottom-color:#d9dee3;border-width:6px;margin-left:-6px}.react-datepicker .react-datepicker-container{width:250px;position:relative}.react-datepicker .react-datepicker-container .react-datepicker-top{text-align:center;background:linear-gradient(#2da1f8,#1789dd);color:#fff;border-top:1px solid #2da1f8;border-left:1px solid #2da1f8;border-right:1px solid #2da1f8;border-top-left-radius:2px;border-top-right-radius:2px}.react-datepicker .react-datepicker-container .react-datepicker-top .week-days{height:35px}.react-datepicker .react-datepicker-container .react-datepicker-top .week-days .week-day{cursor:default;font-weight:400;font-size:13px}.react-datepicker .react-datepicker-container .react-datepicker-top .display{height:35px}.react-datepicker .react-datepicker-container .react-datepicker-top .display .react-datepicker-button{text-decoration:none;padding:4px;text-align:center;font-size:15px;letter-spacing:.5px;cursor:pointer}.react-datepicker .react-datepicker-container .react-datepicker-top .display .react-datepicker-button.button-left{font-size:13px;padding:4px 16px;border-top-left-radius:2px}.react-datepicker .react-datepicker-container .react-datepicker-top .display .react-datepicker-button.button-right{font-size:13px;padding:4px 16px;border-top-right-radius:2px}.react-datepicker .react-datepicker-container .react-datepicker-top .display .react-datepicker-button:hover{background:rgba(0,0,0,.075);border-radius:4px}.react-datepicker .react-datepicker-container .react-datepicker-top .display .react-datepicker-button.fixed:hover{background:transparent;cursor:default}.react-datepicker .react-datepicker-container .react-datepicker-body{border-left:1px solid #d9dee3;border-right:1px solid #d9dee3;border-bottom:1px solid #d9dee3;border-bottom-right-radius:2px;border-bottom-left-radius:2px}.react-datepicker .react-datepicker-container .react-datepicker-body .react-datepicker-row{margin-top:0;width:100%;min-height:30px}.react-datepicker .react-datepicker-container .react-datepicker-body .react-datepicker-row:not(:last-child){border-bottom:1px solid #d9dee3}.react-datepicker .react-datepicker-container .react-datepicker-body .react-datepicker-row:last-child .react-datepicker-picker:first-child{border-bottom-left-radius:2px}.react-datepicker .react-datepicker-container .react-datepicker-body .react-datepicker-row:last-child .react-datepicker-picker:last-child{border-bottom-right-radius:2px}.react-datepicker .react-datepicker-container .react-datepicker-body .react-datepicker-row .react-datepicker-picker{color:#9098a7;background:#fff;cursor:pointer;text-decoration:none;font-weight:400}.react-datepicker .react-datepicker-container .react-datepicker-body .react-datepicker-row .react-datepicker-picker:not(:last-child){border-right:1px solid #d9dee3}.react-datepicker .react-datepicker-container .react-datepicker-body .react-datepicker-row .react-datepicker-picker.day{min-height:30px}.react-datepicker .react-datepicker-container .react-datepicker-body .react-datepicker-row .react-datepicker-picker.month,.react-datepicker .react-datepicker-container .react-datepicker-body .react-datepicker-row .react-datepicker-picker.year{min-height:65px}.react-datepicker .react-datepicker-container .react-datepicker-body .react-datepicker-row .react-datepicker-picker:hover{color:#354052}.react-datepicker .react-datepicker-container .react-datepicker-body .react-datepicker-row .react-datepicker-picker.selected{color:#354052;background:#bad7f2;font-weight:700;margin-left:-1px;margin-top:-1px;margin-bottom:-1px;border:1px solid #bad7f2}.react-datepicker .react-datepicker-container .react-datepicker-body .react-datepicker-row .react-datepicker-picker.current{font-weight:600;color:#354052;background:#f0f3f8}.react-datepicker .react-datepicker-container .react-datepicker-body .react-datepicker-row .react-datepicker-picker.current:hover{color:#354052;background:#dfe5f0}.react-datepicker .react-datepicker-container .react-datepicker-body .react-datepicker-row .react-datepicker-picker.selected.current{color:#354052;background:#bad7f2}.react-datepicker .react-datepicker-container .react-datepicker-body .react-datepicker-row .react-datepicker-picker.disabled{cursor:default;color:#9098a7;background:#fff}.react-datepicker .react-datepicker-container .react-datepicker-body .react-datepicker-row .react-datepicker-picker.disabled:hover{color:#9098a7;background:#fff} -------------------------------------------------------------------------------- /examples/examples.js: -------------------------------------------------------------------------------- 1 | import 'moment/locale/fr.js'; 2 | 3 | import React from 'react'; 4 | import ReactDOM from 'react-dom'; 5 | import { DatePicker, DatePickerInput } from '../src'; 6 | 7 | import '../src/style.scss'; 8 | 9 | class Example extends React.Component { 10 | 11 | state = { 12 | datePickerDate: '2015-05-13', 13 | datePickerInputDate: null, 14 | datePickerInputDate2: null, 15 | showInput: true, 16 | disabled: false 17 | } 18 | 19 | toggleInput = () => this.setState({ showInput: !this.state.showInput }) 20 | 21 | onClear = () => this.setState({ datePickerDate: null }) 22 | 23 | log = (...x) => console.log(...x) // eslint-disable-line no-console 24 | 25 | resetState = () => this.setState({ datePickerInputDate2: undefined }) 26 | 27 | render() { 28 | const yesterday = new Date(); 29 | yesterday.setDate(yesterday.getDate() - 1); 30 | return ( 31 |
32 |

DatePickerInput

33 | 34 |

onChange(jsDate, dateString)

35 |

dateString = "{this.state.datePickerInputDate}"

36 | 37 | {this.state.showInput && 38 |
39 | this.setState({ datePickerInputDate: dateString })} 45 | onShow={this.log.bind(this, 'show')} 46 | onHide={this.log.bind(this, 'hide')} 47 | showOnInputClick 48 | placeholder='placeholder' 49 | locale='de' 50 | onClear={this.onClear} 51 | /> 52 |
53 | } 54 | 55 |

DatePicker (fixed calendar component)

56 |

onChange(jsDate)

57 |

jsDate = {String(this.state.datePickerDate)}

58 | this.setState({ datePickerDate: jsDate })} 63 | /> 64 |

65 |

VALUE LINK

66 | 67 |

jsDate = {String(this.state.datePickerInputDate2)}

68 |
69 | this.setState({ datePickerInputDate2 }) 77 | }} 78 | showOnInputClick 79 | placeholder='placeholder' 80 | locale='de' 81 | /> 82 |
83 |
84 | ); 85 | } 86 | } 87 | 88 | ReactDOM.render(, document.getElementById('container')); 89 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | RC Datepicker 5 | 6 | 7 | <% for (var chunk in htmlWebpackPlugin.files.chunks.main.css) { %> 8 | 13 | <% } %> 14 | 15 | 16 |
17 | <% for (var chunk in htmlWebpackPlugin.files.chunks) { %> 18 | 22 | <% } %> 23 | 24 | 25 | -------------------------------------------------------------------------------- /examples/webpack.base.babel.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import StyleLintPlugin from 'stylelint-webpack-plugin'; 3 | 4 | export const paths = { 5 | SRC: path.resolve(__dirname, '../src'), 6 | ENTRY: path.resolve(__dirname, 'examples.js'), 7 | INDEX_HTML: path.resolve(__dirname, 'index.html'), 8 | BUILD: path.resolve(__dirname, 'build'), 9 | EXAMPLES: path.resolve(__dirname), 10 | NODE_MODULES: path.resolve(__dirname, 'node_modules') 11 | }; 12 | 13 | export default { 14 | output: { 15 | path: paths.BUILD, 16 | filename: 'bundle.js' 17 | }, 18 | 19 | resolve: { 20 | root: [paths.NODE_MODULES] 21 | }, 22 | 23 | plugins: [ 24 | new StyleLintPlugin({ 25 | files: '**/*.scss', 26 | failOnError: false, 27 | syntax: 'scss' 28 | }) 29 | ], 30 | 31 | module: { 32 | loaders: [ 33 | { 34 | test: /\.jsx?$/, 35 | loaders: ['babel'], 36 | include: [paths.SRC, paths.EXAMPLES] 37 | } 38 | ], 39 | preLoaders: [ 40 | { 41 | test: /\.jsx?$/, 42 | loader: 'eslint', 43 | include: [paths.SRC, paths.EXAMPLES], 44 | exclude: /node_modules/ 45 | } 46 | ] 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /examples/webpack.config.babel.js: -------------------------------------------------------------------------------- 1 | import webpack from 'webpack'; 2 | import HtmlWebpackPlugin from 'html-webpack-plugin'; 3 | import ExtractTextPlugin from 'extract-text-webpack-plugin'; 4 | import webpackBase, { paths } from './webpack.base.babel'; 5 | 6 | export default { 7 | ...webpackBase, 8 | 9 | entry: [ 10 | 'webpack/hot/dev-server', 11 | paths.ENTRY 12 | ], 13 | 14 | devtool: 'source-map', 15 | 16 | devServer: { 17 | contentBase: paths.BUILD, 18 | host: '0.0.0.0', 19 | port: '8080' 20 | }, 21 | 22 | module: { 23 | ...webpackBase.module, 24 | loaders: webpackBase.module.loaders.concat([{ 25 | test: /\.scss$/, 26 | loader: ExtractTextPlugin.extract('style', 'css!resolve-url!sass?sourceMap') 27 | }]) 28 | }, 29 | 30 | plugins: [ 31 | ...webpackBase.plugins, 32 | new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify('development') }), 33 | new HtmlWebpackPlugin({ inject: false, template: paths.INDEX_HTML }), 34 | new ExtractTextPlugin('style', 'style.[hash].min.css') 35 | ] 36 | }; 37 | -------------------------------------------------------------------------------- /examples/webpack.config.build.babel.js: -------------------------------------------------------------------------------- 1 | import webpack from 'webpack'; 2 | import CompressionPlugin from 'compression-webpack-plugin'; 3 | import HtmlWebpackPlugin from 'html-webpack-plugin'; 4 | import ExtractTextPlugin from 'extract-text-webpack-plugin'; 5 | import webpackBase, { paths } from './webpack.base.babel'; 6 | 7 | export default { 8 | ...webpackBase, 9 | 10 | entry: paths.ENTRY, 11 | 12 | module: { 13 | ...webpackBase.module, 14 | loaders: webpackBase.module.loaders.concat([{ 15 | test: /\.scss$/, 16 | loader: ExtractTextPlugin.extract('style', 'css!resolve-url!sass?sourceMap') 17 | }]) 18 | }, 19 | 20 | plugins: [ 21 | ...webpackBase.plugins, 22 | new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify('production') }), 23 | new webpack.optimize.UglifyJsPlugin({ 24 | compress: { 25 | warnings: false, 26 | screw_ie8: true 27 | } 28 | }), 29 | new CompressionPlugin({ regExp: /\.js$|\.css$/ }), 30 | new webpack.NoErrorsPlugin(), 31 | new HtmlWebpackPlugin({ inject: false, template: paths.INDEX_HTML }), 32 | new ExtractTextPlugin('style', 'style.[hash].min.css') 33 | ] 34 | }; 35 | -------------------------------------------------------------------------------- /generateReadme.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { generateReadme } from 'react-readme-generator'; 3 | 4 | generateReadme(path.resolve('src/DatePickerInput.js')); 5 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as moment from 'moment'; 3 | 4 | export type Mode = { 5 | 'day': string, 6 | 'month': string, 7 | 'year': string 8 | }; 9 | 10 | export type Position = 'top' | 'bottom'; 11 | 12 | export type Value = string | Date | moment.Moment ; // | MomentDate 13 | 14 | export type ValueLink = { 15 | value? : Value, 16 | requestChange(e: Date): void 17 | } 18 | 19 | export type DateOnChangeHandler = { 20 | (jsDate: Date, dateString: string): void; 21 | } 22 | 23 | export interface DatePickerInputProps { 24 | value?: Value, 25 | valueLink?: ValueLink, 26 | onChange?: DateOnChangeHandler, 27 | onShow?: () => void, 28 | onHide?: () => void, 29 | onClear?: () => void, 30 | small?: boolean, 31 | defaultValue?: Value, 32 | minDate?: Value, 33 | maxDate?: Value, 34 | locale?: string, 35 | startMode?: Mode, 36 | fixedMode?: boolean, 37 | displayFormat?: string, 38 | returnFormat?: string, 39 | format?: string, 40 | validationFormat?: string, 41 | showOnInputClick?: boolean, 42 | closeOnClickOutside?: boolean, 43 | showInputButton?: boolean, 44 | autoClose?: boolean, 45 | floating?: boolean, 46 | disabled?: boolean, 47 | position?: Position, 48 | iconClassName?: string, 49 | iconClearClassName?: string, 50 | className?: string, // used to omit from inputProps 51 | style?: object, // used to omit from inputProps 52 | placeholder?: string 53 | } 54 | 55 | export class DatePickerInput extends React.Component {} 56 | 57 | export interface DatePickerProps { 58 | onChange?: DateOnChangeHandler; 59 | value?: Value, 60 | valueLink?: ValueLink, 61 | defaultValue?: Value, 62 | minDate?: Value, 63 | maxDate?: Value, 64 | locale?: string, 65 | startMode?: Mode, 66 | fixedMode?: boolean, 67 | returnFormat?: string, 68 | floating?: boolean, 69 | closeOnClickOutside?: boolean, // used only with DatePickerInput 70 | className?: string, 71 | prevIconClassName?: string, 72 | nextIconClassName?: string, 73 | position: Position, 74 | style?: object, 75 | placeholder?: string 76 | } 77 | 78 | export class DatePicker extends React.Component {} 79 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib'); 2 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const webpack = require('webpack'); 5 | 6 | const paths = { 7 | SRC: path.resolve(__dirname, 'src'), 8 | TEST: path.resolve(__dirname, 'test') 9 | }; 10 | 11 | module.exports = (config) => { 12 | config.set({ 13 | 14 | browserNoActivityTimeout: 30000, 15 | 16 | browsers: [ 'Chrome' ], 17 | 18 | singleRun: true, 19 | 20 | frameworks: [ 'mocha' ], 21 | 22 | files: [ 23 | 'karma.js' 24 | ], 25 | 26 | preprocessors: { 27 | 'karma.js': [ 'webpack' ] 28 | }, 29 | 30 | reporters: process.env.CONTINUOUS_INTEGRATION ? [ 'bamboo', 'coverage' ] : [ 'dots', 'coverage' ], 31 | 32 | bambooReporter: { 33 | filename: 'mocha.json' 34 | }, 35 | 36 | coverageReporter: { 37 | reporters: [ 38 | process.env.CONTINUOUS_INTEGRATION ? 39 | { type: 'lcov', subdir: 'lcov-report' } : 40 | { type: 'html', subdir: 'html-report' } 41 | ] 42 | }, 43 | 44 | webpack: { 45 | module: { 46 | loaders: [ 47 | { 48 | test: /\.jsx?$/, 49 | loader: 'babel', 50 | include: [paths.SRC, paths.TEST], 51 | exclude: [/node_modules/] 52 | } 53 | ], 54 | preLoaders: [ 55 | { 56 | test: /\.jsx?$/, 57 | loader: 'isparta', 58 | include: paths.SRC, 59 | exclude: /node_modules/ 60 | }, { 61 | test: /\.jsx?$/, 62 | loader: 'eslint', 63 | include: paths.SRC, 64 | exclude: /node_modules/ 65 | } 66 | ] 67 | }, 68 | plugins: [ 69 | new webpack.DefinePlugin({ 70 | 'process.env.NODE_ENV': JSON.stringify('test') 71 | }) 72 | ] 73 | }, 74 | 75 | webpackMiddleware: { 76 | noInfo: true //please don't spam the console when running in karma! 77 | } 78 | 79 | }); 80 | }; 81 | -------------------------------------------------------------------------------- /karma.js: -------------------------------------------------------------------------------- 1 | // called by karma 2 | const testsContext = require.context('./test', true, /tests/); 3 | testsContext.keys().forEach(testsContext); 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rc-datepicker", 3 | "version": "5.0.17", 4 | "description": "DatePicker and DatePickerInput to be used with React", 5 | "main": "index.js", 6 | "files": [ 7 | "src", 8 | "lib", 9 | "examples", 10 | "index.js", 11 | "styles.js", 12 | "index.d.ts" 13 | ], 14 | "types": "./index.d.ts", 15 | "scripts": { 16 | "test": "./node_modules/karma/bin/karma start", 17 | "build": "rm -rf lib && mkdir lib && babel src -d lib && webpack --config webpack.config.build.babel.js && rm lib/bundle.js", 18 | "lint": "scriptoni lint src examples/examples.js test", 19 | "lint-fix": "scriptoni lint src examples/examples.js test --fix", 20 | "lint-style": "scriptoni lint-style ./**/*.scss --syntax scss", 21 | "lint-style-fix": "scriptoni stylefmt ./**/*.scss", 22 | "prepublish": "npm run build", 23 | "preversion": "npm run lint && npm run test && npm run build-examples", 24 | "build-examples": "npm run clean && webpack --config examples/webpack.config.build.babel.js --progress", 25 | "start": "webpack-dev-server --config examples/webpack.config.babel.js --progress --hot --inline", 26 | "clean": "rm -rf examples/build", 27 | "generate-readme": "babel-node ./generateReadme.js", 28 | "release-version": "smooth-release" 29 | }, 30 | "repository": { 31 | "type": "git", 32 | "url": "https://github.com/buildo/rc-datepicker.git" 33 | }, 34 | "keywords": [ 35 | "react", 36 | "react-component", 37 | "datepicker", 38 | "calendar", 39 | "date" 40 | ], 41 | "author": "Francesco Cioria ", 42 | "license": "MIT", 43 | "bugs": { 44 | "url": "https://github.com/buildo/rc-datepicker/issues" 45 | }, 46 | "homepage": "https://github.com/buildo/rc-datepicker", 47 | "devDependencies": { 48 | "@types/react": "^15.6.28", 49 | "babel-cli": "^6.8.0", 50 | "babel-core": "^6.9.0", 51 | "babel-loader": "^6.4.1", 52 | "babel-preset-buildo": "^0.1.1", 53 | "babel-register": "^6.9.0", 54 | "compression-webpack-plugin": "^0.3.2", 55 | "css-loader": "^0.23.1", 56 | "debug": "^2.6.9", 57 | "eslint-loader": "^1.9.0", 58 | "expect": "^1.20.2", 59 | "extract-text-webpack-plugin": "^1.0.1", 60 | "html-webpack-plugin": "^2.30.1", 61 | "isparta-loader": "^2.0.0", 62 | "karma": "^0.13.22", 63 | "karma-bamboo-reporter": "^0.1.2", 64 | "karma-chrome-launcher": "^1.0.1", 65 | "karma-coverage": "^1.1.2", 66 | "karma-mocha": "^1.3.0", 67 | "karma-webpack": "^1.8.1", 68 | "mocha": "^2.5.3", 69 | "node-libs-browser": "^1.1.1", 70 | "node-sass": "^4.13.1", 71 | "react": "^0.14", 72 | "react-addons-test-utils": "^0.14.8", 73 | "react-dom": "^0.14", 74 | "react-readme-generator": "0.0.1", 75 | "require-dir": "^0.3.2", 76 | "resolve-url-loader": "^1.6.1", 77 | "sass-loader": "^3.2.3", 78 | "scriptoni": "^0.6.16", 79 | "smooth-release": "^8.0.3", 80 | "style-loader": "^0.13.2", 81 | "webpack": "^1.15.0", 82 | "webpack-dev-server": "^1.16.5" 83 | }, 84 | "peerDependencies": { 85 | "react": "^0.14 || ^15.0 || ^16.0", 86 | "react-dom": "^0.14 || ^15.0 || ^16.0" 87 | }, 88 | "dependencies": { 89 | "classnames": "^2.2.5", 90 | "lodash": "^4.13.1", 91 | "moment": "^2.13.0", 92 | "react-flexview": "^4.0.4", 93 | "revenge": "^0.4.4", 94 | "tcomb": "^3.2.1", 95 | "tcomb-react": "^0.9.0" 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/DatePicker.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import moment from 'moment'; 3 | import t from 'tcomb'; 4 | import { props } from 'tcomb-react'; 5 | import { format, valueLink, skinnable } from './utils'; 6 | import { Value, Mode } from './utils/model'; 7 | import DayPicker from './daypicker/DayPicker'; 8 | import MonthPicker from './monthpicker/MonthPicker'; 9 | import YearPicker from './yearpicker/YearPicker'; 10 | import cx from 'classnames'; 11 | 12 | @valueLink 13 | @format 14 | @skinnable() 15 | @props({ 16 | onChange: t.maybe(t.Function), 17 | value: t.maybe(Value), 18 | valueLink: t.maybe(t.interface({ 19 | value: t.maybe(Value), 20 | requestChange: t.Function 21 | })), 22 | defaultValue: t.maybe(Value), 23 | minDate: t.maybe(Value), 24 | maxDate: t.maybe(Value), 25 | locale: t.maybe(t.String), 26 | startMode: t.maybe(Mode), 27 | startDate: t.maybe(Value), 28 | fixedMode: t.maybe(t.Boolean), 29 | returnFormat: t.maybe(t.String), 30 | floating: t.maybe(t.Boolean), 31 | closeOnClickOutside: t.maybe(t.Boolean), // used only with DatePickerInput 32 | className: t.maybe(t.String), 33 | prevIconClassName: t.maybe(t.String), 34 | nextIconClassName: t.maybe(t.String), 35 | position: t.maybe(t.enums.of(['top', 'bottom'])), 36 | style: t.maybe(t.Object), 37 | placeholder: t.maybe(t.String) 38 | }) 39 | export default class DatePicker extends React.Component { 40 | 41 | static defaultProps = { 42 | startMode: 'day', 43 | className: '', 44 | prevIconClassName: 'icon-rc-datepicker icon-rc-datepicker_prev', 45 | nextIconClassName: 'icon-rc-datepicker icon-rc-datepicker_next', 46 | style: {}, 47 | position: 'bottom' 48 | } 49 | 50 | constructor(props) { 51 | super(props); 52 | if (props.locale) { 53 | moment.locale(props.locale); 54 | if (process.env.NODE_ENV !== 'production' && moment.locale() !== props.locale) { 55 | console.warn(`Setting "${props.locale}" as locale failed. Did you import it correctly?`); // eslint-disable-line no-console 56 | } 57 | } 58 | this.state = this.getStateFromProps(props); 59 | } 60 | 61 | getStateFromProps = (_props) => { 62 | const { value } = this.getValueLink(_props); 63 | const { defaultValue, startDate, startMode } = _props; 64 | const date = typeof value === 'string' ? this.parsePropDateString(value) : moment(value); 65 | const initialDate = defaultValue ? 66 | typeof defaultValue === 'string' ? this.parsePropDateString(defaultValue) : moment(defaultValue) : 67 | typeof startDate === 'string' ? this.parsePropDateString(startDate) : moment(startDate); 68 | 69 | const visibleDate = value ? date.clone() : initialDate; // must be copy, otherwise they get linked 70 | return { 71 | date: value ? date.clone() : undefined, 72 | visibleDate, 73 | mode: startMode 74 | }; 75 | } 76 | 77 | onChangeVisibleDate = (date) => { 78 | this.setState({ visibleDate: date }); 79 | } 80 | 81 | onChangeSelectedDate = (date) => { 82 | this.setState({ 83 | visibleDate: date.clone(), // must be copy, otherwise they get linked 84 | date 85 | }, () => this.getValueLink().requestChange(date.toDate())); 86 | } 87 | 88 | onChangeMode = (mode) => { 89 | setTimeout(() => this.setState({ mode })); 90 | } 91 | 92 | changeYear = (year) => { 93 | this.setState({ visibleDate: this.state.visibleDate.clone().year(year) }); 94 | } 95 | 96 | changeMonth = (month) => { 97 | this.setState({ visibleDate: this.state.visibleDate.clone().month(month) }); 98 | } 99 | 100 | getLocals({ className, style, floating, minDate, maxDate, fixedMode, prevIconClassName, nextIconClassName, position }) { 101 | const { mode, date, visibleDate } = this.state; 102 | return { 103 | style, 104 | className: cx('react-datepicker', className, { floating, 'position-top': position === 'top' }), 105 | dayPickerProps: mode === Mode('day') && { 106 | date, visibleDate, 107 | minDate, maxDate, 108 | mode, 109 | fixedMode, 110 | prevIconClassName, 111 | nextIconClassName, 112 | changeMonth: this.changeMonth, 113 | onSelectDate: this.onChangeSelectedDate, 114 | onChangeMode: this.onChangeMode 115 | }, 116 | monthPickerProps: mode === Mode('month') && { 117 | date, visibleDate, 118 | minDate, maxDate, 119 | mode, 120 | fixedMode, 121 | prevIconClassName, 122 | nextIconClassName, 123 | changeYear: this.changeYear, 124 | onSelectDate: this.onChangeSelectedDate, 125 | onChangeMode: this.onChangeMode, 126 | onChangeVisibleDate: this.onChangeVisibleDate 127 | }, 128 | yearPickerProps: mode === Mode('year') && { 129 | date, visibleDate, 130 | minDate, maxDate, 131 | mode, 132 | fixedMode, 133 | prevIconClassName, 134 | nextIconClassName, 135 | changeYear: this.changeYear, 136 | onSelectDate: this.onChangeSelectedDate, 137 | onChangeMode: this.onChangeMode, 138 | onChangeVisibleDate: this.onChangeVisibleDate 139 | } 140 | }; 141 | } 142 | 143 | template({ className, style, dayPickerProps, monthPickerProps, yearPickerProps }) { 144 | return ( 145 |
146 | {dayPickerProps && } 147 | {monthPickerProps && } 148 | {yearPickerProps && } 149 |
150 | ); 151 | } 152 | 153 | componentWillReceiveProps(nextProps) { 154 | if (this.getValueLink(nextProps).value !== this.getValueLink().value) { 155 | this.setState(this.getStateFromProps(nextProps)); 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/DatePickerInput.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import moment from 'moment'; 4 | import t from 'tcomb'; 5 | import { props } from 'tcomb-react'; 6 | import omit from 'lodash/omit'; 7 | import DatePicker from './DatePicker'; 8 | import { Value } from './utils/model'; 9 | import { format, valueLink, skinnable } from './utils'; 10 | import cx from 'classnames'; 11 | import Input from './Input'; 12 | 13 | const INVALID = 'Invalid date'; 14 | const ENTER_KEYCODE = 13; 15 | 16 | export const Props = { 17 | value: t.maybe(Value), 18 | valueLink: t.maybe(t.interface({ 19 | value: t.maybe(Value), 20 | requestChange: t.Function 21 | })), 22 | onChange: t.maybe(t.Function), 23 | onShow: t.maybe(t.Function), 24 | onHide: t.maybe(t.Function), 25 | onClear: t.maybe(t.Function), 26 | small: t.maybe(t.Boolean), 27 | defaultValue: t.maybe(Value), 28 | minDate: t.maybe(Value), 29 | maxDate: t.maybe(Value), 30 | locale: t.maybe(t.String), 31 | startMode: t.maybe(t.enums.of(['day', 'month', 'year'])), 32 | startDate: t.maybe(Value), 33 | fixedMode: t.maybe(t.Boolean), 34 | displayFormat: t.maybe(t.String), 35 | returnFormat: t.maybe(t.String), 36 | format: t.maybe(t.String), 37 | validationFormat: t.maybe(t.String), 38 | showOnInputClick: t.maybe(t.Boolean), 39 | closeOnClickOutside: t.maybe(t.Boolean), 40 | showInputButton: t.maybe(t.Boolean), 41 | autoClose: t.maybe(t.Boolean), 42 | floating: t.maybe(t.Boolean), 43 | disabled: t.maybe(t.Boolean), 44 | position: t.maybe(t.enums.of(['top', 'bottom'])), 45 | iconClassName: t.maybe(t.String), 46 | iconClearClassName: t.maybe(t.String), 47 | className: t.maybe(t.String), // used to omit from inputProps 48 | style: t.maybe(t.Object), // used to omit from inputProps 49 | placeholder: t.maybe(t.String) 50 | }; 51 | 52 | /** A decent and pretty date picker to be used with React 53 | * @param value - current date 54 | * @param valueLink - valueLink object to replace "value" and "onChange" 55 | * @param onChange - called when value changes 56 | * @param onShow - called when datepicker is opened 57 | * @param onHide - called when datepicker is closed 58 | * @param onClear - called when value is cleared 59 | * @param small - whether it's small or not 60 | * @param defaultValue - default date 61 | * @param minDate - minimum date selectable by the user 62 | * @param maxDate - maximum date selectable by the user 63 | * @param locale - locale used for translations 64 | * @param startMode - the start view of the datepicker 65 | * @param startDate - specify an initial "visible" date with no need to select a defaultValue 66 | * @param fixedMode - whether the user can use multiple views or not 67 | * @param displayFormat - MomentJS format used to display current date 68 | * @param returnFormat - MomentJS format used to format date before returing through "onChange" 69 | * @param format - MomentJS format used to format date before returing through "onChange" 70 | * @param validationFormat - MomentJS format used to format date before returing through "onChange" 71 | * @param showOnInputClick - whether the datepicker should open when user click on the input 72 | * @param closeOnClickOutside - whether the datepicker should close when user clicks outside of it 73 | * @param showInputButton - whether the input-button should be rendered 74 | * @param autoClose - pass true if you want the datepicker to close automatically after the user selects a value 75 | * @param floating - whether the datepicker should float over the page content (absolute position) 76 | * @param position - whether the datepicker should be rendered above or below the input field 77 | * @param disabled - whether the datepicker should be disabled or not 78 | * @param iconClassName - classname used for the icon 79 | * @param iconClearClassName - classname used for the clear icon 80 | * @param className - className used for the wrapper div 81 | * @param style - style used for the wrapper div 82 | * @param placeholder 83 | */ 84 | 85 | 86 | @format 87 | @valueLink 88 | @skinnable() 89 | @props(Props, { strict: false }) 90 | export default class DatePickerInput extends React.Component { 91 | 92 | static defaultProps = { 93 | onShow: () => {}, 94 | onHide: () => {}, 95 | startMode: 'day', 96 | autoClose: true, 97 | closeOnClickOutside: true, 98 | floating: true, 99 | small: false, 100 | showInputButton: true, 101 | position: 'bottom', 102 | iconClassName: 'icon-rc-datepicker icon-rc-datepicker_calendar', 103 | iconClearClassName: 'icon-rc-datepicker icon-rc-datepicker_clear', 104 | className: '', 105 | style: {} 106 | } 107 | 108 | datePickerInputRef = null; 109 | 110 | constructor(props) { 111 | super(props); 112 | if (props.locale) { 113 | moment.locale(props.locale); 114 | } 115 | const _date = this.getValueLink().value || props.defaultValue; 116 | const date = typeof _date === 'string' ? this.parsePropDateString(_date) : moment(_date); 117 | this.state = { 118 | date: _date ? date : undefined, 119 | hasValue: !!_date, 120 | dateString: _date ? this.formatDisplayedDate(date) : '', 121 | showing: false 122 | }; 123 | } 124 | 125 | componentDidMount() { 126 | if (this.props.closeOnClickOutside) { 127 | this.addOnClickListener(); 128 | } 129 | } 130 | 131 | addOnClickListener = () => { 132 | if (window.attachEvent) { 133 | // Internet Explorer 134 | window.attachEvent('onclick', this.hideOnClickOutside); 135 | } else if (window.addEventListener) { 136 | window.addEventListener('click', this.hideOnClickOutside, false); 137 | } 138 | } 139 | 140 | removeOnClickListener = () => { 141 | if (window.detachEvent) { 142 | // Internet Explorer 143 | window.detachEvent('onclick', this.hideOnClickOutside); 144 | } else if (window.removeEventListener) { 145 | window.removeEventListener('click', this.hideOnClickOutside, false); 146 | } 147 | } 148 | 149 | getDatePickerInput = () => { 150 | return ReactDOM.findDOMNode(this.datePickerInputRef); 151 | } 152 | 153 | isEventInsideDatePickerInput = (el) => { 154 | if (el === this.getDatePickerInput()) { 155 | return true; 156 | } else if (el.parentNode) { 157 | return this.isEventInsideDatePickerInput(el.parentNode); 158 | } else { 159 | return false; 160 | } 161 | } 162 | 163 | hideOnClickOutside = (e) => { 164 | if (!this.isEventInsideDatePickerInput(e.target) && this.state.showing) { 165 | this.hide(); 166 | } 167 | } 168 | 169 | hide = () => { 170 | this.setState({ showing: false }, this.props.onHide); 171 | } 172 | 173 | show = () => { 174 | if (!this.state.showing) { 175 | this.setState({ showing: true }, this.props.onShow); 176 | } 177 | } 178 | 179 | toggleDatePicker = () => { 180 | const callback = this.state.showing ? this.props.onHide : this.props.onShow; 181 | this.setState({ showing: !this.state.showing }, callback); 182 | } 183 | 184 | hideOnEnterKey = (e) => { 185 | if (e.keyCode === ENTER_KEYCODE) { 186 | this.hide(); 187 | } 188 | } 189 | 190 | onClear = () => { 191 | const _date = this.props.defaultValue; 192 | const date = typeof _date === 'string' ? this.parsePropDateString(_date) : moment(_date); 193 | this.setState( 194 | { 195 | date: _date ? date : undefined, 196 | dateString: _date ? this.formatDisplayedDate(date) : '', 197 | showing: false 198 | }, 199 | this.props.onClear 200 | ); 201 | } 202 | 203 | _onChangeDate = (jsDate) => { 204 | const newDate = moment(jsDate); 205 | const newDateString = this.formatDisplayedDate(newDate); 206 | if (this.props.autoClose) { 207 | this.hide(); 208 | } 209 | this.getValueLink().requestChange(jsDate, this.formatReturnedDate(newDate)); 210 | if (newDateString !== this.state.dateString) { 211 | this.setState({ 212 | hasValue: true, 213 | date: newDate, 214 | dateString: newDateString 215 | }); 216 | } 217 | } 218 | 219 | onChangeInput = ({ target: { value: dateString } }) => { 220 | if (dateString || this.state.date) { 221 | const parsedDate = this.parseInputDateString(dateString); 222 | const date = parsedDate.isValid() ? parsedDate : this.state.date; 223 | 224 | const jsDate = parsedDate.isValid() ? parsedDate.toDate() : INVALID; 225 | const returnedDateString = jsDate ? this.formatReturnedDate(parsedDate) : INVALID; 226 | 227 | this.setState({ 228 | dateString, 229 | date, 230 | hasValue: parsedDate.isValid() 231 | }, () => this.getValueLink().requestChange(jsDate, returnedDateString)); 232 | } else if (!dateString) { 233 | this.setState({ dateString }); 234 | } 235 | } 236 | 237 | getLocals(props) { 238 | const { 239 | showInputButton, 240 | iconClassName, 241 | showOnInputClick, 242 | onClear, 243 | small, 244 | iconClearClassName, 245 | defaultValue, 246 | minDate, maxDate, 247 | locale, 248 | startMode, 249 | startDate, 250 | fixedMode, 251 | floating, 252 | closeOnClickOutside, 253 | className, 254 | disabled, 255 | position, 256 | placeholder, 257 | style 258 | } = props; 259 | const { showing: active, hasValue, dateString: value, date } = this.state; 260 | 261 | const inputProps = omit(props, Object.keys(Props)); 262 | const onInputClick = showOnInputClick ? this.show : undefined; 263 | const onButtonClick = showInputButton ? this.toggleDatePicker : undefined; 264 | const onInputClear = onClear ? this.onClear : undefined; 265 | 266 | return { 267 | style, 268 | className: cx('react-datepicker-component', { 'is-disabled': disabled }, className), 269 | inputProps: { 270 | value, 271 | small, active, hasValue, 272 | iconClassName, iconClearClassName, 273 | onInputClick, onButtonClick, onInputClear, 274 | onInputChange: this.onChangeInput, 275 | onInputKeyUp: this.hideOnEnterKey, 276 | placeholder, 277 | ...inputProps 278 | }, 279 | datePickerProps: active && { 280 | defaultValue, 281 | minDate, 282 | maxDate, 283 | locale, 284 | startMode, 285 | startDate, 286 | fixedMode, 287 | floating, 288 | position, 289 | closeOnClickOutside, 290 | value: date ? date.toDate() : undefined, 291 | onChange: this._onChangeDate 292 | } 293 | }; 294 | } 295 | 296 | template({ className, style, inputProps, datePickerProps }) { 297 | return ( 298 |
{ this.datePickerInputRef = input; }}> 299 | 300 | {datePickerProps && } 301 |
302 | ); 303 | } 304 | 305 | componentWillReceiveProps(nextProps) { 306 | const { value } = this.getValueLink(nextProps); 307 | 308 | // Update `date` and `dateString` if `props.value` has changed 309 | if (value !== INVALID && value !== this.getValueLink().value) { 310 | if (value) { 311 | const date = typeof value === 'string' ? 312 | this.parsePropDateString(value, nextProps) : moment(value); 313 | this.setState({ 314 | date, 315 | dateString: date.isValid() ? 316 | this.formatDisplayedDate(date, nextProps) : this.state.dateString 317 | }); 318 | } else { 319 | this.setState({ 320 | date: undefined, 321 | dateString: '' 322 | }); 323 | } 324 | } 325 | 326 | // Close datepicker if `disabled` has switched to `true` 327 | if (nextProps.disabled && !this.props.disabled) { 328 | this.hide(); 329 | } 330 | } 331 | 332 | componentWillUnmount() { 333 | if (this.props.closeOnClickOutside) { 334 | this.removeOnClickListener(); 335 | } 336 | } 337 | } 338 | -------------------------------------------------------------------------------- /src/Input.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import cx from 'classnames'; 3 | import { props } from 'tcomb-react'; 4 | import t from 'tcomb'; 5 | import View from 'react-flexview'; 6 | import skinnable from './utils/skinnable'; 7 | import pure from './utils/pure'; 8 | 9 | @pure 10 | @skinnable() 11 | @props({ 12 | value: t.maybe(t.String), 13 | onInputChange: t.Function, 14 | iconClearClassName: t.String, 15 | iconClassName: t.String, 16 | hasValue: t.Boolean, 17 | active: t.Boolean, 18 | small: t.Boolean, 19 | onButtonClick: t.maybe(t.Function), 20 | onInputClick: t.maybe(t.Function), 21 | onInputClear: t.maybe(t.Function), 22 | onInputKeyUp: t.Function 23 | }, { strict: false }) 24 | export default class Input extends React.Component { 25 | 26 | getLocals(props) { 27 | const { 28 | value, 29 | iconClearClassName, 30 | iconClassName, 31 | hasValue, 32 | active, 33 | small, 34 | onButtonClick, 35 | onInputClick, 36 | onInputChange, 37 | onInputClear, 38 | onInputKeyUp, 39 | ...inputProps 40 | } = props; 41 | 42 | return { 43 | className: cx('react-datepicker-input', { 44 | 'is-open': active, 45 | 'has-value': hasValue, 46 | 'is-small': small 47 | }), 48 | inputButtonProps: onButtonClick && { 49 | onButtonClick, iconClassName, 50 | className: cx('input-button', { active }) 51 | }, 52 | clearButtonProps: onInputClear && hasValue && { 53 | onInputClear, iconClearClassName 54 | }, 55 | inputProps: { 56 | value, 57 | onChange: onInputChange, 58 | onClick: onInputClick, 59 | onKeyUp: onInputKeyUp, 60 | ...inputProps 61 | } 62 | }; 63 | } 64 | 65 | templateInputButton({ className, onButtonClick, iconClassName }) { 66 | return ( 67 | 68 | 69 | 70 | ); 71 | } 72 | 73 | templateClearButton({ onInputClear, iconClearClassName }) { 74 | return ( 75 | 76 | 77 | 78 | ); 79 | } 80 | 81 | template({ className, inputButtonProps, clearButtonProps, inputProps }) { 82 | return ( 83 |
84 | 85 | 86 | {clearButtonProps && this.templateClearButton(clearButtonProps)} 87 | {inputButtonProps && this.templateInputButton(inputButtonProps)} 88 | 89 |
90 | ); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/InvalidDate.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import t from 'tcomb'; 3 | import { props } from 'tcomb-react'; 4 | import { pure, skinnable } from './utils'; 5 | 6 | @pure 7 | @skinnable() 8 | @props({ 9 | invalidDate: t.maybe(t.String) 10 | }) 11 | export default class InvalidDate extends React.Component { 12 | template({ invalidDate }) { 13 | return ( 14 |
15 |

16 | {invalidDate} 17 |

18 |
19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Picker.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import cx from 'classnames'; 3 | import t from 'tcomb'; 4 | import { props } from 'tcomb-react'; 5 | import View from 'react-flexview'; 6 | import { pure, skinnable } from './utils'; 7 | import { MomentDate, Value, Mode } from './utils/model'; 8 | 9 | @pure 10 | @skinnable() 11 | @props({ 12 | date: MomentDate, 13 | minDate: t.maybe(Value), 14 | maxDate: t.maybe(Value), 15 | isSelected: t.Boolean, 16 | isCurrent: t.Boolean, 17 | isEnabled: t.Boolean, 18 | isDisabled: t.maybe(t.Boolean), 19 | onSelectDate: t.Function, 20 | mode: Mode 21 | }) 22 | export default class Picker extends React.Component { 23 | 24 | onClick = e => { 25 | e.preventDefault(); 26 | if (this.props.isEnabled) { 27 | this.props.onSelectDate(this.props.date); 28 | } 29 | } 30 | 31 | getFormat = mode => { 32 | switch (mode) { 33 | case Mode('day'): return 'D'; 34 | case Mode('month'): return 'MMM'; 35 | case Mode('year'): return 'YYYY'; 36 | } 37 | } 38 | 39 | getLocals({ date, mode, isCurrent, isSelected, isEnabled }) { 40 | const string = date.format(this.getFormat(mode)); 41 | 42 | return { 43 | value: string.charAt(0).toUpperCase() + string.slice(1), // first letter always uppercase 44 | className: cx('react-datepicker-picker', { 45 | [mode]: true, 46 | current: isCurrent, 47 | selected: isSelected, 48 | disabled: !isEnabled 49 | }), 50 | onClick: this.onClick 51 | }; 52 | } 53 | 54 | template({ className, onClick, value }) { 55 | return ( 56 | 61 | {value} 62 | 63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/PickerTop.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import cx from 'classnames'; 3 | import t from 'tcomb'; 4 | import { props, ReactChildren } from 'tcomb-react'; 5 | import View from 'react-flexview'; 6 | import { pure, skinnable } from './utils'; 7 | 8 | @pure 9 | @skinnable() 10 | @props({ 11 | fixed: t.maybe(t.Boolean), 12 | handleClick: t.maybe(t.Function), 13 | nextDate: t.maybe(t.Function), 14 | previousDate: t.maybe(t.Function), 15 | value: t.union([t.String, t.Number]), 16 | weekDays: t.maybe(ReactChildren), 17 | prevIconClassName: t.String, 18 | nextIconClassName: t.String 19 | }) 20 | export default class PickerTop extends React.Component { 21 | template({ value, fixed, previousDate, nextDate, handleClick, weekDays, prevIconClassName, nextIconClassName }) { 22 | return ( 23 | 24 | 25 | 30 | 31 | 32 | 37 | {value} 38 | 39 | 44 | 45 | 46 | 47 | {weekDays} 48 | 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | # DatePickerInput 2 | 3 | A decent and pretty date picker to be used with React 4 | 5 | ## Props 6 | |Name|Type|Default|Description| 7 | |----|----|-------|-----------| 8 | | **value** | union(String | Date | MomentDate) | | *optional*. Current date | 9 | | **valueLink** | {value: ?String | Date | MomentDate, requestChange: Function} | | *optional*. ValueLink object to replace "value" and "onChange" | 10 | | **onChange** | {jsDate: Date, dateString: string} | | *optional*. Called when value changes | 11 | | **onShow** | Function | "onShow" | *optional*. Called when datepicker is opened | 12 | | **onHide** | Function | "onHide" | *optional*. Called when datepicker is closed | 13 | | **onClear** | Function | | *optional*. Called when value is cleared | 14 | | **small** | Boolean | false | *optional*. Whether it's small or not | 15 | | **defaultValue** | union(String | Date | MomentDate) | | *optional*. Default date | 16 | | **minDate** | union(String | Date | MomentDate) | | *optional*. Minimum date selectable by the user | 17 | | **maxDate** | union(String | Date | MomentDate) | | *optional*. Maximum date selectable by the user | 18 | | **locale** | String | | *optional*. Locale used for translations | 19 | | **startMode** | enum("day" | "month" | "year") | "day" | *optional*. The start view of the datepicker | 20 | | **startDate** | union(String | Date | MomentDate) | | *optional*. Specify an initial "visible" date with no need to select a defaultValue | 21 | | **fixedMode** | Boolean | | *optional*. Whether the user can use multiple views or not | 22 | | **displayFormat** | String | | *optional*. MomentJS format used to display current date | 23 | | **returnFormat** | String | | *optional*. MomentJS format used to format date before returing through "onChange" | 24 | | **format** | String | | *optional*. MomentJS format used to format date before returing through "onChange" | 25 | | **validationFormat** | String | | *optional*. MomentJS format used to format date before returing through "onChange" | 26 | | **showOnInputClick** | Boolean | | *optional*. Whether the datepicker should open when user click on the input | 27 | | **closeOnClickOutside** | Boolean | true | *optional*. Whether the datepicker should close when user clicks outside of it | 28 | | **showInputButton** | Boolean | true | *optional*. Whether the input-button should be rendered | 29 | | **autoClose** | Boolean | true | *optional*. Pass true if you want the datepicker to close automatically after the user selects a value | 30 | | **floating** | Boolean | true | *optional*. Whether the datepicker should float over the page content (absolute position) | 31 | | **disabled** | Boolean | | *optional*. Whether the datepicker should be disabled or not | 32 | | **position** | enum("top" | "bottom") | "bottom" | *optional*. Whether the datepicker should be rendered above or below the input field | 33 | | **iconClassName** | String | "icon-rc-datepicker icon-rc-datepicker_calendar" | *optional*. Classname used for the icon | 34 | | **iconClearClassName** | String | "icon-rc-datepicker icon-rc-datepicker_clear" | *optional*. Classname used for the clear icon | 35 | | **className** | String | "" | *optional*. ClassName used for the wrapper div | 36 | | **style** | Object | {} | *optional*. Style used for the wrapper div | 37 | | **placeholder** | String | | *optional*. | -------------------------------------------------------------------------------- /src/Row.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import cx from 'classnames'; 3 | import t from 'tcomb'; 4 | import { props, ReactChildren } from 'tcomb-react'; 5 | import View from 'react-flexview'; 6 | import { pure, skinnable } from './utils'; 7 | import { Mode } from './utils/model'; 8 | 9 | @pure 10 | @skinnable() 11 | @props({ 12 | pickers: t.list(ReactChildren), 13 | mode: Mode 14 | }) 15 | export default class Row extends React.Component { 16 | 17 | getLocals({ mode, pickers }) { 18 | return { 19 | pickers, 20 | className: cx('react-datepicker-row', mode) 21 | }; 22 | } 23 | 24 | template({ className, pickers }) { 25 | return ( 26 | 27 | {pickers} 28 | 29 | ); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/daypicker/DayPicker.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import t from 'tcomb'; 3 | import { props } from 'tcomb-react'; 4 | import View from 'react-flexview'; 5 | import { pure, skinnable } from '../utils'; 6 | import { Value, Mode, MomentDate } from '../utils/model'; 7 | import DayPickerTop from './DayPickerTop'; 8 | import DayPickerBody from './DayPickerBody'; 9 | 10 | @pure 11 | @skinnable() 12 | @props({ 13 | changeMonth: t.Function, 14 | visibleDate: MomentDate, 15 | date: t.maybe(Value), 16 | minDate: t.maybe(Value), 17 | maxDate: t.maybe(Value), 18 | onSelectDate: t.Function, 19 | onChangeMode: t.Function, 20 | mode: Mode, 21 | fixedMode: t.maybe(t.Boolean), 22 | prevIconClassName: t.String, 23 | nextIconClassName: t.String 24 | }) 25 | export default class DayPicker extends React.Component { 26 | 27 | getLocals({ 28 | date, visibleDate, onSelectDate, minDate, 29 | maxDate, changeMonth, onChangeMode, mode, fixedMode, 30 | prevIconClassName, nextIconClassName 31 | }) { 32 | return { 33 | dayPickerTopProps: { 34 | visibleDate, 35 | changeMonth, 36 | onChangeMode, 37 | fixedMode, 38 | prevIconClassName, 39 | nextIconClassName 40 | }, 41 | dayPickerBodyProps: { 42 | date, visibleDate, 43 | minDate, maxDate, 44 | onSelectDate, 45 | mode 46 | } 47 | }; 48 | } 49 | 50 | template({ dayPickerTopProps, dayPickerBodyProps }) { 51 | return ( 52 | 53 | 54 | 55 | 56 | ); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/daypicker/DayPickerBody.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import range from 'lodash/range'; 3 | import t from 'tcomb'; 4 | import { props } from 'tcomb-react'; 5 | import View from 'react-flexview'; 6 | import { pure, skinnable } from '../utils'; 7 | import { Value, Mode, MomentDate } from '../utils/model'; 8 | import InvalidDate from '../InvalidDate'; 9 | import Picker from '../Picker'; 10 | import Row from '../Row'; 11 | import { isInsideTheEnabledArea, getVisibleDays } from '../utils/DateUtils'; 12 | 13 | const COLUMNS = 7; 14 | const ROWS = 6; 15 | 16 | @pure 17 | @skinnable() 18 | @props({ 19 | visibleDate: MomentDate, 20 | date: t.maybe(Value), 21 | minDate: t.maybe(Value), 22 | maxDate: t.maybe(Value), 23 | onSelectDate: t.Function, 24 | mode: Mode 25 | }) 26 | export default class DayPickerBody extends React.Component { 27 | 28 | getLocals({ date, visibleDate, minDate, maxDate, onSelectDate, mode }) { 29 | if (!visibleDate.isValid()) { 30 | return ; 31 | } 32 | const year = visibleDate.year(); 33 | const month = visibleDate.month(); 34 | const selectedDateString = date ? date.format('DD/MM/YYYY') : undefined; 35 | 36 | const visibleDays = getVisibleDays(month, year); 37 | const pickers = visibleDays.days.map((dayOfMonth, index) => { 38 | const date = visibleDate.clone(); 39 | const isCurrent = index >= visibleDays.startCurrent && index <= visibleDays.endCurrent; 40 | if (!isCurrent) { 41 | date.add(index < visibleDays.startCurrent ? -1 : 1, 'M'); 42 | } 43 | date.date(dayOfMonth); 44 | const dateString = date.format('DD/MM/YYYY'); 45 | return { 46 | date, 47 | isCurrent, 48 | onSelectDate, 49 | mode, 50 | isSelected: dateString === selectedDateString, 51 | isEnabled: isInsideTheEnabledArea(date, mode, minDate, maxDate), 52 | key: dateString 53 | }; 54 | }); 55 | 56 | return { pickers, mode }; 57 | } 58 | 59 | templateDays = ({ pickers }) => pickers.map(p => ) 60 | 61 | template({ pickers, mode }) { 62 | const days = this.templateDays({ pickers }); 63 | const rows = range(ROWS).map(index => 64 | 65 | ); 66 | 67 | return ( 68 | 69 | {rows} 70 | 71 | ); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/daypicker/DayPickerTop.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import capitalize from 'lodash/capitalize'; 3 | import t from 'tcomb'; 4 | import { props } from 'tcomb-react'; 5 | import View from 'react-flexview'; 6 | import { pure, skinnable } from '../utils'; 7 | import { MomentDate, Mode } from '../utils/model'; 8 | import { getWeekdaysMin } from '../utils/DateUtils.js'; 9 | import PickerTop from '../PickerTop'; 10 | 11 | @pure 12 | @skinnable() 13 | @props({ 14 | changeMonth: t.Function, 15 | visibleDate: MomentDate, 16 | onChangeMode: t.Function, 17 | fixedMode: t.maybe(t.Boolean), 18 | prevIconClassName: t.String, 19 | nextIconClassName: t.String 20 | }) 21 | export default class DayPickerTop extends React.Component { 22 | 23 | onChangeMode = () => { 24 | if (!this.props.fixedMode) { 25 | this.props.onChangeMode(Mode('month')); 26 | } 27 | } 28 | 29 | getMonth = () => this.props.visibleDate.month() 30 | 31 | previousDate = () => this.props.changeMonth(this.getMonth() - 1) 32 | 33 | nextDate = () => this.props.changeMonth(this.getMonth() + 1) 34 | 35 | getLocals({ visibleDate, fixedMode, prevIconClassName, nextIconClassName }) { 36 | return { 37 | fixed: !!fixedMode, 38 | value: capitalize(visibleDate.format('MMMM YYYY')), 39 | handleClick: this.onChangeMode, 40 | previousDate: this.previousDate, 41 | nextDate: this.nextDate, 42 | weekDays: getWeekdaysMin(), 43 | prevIconClassName, 44 | nextIconClassName 45 | }; 46 | } 47 | 48 | templateWeekDays = ({ weekDays }) => ( 49 | 50 | {weekDays.map((dayMin, i) => ( 51 | 56 | {dayMin} 57 | 58 | ))} 59 | 60 | ) 61 | 62 | template({ weekDays, ...locales }) { 63 | return ( 64 | 68 | ); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/icons.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'rc-datepicker'; 3 | src: url('data:application/x-font-ttf;charset=utf-8;base64,AAEAAAALAIAAAwAwT1MvMg8SDIcAAAC8AAAAYGNtYXAAitFNAAABHAAAAGRnYXNwAAAAEAAAAYAAAAAIZ2x5ZmYIkl0AAAGIAAAC7GhlYWQLeirXAAAEdAAAADZoaGVhB3kDyQAABKwAAAAkaG10eBKTAOAAAATQAAAAIGxvY2ECQgFeAAAE8AAAABJtYXhwABgAfgAABQQAAAAgbmFtZUzHCYMAAAUkAAABznBvc3QAAwAAAAAG9AAAACAAAwLqAZAABQAAApkCzAAAAI8CmQLMAAAB6wAzAQkAAAAAAAAAAAAAAAAAAAABEAAAAAAAAAAAAAAAAAAAAABAAADwcwPA/8AAQAPAAEAAAAABAAAAAAAAAAAAAAAgAAAAAAADAAAAAwAAABwAAQADAAAAHAADAAEAAAAcAAQASAAAAA4ACAACAAYAAQAg8A3wVPBz//3//wAAAAAAIPAN8FPwc//9//8AAf/jD/cPsg+UAAMAAQAAAAAAAAAAAAAAAAAAAAEAAf//AA8AAQAAAAAAAAAAAAIAADc5AQAAAAABAAAAAAAAAAAAAgAANzkBAAAAAAEAAAAAAAAAAAACAAA3OQEAAAAAAQA/AD8C5gLmADwAACUUBg8BDgEjIiYvAQcOASMiJi8BLgE1NDY/AScuATU0Nj8BPgEzMhYfATc+ATMyFh8BHgEVFAYPARceARUC5gkHTggUCwsUCKioBxULChUHTggICAioqAgICAhOBxUKCxUHqKgIFAsLFAhOBwkJB6ioBwnDChUHTggICAioqAgICAhOBxUKCxUHqKgIFAsLFAhOBwkJB6ioBwkJB04IFAsLFAioqAcVCwAAAAEAYwAaAp0DnQAVAAAJAhYUDwEGIicBJjQ3ATYyHwEWFAcCnf7RAS8LC18KHgv+WAsLAagLHgpfCwsDC/7Q/tELHgpfCwsBqAoeCwGoCwtfCh4LAAEAPgAaAnkDnQAVAAAJAQYiLwEmNDcJASY0PwE2MhcBFhQHAnn+WAseC18KCgEw/tAKCl8LHgsBqAoKAcL+WAsLXwoeCwEvATALHgpfCwv+WAseCgAAAAAPAAD/twO3A7cAAwAIAAwAEQAVABoAHwAjACgAOAA8AEEARQBWAHsAADczNSMXMzUjFSczNSMXMzUjFSczNSMBMzUjFQMzNSMVATM1IyczNSMVAzU0JisBIgYdARQWOwEyNgEzNSMnMzUjFTsBNSM3NTQmKwEiBh0BFBY7ATI2NTcRFAYjISImNRE0NjsBNTQ2OwEyFh0BMzU0NjsBMhYdATMyFhVJpaXJt7fJpaXJt7fJpaUBpbe33Le3Abelpdu3t8kLCCQICwsIJAgLAaSlpdu3t9ulpRILByQICwsIJAcL3Cse/NseKyseSTYmJCY22zYmJCY2SR4rAKWlpaXJt7e3t9yk/belpQGlpKT+W6Ukt7cB7qQICwsIpAcLC/4ZtyWkpKRupAgLCwikBwsLByT9JR4rKx4C2x4sNiY2NiY2NiY2NiY2LB4AAAEAAAABAABplrQ9Xw889QALBAAAAAAA1FHzNgAAAADUUfM2AAD/twO3A7cAAAAIAAIAAAAAAAAAAQAAA8D/wAAABAAAAAAAA7cAAQAAAAAAAAAAAAAAAAAAAAgEAAAAAAAAAAAAAAACAAAAAyUAPwMAAGMCtwA+A7cAAAAAAAAACgAUAB4AegCkANABdgAAAAEAAAAIAHwADwAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAOAK4AAQAAAAAAAQANAAAAAQAAAAAAAgAHAJYAAQAAAAAAAwANAEgAAQAAAAAABAANAKsAAQAAAAAABQALACcAAQAAAAAABgANAG8AAQAAAAAACgAaANIAAwABBAkAAQAaAA0AAwABBAkAAgAOAJ0AAwABBAkAAwAaAFUAAwABBAkABAAaALgAAwABBAkABQAWADIAAwABBAkABgAaAHwAAwABBAkACgA0AOxyYy1kYXRlcGlja2VyAHIAYwAtAGQAYQB0AGUAcABpAGMAawBlAHJWZXJzaW9uIDEuMABWAGUAcgBzAGkAbwBuACAAMQAuADByYy1kYXRlcGlja2VyAHIAYwAtAGQAYQB0AGUAcABpAGMAawBlAHJyYy1kYXRlcGlja2VyAHIAYwAtAGQAYQB0AGUAcABpAGMAawBlAHJSZWd1bGFyAFIAZQBnAHUAbABhAHJyYy1kYXRlcGlja2VyAHIAYwAtAGQAYQB0AGUAcABpAGMAawBlAHJGb250IGdlbmVyYXRlZCBieSBJY29Nb29uLgBGAG8AbgB0ACAAZwBlAG4AZQByAGEAdABlAGQAIABiAHkAIABJAGMAbwBNAG8AbwBuAC4AAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') format('truetype'); 4 | font-weight: 400; 5 | font-style: normal; 6 | } 7 | 8 | .icon-rc-datepicker { 9 | /* use !important to prevent issues with browser extensions that change fonts */ 10 | font-family: 'rc-datepicker' !important; 11 | speak: none; 12 | font-style: normal; 13 | font-weight: 400; 14 | font-variant: normal; 15 | text-transform: none; 16 | line-height: 1; 17 | 18 | /* Better Font Rendering =========== */ 19 | -webkit-font-smoothing: antialiased; 20 | -moz-osx-font-smoothing: grayscale; 21 | } 22 | 23 | .icon-rc-datepicker_clear::before { 24 | content: '\f00d'; 25 | } 26 | 27 | .icon-rc-datepicker_prev::before { 28 | content: '\f053'; 29 | } 30 | 31 | .icon-rc-datepicker_next::before { 32 | content: '\f054'; 33 | } 34 | 35 | .icon-rc-datepicker_calendar::before { 36 | content: '\f073'; 37 | } 38 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import DatePicker from './DatePicker'; 2 | import DatePickerInput from './DatePickerInput'; 3 | 4 | export DatePicker from './DatePicker'; 5 | export DatePickerInput from './DatePickerInput'; 6 | 7 | export default { DatePicker, DatePickerInput }; 8 | -------------------------------------------------------------------------------- /src/monthpicker/MonthPicker.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import t from 'tcomb'; 3 | import { props } from 'tcomb-react'; 4 | import { pure, skinnable } from '../utils'; 5 | import { MomentDate, Value, Mode } from '../utils/model'; 6 | import MonthPickerTop from './MonthPickerTop'; 7 | import MonthPickerBody from './MonthPickerBody'; 8 | 9 | @pure 10 | @skinnable() 11 | @props({ 12 | changeYear: t.Function, 13 | visibleDate: MomentDate, 14 | date: t.maybe(Value), 15 | minDate: t.maybe(Value), 16 | maxDate: t.maybe(Value), 17 | onChangeVisibleDate: t.Function, 18 | onSelectDate: t.Function, 19 | onChangeMode: t.Function, 20 | mode: Mode, 21 | fixedMode: t.maybe(t.Boolean), 22 | prevIconClassName: t.String, 23 | nextIconClassName: t.String 24 | }) 25 | export default class MonthPicker extends React.Component { 26 | 27 | onSelectDate = (date) => { 28 | const { fixedMode, onSelectDate, onChangeMode, onChangeVisibleDate } = this.props; 29 | if (fixedMode) { 30 | onSelectDate(date); 31 | } else { 32 | onChangeVisibleDate(date); 33 | onChangeMode(Mode('day')); 34 | } 35 | } 36 | 37 | getLocals({ 38 | date, visibleDate, minDate, 39 | maxDate, changeYear, onChangeMode, mode, fixedMode, 40 | prevIconClassName, nextIconClassName 41 | }) { 42 | return { 43 | monthPickerTopProps: { 44 | visibleDate, 45 | changeYear, 46 | onChangeMode, 47 | fixedMode, 48 | prevIconClassName, 49 | nextIconClassName 50 | }, 51 | monthPickerBodyProps: { 52 | date, visibleDate, 53 | minDate, maxDate, 54 | mode, 55 | onSelectDate: this.onSelectDate 56 | } 57 | }; 58 | } 59 | 60 | template({ monthPickerTopProps, monthPickerBodyProps }) { 61 | return ( 62 |
63 | 64 | 65 |
66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/monthpicker/MonthPickerBody.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import moment from 'moment'; 3 | import t from 'tcomb'; 4 | import { props } from 'tcomb-react'; 5 | import { pure, skinnable } from '../utils'; 6 | import { Value, Mode, MomentDate } from '../utils/model'; 7 | import InvalidDate from '../InvalidDate'; 8 | import Picker from '../Picker'; 9 | import Row from '../Row'; 10 | import { isInsideTheEnabledArea } from '../utils/DateUtils'; 11 | import range from 'lodash/range'; 12 | 13 | const COLUMNS = 4; 14 | const ROWS = 3; 15 | 16 | @pure 17 | @skinnable() 18 | @props({ 19 | visibleDate: MomentDate, 20 | date: t.maybe(Value), 21 | minDate: t.maybe(Value), 22 | maxDate: t.maybe(Value), 23 | onSelectDate: t.Function, 24 | mode: Mode 25 | }) 26 | export default class MonthPickerBody extends React.Component { 27 | 28 | getLocals({ date, visibleDate, minDate, maxDate, onSelectDate, mode }) { 29 | if (!visibleDate.isValid()) { 30 | return ; 31 | } 32 | const year = visibleDate.year(); 33 | const selectedMonth = date ? date.month() : -1; 34 | const selectedYear = date ? date.year() : -1; 35 | const pickers = moment.months().map((_, index) => { 36 | const date = moment([year, index, 1]); 37 | return { 38 | date, 39 | onSelectDate, 40 | mode, 41 | isCurrent: true, 42 | isSelected: selectedMonth === index && selectedYear === year, 43 | isEnabled: isInsideTheEnabledArea(date, mode, minDate, maxDate), 44 | key: index 45 | }; 46 | }); 47 | 48 | return { pickers, mode }; 49 | } 50 | 51 | templateMonths = ({ pickers }) => pickers.map(p => ) 52 | 53 | template({ pickers, mode }) { 54 | const months = this.templateMonths({ pickers }); 55 | const rows = range(ROWS).map(index => ( 56 | 61 | )); 62 | 63 | return ( 64 |
65 | {rows} 66 |
67 | ); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/monthpicker/MonthPickerTop.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import t from 'tcomb'; 3 | import { props } from 'tcomb-react'; 4 | import { pure, skinnable } from '../utils'; 5 | import { MomentDate, Mode } from '../utils/model'; 6 | import PickerTop from '../PickerTop'; 7 | 8 | @pure 9 | @skinnable() 10 | @props({ 11 | visibleDate: MomentDate, 12 | onChangeMode: t.Function, 13 | changeYear: t.Function, 14 | fixedMode: t.maybe(t.Boolean), 15 | prevIconClassName: t.String, 16 | nextIconClassName: t.String 17 | }) 18 | export default class MonthPickerTop extends React.Component { 19 | 20 | onChangeMode = () => { 21 | if (!this.props.fixedMode) { 22 | this.props.onChangeMode(Mode('year')); 23 | } 24 | } 25 | 26 | getYear = () => this.props.visibleDate.year() 27 | 28 | previousDate = () => this.props.changeYear(this.getYear() - 1) 29 | 30 | nextDate = () => this.props.changeYear(this.getYear() + 1) 31 | 32 | getLocals({ fixedMode }) { 33 | return { 34 | prevIconClassName: this.props.prevIconClassName, 35 | nextIconClassName: this.props.nextIconClassName, 36 | fixed: !!fixedMode, 37 | value: this.getYear(), 38 | handleClick: this.onChangeMode, 39 | previousDate: this.previousDate, 40 | nextDate: this.nextDate 41 | }; 42 | } 43 | 44 | template(locales) { 45 | return ; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/style.scss: -------------------------------------------------------------------------------- 1 | @import './icons.scss'; 2 | 3 | // PALETTE 4 | 5 | $_gradientGrey: linear-gradient(#fff, #f2f4f7); 6 | $_gradientDarkGrey: linear-gradient(#fff, #dfe3e8); 7 | $_gradientAzure: linear-gradient(#2da1f8, #1789dd); 8 | $_white: #fff; 9 | $_paleGrey: #f0f3f8; 10 | $_silver: #ced0da; 11 | $_cloudyGrey: #b5c0ce; 12 | $_coolGrey: #9098a7; 13 | $_darkGrey: #354052; 14 | $_cerulean: #2da1f8; 15 | $_waterBlue: #1991eb; 16 | $_strawberry: #db242c; 17 | 18 | // INPUT VARIABLES 19 | 20 | $font-size: 14px !default; 21 | $font-weight: 600 !default; 22 | $font-weight-selected: bold !default; 23 | 24 | $input-height: 36px !default; 25 | $input-height-small: 32px !default; 26 | $input-min-width: 150px !default; 27 | 28 | $input-font-size: 14px !default; 29 | 30 | $input-border-radius: 4px !default; 31 | $input-border-color: $_silver !default; 32 | $input-border-color-hover: $input-border-color !default; 33 | $input-border-color-open: $_cerulean !default; 34 | 35 | $input-background: $_gradientGrey !default; 36 | $input-background-hover: $_gradientDarkGrey !default; 37 | $input-background-open: $_gradientDarkGrey !default; 38 | 39 | $input-color: $_darkGrey !default; 40 | $input-color-hover: $input-color !default; 41 | $input-color-open: $input-color !default; 42 | $input-color-has-value: $_darkGrey !default; 43 | 44 | $input-placeholder-color: $_coolGrey !default; 45 | $input-placeholder-color-hover: $input-placeholder-color !default; 46 | 47 | $input-button-icon-color: $_cloudyGrey !default; 48 | $input-button-icon-color-hover: $_coolGrey !default; 49 | $input-button-icon-color-open: $input-button-icon-color !default; 50 | 51 | $input-button-icon-size: 15px !default; 52 | 53 | $input-button-background: transparent !default; 54 | $input-button-background-hover: transparent !default; 55 | 56 | $input-clear-button-icon-size: 13px !default; 57 | $input-clear-button-color: $input-button-icon-color !default; 58 | $input-clear-button-color-hover: $_strawberry !default; 59 | 60 | $input-opacity-disabled: .5 !default; 61 | 62 | // PICKER VARIABLES 63 | 64 | $picker-width: 250px !default; 65 | 66 | $picker-color: $_coolGrey !default; 67 | $picker-color-hover: $_darkGrey !default; 68 | 69 | $picker-background: $_white !default; 70 | $picker-background-hover: $_paleGrey !default; 71 | 72 | $picker-border-radius: 2px !default; 73 | $picker-border-color: #d9dee3 !default; 74 | 75 | $picker-box-shadow: 2px 2px 2px $_coolGrey !default; 76 | $picker-arrow-size: 5px !default; 77 | 78 | $picker-header-background: $_gradientAzure !default; 79 | $picker-header-background-hover: rgba(0, 0, 0, .075) !default; 80 | $picker-header-color: $_white !default; 81 | $picker-header-border-color: $_cerulean !default; 82 | 83 | $picker-current-background: $_paleGrey !default; 84 | $picker-current-background-hover: darken($picker-current-background, 5%) !default; 85 | $picker-current-color: $_darkGrey !default; 86 | $picker-current-color-hover: $picker-current-color !default; 87 | 88 | $picker-selected-background: #bad7f2 !default; 89 | $picker-selected-background-hover: $picker-selected-background !default; 90 | $picker-selected-color: $_darkGrey !default; 91 | $picker-selected-color-hover: $picker-selected-color !default; 92 | 93 | $picker-disabled-background: $_white !default; 94 | $picker-disabled-color: $_coolGrey !default; 95 | 96 | // STYLE 97 | 98 | @mixin placeholder { 99 | &::-webkit-input-placeholder { 100 | @content; 101 | } 102 | 103 | &:-moz-placeholder { 104 | @content; 105 | } 106 | 107 | &::-moz-placeholder { 108 | @content; 109 | } 110 | 111 | &:-ms-input-placeholder { 112 | @content; 113 | } 114 | } 115 | 116 | // DatePickerInput 117 | 118 | .react-datepicker-component { 119 | position: relative; 120 | font-size: $font-size; 121 | font-weight: $font-weight; 122 | 123 | &.is-disabled { 124 | pointer-events: none; 125 | opacity: $input-opacity-disabled; 126 | } 127 | 128 | .react-datepicker { 129 | margin-left: 5px; 130 | margin-top: 5px; 131 | display: inherit; 132 | } 133 | 134 | .react-datepicker-input { 135 | position: relative; 136 | min-width: $input-min-width; 137 | height: $input-height; 138 | background: $input-background; 139 | border: 1px solid $input-border-color; 140 | border-radius: $input-border-radius; 141 | 142 | &.is-small { 143 | height: $input-height-small; 144 | } 145 | 146 | input { 147 | width: 100%; 148 | height: 100%; 149 | background: transparent; 150 | border: none; 151 | box-sizing: border-box; 152 | padding-left: 15px; 153 | padding-right: 60px; 154 | font-size: $input-font-size; 155 | color: $input-color; 156 | font-weight: 600; 157 | 158 | &:focus { 159 | outline: none; 160 | } 161 | 162 | @include placeholder() { 163 | color: $input-placeholder-color; 164 | font-weight: 600; 165 | } 166 | } 167 | 168 | .button-wrapper { 169 | position: absolute; 170 | right: 0; 171 | top: 0; 172 | height: 100%; 173 | 174 | .input-button { 175 | margin: 0 10px; 176 | background: $input-button-background; 177 | font-size: $input-button-icon-size; 178 | border-radius: 0 $input-border-radius $input-border-radius 0; 179 | cursor: pointer; 180 | color: $input-button-icon-color; 181 | 182 | &:hover { 183 | background: $input-button-background-hover; 184 | color: $input-button-icon-color-hover; 185 | } 186 | } 187 | 188 | .clear-button { 189 | cursor: pointer; 190 | font-size: $input-clear-button-icon-size; 191 | color: $input-clear-button-color; 192 | 193 | &:hover { 194 | color: $input-clear-button-color-hover; 195 | } 196 | } 197 | } 198 | 199 | &:hover { 200 | background: $input-background-hover; 201 | border: 1px solid $input-border-color-hover; 202 | 203 | input { 204 | color: $input-color-hover; 205 | 206 | @include placeholder() { 207 | color: $input-placeholder-color-hover; 208 | } 209 | } 210 | 211 | .button-wrapper .input-button { 212 | color: $input-button-icon-color-hover; 213 | } 214 | } 215 | 216 | &.is-open { 217 | background: $input-background-open; 218 | border: 1px solid $input-border-color-open; 219 | 220 | input { 221 | color: $input-color-open; 222 | 223 | @include placeholder() { 224 | color: $input-color-open; 225 | } 226 | } 227 | 228 | .button-wrapper .input-button { 229 | color: $input-button-icon-color-open; 230 | } 231 | } 232 | 233 | &.has-value input { 234 | color: $input-color-has-value; 235 | } 236 | } 237 | } 238 | 239 | // DatePicker 240 | 241 | .react-datepicker { 242 | -webkit-touch-callout: none; 243 | -webkit-user-select: none; 244 | -khtml-user-select: none; 245 | -moz-user-select: none; 246 | -ms-user-select: none; 247 | user-select: none; 248 | display: inline-block; 249 | font-size: $font-size; 250 | font-weight: $font-weight; 251 | 252 | &.floating { 253 | position: absolute; 254 | z-index: 10000; 255 | box-shadow: 1px 1px 5px 1px rgba(0, 0, 0, .1); 256 | } 257 | 258 | &.position-top { 259 | top: auto; 260 | bottom: 100%; 261 | margin-bottom: 5px; 262 | 263 | .react-datepicker-container { 264 | &::after, 265 | &::before { 266 | top: 100%; 267 | left: 50%; 268 | border: solid transparent; 269 | content: ' '; 270 | height: 0; 271 | width: 0; 272 | position: absolute; 273 | pointer-events: none; 274 | } 275 | 276 | &::after { 277 | border-top-color: $picker-border-color; 278 | border-width: $picker-arrow-size; 279 | margin-left: -1 * $picker-arrow-size; 280 | } 281 | 282 | &::before { 283 | border-top-color: $picker-border-color; 284 | border-width: ($picker-arrow-size + 1); 285 | margin-left: -1 * ($picker-arrow-size + 1); 286 | } 287 | } 288 | } 289 | 290 | &:not(.position-top) { 291 | .react-datepicker-container { 292 | .react-datepicker-top { 293 | &::after, 294 | &::before { 295 | bottom: 100%; 296 | left: 50%; 297 | border: solid transparent; 298 | content: ' '; 299 | height: 0; 300 | width: 0; 301 | position: absolute; 302 | pointer-events: none; 303 | } 304 | 305 | &::after { 306 | border-bottom-color: $picker-header-border-color; 307 | border-width: $picker-arrow-size; 308 | margin-left: -1 * $picker-arrow-size; 309 | } 310 | 311 | &::before { 312 | border-bottom-color: $picker-border-color; 313 | border-width: ($picker-arrow-size + 1); 314 | margin-left: -1 * ($picker-arrow-size + 1); 315 | } 316 | } 317 | } 318 | } 319 | 320 | .react-datepicker-container { 321 | width: $picker-width; 322 | position: relative; 323 | 324 | .react-datepicker-top { 325 | text-align: center; 326 | background: $picker-header-background; 327 | color: $picker-header-color; 328 | border-top: 1px solid $picker-header-border-color; 329 | border-left: 1px solid $picker-header-border-color; 330 | border-right: 1px solid $picker-header-border-color; 331 | border-top-left-radius: $picker-border-radius; 332 | border-top-right-radius: $picker-border-radius; 333 | 334 | .week-days { 335 | height: 35px; 336 | 337 | .week-day { 338 | cursor: default; 339 | font-weight: 400; 340 | font-size: 13px; 341 | } 342 | } 343 | 344 | .display { 345 | height: 35px; 346 | 347 | .react-datepicker-button { 348 | text-decoration: none; 349 | padding: 4px; 350 | text-align: center; 351 | font-size: 15px; 352 | letter-spacing: .5px; 353 | cursor: pointer; 354 | 355 | &.button-left { 356 | font-size: 13px; 357 | padding: 4px 16px; 358 | border-top-left-radius: $picker-border-radius; 359 | } 360 | 361 | &.button-right { 362 | font-size: 13px; 363 | padding: 4px 16px; 364 | border-top-right-radius: $picker-border-radius; 365 | } 366 | 367 | &:hover { 368 | background: $picker-header-background-hover; 369 | border-radius: 4px; 370 | } 371 | 372 | &.fixed:hover { 373 | background: transparent; 374 | cursor: default; 375 | } 376 | } 377 | } 378 | } 379 | 380 | .react-datepicker-body { 381 | border-left: 1px solid $picker-border-color; 382 | border-right: 1px solid $picker-border-color; 383 | border-bottom: 1px solid $picker-border-color; 384 | border-bottom-right-radius: $picker-border-radius; 385 | border-bottom-left-radius: $picker-border-radius; 386 | 387 | .react-datepicker-row { 388 | margin-top: 0; 389 | width: 100%; 390 | min-height: 30px; 391 | 392 | &:not(:last-child) { 393 | border-bottom: 1px solid $picker-border-color; 394 | } 395 | 396 | &:last-child .react-datepicker-picker { 397 | &:first-child { 398 | border-bottom-left-radius: $picker-border-radius; 399 | } 400 | 401 | &:last-child { 402 | border-bottom-right-radius: $picker-border-radius; 403 | } 404 | } 405 | 406 | .react-datepicker-picker { 407 | color: $picker-color; 408 | background: $picker-background; 409 | cursor: pointer; 410 | text-decoration: none; 411 | font-weight: 400; 412 | 413 | &:not(:last-child) { 414 | border-right: 1px solid $picker-border-color; 415 | } 416 | 417 | &.day { 418 | min-height: 30px !important; 419 | } 420 | 421 | &.month { 422 | min-height: 65px !important; 423 | } 424 | 425 | &.year { 426 | min-height: 65px !important; 427 | } 428 | 429 | &:hover { 430 | color: $picker-color-hover; 431 | background: $picker-background-hover; 432 | } 433 | 434 | &.selected { 435 | color: $picker-selected-color; 436 | background: $picker-selected-background; 437 | font-weight: $font-weight-selected; 438 | } 439 | 440 | &.current { 441 | font-weight: 600; 442 | color: $picker-current-color; 443 | background: $picker-current-background; 444 | 445 | &:hover { 446 | color: $picker-current-color-hover; 447 | background: $picker-current-background-hover; 448 | } 449 | } 450 | 451 | &.selected.current { 452 | color: $picker-selected-color; 453 | background: $picker-selected-background; 454 | } 455 | 456 | &.disabled { 457 | cursor: default; 458 | color: $picker-disabled-color; 459 | background: $picker-disabled-background; 460 | 461 | &:hover { 462 | color: $picker-disabled-color; 463 | background: $picker-disabled-background; 464 | } 465 | } 466 | } 467 | } 468 | } 469 | } 470 | } 471 | -------------------------------------------------------------------------------- /src/utils/DateUtils.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | import range from 'lodash/range'; 3 | 4 | export const daysInMonthCount = (month, year) => moment([year, month]).endOf('month').date(); 5 | 6 | export const getArrayByBoundary = (start, end) => { 7 | return range(end - start).map(i => i + start); 8 | }; 9 | 10 | export const getWeekdaysMin = () => { 11 | const offset = moment().localeData().firstDayOfWeek(); 12 | const weekdaysMin = moment.weekdaysMin(); 13 | 14 | range(offset).forEach(() => { 15 | const firstDay = weekdaysMin.shift(); 16 | weekdaysMin.push(firstDay); 17 | }); 18 | return weekdaysMin; 19 | }; 20 | 21 | export const getVisibleDays = (month, year) => { 22 | const offset = moment([year, month]).startOf('month').weekday(); 23 | const previousMonth = month === 0 ? 11 : (month - 1); 24 | const previousYear = month === 0 ? (year - 1) : year; 25 | 26 | const currentMonthLength = daysInMonthCount(month, year) + 1; 27 | const previousMonthLength = daysInMonthCount(previousMonth, previousYear) + 1; // We need the last number too 28 | 29 | const previous = getArrayByBoundary(previousMonthLength - offset, previousMonthLength); 30 | const current = getArrayByBoundary(1, currentMonthLength); 31 | const following = getArrayByBoundary(1, 43 - previous.length - current.length); 32 | return { 33 | startCurrent: previous.length, 34 | endCurrent: previous.length + current.length - 1, 35 | days: previous.concat(current).concat(following) 36 | }; 37 | }; 38 | 39 | export const getVisibleYears = (year) => { 40 | const startDecadeYear = parseInt(year / 10, 10) * 10; 41 | const endDecadeYear = startDecadeYear + 9; 42 | const previous = [startDecadeYear - 1]; 43 | const current = getArrayByBoundary(startDecadeYear, endDecadeYear + 1); 44 | const following = [endDecadeYear + 1]; 45 | return { 46 | startCurrent: previous.length, 47 | endCurrent: previous.length + current.length - 1, 48 | years: previous.concat(current).concat(following) 49 | }; 50 | }; 51 | 52 | export const evaluateDateProp = (props, propName, componentName) => { 53 | const dateProp = props[propName]; 54 | if (dateProp && (typeof dateProp !== 'string' && !(dateProp instanceof Date) && !moment.isMoment(dateProp))) { 55 | return new Error(`${propName} validation failed in ${componentName}`); 56 | } 57 | }; 58 | 59 | export const isInsideTheEnabledArea = (date, mode, minDate, maxDate) => { 60 | if (!minDate && !maxDate) { 61 | return true; 62 | } 63 | 64 | const minDateMoment = typeof minDate === 'string' ? moment(minDate, moment.ISO_8601, true) : moment(minDate); 65 | const maxDateMoment = typeof maxDate === 'string' ? moment(maxDate, moment.ISO_8601, true) : moment(maxDate); 66 | 67 | let format; 68 | switch (mode) { 69 | case 'day': 70 | format = 'YYYY/MM/DD'; 71 | break; 72 | 73 | case 'month': 74 | format = 'YYYY/MM'; 75 | break; 76 | 77 | case 'year': 78 | format = 'YYYY'; 79 | break; 80 | } 81 | 82 | return (!minDate || date.format(format) >= minDateMoment.format(format)) && (!maxDate || date.format(format) <= maxDateMoment.format(format)); 83 | }; 84 | -------------------------------------------------------------------------------- /src/utils/format.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | 3 | export default function format(Component) { 4 | 5 | Component.prototype.getDisplayFormat = function(props) { 6 | const { displayFormat, fixedMode, startMode } = (props || this.props); 7 | if (displayFormat) { 8 | return displayFormat; 9 | } 10 | if (fixedMode) { 11 | switch (startMode) { 12 | case 'day': 13 | return 'DD'; 14 | case 'month': 15 | return 'MMMM'; 16 | case 'year': 17 | return 'YYYY'; 18 | } 19 | } 20 | 21 | return 'L'; 22 | }; 23 | 24 | Component.prototype.formatReturnedDate = function(date, props) { 25 | const { returnFormat } = (props || this.props); 26 | return date.format(returnFormat); 27 | }; 28 | 29 | Component.prototype.formatDisplayedDate = function(date, props) { 30 | return date.format(this.getDisplayFormat(props)); 31 | }; 32 | 33 | Component.prototype.parsePropDateString = function(dateString, props) { 34 | const { returnFormat } = (props || this.props); 35 | if (!returnFormat) { 36 | return moment(dateString); 37 | } else { 38 | return moment(dateString, returnFormat, true); 39 | } 40 | }; 41 | 42 | Component.prototype.parseInputDateString = function(dateString, props) { 43 | const format = this.getDisplayFormat(props); 44 | if (!format) { 45 | return moment(dateString); 46 | } else { 47 | return moment(dateString, format, true); 48 | } 49 | }; 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | export format from './format'; 2 | export valueLink from './valueLink'; 3 | export pure from './pure'; 4 | export skinnable from './skinnable'; 5 | -------------------------------------------------------------------------------- /src/utils/model.js: -------------------------------------------------------------------------------- 1 | import t from 'tcomb'; 2 | import moment from 'moment'; 3 | 4 | export const Mode = t.enums.of(['day', 'month', 'year']); 5 | 6 | export const MomentDate = t.irreducible('MomentDate', x => moment.isMoment(x)); 7 | 8 | export const Value = t.union([t.String, t.Date, MomentDate]); 9 | -------------------------------------------------------------------------------- /src/utils/pure.js: -------------------------------------------------------------------------------- 1 | export default from 'revenge/lib/decorators/pure'; 2 | -------------------------------------------------------------------------------- /src/utils/skinnable.js: -------------------------------------------------------------------------------- 1 | export default from 'revenge/lib/decorators/skinnable'; 2 | -------------------------------------------------------------------------------- /src/utils/valueLink.js: -------------------------------------------------------------------------------- 1 | export default function format(Component) { 2 | Component.prototype.getValueLink = function(_props) { 3 | const props = _props || this.props; 4 | return props.valueLink || { 5 | value: props.value, 6 | requestChange: props.onChange 7 | }; 8 | }; 9 | } 10 | -------------------------------------------------------------------------------- /src/yearpicker/YearPicker.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import t from 'tcomb'; 3 | import { props } from 'tcomb-react'; 4 | import View from 'react-flexview'; 5 | import { pure, skinnable } from '../utils'; 6 | import { MomentDate, Value, Mode } from '../utils/model'; 7 | import YearPickerTop from './YearPickerTop'; 8 | import YearPickerBody from './YearPickerBody'; 9 | 10 | @pure 11 | @skinnable() 12 | @props({ 13 | changeYear: t.Function, 14 | visibleDate: MomentDate, 15 | date: t.maybe(Value), 16 | minDate: t.maybe(Value), 17 | maxDate: t.maybe(Value), 18 | onChangeVisibleDate: t.Function, 19 | onSelectDate: t.Function, 20 | onChangeMode: t.Function, 21 | mode: Mode, 22 | fixedMode: t.maybe(t.Boolean), 23 | prevIconClassName: t.String, 24 | nextIconClassName: t.String 25 | }) 26 | export default class YearPicker extends React.Component { 27 | 28 | onSelectDate = (date) => { 29 | const { fixedMode, onSelectDate, onChangeMode, onChangeVisibleDate } = this.props; 30 | if (fixedMode) { 31 | onSelectDate(date); 32 | } else { 33 | onChangeVisibleDate(date); 34 | onChangeMode(Mode('month')); 35 | } 36 | } 37 | 38 | getLocals({ 39 | date, visibleDate, minDate, 40 | maxDate, changeYear, mode, 41 | prevIconClassName, nextIconClassName 42 | }) { 43 | return { 44 | yearPickerTopProps: { 45 | visibleDate, 46 | changeYear, 47 | prevIconClassName, 48 | nextIconClassName 49 | }, 50 | yearPickerBodyProps: { 51 | date, visibleDate, 52 | minDate, maxDate, 53 | mode, 54 | onSelectDate: this.onSelectDate 55 | } 56 | }; 57 | } 58 | 59 | template({ yearPickerTopProps, yearPickerBodyProps }) { 60 | return ( 61 | 62 | 63 | 64 | 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/yearpicker/YearPickerBody.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import moment from 'moment'; 3 | import t from 'tcomb'; 4 | import { props } from 'tcomb-react'; 5 | import range from 'lodash/range'; 6 | import { pure, skinnable } from '../utils'; 7 | import { Value, Mode, MomentDate } from '../utils/model'; 8 | import { isInsideTheEnabledArea, getVisibleYears } from '../utils/DateUtils'; 9 | import InvalidDate from '../InvalidDate'; 10 | import Picker from '../Picker'; 11 | import Row from '../Row'; 12 | 13 | const COLUMNS = 4; 14 | const ROWS = 3; 15 | 16 | @pure 17 | @skinnable() 18 | @props({ 19 | visibleDate: MomentDate, 20 | date: t.maybe(Value), 21 | minDate: t.maybe(Value), 22 | maxDate: t.maybe(Value), 23 | onSelectDate: t.Function, 24 | mode: Mode 25 | }) 26 | export default class YearPickerBody extends React.Component { 27 | 28 | getLocals({ date, visibleDate, minDate, maxDate, onSelectDate, mode }) { 29 | if (!visibleDate.isValid()) { 30 | return ; 31 | } 32 | const year = visibleDate.year(); 33 | const selectedYear = date ? date.year() : -1; 34 | 35 | const visibleYears = getVisibleYears(year); 36 | const pickers = visibleYears.years.map((_year, index) => { 37 | const date = moment([_year, 0, 1]); 38 | const isCurrent = index >= visibleYears.startCurrent && index <= visibleYears.endCurrent; 39 | return { 40 | date, 41 | onSelectDate, 42 | mode, 43 | isCurrent, 44 | isSelected: selectedYear === _year, 45 | isEnabled: isInsideTheEnabledArea(date, mode, minDate, maxDate), 46 | key: index 47 | }; 48 | }); 49 | 50 | return { pickers, mode }; 51 | } 52 | 53 | templateYears = ({ pickers }) => pickers.map(p => ) 54 | 55 | template({ pickers, mode }) { 56 | const years = this.templateYears({ pickers }); 57 | const rows = range(ROWS).map(index => 58 | 59 | ); 60 | 61 | return ( 62 |
63 | {rows} 64 |
65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/yearpicker/YearPickerTop.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import t from 'tcomb'; 3 | import { props } from 'tcomb-react'; 4 | import { pure, skinnable } from '../utils'; 5 | import { MomentDate } from '../utils/model'; 6 | import PickerTop from '../PickerTop'; 7 | 8 | @pure 9 | @skinnable() 10 | @props({ 11 | visibleDate: MomentDate, 12 | changeYear: t.Function, 13 | prevIconClassName: t.String, 14 | nextIconClassName: t.String 15 | }) 16 | export default class YearPickerTop extends React.Component { 17 | 18 | getYear = () => this.props.visibleDate.year() 19 | 20 | previousDate = () => this.props.changeYear(this.getYear() - 10) 21 | 22 | nextDate = () => this.props.changeYear(this.getYear() + 10) 23 | 24 | getLocals({ prevIconClassName, nextIconClassName }) { 25 | const year = this.getYear(); 26 | const startDecadeYear = parseInt(year / 10, 10) * 10; 27 | const endDecadeYear = startDecadeYear + 9; 28 | 29 | return { 30 | prevIconClassName, 31 | nextIconClassName, 32 | fixed: true, 33 | previousDate: this.previousDate, 34 | nextDate: this.nextDate, 35 | value: `${startDecadeYear}-${endDecadeYear}` 36 | }; 37 | } 38 | 39 | template(locals) { 40 | return ; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /styles.js: -------------------------------------------------------------------------------- 1 | require('./src/style.scss'); 2 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "globals": { 3 | "describe": true, 4 | "it": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | // called by mocha 2 | const requireDir = require('require-dir'); 3 | 4 | require('babel-register')({ 5 | ignore: /node_modules/, 6 | extensions: ['.js', '.jsx'], 7 | presets: ['es2015', 'react', 'stage-0'], 8 | plugins: [ 9 | 'transform-decorators-legacy', 10 | 'lodash' 11 | ] 12 | }); 13 | 14 | requireDir('./tests', { 15 | recurse: true 16 | }); 17 | -------------------------------------------------------------------------------- /test/tests/DatePickerInput-test.js: -------------------------------------------------------------------------------- 1 | import 'moment/locale/fr'; 2 | import 'moment/locale/de'; 3 | 4 | import React from 'react'; 5 | import TestUtils from 'react-addons-test-utils'; 6 | import expect from 'expect'; 7 | import { DatePickerInput, DatePicker } from '../../src'; 8 | 9 | describe('DatePickerInput', () => { 10 | 11 | it('presents the DatePicker when clicking on the calendar button', () => { 12 | 13 | const input = TestUtils.renderIntoDocument( {}} />); 14 | 15 | let datePickers = TestUtils.scryRenderedComponentsWithType(input, DatePicker); 16 | expect(datePickers.length).toBe(0); 17 | 18 | const calendarButton = TestUtils.findRenderedDOMComponentWithClass(input, 'input-button'); 19 | TestUtils.Simulate.click(calendarButton); 20 | 21 | datePickers = TestUtils.scryRenderedComponentsWithType(input, DatePicker); 22 | expect(datePickers.length).toBe(1, 'DatePicker was not displayed after clicking on the calendar button'); 23 | 24 | }); 25 | 26 | it('presents the DatePicker when clicking on the input area', () => { 27 | 28 | const input = TestUtils.renderIntoDocument( {}} showOnInputClick />); 29 | 30 | let datePickers = TestUtils.scryRenderedComponentsWithType(input, DatePicker); 31 | expect(datePickers.length).toBe(0); 32 | 33 | const datePickerInputArea = TestUtils.findRenderedDOMComponentWithTag(input, 'input'); 34 | TestUtils.Simulate.click(datePickerInputArea); 35 | 36 | datePickers = TestUtils.scryRenderedComponentsWithType(input, DatePicker); 37 | expect(datePickers.length).toBe(1, 'DatePicker was not displayed after clicking on the input area'); 38 | 39 | }); 40 | 41 | it('should pass the name prop down to the underlying input field', () => { 42 | 43 | const input = TestUtils.renderIntoDocument( {}} name='foobar' />); 44 | 45 | const datePickerInputArea = TestUtils.findRenderedDOMComponentWithTag(input, 'input'); 46 | expect(datePickerInputArea.name).toBe('foobar', `Underlying input's 'name' prop is '${datePickerInputArea.name}' instead of 'foobar'`); 47 | 48 | }); 49 | 50 | it('DatePicker should be floating above content', () => { 51 | 52 | const input = TestUtils.renderIntoDocument( {}} />); 53 | 54 | const input2 = TestUtils.renderIntoDocument( {}} />); 55 | 56 | const wrapper1 = TestUtils.findRenderedDOMComponentWithClass(input, 'react-datepicker-component'); 57 | 58 | const calendarButton = TestUtils.findRenderedDOMComponentWithClass(input2, 'input-button'); 59 | TestUtils.Simulate.click(calendarButton); 60 | 61 | const wrapper2 = TestUtils.findRenderedDOMComponentWithClass(input2, 'react-datepicker-component'); 62 | 63 | const previousHeight = wrapper1.clientHeight; 64 | const newHeight = wrapper2.clientHeight; 65 | expect(newHeight).toBe(previousHeight, `datepicker component height is ${newHeight} instead of ${previousHeight}`); 66 | 67 | }); 68 | 69 | it('DatePickers should have correct locales', () => { 70 | 71 | const inputFr = TestUtils.renderIntoDocument( {}} locale='fr' className='french' />); 72 | const inputDe = TestUtils.renderIntoDocument( {}} locale='de' className='german' />); 73 | 74 | const frenchWeekDays = TestUtils.scryRenderedDOMComponentsWithClass(inputFr, 'week-day'); 75 | const germanWeekDays = TestUtils.scryRenderedDOMComponentsWithClass(inputDe, 'week-day'); 76 | 77 | expect(frenchWeekDays[0].innerHTML).toBe('lu', 'First DatePicker is not in french'); 78 | expect(germanWeekDays[0].innerHTML).toBe('Mo', 'Second DatePicker is not in german'); 79 | 80 | }); 81 | 82 | 83 | it('DatePicker should work with valueLink', () => { 84 | 85 | let date = new Date('2015-07-15'); 86 | 87 | const onChange = (jsDate) => { 88 | date = jsDate; 89 | }; 90 | 91 | const valueLink = { 92 | value: date, 93 | requestChange: onChange 94 | }; 95 | 96 | const input = TestUtils.renderIntoDocument(); 97 | 98 | const datePickerInputArea = TestUtils.findRenderedDOMComponentWithTag(input, 'input'); 99 | TestUtils.Simulate.click(datePickerInputArea); 100 | expect(datePickerInputArea.value).toBe('15.07.2015', 'initial value is wrong'); 101 | 102 | const pickerButton = TestUtils.scryRenderedDOMComponentsWithClass(input, 'react-datepicker-picker')[0]; 103 | TestUtils.Simulate.click(pickerButton); 104 | expect(datePickerInputArea.value).toBe('29.06.2015', 'displayed value didn\'t change correctly'); 105 | 106 | const formattedDate = [date.getDate(), date.getMonth() + 1, date.getFullYear()].join('.'); 107 | expect(formattedDate).toBe('29.6.2015', 'stored value didn\'t change correctly'); 108 | 109 | }); 110 | 111 | it('DatePicker should close on select date', () => { 112 | 113 | const input = TestUtils.renderIntoDocument( {}} showOnInputClick autoClose />); 114 | 115 | let datePickers = TestUtils.scryRenderedComponentsWithType(input, DatePicker); 116 | expect(datePickers.length).toBe(0); 117 | 118 | const datePickerInputArea = TestUtils.findRenderedDOMComponentWithTag(input, 'input'); 119 | TestUtils.Simulate.click(datePickerInputArea); 120 | 121 | datePickers = TestUtils.scryRenderedComponentsWithType(input, DatePicker); 122 | expect(datePickers.length).toBe(1); 123 | 124 | const pickerButton = TestUtils.scryRenderedDOMComponentsWithClass(input, 'react-datepicker-picker')[0]; 125 | TestUtils.Simulate.click(pickerButton); 126 | 127 | datePickers = TestUtils.scryRenderedComponentsWithType(input, DatePicker); 128 | expect(datePickers.length).toBe(0, 'DatePicker didn\'t close correctly'); 129 | 130 | }); 131 | 132 | it('DatePicker should close on enter key event on input', () => { 133 | 134 | const input = TestUtils.renderIntoDocument( {}} showOnInputClick />); 135 | 136 | let datePickers = TestUtils.scryRenderedComponentsWithType(input, DatePicker); 137 | expect(datePickers.length).toBe(0); 138 | 139 | const datePickerInputArea = TestUtils.findRenderedDOMComponentWithTag(input, 'input'); 140 | TestUtils.Simulate.click(datePickerInputArea); 141 | 142 | datePickers = TestUtils.scryRenderedComponentsWithType(input, DatePicker); 143 | expect(datePickers.length).toBe(1); 144 | 145 | TestUtils.Simulate.keyUp(datePickerInputArea, { keyCode: 13 }); 146 | 147 | datePickers = TestUtils.scryRenderedComponentsWithType(input, DatePicker); 148 | expect(datePickers.length).toBe(0, 'DatePicker didn\'t close correctly'); 149 | 150 | }); 151 | 152 | }); 153 | -------------------------------------------------------------------------------- /webpack.config.build.babel.js: -------------------------------------------------------------------------------- 1 | import ExtractTextPlugin from 'extract-text-webpack-plugin'; 2 | 3 | import path from 'path'; 4 | 5 | const paths = { 6 | SRC: path.resolve(__dirname, 'src'), 7 | ENTRY: path.resolve(__dirname, 'styles.js'), 8 | BUILD: path.resolve(__dirname, 'lib'), 9 | NODE_MODULES: path.resolve(__dirname, 'node_modules') 10 | }; 11 | 12 | export default { 13 | 14 | output: { 15 | path: paths.BUILD, 16 | filename: 'bundle.js' 17 | }, 18 | 19 | entry: paths.ENTRY, 20 | 21 | resolve: { 22 | root: [paths.NODE_MODULES] 23 | }, 24 | 25 | module: { 26 | loaders: [{ 27 | test: /\.jsx?$/, 28 | loaders: ['babel'], 29 | include: [paths.SRC] 30 | }, { 31 | test: /\.scss$/, 32 | loader: ExtractTextPlugin.extract('style', 'css!resolve-url!sass?sourceMap') 33 | }] 34 | }, 35 | 36 | plugins: [ 37 | new ExtractTextPlugin('style', 'style.css') 38 | ] 39 | }; 40 | --------------------------------------------------------------------------------