├── .editorconfig ├── .gitignore ├── .stylelintrc ├── .travis.yml ├── HISTORY.md ├── README.md ├── assets ├── common │ ├── Animation.less │ ├── Calendar.less │ ├── ConfirmPanel.less │ ├── DatePicker.less │ ├── ShortcutPanel.less │ ├── SingleMonth.less │ ├── TimePicker.less │ ├── WeekPanel.less │ ├── icon.svg │ └── index.less └── index.less ├── examples ├── basic.html └── basic.tsx ├── index.android.js ├── index.ios.js ├── index.js ├── package.json ├── src ├── Calendar.tsx ├── CalendarProps.ts ├── DatePicker.base.tsx ├── DatePicker.tsx ├── DatePickerProps.ts ├── TimePicker.tsx ├── calendar │ ├── AnimateWrapper.tsx │ ├── ConfirmPanel.tsx │ ├── Header.tsx │ └── ShortcutPanel.tsx ├── date │ ├── DataTypes.ts │ ├── SingleMonth.tsx │ └── WeekPanel.tsx ├── index.ts ├── locale │ ├── en_US.ts │ ├── pt_BR.ts │ └── zh_CN.ts └── util │ └── index.ts ├── tests ├── Calendar.spec.tsx ├── DatePicker.spec.tsx └── __snapshots__ │ ├── Calendar.spec.tsx.snap │ └── DatePicker.spec.tsx.snap ├── tsconfig.json ├── tslint.json └── typings └── models.d.ts /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | # Unix-style newlines with a newline ending every file 5 | [*.{js,css}] 6 | end_of_line = lf 7 | insert_final_newline = true 8 | indent_style = space 9 | indent_size = 2 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | *.log 3 | *.log.* 4 | .idea 5 | .ipr 6 | .iws 7 | *~ 8 | ~* 9 | *.diff 10 | *.patch 11 | *.bak 12 | .DS_Store 13 | Thumbs.db 14 | .project 15 | .*proj 16 | .svn 17 | *.swp 18 | *.swo 19 | *.pyc 20 | *.pyo 21 | node_modules 22 | .cache 23 | *.css 24 | build 25 | lib 26 | es 27 | coverage 28 | *.js 29 | *.jsx 30 | *.map 31 | !tests/index.js 32 | !/index*.js 33 | ios/ 34 | android/ 35 | xcuserdata 36 | yarn.lock 37 | _ts2js 38 | package-lock.json 39 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "stylelint-config-standard", 3 | "rules": { 4 | at-rule-empty-line-before: null, 5 | at-rule-name-space-after: null, 6 | at-rule-no-unknown: null, 7 | comment-empty-line-before: null, 8 | declaration-bang-space-before: null, 9 | declaration-empty-line-before: null, 10 | function-comma-newline-after: null, 11 | function-name-case: null, 12 | function-parentheses-newline-inside: null, 13 | function-max-empty-lines: null, 14 | function-whitespace-after: null, 15 | indentation: null, 16 | number-leading-zero: null, 17 | number-no-trailing-zeros: null, 18 | rule-empty-line-before: null, 19 | selector-combinator-space-after: null, 20 | selector-list-comma-newline-after: null, 21 | selector-pseudo-element-colon-notation: null, 22 | unit-no-unknown: null, 23 | value-list-max-empty-lines: null, 24 | unit-case: null, 25 | color-hex-case: null, 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | sudo: false 4 | 5 | notifications: 6 | email: 7 | - zhang740@qq.com 8 | 9 | node_js: 10 | - 6.9.1 11 | 12 | before_install: 13 | - | 14 | if ! git diff --name-only $TRAVIS_COMMIT_RANGE | grep -qvE '(\.md$)|(^(docs|examples))/' 15 | then 16 | echo "Only docs were updated, stopping build process." 17 | exit 18 | fi 19 | 20 | script: 21 | - | 22 | if [ "$TEST_TYPE" = test ]; then 23 | npm test 24 | else 25 | npm run $TEST_TYPE 26 | fi 27 | env: 28 | matrix: 29 | - TEST_TYPE=lint 30 | - TEST_TYPE=test 31 | - TEST_TYPE=coverage:upload 32 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | # History 2 | ---- 3 | 4 | ## 0.0.1 / 2017-08-10 5 | 6 | - TODO -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rmc-calendar 2 | --- 3 | 4 | React Mobile Calendar Component (web) 5 | 6 | 7 | [![NPM version][npm-image]][npm-url] 8 | ![react](https://img.shields.io/badge/react-%3E%3D_15.2.0-green.svg) 9 | [![build status][travis-image]][travis-url] 10 | [![Test coverage][coveralls-image]][coveralls-url] 11 | [![gemnasium deps][gemnasium-image]][gemnasium-url] 12 | [![npm download][download-image]][download-url] 13 | 14 | [npm-image]: http://img.shields.io/npm/v/rmc-calendar.svg?style=flat-square 15 | [npm-url]: http://npmjs.org/package/rmc-calendar 16 | [travis-image]: https://img.shields.io/travis/react-component/m-calendar.svg?style=flat-square 17 | [travis-url]: https://travis-ci.org/react-component/m-calendar 18 | [coveralls-image]: https://img.shields.io/coveralls/react-component/m-calendar.svg?style=flat-square 19 | [coveralls-url]: https://coveralls.io/r/react-component/m-calendar?branch=master 20 | [gemnasium-image]: http://img.shields.io/gemnasium/react-component/m-calendar.svg?style=flat-square 21 | [gemnasium-url]: https://gemnasium.com/react-component/m-calendar 22 | [node-image]: https://img.shields.io/badge/node.js-%3E=_0.10-green.svg?style=flat-square 23 | [node-url]: http://nodejs.org/download/ 24 | [download-image]: https://img.shields.io/npm/dm/rmc-calendar.svg?style=flat-square 25 | [download-url]: https://npmjs.org/package/rmc-calendar 26 | 27 | ## Screenshots 28 | 29 | 30 | 31 | 32 | ## Development 33 | 34 | ``` 35 | npm i 36 | npm start 37 | ``` 38 | 39 | ## Example 40 | 41 | http://localhost:8000/examples/ 42 | 43 | online example: http://react-component.github.io/m-calendar/ 44 | 45 | ## react-native (TBC) 46 | 47 | ``` 48 | ./node_modules/rc-tools run react-native-init 49 | npm run watch-tsc 50 | react-native start 51 | react-native run-ios 52 | ``` 53 | 54 | ## install 55 | 56 | [![rmc-calendar](https://nodei.co/npm/rmc-calendar.png)](https://npmjs.org/package/rmc-calendar) 57 | 58 | 59 | # docs 60 | 61 | ## Usage 62 | ```jsx 63 | import React, { Component } from 'react'; 64 | 65 | import { Calendar } from 'rmc-calendar'; 66 | import 'rmc-calendar/assets/index.css'; 67 | 68 | class App extends Component { 69 | constructor(props) { 70 | super(props); 71 | this.state = { 72 | visible: false, 73 | }; 74 | } 75 | 76 | setVisiable = () => { 77 | this.setState({ 78 | visible: !this.state.visible, 79 | }); 80 | } 81 | 82 | render() { 83 | return ( 84 |
85 | 90 |
91 | ); 92 | } 93 | } 94 | 95 | export default App; 96 | ``` 97 | 98 | ## API 99 | 100 | ### Calendar props 101 | ```ts 102 | interface PropsType { 103 | /** enter direction,default: vertical */ 104 | enterDirection?: 'horizontal' | 'vertical'; 105 | /** locale */ 106 | locale?: GlobalModels.Locale; 107 | onCancel?: () => void; 108 | onConfirm?: (startDateTime?: Date, endDateTime?: Date) => void; 109 | /** choose time,default: false */ 110 | pickTime?: boolean; 111 | /** (web only) prefix class,default: rmc-calendar */ 112 | prefixCls?: string; 113 | /** shortcut render, need showShortcut: true */ 114 | renderShortcut?: (select: (startDate?: Date, endDate?: Date) => void) => React.ReactNode; 115 | /** show header, default: true */ 116 | showHeader?: boolean; 117 | /** show shortcut, default: false */ 118 | showShortcut?: boolean; 119 | /** header title, default: {locale.title} */ 120 | title?: string; 121 | /** select type, default: range,one: one-day, range: range */ 122 | type?: 'one' | 'range'; 123 | /** visible, default: false */ 124 | visible?: boolean; 125 | 126 | // DatePicker Component 127 | /** default date for show, default: today */ 128 | defaultDate?: Date; 129 | /** extra info of date */ 130 | getDateExtra?: (date: Date) => DateModels.ExtraData; 131 | /** infinite scroll, default: true */ 132 | infinite?: boolean; 133 | /** infinite scroll optimization, default: false */ 134 | infiniteOpt?: boolean; 135 | /** inital generate months, default: 6 */ 136 | initalMonths?: number; 137 | /** max date */ 138 | maxDate?: Date; 139 | /** min date */ 140 | minDate?: Date; 141 | /** select range has disable date */ 142 | onSelectHasDisableDate?: (date: Date[]) => void; 143 | 144 | // TimePicker Component 145 | /** inital time of TimePicker */ 146 | defaultTimeValue?: Date; 147 | } 148 | ``` 149 | 150 | ### DatePicker props 151 | ```ts 152 | export default interface PropsType { 153 | /** default date for show, default: today */ 154 | defaultDate?: Date; 155 | /** select value of start date */ 156 | startDate?: Date; 157 | /** select value of end date */ 158 | endDate?: Date; 159 | /** extra info of date */ 160 | getDateExtra?: (date: Date) => Models.ExtraData; 161 | /** infinite scroll, default: true */ 162 | infinite?: boolean; 163 | /** infinite scroll optimization, default: false */ 164 | infiniteOpt?: boolean; 165 | /** inital generate months, default: 6 */ 166 | initalMonths?: number; 167 | /** locale */ 168 | locale?: GlobalModels.Locale; 169 | /** max date */ 170 | maxDate?: Date; 171 | /** min date */ 172 | minDate?: Date; 173 | /** callback when click the cell of date */ 174 | onCellClick?: (date: Date) => void; 175 | /** select range has disable date */ 176 | onSelectHasDisableDate?: (date: Date[]) => void; 177 | /** (web only) prefix class */ 178 | prefixCls?: string; 179 | /** select type, default: range,one: one-day, range: range */ 180 | type?: 'one' | 'range'; 181 | } 182 | ``` 183 | 184 | ## Test Case 185 | 186 | ``` 187 | npm test 188 | npm run chrome-test 189 | ``` 190 | 191 | ## Coverage 192 | 193 | ``` 194 | npm run coverage 195 | ``` 196 | 197 | open coverage/ dir 198 | 199 | ## License 200 | 201 | rmc-calendar is released under the MIT license. 202 | -------------------------------------------------------------------------------- /assets/common/Animation.less: -------------------------------------------------------------------------------- 1 | .@{prefixClass} { 2 | 3 | .animate { 4 | animation-duration: .3s; 5 | animation-fill-mode: both; 6 | } 7 | 8 | @keyframes fadeIn { 9 | 0% { 10 | opacity: 0; 11 | } 12 | 13 | to { 14 | opacity: 1; 15 | } 16 | } 17 | 18 | @keyframes fadeOut { 19 | 0% { 20 | opacity: 1; 21 | } 22 | 23 | to { 24 | opacity: 0; 25 | } 26 | } 27 | 28 | .fade-enter { 29 | animation-name: fadeIn; 30 | } 31 | 32 | .fade-leave { 33 | animation-name: fadeOut; 34 | } 35 | 36 | @keyframes slideInUp { 37 | 0% { 38 | transform: translate3d(0, 100%, 0); 39 | visibility: visible; 40 | } 41 | 42 | to { 43 | transform: translateZ(0); 44 | } 45 | } 46 | 47 | @keyframes slideInDown { 48 | 0% { 49 | transform: translateZ(0); 50 | visibility: visible; 51 | } 52 | 53 | to { 54 | transform: translate3d(0, 100%, 0); 55 | } 56 | } 57 | 58 | @keyframes slideInLeft { 59 | 0% { 60 | transform: translate3d(100%, 0, 0); 61 | visibility: visible; 62 | } 63 | 64 | to { 65 | transform: translateZ(0); 66 | } 67 | } 68 | 69 | @keyframes slideInRight { 70 | 0% { 71 | transform: translateZ(0); 72 | visibility: visible; 73 | } 74 | 75 | to { 76 | transform: translate3d(100%, 0, 0); 77 | } 78 | } 79 | 80 | .slideV-enter { 81 | animation-name: slideInUp; 82 | } 83 | 84 | .slideV-leave { 85 | animation-name: slideInDown; 86 | } 87 | 88 | .slideH-enter { 89 | animation-name: slideInLeft; 90 | } 91 | 92 | .slideH-leave { 93 | animation-name: slideInRight; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /assets/common/Calendar.less: -------------------------------------------------------------------------------- 1 | .@{prefixClass} { 2 | .mask { 3 | position: fixed; 4 | width: 100%; 5 | height: 100%; 6 | top: 0; 7 | left: 0; 8 | z-index: 999; 9 | background: rgba(0, 0, 0, .5); 10 | } 11 | 12 | .content { 13 | position: fixed; 14 | display: flex; 15 | flex-direction: column; 16 | width: 100%; 17 | height: 100%; 18 | top: 0; 19 | left: 0; 20 | z-index: 999; 21 | background: #fff; 22 | } 23 | 24 | .header { 25 | margin: 5px; 26 | display: flex; 27 | flex-shrink: 0; 28 | align-items: center; 29 | 30 | .title { 31 | text-align: center; 32 | width: 100%; 33 | font-weight: 700; 34 | } 35 | 36 | .left { 37 | position: absolute; 38 | display: flex; 39 | justify-content: center; 40 | align-items: center; 41 | padding: 0 8px; 42 | height: 24px; 43 | left: 5px; 44 | top: 5px; 45 | color: #068EEF; 46 | } 47 | 48 | .right { 49 | position: absolute; 50 | display: flex; 51 | justify-content: center; 52 | align-items: center; 53 | padding: 0 8px; 54 | height: 24px; 55 | right: 5px; 56 | top: 5px; 57 | color: #068EEF; 58 | font-size: 14px; 59 | } 60 | } 61 | 62 | .timePicker { 63 | border-top: 1px #ccc solid; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /assets/common/ConfirmPanel.less: -------------------------------------------------------------------------------- 1 | .@{prefixClass} .confirm-panel { 2 | display: flex; 3 | flex-shrink: 0; 4 | justify-content: center; 5 | align-items: center; 6 | background: #eee; 7 | padding: 2px 10px; 8 | border-top: #E5E4E4 1px solid; 9 | 10 | .info { 11 | font-size: 12px; 12 | 13 | p { 14 | margin: 0; 15 | } 16 | 17 | .grey { 18 | color: #999; 19 | } 20 | } 21 | 22 | .button { 23 | padding: 5px 20px; 24 | border-radius: 5px; 25 | align-self: center; 26 | margin: 5px 0 5px auto; 27 | color: #fff; 28 | font-size: 14px; 29 | background: #1A7BE6; 30 | } 31 | 32 | .button-disable { 33 | color: #aaa; 34 | background: #d5d5d5; 35 | } 36 | 37 | .button-full { 38 | margin: 5px; 39 | width: 100%; 40 | text-align: center; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /assets/common/DatePicker.less: -------------------------------------------------------------------------------- 1 | .@{prefixClass} .date-picker { 2 | display: flex; 3 | flex-direction: column; 4 | background: #eee; 5 | 6 | .wrapper { 7 | height: auto; 8 | position: relative; 9 | } 10 | 11 | .months { 12 | background: #fff; 13 | } 14 | 15 | .load-tip { 16 | position: absolute; 17 | display: flex; 18 | justify-content: center; 19 | align-items: flex-end; 20 | left: 0; 21 | right: 0; 22 | padding: 10px 0; 23 | top: -40px; 24 | color: #bbb; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /assets/common/ShortcutPanel.less: -------------------------------------------------------------------------------- 1 | .@{prefixClass} .shortcut-panel { 2 | display: flex; 3 | flex-direction: row; 4 | flex-shrink: 0; 5 | justify-content: space-between; 6 | padding: 8px 20px; 7 | border-top: #E5E4E4 1px solid; 8 | 9 | .item { 10 | display: inline-block; 11 | color: #068EEF; 12 | font-size: 14px; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /assets/common/SingleMonth.less: -------------------------------------------------------------------------------- 1 | .@{prefixClass} .single-month { 2 | padding: 10px 2px 0; 3 | overflow: hidden; 4 | 5 | .month-title { 6 | margin-left: 15px; 7 | } 8 | 9 | .row { 10 | display: flex; 11 | padding: 3px 0; 12 | @cell-size: 35px; 13 | 14 | .cell { 15 | display: flex; 16 | flex-direction: column; 17 | width: 100/7%; 18 | justify-content: center; 19 | align-items: center; 20 | 21 | .date-wrapper { 22 | display: flex; 23 | height: @cell-size; 24 | width: 100%; 25 | justify-content: center; 26 | align-items: center; 27 | margin-bottom: 1px; 28 | 29 | .disable { 30 | color: #aaa; 31 | background: #eee; 32 | border: none; 33 | border-radius: 100%; 34 | } 35 | 36 | .date { 37 | display: flex; 38 | justify-content: center; 39 | align-items: center; 40 | width: @cell-size; 41 | height: @cell-size; 42 | flex-shrink: 0; 43 | } 44 | 45 | .grey { 46 | color: #999; 47 | } 48 | 49 | .important { 50 | border: 1px #999 solid; 51 | border-radius: 100%; 52 | } 53 | 54 | .left, 55 | .right { 56 | border: none; 57 | width: 100%; 58 | height: 100%; 59 | } 60 | 61 | .date-selected { 62 | border: none; 63 | background: rgb(26, 123, 230); 64 | color: #fff; 65 | } 66 | 67 | .selected-start { 68 | border-radius: 100% 0 0 100%; 69 | } 70 | 71 | .selected-single { 72 | border-radius: 100%; 73 | } 74 | 75 | .selected-middle { 76 | border-radius: 0; 77 | } 78 | 79 | .selected-end { 80 | border-radius: 0 100% 100% 0; 81 | } 82 | } 83 | 84 | .info { 85 | height: 15px; 86 | width: 100%; 87 | padding: 0 5px; 88 | font-size: 12px; 89 | color: #999; 90 | white-space: nowrap; 91 | text-overflow: ellipsis; 92 | overflow: hidden; 93 | text-align: center; 94 | } 95 | 96 | .date-selected { 97 | color: rgb(26, 123, 230); 98 | } 99 | } 100 | } 101 | 102 | .row+.row { 103 | margin-top: 6px; 104 | } 105 | 106 | .row-xl+.row-xl { 107 | margin-top: 21px; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /assets/common/TimePicker.less: -------------------------------------------------------------------------------- 1 | .@{prefixClass} .time-picker { 2 | flex-shrink: 0; 3 | text-align: center; 4 | background: #fff; 5 | 6 | .title { 7 | font-size: 13px; 8 | padding: 5px 0; 9 | border-top: #E5E4E4 1px solid; 10 | border-bottom: #E5E4E4 1px solid; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /assets/common/WeekPanel.less: -------------------------------------------------------------------------------- 1 | .@{prefixClass} .week-panel { 2 | background: #fff; 3 | display: flex; 4 | flex-shrink: 0; 5 | padding: 0 2px; 6 | border-bottom: 1px #ccc solid; 7 | 8 | .cell { 9 | height: 24px; 10 | display: flex; 11 | width: 100/7%; 12 | justify-content: center; 13 | align-items: center; 14 | } 15 | 16 | .cell-grey { 17 | color: #999; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /assets/common/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /assets/common/index.less: -------------------------------------------------------------------------------- 1 | @prefixClass: rmc-calendar; 2 | .@{prefixClass} { 3 | font-family: Arial, "Hiragino Sans GB", "Microsoft Yahei", "Microsoft Sans Serif", "WenQuanYi Micro Hei", sans-serif; 4 | } 5 | 6 | .@{prefixClass}-hidden { 7 | display: none; 8 | } 9 | 10 | @import "./Animation.less"; 11 | @import "./Calendar.less"; 12 | @import "./WeekPanel.less"; 13 | @import "./DatePicker.less"; 14 | @import "./ConfirmPanel.less"; 15 | @import "./TimePicker.less"; 16 | @import "./SingleMonth.less"; 17 | @import "./ShortcutPanel.less"; 18 | -------------------------------------------------------------------------------- /assets/index.less: -------------------------------------------------------------------------------- 1 | @import './common/index.less'; 2 | -------------------------------------------------------------------------------- /examples/basic.html: -------------------------------------------------------------------------------- 1 | placeholder -------------------------------------------------------------------------------- /examples/basic.tsx: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-console */ 2 | 3 | import 'rmc-picker/assets/index.css'; 4 | import 'rmc-date-picker/assets/index.css'; 5 | import 'rmc-calendar/assets/index.less'; 6 | 7 | import React from 'react'; 8 | import ReactDOM from 'react-dom'; 9 | import { Calendar, ExtraData, CalendarPropsType } from '../src'; 10 | 11 | import zhCN from '../src/locale/zh_CN'; 12 | import enUS from '../src/locale/en_US'; 13 | const en = location.search.indexOf('en') !== -1; 14 | 15 | const extra: { [key: string]: ExtraData } = { 16 | 1501516800000: { info: '建军节' }, 17 | '2017/06/14': { info: '禁止选择', disable: true }, 18 | '2017/06/15': { info: 'Disable', disable: true }, 19 | }; 20 | 21 | const now = new Date; 22 | extra[+new Date(now.getFullYear(), now.getMonth(), now.getDate() + 5)] = { info: '禁止选择', disable: true }; 23 | extra[+new Date(now.getFullYear(), now.getMonth(), now.getDate() + 6)] = { info: 'Disable', disable: true }; 24 | extra[+new Date(now.getFullYear(), now.getMonth(), now.getDate() + 7)] = { info: 'Disable', disable: true }; 25 | extra[+new Date(now.getFullYear(), now.getMonth(), now.getDate() + 8)] = { info: 'Disable', disable: true }; 26 | 27 | for (let key in extra) { 28 | if (extra.hasOwnProperty(key)) { 29 | let info = extra[key]; 30 | const date = new Date(key); 31 | if (!Number.isNaN(+date) && !extra[+date]) { 32 | extra[+date] = info; 33 | } 34 | } 35 | } 36 | 37 | class BasicDemo extends React.Component<{}, { 38 | show: boolean; 39 | config?: CalendarPropsType; 40 | startTime?: Date; 41 | endTime?: Date; 42 | }> { 43 | originbodyScrollY = document.getElementsByTagName('body')[0].style.overflowY; 44 | 45 | constructor(props: any) { 46 | super(props); 47 | this.state = { 48 | show: false, 49 | config: {}, 50 | }; 51 | } 52 | 53 | renderBtn(text: string, text2: string, config: CalendarPropsType = {}) { 54 | return
{ 56 | document.getElementsByTagName('body')[0].style.overflowY = 'hidden'; 57 | this.setState({ 58 | show: true, 59 | config, 60 | }); 61 | }}> 62 |

{text}

63 |

{text2}

64 |
; 65 | } 66 | 67 | render() { 68 | return ( 69 |
70 | {this.renderBtn('选择日期区间', 'Select Date Range')} 71 | {this.renderBtn('选择日期时间区间', 'Select DateTime Range', { pickTime: true })} 72 | {this.renderBtn('选择日期', 'Select Date', { type: 'one' })} 73 | {this.renderBtn('选择日期时间', 'Select DateTime', { type: 'one', pickTime: true })} 74 | {this.renderBtn('选择日期区间(快捷)', 'Select Date Range (Shortcut)', { showShortcut: true })} 75 | {this.renderBtn('选择日期时间区间(快捷)', 'Select DateTime Range (Shortcut)', { pickTime: true, showShortcut: true })} 76 | {this.renderBtn('水平进入', 'Horizontal Enter Aniamtion', { enterDirection: 'horizontal' })} 77 | {this.renderBtn('默认选择范围', 'Selected Date Range', { defaultValue: [new Date(+new Date - 1 * 24 * 3600 * 1000), new Date(+new Date - 4 * 24 * 3600 * 1000)] })} 78 | {this.renderBtn('onSelectAPI', 'onSelectAPI', { 79 | onSelect: (date) => { 80 | console.log('onSelect', date); 81 | return [date, new Date(+new Date - 7 * 24 * 3600 * 1000)]; 82 | } 83 | })} 84 |
85 | { 86 | this.state.startTime && 87 |

开始时间:{this.state.startTime.toLocaleString()}

88 | } 89 | { 90 | this.state.endTime && 91 |

结束时间:{this.state.endTime.toLocaleString()}

92 | } 93 |
94 | { 99 | document.getElementsByTagName('body')[0].style.overflowY = this.originbodyScrollY; 100 | this.setState({ 101 | show: false, 102 | startTime: undefined, 103 | endTime: undefined, 104 | }); 105 | }} 106 | onConfirm={(startTime, endTime) => { 107 | console.log('onConfirm', startTime, endTime); 108 | document.getElementsByTagName('body')[0].style.overflowY = this.originbodyScrollY; 109 | this.setState({ 110 | show: false, 111 | startTime, 112 | endTime, 113 | }); 114 | }} 115 | onSelectHasDisableDate={(dates: Date[]) => { 116 | console.warn('onSelectHasDisableDate', dates); 117 | }} 118 | getDateExtra={(date) => { 119 | return extra[+date]; 120 | }} 121 | minDate={new Date(+new Date - 62 * 24 * 3600 * 1000)} 122 | maxDate={new Date(+new Date + 365 * 24 * 3600 * 1000)} 123 | /> 124 |
125 | ); 126 | } 127 | } 128 | 129 | ReactDOM.render(, document.getElementById('__react-content')); 130 | -------------------------------------------------------------------------------- /index.android.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./index.ios'); 2 | -------------------------------------------------------------------------------- /index.ios.js: -------------------------------------------------------------------------------- 1 | // only for react-native examples 2 | 3 | import getList from 'react-native-index-page'; 4 | 5 | getList({ 6 | demos: [ 7 | ], 8 | title: require('./package.json').name, 9 | }); 10 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // export this package's api 2 | import Picker from './src/'; 3 | export default Picker; 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rmc-calendar", 3 | "version": "1.1.4", 4 | "description": "React Mobile Calendar Component(web and react-native)", 5 | "keywords": [ 6 | "react", 7 | "react-component", 8 | "react-m-calendar", 9 | "m-calendar" 10 | ], 11 | "homepage": "https://github.com/react-component/m-calendar", 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/react-component/m-calendar.git" 15 | }, 16 | "bugs": { 17 | "url": "https://github.com/react-component/m-calendar/issues" 18 | }, 19 | "files": [ 20 | "lib", 21 | "es", 22 | "assets/*.css" 23 | ], 24 | "licenses": "MIT", 25 | "main": "./lib/index", 26 | "module": "./es/index", 27 | "config": { 28 | "port": 8021 29 | }, 30 | "scripts": { 31 | "watch-tsc": "rc-tools run watch-tsc", 32 | "compile": "rc-tools run compile --babel-runtime", 33 | "build": "rc-tools run build", 34 | "gh-pages": "rc-tools run gh-pages", 35 | "start": "rc-tools run server", 36 | "pub": "rc-tools run pub --babel-runtime", 37 | "lint": "rc-tools run lint --no-js-lint", 38 | "rn-start": "node node_modules/react-native/local-cli/cli.js start", 39 | "test": "jest", 40 | "update-snap": "jest --updateSnapshot", 41 | "coverage": "jest --coverage", 42 | "coverage:upload": "npm run coverage && cat ./coverage/lcov.info | coveralls" 43 | }, 44 | "jest": { 45 | "testMatch": [ 46 | "**/__tests__/**/*.ts?(x)", 47 | "**/?(*.)(spec|test).ts?(x)" 48 | ], 49 | "coverageDirectory": "./coverage/", 50 | "moduleFileExtensions": [ 51 | "ts", 52 | "tsx", 53 | "js" 54 | ], 55 | "collectCoverageFrom": [ 56 | "src/**/*" 57 | ], 58 | "transform": { 59 | "\\.tsx?$": "./node_modules/rc-tools/scripts/jestPreprocessor.js", 60 | "\\.jsx?$": "./node_modules/rc-tools/scripts/jestPreprocessor.js" 61 | } 62 | }, 63 | "dependencies": { 64 | "babel-runtime": "6.x", 65 | "rc-animate": "^2.4.1", 66 | "rmc-date-picker": "~6.0.0-alpha.10" 67 | }, 68 | "devDependencies": { 69 | "@types/jest": "^21.1.2", 70 | "jest": "^21.2.1", 71 | "@types/enzyme": "^2.8.11", 72 | "enzyme": "^3.1.0", 73 | "@types/enzyme-to-json": "^1.5.0", 74 | "enzyme-to-json": "^3.1.2", 75 | "enzyme-adapter-react-15": "^1.0.1", 76 | "coveralls": "^3.0.0", 77 | "@types/react": "^16.0.10", 78 | "@types/react-dom": "^16.0.1", 79 | "pre-commit": "1.x", 80 | "rc-test": "^6.0.8", 81 | "rc-tools": "^7.0.0", 82 | "react": "^15.x", 83 | "react-dom": "^15.x", 84 | "react-test-renderer": "^15.x", 85 | "stylelint-config-standard": "^17.0.0" 86 | }, 87 | "typings": "./lib/index.d.ts", 88 | "pre-commit": [ 89 | "lint" 90 | ] 91 | } 92 | -------------------------------------------------------------------------------- /src/Calendar.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Animate from 'rc-animate'; 3 | import TimePicker from './TimePicker'; 4 | 5 | import DatePicker from './DatePicker'; 6 | import ConfirmPanel from './calendar/ConfirmPanel'; 7 | import ShortcutPanel from './calendar/ShortcutPanel'; 8 | import AnimateWrapper from './calendar/AnimateWrapper'; 9 | import Header from './calendar/Header'; 10 | import { Models } from './date/DataTypes'; 11 | import PropsType from './CalendarProps'; 12 | 13 | import { mergeDateTime } from './util'; 14 | 15 | import defaultLocale from './locale/zh_CN'; 16 | 17 | export type ExtraData = Models.ExtraData; 18 | export { PropsType }; 19 | 20 | export class StateType { 21 | showTimePicker: boolean = false; 22 | timePickerTitle?: string; 23 | startDate?: Date = undefined; 24 | endDate?: Date = undefined; 25 | disConfirmBtn?: boolean = true; 26 | clientHight?: number = 0; 27 | } 28 | export default class Calendar extends React.PureComponent { 29 | public static DefaultHeader = Header; 30 | public static DefaultShortcut = ShortcutPanel; 31 | 32 | static defaultProps = { 33 | visible: false, 34 | showHeader: true, 35 | locale: defaultLocale, 36 | pickTime: false, 37 | showShortcut: false, 38 | prefixCls: 'rmc-calendar', 39 | type: 'range', 40 | defaultTimeValue: new Date(2000, 0, 1, 8), 41 | } as PropsType; 42 | 43 | constructor(props: PropsType) { 44 | super(props); 45 | 46 | this.state = new StateType; 47 | if (props.defaultValue) { 48 | const defaultValue = props.defaultValue; 49 | this.state = { 50 | ...this.state, 51 | ...this.selectDate(defaultValue[1], true, { startDate: defaultValue[0] }, props), 52 | }; 53 | } 54 | } 55 | 56 | componentWillReceiveProps(nextProps: PropsType) { 57 | if (!this.props.visible && nextProps.visible && nextProps.defaultValue) { 58 | this.shortcutSelect(nextProps.defaultValue[0], nextProps.defaultValue[1], nextProps); 59 | } 60 | } 61 | 62 | selectDate = (date: Date, useDateTime = false, oldState: { startDate?: Date, endDate?: Date } = {}, props = this.props) => { 63 | if (!date) return {} as StateType; 64 | let newState = {} as StateType; 65 | const { type, pickTime, defaultTimeValue, locale = {} as Models.Locale } = props; 66 | const newDate = pickTime && !useDateTime ? mergeDateTime(date, defaultTimeValue) : date; 67 | const { startDate, endDate } = oldState; 68 | 69 | switch (type) { 70 | case 'one': 71 | newState = { 72 | ...newState, 73 | startDate: newDate, 74 | disConfirmBtn: false, 75 | }; 76 | if (pickTime) { 77 | newState = { 78 | ...newState, 79 | timePickerTitle: locale.selectTime, 80 | showTimePicker: true, 81 | }; 82 | } 83 | break; 84 | 85 | case 'range': 86 | if (!startDate || endDate) { 87 | newState = { 88 | ...newState, 89 | startDate: newDate, 90 | endDate: undefined, 91 | disConfirmBtn: true, 92 | }; 93 | if (pickTime) { 94 | newState = { 95 | ...newState, 96 | timePickerTitle: locale.selectStartTime, 97 | showTimePicker: true, 98 | }; 99 | } 100 | } else { 101 | newState = { 102 | ...newState, 103 | timePickerTitle: +newDate >= +startDate ? locale.selectEndTime : locale.selectStartTime, 104 | disConfirmBtn: false, 105 | endDate: (pickTime && !useDateTime && +newDate >= +startDate) ? 106 | new Date(+mergeDateTime(newDate, startDate) + 3600000) : newDate, 107 | }; 108 | } 109 | break; 110 | } 111 | return newState; 112 | } 113 | 114 | onSelectedDate = (date: Date) => { 115 | const { startDate, endDate } = this.state; 116 | const { onSelect } = this.props; 117 | if (onSelect) { 118 | let value = onSelect(date, [startDate, endDate]); 119 | if (value) { 120 | this.shortcutSelect(value[0], value[1]); 121 | return; 122 | } 123 | } 124 | this.setState(this.selectDate(date, false, { startDate, endDate })); 125 | } 126 | 127 | onSelectHasDisableDate = (date: Date[]) => { 128 | this.onClear(); 129 | if (this.props.onSelectHasDisableDate) { 130 | this.props.onSelectHasDisableDate(date); 131 | } 132 | } 133 | 134 | onClose = () => { 135 | this.setState(new StateType); 136 | } 137 | 138 | onCancel = () => { 139 | this.props.onCancel && this.props.onCancel(); 140 | this.onClose(); 141 | } 142 | 143 | onConfirm = () => { 144 | const { onConfirm } = this.props; 145 | let { startDate, endDate } = this.state; 146 | if (startDate && endDate && +startDate > +endDate) { 147 | return onConfirm && onConfirm(endDate, startDate); 148 | } 149 | onConfirm && onConfirm(startDate, endDate); 150 | this.onClose(); 151 | } 152 | 153 | onTimeChange = (date: Date) => { 154 | const { startDate, endDate } = this.state; 155 | if (endDate) { 156 | this.setState({ 157 | endDate: date, 158 | }); 159 | } else if (startDate) { 160 | this.setState({ 161 | startDate: date, 162 | }); 163 | } 164 | } 165 | 166 | onClear = () => { 167 | this.setState({ 168 | startDate: undefined, 169 | endDate: undefined, 170 | showTimePicker: false, 171 | }); 172 | this.props.onClear && this.props.onClear(); 173 | } 174 | 175 | shortcutSelect = (startDate: Date, endDate: Date, props = this.props) => { 176 | this.setState({ 177 | startDate, 178 | ...this.selectDate(endDate, true, { startDate }, props), 179 | showTimePicker: false, 180 | }); 181 | } 182 | 183 | setClientHeight = (height: number) => { 184 | this.setState({ 185 | clientHight: height, 186 | }); 187 | } 188 | 189 | render() { 190 | const { 191 | type, locale = {} as Models.Locale, prefixCls, visible, pickTime, showShortcut, renderHeader, 192 | infiniteOpt, initalMonths, defaultDate, minDate, maxDate, getDateExtra, rowSize, 193 | defaultTimeValue, renderShortcut, enterDirection, timePickerPrefixCls, timePickerPickerPrefixCls, 194 | style, 195 | } = this.props; 196 | const { 197 | showTimePicker, timePickerTitle, 198 | startDate, endDate, 199 | disConfirmBtn, clientHight 200 | } = this.state; 201 | 202 | const headerProps = { 203 | locale, 204 | showClear: !!startDate, 205 | onCancel: this.onCancel, 206 | onClear: this.onClear, 207 | }; 208 | 209 | return ( 210 |
211 | 212 | 213 | 214 | 215 | 216 | 217 | { 218 | renderHeader ? renderHeader(headerProps) :
219 | } 220 | 237 | { 238 | showTimePicker && 239 | 251 | } 252 | { 253 | showShortcut && !showTimePicker && 254 | ( 255 | renderShortcut ? 256 | renderShortcut(this.shortcutSelect) : 257 | 258 | ) 259 | } 260 | { 261 | startDate && 262 | 271 | } 272 | 273 | 274 |
275 | ); 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /src/CalendarProps.ts: -------------------------------------------------------------------------------- 1 | import { Models } from './date/DataTypes'; 2 | import { PropsType as HeaderPropsType } from './calendar/Header'; 3 | 4 | export type SelectDateType = [Date, Date] | [Date]; 5 | 6 | export default interface PropsType { 7 | /** 入场方向,default: vertical,vertical: 垂直,horizontal: 水平 */ 8 | enterDirection?: 'horizontal' | 'vertical'; 9 | /** 本地化 */ 10 | locale?: Models.Locale; 11 | /** 关闭时回调 */ 12 | onCancel?: () => void; 13 | /** 清除时回调 */ 14 | onClear?: () => void; 15 | /** 确认时回调 */ 16 | onConfirm?: (startDateTime?: Date, endDateTime?: Date) => void; 17 | /** 是否选择时间,default: false */ 18 | pickTime?: boolean; 19 | /** (web only) 样式前缀,default: rmc-calendar */ 20 | prefixCls?: string; 21 | /** 替换快捷选择栏,需要设置showShortcut: true */ 22 | renderShortcut?: (select: (startDate?: Date, endDate?: Date) => void) => React.ReactNode; 23 | /** 替换标题栏 */ 24 | renderHeader?: (prop: HeaderPropsType) => React.ReactNode; 25 | /** 快捷日期选择, default: false */ 26 | showShortcut?: boolean; 27 | style?: React.CSSProperties; 28 | /** header title, default: {locale.title} */ 29 | title?: string; 30 | /** 选择类型,default: range,one: 单日,range: 日期区间 */ 31 | type?: 'one' | 'range'; 32 | /** 是否显示,default: false */ 33 | visible?: boolean; 34 | /** 默认选择值,开始时间、结束时间 */ 35 | defaultValue?: SelectDateType; 36 | 37 | // DatePicker 38 | /** 显示开始日期,default: today */ 39 | defaultDate?: Date; 40 | /** 日期扩展数据 */ 41 | getDateExtra?: (date: Date) => Models.ExtraData; 42 | /** 无限滚动优化(大范围选择),default: false */ 43 | infiniteOpt?: boolean; 44 | /** 初始化月个数,default: 6 */ 45 | initalMonths?: number; 46 | /** 最大日期 */ 47 | maxDate?: Date; 48 | /** 最小日期 */ 49 | minDate?: Date; 50 | /** 选择区间包含不可用日期 */ 51 | onSelectHasDisableDate?: (date: Date[]) => void; 52 | /** 选择日期回调,如果有返回值,选择范围将使用返回值 */ 53 | onSelect?: (date: Date, state?: [Date | undefined, Date | undefined]) => SelectDateType | void; 54 | /** 行大小,default: normal */ 55 | rowSize?: 'normal' | 'xl'; 56 | 57 | // TimePicker 58 | /** 默认时间选择值 */ 59 | defaultTimeValue?: Date; 60 | timePickerPrefixCls?: string; 61 | timePickerPickerPrefixCls?: string; 62 | } 63 | -------------------------------------------------------------------------------- /src/DatePicker.base.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Models } from './date/DataTypes'; 3 | import PropsType from './DatePickerProps'; 4 | import { formatDate, shallowEqual } from './util'; 5 | 6 | import defaultLocale from './locale/zh_CN'; 7 | 8 | export interface StateType { 9 | months: Models.MonthData[]; 10 | } 11 | export default abstract class DatePicker extends React.Component { 12 | static defaultProps = { 13 | prefixCls: 'rmc-calendar', 14 | infinite: false, 15 | infiniteOpt: false, 16 | defaultDate: new Date, 17 | initalMonths: 6, 18 | locale: defaultLocale, 19 | } as PropsType; 20 | 21 | visibleMonth: Models.MonthData[] = []; 22 | abstract genMonthComponent: (data: Models.MonthData) => React.ReactNode; 23 | 24 | constructor(props: PropsType) { 25 | super(props); 26 | 27 | this.state = { 28 | months: [], 29 | }; 30 | } 31 | 32 | shouldComponentUpdate(nextProps: PropsType, nextState: StateType, nextContext: any) { 33 | return !shallowEqual(this.props, nextProps, ['startDate', 'endDate']) || 34 | !shallowEqual(this.state, nextState) || 35 | !shallowEqual(this.context, nextContext); 36 | } 37 | 38 | componentWillReceiveProps(nextProps: PropsType) { 39 | const oldValue = this.props; 40 | const newValue = nextProps; 41 | 42 | if (oldValue.startDate !== newValue.startDate || oldValue.endDate !== newValue.endDate) { 43 | if (oldValue.startDate) { 44 | this.selectDateRange(oldValue.startDate, oldValue.endDate, true); 45 | } 46 | if (newValue.startDate) { 47 | this.selectDateRange(newValue.startDate, newValue.endDate); 48 | } 49 | } 50 | } 51 | 52 | componentWillMount() { 53 | const { initalMonths = 6, defaultDate } = this.props; 54 | for (let i = 0; i < initalMonths; i++) { 55 | this.canLoadNext() && this.genMonthData(defaultDate, i); 56 | } 57 | this.visibleMonth = [...this.state.months]; 58 | } 59 | 60 | getMonthDate(date = new Date, addMonth = 0) { 61 | const y = date.getFullYear(), m = date.getMonth(); 62 | return { 63 | firstDate: new Date(y, m + addMonth, 1), 64 | lastDate: new Date(y, m + 1 + addMonth, 0), 65 | }; 66 | } 67 | 68 | canLoadPrev() { 69 | const { minDate } = this.props; 70 | return !minDate || this.state.months.length <= 0 || +this.getMonthDate(minDate).firstDate < +this.state.months[0].firstDate; 71 | } 72 | 73 | canLoadNext() { 74 | const { maxDate } = this.props; 75 | return !maxDate || this.state.months.length <= 0 || +this.getMonthDate(maxDate).firstDate > +this.state.months[this.state.months.length - 1].firstDate; 76 | } 77 | 78 | getDateWithoutTime = (date?: Date) => { 79 | if (!date) return 0; 80 | return +new Date( 81 | date.getFullYear(), 82 | date.getMonth(), 83 | date.getDate(), 84 | ); 85 | } 86 | 87 | genWeekData = (firstDate: Date) => { 88 | const minDateTime = this.getDateWithoutTime(this.props.minDate); 89 | const maxDateTime = this.getDateWithoutTime(this.props.maxDate) || Number.POSITIVE_INFINITY; 90 | 91 | const weeks: Models.CellData[][] = []; 92 | const nextMonth = this.getMonthDate(firstDate, 1).firstDate; 93 | let currentDay = firstDate; 94 | let currentWeek: Models.CellData[] = []; 95 | weeks.push(currentWeek); 96 | 97 | let startWeekday = currentDay.getDay(); 98 | if (startWeekday > 0) { 99 | for (let i = 0; i < startWeekday; i++) { 100 | currentWeek.push({} as Models.CellData); 101 | } 102 | } 103 | while (currentDay < nextMonth) { 104 | if (currentWeek.length === 7) { 105 | currentWeek = []; 106 | weeks.push(currentWeek); 107 | } 108 | const dayOfMonth = currentDay.getDate(); 109 | const tick = +currentDay; 110 | currentWeek.push({ 111 | tick, 112 | dayOfMonth, 113 | selected: Models.SelectType.None, 114 | isFirstOfMonth: dayOfMonth === 1, 115 | isLastOfMonth: false, 116 | outOfDate: tick < minDateTime || tick > maxDateTime, 117 | }); 118 | currentDay = new Date(currentDay.getTime() + 3600 * 24 * 1000); 119 | } 120 | currentWeek[currentWeek.length - 1].isLastOfMonth = true; 121 | return weeks; 122 | } 123 | 124 | genMonthData(date?: Date, addMonth: number = 0) { 125 | if (!date) { 126 | date = addMonth >= 0 ? this.state.months[this.state.months.length - 1].firstDate : this.state.months[0].firstDate; 127 | } 128 | if (!date) { 129 | date = new Date; 130 | } 131 | const { locale } = this.props; 132 | const { firstDate, lastDate } = this.getMonthDate(date, addMonth); 133 | 134 | const weeks = this.genWeekData(firstDate); 135 | const title = formatDate(firstDate, locale ? locale.monthTitle : 'yyyy/MM', this.props.locale); 136 | const data = { 137 | title, 138 | firstDate, 139 | lastDate, 140 | weeks, 141 | } as Models.MonthData; 142 | data.component = this.genMonthComponent(data); 143 | if (addMonth >= 0) { 144 | this.state.months.push(data); 145 | } else { 146 | this.state.months.unshift(data); 147 | } 148 | const { startDate, endDate } = this.props; 149 | if (startDate) { 150 | this.selectDateRange(startDate, endDate); 151 | } 152 | return data; 153 | } 154 | 155 | inDate(date: number, tick: number) { 156 | return date <= tick && tick < date + 24 * 3600000; 157 | } 158 | 159 | selectDateRange = (startDate: Date, endDate?: Date, clear = false) => { 160 | const { getDateExtra, type, onSelectHasDisableDate } = this.props; 161 | if (type === 'one') { 162 | endDate = undefined; 163 | } 164 | const time1 = this.getDateWithoutTime(startDate), time2 = this.getDateWithoutTime(endDate); 165 | const startDateTick = !time2 || time1 < time2 ? time1 : time2; 166 | const endDateTick = time2 && time1 > time2 ? time1 : time2; 167 | 168 | const startMonthDate = this.getMonthDate(new Date(startDateTick)).firstDate; 169 | const endMonthDate = endDateTick ? new Date(endDateTick) : this.getMonthDate(new Date(startDateTick)).lastDate; 170 | 171 | let unuseable: number[] = [], needUpdate = false; 172 | this.state.months 173 | .filter(m => { 174 | return m.firstDate >= startMonthDate && m.firstDate <= endMonthDate; 175 | }) 176 | .forEach(m => { 177 | m.weeks.forEach(w => w 178 | .filter(d => { 179 | if (!endDateTick) { 180 | return d.tick && this.inDate(startDateTick, d.tick); 181 | } else { 182 | return d.tick && d.tick >= startDateTick && d.tick <= endDateTick; 183 | } 184 | }) 185 | .forEach(d => { 186 | const oldValue = d.selected; 187 | if (clear) { 188 | d.selected = Models.SelectType.None; 189 | } else { 190 | const info = getDateExtra && getDateExtra(new Date(d.tick)) || {}; 191 | if (d.outOfDate || info.disable) { 192 | unuseable.push(d.tick); 193 | } 194 | if (this.inDate(startDateTick, d.tick)) { 195 | if (type === 'one') { 196 | d.selected = Models.SelectType.Single; 197 | } else if (!endDateTick) { 198 | d.selected = Models.SelectType.Only; 199 | } else if (startDateTick !== endDateTick) { 200 | d.selected = Models.SelectType.Start; 201 | } else { 202 | d.selected = Models.SelectType.All; 203 | } 204 | } else if (this.inDate(endDateTick, d.tick)) { 205 | d.selected = Models.SelectType.End; 206 | } else { 207 | d.selected = Models.SelectType.Middle; 208 | } 209 | } 210 | needUpdate = needUpdate || d.selected !== oldValue; 211 | }) 212 | ); 213 | if (needUpdate && m.componentRef) { 214 | m.componentRef.updateWeeks(); 215 | m.componentRef.forceUpdate(); 216 | }; 217 | }); 218 | if (unuseable.length > 0) { 219 | if (onSelectHasDisableDate) { 220 | onSelectHasDisableDate(unuseable.map(tick => new Date(tick))); 221 | } else { 222 | console.warn('Unusable date. You can handle by onSelectHasDisableDate.', unuseable); 223 | } 224 | } 225 | } 226 | 227 | computeVisible = (clientHeight: number, scrollTop: number) => { 228 | let needUpdate = false; 229 | const MAX_VIEW_PORT = clientHeight * 2; 230 | const MIN_VIEW_PORT = clientHeight; 231 | 232 | // 大缓冲区外过滤规则 233 | const filterFunc = (vm: Models.MonthData) => vm.y && vm.height && (vm.y + vm.height > scrollTop - MAX_VIEW_PORT && vm.y < scrollTop + clientHeight + MAX_VIEW_PORT); 234 | 235 | if (this.props.infiniteOpt && this.visibleMonth.length > 12) { 236 | this.visibleMonth = this.visibleMonth.filter(filterFunc).sort((a, b) => +a.firstDate - +b.firstDate); 237 | } 238 | 239 | // 当小缓冲区不满时填充 240 | if (this.visibleMonth.length > 0) { 241 | const last = this.visibleMonth[this.visibleMonth.length - 1]; 242 | if (last.y !== undefined && last.height && last.y + last.height < scrollTop + clientHeight + MIN_VIEW_PORT) { 243 | const lastIndex = this.state.months.indexOf(last); 244 | for (let i = 1; i <= 2; i++) { 245 | const index = lastIndex + i; 246 | if (index < this.state.months.length && this.visibleMonth.indexOf(this.state.months[index]) < 0) { 247 | this.visibleMonth.push(this.state.months[index]); 248 | } else { 249 | this.canLoadNext() && this.genMonthData(undefined, 1); 250 | } 251 | } 252 | needUpdate = true; 253 | } 254 | 255 | const first = this.visibleMonth[0]; 256 | if (first.y !== undefined && first.height && first.y > scrollTop - MIN_VIEW_PORT) { 257 | const firstIndex = this.state.months.indexOf(first); 258 | for (let i = 1; i <= 2; i++) { 259 | const index = firstIndex - i; 260 | if (index >= 0 && this.visibleMonth.indexOf(this.state.months[index]) < 0) { 261 | this.visibleMonth.unshift(this.state.months[index]); 262 | needUpdate = true; 263 | } 264 | } 265 | } 266 | } else if (this.state.months.length > 0) { 267 | this.visibleMonth = this.state.months.filter(filterFunc); 268 | needUpdate = true; 269 | } 270 | 271 | return needUpdate; 272 | } 273 | 274 | createOnScroll = () => { 275 | let timer: any; 276 | let clientHeight = 0, scrollTop = 0; 277 | 278 | return (data: { full: number, client: number, top: number }) => { 279 | const { client, top } = data; 280 | clientHeight = client; 281 | scrollTop = top; 282 | 283 | if (timer) { 284 | return; 285 | } 286 | 287 | timer = setTimeout(() => { 288 | timer = undefined; 289 | if (this.computeVisible(clientHeight, scrollTop)) { 290 | this.forceUpdate(); 291 | } 292 | }, 64); 293 | }; 294 | } 295 | 296 | onCellClick = (day: Models.CellData) => { 297 | if (!day.tick) return; 298 | this.props.onCellClick && this.props.onCellClick(new Date(day.tick)); 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /src/DatePicker.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import PropsType from './DatePickerProps'; 3 | import Component from './DatePicker.base'; 4 | import WeekPanel from './date/WeekPanel'; 5 | import SingleMonth from './date/SingleMonth'; 6 | import { Models } from './date/DataTypes'; 7 | 8 | export { PropsType }; 9 | export default class DatePicker extends Component { 10 | 11 | panel: HTMLDivElement; 12 | transform: string = ''; 13 | 14 | genMonthComponent = (data?: Models.MonthData) => { 15 | if (!data) return; 16 | 17 | return { 24 | // FIXME?: sometimes will callback twice, and the second is null, when use preact. 25 | data.componentRef = dom || data.componentRef || undefined; 26 | data.updateLayout = () => { 27 | this.computeHeight(data, dom); 28 | }; 29 | data.updateLayout(); 30 | }} 31 | />; 32 | } 33 | 34 | computeHeight = (data: Models.MonthData, singleMonth: SingleMonth | null) => { 35 | if (singleMonth && singleMonth.wrapperDivDOM) { 36 | // preact, ref时dom有可能无height, offsetTop数据。 37 | if (!data.height && !singleMonth.wrapperDivDOM.clientHeight) { 38 | setTimeout(() => this.computeHeight(data, singleMonth), 500); 39 | return; 40 | } 41 | data.height = singleMonth.wrapperDivDOM.clientHeight || data.height || 0; 42 | data.y = singleMonth.wrapperDivDOM.offsetTop || data.y || 0; 43 | } 44 | } 45 | 46 | setLayout = (dom: HTMLDivElement) => { 47 | if (dom) { 48 | const { onLayout } = this.props; 49 | onLayout && onLayout(dom.clientHeight); 50 | 51 | const scrollHandler = this.createOnScroll(); 52 | dom.onscroll = (evt) => { 53 | scrollHandler({ 54 | client: dom.clientHeight, 55 | full: (evt.currentTarget as HTMLDivElement).clientHeight, 56 | top: (evt.currentTarget as HTMLDivElement).scrollTop, 57 | }); 58 | }; 59 | } 60 | } 61 | 62 | setPanel = (dom: HTMLDivElement) => { 63 | this.panel = dom; 64 | } 65 | 66 | // tslint:disable-next-line:member-ordering 67 | touchHandler = (() => { 68 | const initDelta = 0; 69 | let lastY = 0; 70 | let delta = initDelta; 71 | 72 | return { 73 | onTouchStart: (evt: React.TouchEvent) => { 74 | lastY = evt.touches[0].screenY; 75 | delta = initDelta; 76 | }, 77 | onTouchMove: (evt: React.TouchEvent) => { 78 | const ele = evt.currentTarget; 79 | const isReachTop = ele.scrollTop === 0; 80 | 81 | if (isReachTop) { 82 | delta = evt.touches[0].screenY - lastY; 83 | if (delta > 0) { 84 | evt.preventDefault(); 85 | if (delta > 80) { 86 | delta = 80; 87 | } 88 | } else { 89 | delta = 0; 90 | } 91 | this.setTransform(this.panel.style, `translate3d(0,${delta}px,0)`); 92 | } 93 | }, 94 | 95 | onTouchEnd: () => { 96 | this.touchHandler.onFinish(); 97 | }, 98 | 99 | onTouchCancel: () => { 100 | this.touchHandler.onFinish(); 101 | }, 102 | 103 | onFinish: () => { 104 | if (delta > 40 && this.canLoadPrev()) { 105 | this.genMonthData(this.state.months[0].firstDate, -1); 106 | 107 | this.visibleMonth = this.state.months.slice(0, this.props.initalMonths); 108 | 109 | this.state.months.forEach((m) => { 110 | m.updateLayout && m.updateLayout(); 111 | }); 112 | this.forceUpdate(); 113 | } 114 | this.setTransform(this.panel.style, `translate3d(0,0,0)`); 115 | this.setTransition(this.panel.style, '.3s'); 116 | setTimeout(() => { 117 | this.panel && this.setTransition(this.panel.style, ''); 118 | }, 300); 119 | } 120 | }; 121 | })(); 122 | 123 | setTransform(nodeStyle: CSSStyleDeclaration, value: any) { 124 | this.transform = value; 125 | nodeStyle.transform = value; 126 | nodeStyle.webkitTransform = value; 127 | } 128 | 129 | setTransition(nodeStyle: CSSStyleDeclaration, value: any) { 130 | nodeStyle.transition = value; 131 | nodeStyle.webkitTransition = value; 132 | } 133 | 134 | render() { 135 | const { prefixCls = '', locale = {} as Models.Locale } = this.props; 136 | const style: any = { 137 | transform: this.transform, 138 | }; 139 | 140 | return ( 141 |
142 | 143 |
151 |
152 | { 153 | this.canLoadPrev() &&
{locale.loadPrevMonth}
154 | } 155 |
156 | { 157 | this.state.months.map((m) => { 158 | const hidden = m.height && this.visibleMonth.indexOf(m) < 0; 159 | if (hidden) { 160 | return
; 161 | } 162 | return m.component; 163 | }) 164 | } 165 |
166 |
167 |
168 |
169 | ); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/DatePickerProps.ts: -------------------------------------------------------------------------------- 1 | import { Models } from './date/DataTypes'; 2 | 3 | export default interface PropsType { 4 | /** 默认日期,default: today */ 5 | defaultDate?: Date; 6 | /** 选择值 */ 7 | startDate?: Date; 8 | /** 选择值 */ 9 | endDate?: Date; 10 | /** 日期扩展数据 */ 11 | getDateExtra?: (date: Date) => Models.ExtraData; 12 | /** 无限滚动优化(大范围选择),default: false */ 13 | infiniteOpt?: boolean; 14 | /** 初始化月个数,default: 6 */ 15 | initalMonths?: number; 16 | /** 本地化 */ 17 | locale?: Models.Locale; 18 | /** 最大日期 */ 19 | maxDate?: Date; 20 | /** 最小日期 */ 21 | minDate?: Date; 22 | /** 日期点击回调 */ 23 | onCellClick?: (date: Date) => void; 24 | onLayout?: (clientHight: number) => void; 25 | /** 选择区间包含不可用日期 */ 26 | onSelectHasDisableDate?: (date: Date[]) => void; 27 | /** (web only) 样式前缀 */ 28 | prefixCls?: string; 29 | /** 行大小 */ 30 | rowSize?: 'normal' | 'xl'; 31 | /** 选择类型,default: range,one: 单日,range: 日期区间 */ 32 | type?: 'one' | 'range'; 33 | } 34 | -------------------------------------------------------------------------------- /src/TimePicker.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import DateTimePicker from 'rmc-date-picker'; 3 | import { Models } from './date/DataTypes'; 4 | 5 | export interface PropsType { 6 | locale: Models.Locale; 7 | prefixCls?: string; 8 | pickerPrefixCls?: string; 9 | title?: string; 10 | defaultValue?: Date; 11 | value?: Date; 12 | onValueChange?: (time: Date) => void; 13 | 14 | minDate?: Date; 15 | maxDate?: Date; 16 | clientHeight?: number; 17 | } 18 | export interface StateType { 19 | } 20 | export default class TimePicker extends React.PureComponent { 21 | static defaultProps = { 22 | minDate: new Date(0, 0, 0, 0, 0), 23 | maxDate: new Date(9999, 11, 31, 23, 59, 59), 24 | defaultValue: new Date(2000, 1, 1, 8), 25 | } as PropsType; 26 | 27 | onDateChange = (date: Date) => { 28 | const { onValueChange } = this.props; 29 | onValueChange && onValueChange(date); 30 | } 31 | 32 | getMinTime(date?: Date) { 33 | const minDate = this.props.minDate as Date; 34 | if (!date || 35 | date.getFullYear() > minDate.getFullYear() || 36 | date.getMonth() > minDate.getMonth() || 37 | date.getDate() > minDate.getDate() 38 | ) { 39 | return TimePicker.defaultProps.minDate; 40 | } 41 | return minDate; 42 | } 43 | 44 | getMaxTime(date?: Date) { 45 | const maxDate = this.props.maxDate as Date; 46 | if (!date || 47 | date.getFullYear() < maxDate.getFullYear() || 48 | date.getMonth() < maxDate.getMonth() || 49 | date.getDate() < maxDate.getDate() 50 | ) { 51 | return TimePicker.defaultProps.maxDate; 52 | } 53 | return maxDate; 54 | } 55 | 56 | render() { 57 | const { locale, title, value, defaultValue, prefixCls, pickerPrefixCls, clientHeight } = this.props; 58 | const date = value || defaultValue || undefined; 59 | const height = (clientHeight && clientHeight * 3 / 8 - 52) || Number.POSITIVE_INFINITY; 60 | 61 | return ( 62 |
63 |
{title}
64 | 164 || height < 0 ? 164 : height, 69 | overflow: 'hidden' 70 | } as React.CSSProperties} 71 | mode="time" 72 | date={date} 73 | locale={locale} 74 | minDate={this.getMinTime(date)} 75 | maxDate={this.getMaxTime(date)} 76 | onDateChange={this.onDateChange} 77 | use12Hours 78 | /> 79 |
80 | ); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/calendar/AnimateWrapper.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export interface PropsType { 4 | visible: boolean; 5 | className?: string; 6 | displayType?: string; 7 | } 8 | export default class AnimateWrapper extends React.PureComponent { 9 | static defaultProps = { 10 | className: '', 11 | displayType: 'flex', 12 | } as PropsType; 13 | 14 | render() { 15 | const { className, displayType, visible } = this.props; 16 | 17 | return
20 | {visible && this.props.children} 21 |
; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/calendar/ConfirmPanel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { formatDate } from '../util'; 3 | import { Models } from '../date/DataTypes'; 4 | 5 | export interface ConfirmPanelPropsType { 6 | type?: 'one' | 'range'; 7 | locale: Models.Locale; 8 | onlyConfirm?: boolean; 9 | disableBtn?: boolean; 10 | startDateTime?: Date; 11 | endDateTime?: Date; 12 | formatStr?: string; 13 | onConfirm: () => void; 14 | } 15 | export default class ConfirmPanel extends React.PureComponent { 16 | static defaultProps = { 17 | formatStr: 'yyyy-MM-dd hh:mm' 18 | } as ConfirmPanelPropsType; 19 | 20 | 21 | onConfirm = () => { 22 | const { onConfirm, disableBtn } = this.props; 23 | !disableBtn && onConfirm(); 24 | } 25 | 26 | formatDate(date: Date) { 27 | const { formatStr = '', locale } = this.props; 28 | return formatDate(date, formatStr, locale); 29 | } 30 | 31 | render() { 32 | const { type, locale, disableBtn } = this.props; 33 | let { startDateTime, endDateTime } = this.props; 34 | if (startDateTime && endDateTime && +startDateTime > +endDateTime) { 35 | const tmp = startDateTime; 36 | startDateTime = endDateTime; 37 | endDateTime = tmp; 38 | } 39 | 40 | const startTimeStr = startDateTime ? this.formatDate(startDateTime) : locale.noChoose; 41 | const endTimeStr = endDateTime ? this.formatDate(endDateTime) : locale.noChoose; 42 | let btnCls = disableBtn ? 'button button-disable' : 'button'; 43 | if (type === 'one') { 44 | btnCls += ' button-full'; 45 | } 46 | 47 | return ( 48 |
49 | { 50 | type === 'range' && 51 |
52 |

{locale.start}: {startTimeStr}

53 |

{locale.end}: {endTimeStr}

54 |
55 | } 56 |
57 | {locale.confirm} 58 |
59 |
60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/calendar/Header.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Models } from '../date/DataTypes'; 3 | 4 | export interface PropsType { 5 | title?: string; 6 | locale?: Models.Locale; 7 | showClear?: boolean; 8 | onCancel?: () => void; 9 | onClear?: () => void; 10 | closeIcon?: React.ReactNode; 11 | clearIcon?: React.ReactNode; 12 | } 13 | 14 | export default class Header extends React.PureComponent { 15 | static defaultProps = { 16 | closeIcon: 'X', 17 | }; 18 | 19 | render() { 20 | const { 21 | title, 22 | locale = {} as Models.Locale, 23 | onCancel, 24 | onClear, 25 | showClear, 26 | closeIcon, 27 | clearIcon, 28 | } = this.props; 29 | 30 | return ( 31 |
32 | onCancel && onCancel()}>{closeIcon} 33 | {title || locale.title} 34 | { 35 | showClear && 36 | onClear && onClear()} 38 | >{clearIcon || locale.clear} 39 | } 40 |
41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/calendar/ShortcutPanel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Models } from '../date/DataTypes'; 3 | 4 | export interface PropsType { 5 | locale: Models.Locale; 6 | onSelect: (startDate?: Date, endDate?: Date) => void; 7 | } 8 | export default class ShortcutPanel extends React.PureComponent { 9 | 10 | onClick = (type: string) => { 11 | const { onSelect } = this.props; 12 | const today = new Date; 13 | 14 | switch (type) { 15 | case 'today': 16 | onSelect( 17 | new Date(today.getFullYear(), today.getMonth(), today.getDate(), 0), 18 | new Date(today.getFullYear(), today.getMonth(), today.getDate(), 12) 19 | ); 20 | break; 21 | 22 | case 'yesterday': 23 | onSelect( 24 | new Date(today.getFullYear(), today.getMonth(), today.getDate() - 1, 0), 25 | new Date(today.getFullYear(), today.getMonth(), today.getDate() - 1, 12) 26 | ); 27 | break; 28 | 29 | case 'lastweek': 30 | onSelect( 31 | new Date(today.getFullYear(), today.getMonth(), today.getDate() - 6, 0), 32 | new Date(today.getFullYear(), today.getMonth(), today.getDate(), 12) 33 | ); 34 | break; 35 | 36 | case 'lastmonth': 37 | onSelect( 38 | new Date(today.getFullYear(), today.getMonth(), today.getDate() - 29, 0), 39 | new Date(today.getFullYear(), today.getMonth(), today.getDate(), 12) 40 | ); 41 | break; 42 | } 43 | } 44 | 45 | render() { 46 | const { locale } = this.props; 47 | 48 | return ( 49 |
50 |
this.onClick('today')}>{locale.today}
51 |
this.onClick('yesterday')}>{locale.yesterday}
52 |
this.onClick('lastweek')}>{locale.lastWeek}
53 |
this.onClick('lastmonth')}>{locale.lastMonth}
54 |
55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/date/DataTypes.ts: -------------------------------------------------------------------------------- 1 | import SingleMonth from './SingleMonth'; 2 | 3 | export namespace Models { 4 | export enum SelectType { 5 | None, 6 | /** 单选 */ 7 | Single, 8 | /** 起/止 */ 9 | All, 10 | /** 区间仅选择了 起 */ 11 | Only, 12 | /** 区间起 */ 13 | Start, 14 | /** 区间中 */ 15 | Middle, 16 | /** 区间止 */ 17 | End, 18 | } 19 | 20 | export interface CellData { 21 | tick: number; 22 | dayOfMonth: number; 23 | selected: SelectType; 24 | isFirstOfMonth: boolean; 25 | isLastOfMonth: boolean; 26 | outOfDate: boolean; 27 | } 28 | 29 | export interface ExtraData { 30 | /** 扩展信息 */ 31 | info?: string; 32 | /** 是否禁止选择 */ 33 | disable?: boolean; 34 | /** (web only) 附加cell样式 className */ 35 | cellCls?: any; 36 | cellRender?: (date: Date) => React.ReactNode; 37 | } 38 | 39 | export interface MonthData { 40 | title: string; 41 | firstDate: Date; 42 | lastDate: Date; 43 | weeks: Models.CellData[][]; 44 | component?: React.ReactNode; 45 | height?: number; 46 | y?: number; 47 | updateLayout?: Function; 48 | componentRef?: SingleMonth; 49 | } 50 | 51 | export interface Locale { 52 | title: string; 53 | today: string; 54 | month: string; 55 | year: string; 56 | am: string; 57 | pm: string; 58 | dateFormat: string; 59 | dateTimeFormat: string; 60 | noChoose: string; 61 | week: string[]; 62 | clear: string; 63 | selectTime: string; 64 | selectStartTime: string; 65 | selectEndTime: string; 66 | start: string; 67 | end: string; 68 | begin: string; 69 | over: string; 70 | begin_over: string; 71 | confirm: string; 72 | monthTitle: string; 73 | loadPrevMonth: string; 74 | yesterday: string; 75 | lastWeek: string; 76 | lastMonth: string; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/date/SingleMonth.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Models } from './DataTypes'; 3 | 4 | export interface PropsType { 5 | locale: Models.Locale; 6 | monthData: Models.MonthData; 7 | rowSize?: 'normal' | 'xl'; 8 | getDateExtra?: (date: Date) => Models.ExtraData; 9 | onCellClick?: (data: Models.CellData, monthData: Models.MonthData) => void; 10 | } 11 | export default class SingleMonth extends React.PureComponent { 14 | static defaultProps = { 15 | rowSize: 'normal', 16 | } as PropsType; 17 | 18 | public wrapperDivDOM: HTMLDivElement | null; 19 | 20 | constructor(props: PropsType) { 21 | super(props); 22 | 23 | this.state = { 24 | weekComponents: [], 25 | }; 26 | } 27 | 28 | componentWillMount() { 29 | this.props.monthData.weeks.forEach((week, index) => { 30 | this.genWeek(week, index); 31 | }); 32 | } 33 | 34 | genWeek = (weeksData: Models.CellData[], index: number) => { 35 | const { getDateExtra, monthData, onCellClick, locale, rowSize } = this.props; 36 | let rowCls = 'row'; 37 | if (rowSize === 'xl') { 38 | rowCls += ' row-xl'; 39 | } 40 | this.state.weekComponents[index] = ( 41 |
42 | { 43 | weeksData.map((day, dayOfWeek) => { 44 | const extra = (getDateExtra && getDateExtra(new Date(day.tick))) || {}; 45 | let info = extra.info; 46 | const disable = extra.disable || day.outOfDate; 47 | 48 | let cls = 'date'; 49 | let lCls = 'left'; 50 | let rCls = 'right'; 51 | let infoCls = 'info'; 52 | 53 | if (dayOfWeek === 0 || dayOfWeek === 6) { 54 | cls += ' grey'; 55 | } 56 | 57 | if (disable) { 58 | cls += ' disable'; 59 | } else if (info) { 60 | cls += ' important'; 61 | } 62 | 63 | if (day.selected) { 64 | cls += ' date-selected'; 65 | let styleType = day.selected; 66 | switch (styleType) { 67 | case Models.SelectType.Only: 68 | info = locale.begin; 69 | infoCls += ' date-selected'; 70 | break; 71 | case Models.SelectType.All: 72 | info = locale.begin_over; 73 | infoCls += ' date-selected'; 74 | break; 75 | 76 | case Models.SelectType.Start: 77 | info = locale.begin; 78 | infoCls += ' date-selected'; 79 | if (dayOfWeek === 6 || day.isLastOfMonth) { 80 | styleType = Models.SelectType.All; 81 | } 82 | break; 83 | case Models.SelectType.Middle: 84 | if (dayOfWeek === 0 || day.isFirstOfMonth) { 85 | if (day.isLastOfMonth || dayOfWeek === 6) { 86 | styleType = Models.SelectType.All; 87 | } else { 88 | styleType = Models.SelectType.Start; 89 | } 90 | } else if (dayOfWeek === 6 || day.isLastOfMonth) { 91 | styleType = Models.SelectType.End; 92 | } 93 | break; 94 | case Models.SelectType.End: 95 | info = locale.over; 96 | infoCls += ' date-selected'; 97 | if (dayOfWeek === 0 || day.isFirstOfMonth) { 98 | styleType = Models.SelectType.All; 99 | } 100 | break; 101 | } 102 | 103 | switch (styleType) { 104 | case Models.SelectType.Single: 105 | case Models.SelectType.Only: 106 | case Models.SelectType.All: 107 | cls += ' selected-single'; 108 | break; 109 | case Models.SelectType.Start: 110 | cls += ' selected-start'; 111 | rCls += ' date-selected'; 112 | break; 113 | case Models.SelectType.Middle: 114 | cls += ' selected-middle'; 115 | lCls += ' date-selected'; 116 | rCls += ' date-selected'; 117 | break; 118 | case Models.SelectType.End: 119 | cls += ' selected-end'; 120 | lCls += ' date-selected'; 121 | break; 122 | } 123 | } 124 | 125 | const defaultContent = [ 126 |
127 | 128 |
129 | {day.dayOfMonth} 130 |
131 | 132 |
133 | , 134 |
{info}
135 | ]; 136 | 137 | return ( 138 |
{ 139 | !disable && onCellClick && onCellClick(day, monthData); 140 | }}> 141 | { 142 | extra.cellRender ? 143 | extra.cellRender(new Date(day.tick)) 144 | : 145 | defaultContent 146 | } 147 |
148 | ); 149 | }) 150 | } 151 |
152 | ); 153 | } 154 | 155 | updateWeeks = (monthData?: Models.MonthData) => { 156 | (monthData || this.props.monthData).weeks.forEach((week, index) => { 157 | this.genWeek(week, index); 158 | }); 159 | } 160 | 161 | componentWillReceiveProps(nextProps: PropsType) { 162 | if (this.props.monthData !== nextProps.monthData) { 163 | this.updateWeeks(nextProps.monthData); 164 | } 165 | } 166 | 167 | setWarpper = (dom: HTMLDivElement) => { 168 | this.wrapperDivDOM = dom; 169 | } 170 | 171 | render() { 172 | const { title } = this.props.monthData; 173 | const { weekComponents } = this.state; 174 | 175 | return ( 176 |
177 |
178 | {title} 179 |
180 |
181 | {weekComponents} 182 |
183 |
184 | ); 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/date/WeekPanel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Models } from './DataTypes'; 3 | 4 | export interface PropsType { 5 | locale: Models.Locale; 6 | } 7 | 8 | export default class WeekPanel extends React.PureComponent { 9 | constructor(props: PropsType) { 10 | super(props); 11 | } 12 | 13 | render() { 14 | const { locale } = this.props; 15 | const { week } = locale; 16 | return ( 17 |
18 |
{week[0]}
19 |
{week[1]}
20 |
{week[2]}
21 |
{week[3]}
22 |
{week[4]}
23 |
{week[5]}
24 |
{week[6]}
25 |
26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { Models } from './date/DataTypes'; 2 | 3 | export { default as Calendar, ExtraData, PropsType as CalendarPropsType } from './Calendar'; 4 | export { default as DatePicker, PropsType as DatePickerPropsType } from './DatePicker'; 5 | 6 | import zhCN from './locale/zh_CN'; 7 | import enUS from './locale/en_US'; 8 | const Locale = { zhCN, enUS }; 9 | 10 | type LocaleType = Models.Locale; 11 | export { Locale, LocaleType }; 12 | -------------------------------------------------------------------------------- /src/locale/en_US.ts: -------------------------------------------------------------------------------- 1 | import { Models } from '../date/DataTypes'; 2 | 3 | const locale: Models.Locale = { 4 | title: 'Calendar', 5 | today: 'Today', 6 | month: 'Month', 7 | year: 'Year', 8 | am: 'AM', 9 | pm: 'PM', 10 | dateTimeFormat: 'MM/dd/yyyy w hh:mm', 11 | dateFormat: 'yyyy/MM/dd w', 12 | noChoose: 'No Choose', 13 | week: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fir', 'Sat'], 14 | clear: 'Clear', 15 | selectTime: 'Select Time', 16 | selectStartTime: 'Select Start Time', 17 | selectEndTime: 'Select End Time', 18 | start: 'Start', 19 | end: 'End', 20 | begin: 'Start', 21 | over: 'End', 22 | begin_over: 'S/E', 23 | confirm: 'Confirm', 24 | monthTitle: 'yyyy/MM', 25 | loadPrevMonth: 'Load Prev Month', 26 | yesterday: 'Yesterday', 27 | lastWeek: 'Last Week', 28 | lastMonth: 'Last Month', 29 | }; 30 | export default locale; 31 | -------------------------------------------------------------------------------- /src/locale/pt_BR.ts: -------------------------------------------------------------------------------- 1 | import { Models } from '../date/DataTypes'; 2 | 3 | const locale: Models.Locale = { 4 | title: 'Calendário', 5 | today: 'Hoje', 6 | month: 'Mês', 7 | year: 'Ano', 8 | am: 'AM', 9 | pm: 'PM', 10 | dateTimeFormat: 'dd/MM/yyyy w hh:mm', 11 | dateFormat: 'dd/MM/yyyy w', 12 | noChoose: 'Não Escolhido', 13 | week: ['dom', 'seg', 'ter', 'qua', 'qui', 'sex', 'sab'], 14 | clear: 'Limpar', 15 | selectTime: 'Selecionar Tempo', 16 | selectStartTime: 'Selecione Tempo Inicial', 17 | selectEndTime: 'Selecione Tempo Final', 18 | start: 'Início', 19 | end: 'Fim', 20 | begin: 'Início', 21 | over: 'Fim', 22 | begin_over: 'S/E', 23 | confirm: 'Confirmar', 24 | monthTitle: 'MM/yyyy', 25 | loadPrevMonth: 'Carregar Mês Anterior', 26 | yesterday: 'Ontem', 27 | lastWeek: 'Última Semana', 28 | lastMonth: 'Último Mês', 29 | }; 30 | export default locale; 31 | -------------------------------------------------------------------------------- /src/locale/zh_CN.ts: -------------------------------------------------------------------------------- 1 | import { Models } from '../date/DataTypes'; 2 | 3 | const locale: Models.Locale = { 4 | title: '日期选择', 5 | today: '今天', 6 | month: '月', 7 | year: '年', 8 | am: '上午', 9 | pm: '下午', 10 | dateTimeFormat: 'yyyy年MM月dd日 星期w hh:mm', 11 | dateFormat: 'yyyy年MM月dd日 星期w', 12 | noChoose: '未选择', 13 | week: ['日', '一', '二', '三', '四', '五', '六'], 14 | clear: '清除', 15 | selectTime: '选择时间', 16 | selectStartTime: '选择开始时间', 17 | selectEndTime: '选择结束时间', 18 | start: '开始', 19 | end: '结束', 20 | begin: '起', 21 | over: '止', 22 | begin_over: '起/止', 23 | confirm: '确认', 24 | monthTitle: 'yyyy年MM月', 25 | loadPrevMonth: '加载上一个月', 26 | yesterday: '昨天', 27 | lastWeek: '近一周', 28 | lastMonth: '近一个月', 29 | }; 30 | export default locale; 31 | -------------------------------------------------------------------------------- /src/util/index.ts: -------------------------------------------------------------------------------- 1 | import { Models } from '../date/DataTypes'; 2 | 3 | export const mergeDateTime = (date?: Date, time?: Date) => { 4 | date = date || new Date; 5 | if (!time) return date; 6 | return new Date( 7 | date.getFullYear(), 8 | date.getMonth(), 9 | date.getDate(), 10 | time.getHours(), 11 | time.getMinutes(), 12 | time.getSeconds() 13 | ); 14 | }; 15 | 16 | export const formatDate = (date: Date, format: string, locale?: Models.Locale) => { 17 | const week = locale && locale.week; 18 | 19 | let o: { [key: string]: any } = { 20 | 'M+': date.getMonth() + 1, 21 | 'd+': date.getDate(), 22 | 'h+': date.getHours(), 23 | 'm+': date.getMinutes(), 24 | 's+': date.getSeconds(), 25 | 'q+': Math.floor((date.getMonth() + 3) / 3), 26 | 'w+': week && week[date.getDay()], 27 | 'S': date.getMilliseconds(), 28 | }; 29 | if (/(y+)/.test(format)) format = format.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length)); 30 | for (let k in o) { 31 | if (new RegExp('(' + k + ')').test(format)) { 32 | format = format.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length)); 33 | } 34 | } 35 | return format; 36 | }; 37 | 38 | const hasOwnProperty = Object.prototype.hasOwnProperty; 39 | function is(x: any, y: any): boolean { 40 | if (x === y) { 41 | return x !== 0 || y !== 0 || 1 / x === 1 / y; 42 | } else { 43 | return x !== x && y !== y; 44 | } 45 | } 46 | 47 | export function shallowEqual(objA: any, objB: any, exclude: string[] = []): boolean { 48 | if (is(objA, objB)) { 49 | return true; 50 | } 51 | 52 | if (typeof objA !== 'object' || objA === null || 53 | typeof objB !== 'object' || objB === null) { 54 | return false; 55 | } 56 | 57 | const keysA = Object.keys(objA); 58 | const keysB = Object.keys(objB); 59 | 60 | if (keysA.length !== keysB.length) { 61 | return false; 62 | } 63 | 64 | for (let i = 0; i < keysA.length; i++) { 65 | if (exclude.indexOf(keysA[i]) >= 0) continue; 66 | 67 | if ( 68 | !hasOwnProperty.call(objB, keysA[i]) || 69 | !is(objA[keysA[i]], objB[keysA[i]]) 70 | ) { 71 | return false; 72 | } 73 | } 74 | 75 | return true; 76 | } 77 | -------------------------------------------------------------------------------- /tests/Calendar.spec.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Adapter from 'enzyme-adapter-react-15'; 3 | import { default as Enzyme, render } from 'enzyme'; 4 | import { renderToJson } from 'enzyme-to-json'; 5 | import { Calendar, Locale } from '../src'; 6 | 7 | Enzyme.configure({ adapter: new Adapter() }); 8 | 9 | describe('Calendar', () => { 10 | it('base.', () => { 11 | const wrapper = render( 12 | 15 | ); 16 | expect(renderToJson(wrapper)).toMatchSnapshot(); 17 | }); 18 | 19 | it('show shortcut.', () => { 20 | const wrapper = render( 21 | 26 | ); 27 | expect(renderToJson(wrapper)).toMatchSnapshot(); 28 | }); 29 | }); 30 | 31 | describe('Calendar english.', () => { 32 | it('renders correctly', () => { 33 | const wrapper = render( 34 | 39 | ); 40 | expect(renderToJson(wrapper)).toMatchSnapshot(); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /tests/DatePicker.spec.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Adapter from 'enzyme-adapter-react-15'; 3 | import { default as Enzyme, render } from 'enzyme'; 4 | import { renderToJson } from 'enzyme-to-json'; 5 | import { DatePicker } from '../src'; 6 | 7 | Enzyme.configure({ adapter: new Adapter() }); 8 | 9 | describe('DatePicker', () => { 10 | it('renders correctly', () => { 11 | const wrapper = render( 12 | 14 | ); 15 | expect(renderToJson(wrapper)).toMatchSnapshot(); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /tests/__snapshots__/DatePicker.spec.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`DatePicker renders correctly 1`] = ` 4 |
7 |
10 |
13 | 日 14 |
15 |
18 | 一 19 |
20 |
23 | 二 24 |
25 |
28 | 三 29 |
30 |
33 | 四 34 |
35 |
38 | 五 39 |
40 |
43 | 六 44 |
45 |
46 |
50 |
53 |
56 | 加载上一个月 57 |
58 |
61 |
64 |
67 | 2018年06月 68 |
69 |
72 |
75 |
78 |
81 | 84 |
87 | 90 |
91 |
94 |
95 |
98 |
101 | 104 |
107 | 110 |
111 |
114 |
115 |
118 |
121 | 124 |
127 | 130 |
131 |
134 |
135 |
138 |
141 | 144 |
147 | 150 |
151 |
154 |
155 |
158 |
161 | 164 |
167 | 170 |
171 |
174 |
175 |
178 |
181 | 184 |
187 | 1 188 |
189 | 192 |
193 |
196 |
197 |
200 |
203 | 206 |
209 | 2 210 |
211 | 214 |
215 |
218 |
219 |
220 |
223 |
226 |
229 | 232 |
235 | 3 236 |
237 | 240 |
241 |
244 |
245 |
248 |
251 | 254 |
257 | 4 258 |
259 | 262 |
263 |
266 |
267 |
270 |
273 | 276 |
279 | 5 280 |
281 | 284 |
285 |
288 |
289 |
292 |
295 | 298 |
301 | 6 302 |
303 | 306 |
307 |
310 |
311 |
314 |
317 | 320 |
323 | 7 324 |
325 | 328 |
329 |
332 |
333 |
336 |
339 | 342 |
345 | 8 346 |
347 | 350 |
351 |
354 |
355 |
358 |
361 | 364 |
367 | 9 368 |
369 | 372 |
373 |
376 |
377 |
378 |
381 |
384 |
387 | 390 |
393 | 10 394 |
395 | 398 |
399 |
402 |
403 |
406 |
409 | 412 |
415 | 11 416 |
417 | 420 |
421 |
424 |
425 |
428 |
431 | 434 |
437 | 12 438 |
439 | 442 |
443 |
446 |
447 |
450 |
453 | 456 |
459 | 13 460 |
461 | 464 |
465 |
468 |
469 |
472 |
475 | 478 |
481 | 14 482 |
483 | 486 |
487 |
490 |
491 |
494 |
497 | 500 |
503 | 15 504 |
505 | 508 |
509 |
512 |
513 |
516 |
519 | 522 |
525 | 16 526 |
527 | 530 |
531 |
534 |
535 |
536 |
539 |
542 |
545 | 548 |
551 | 17 552 |
553 | 556 |
557 |
560 |
561 |
564 |
567 | 570 |
573 | 18 574 |
575 | 578 |
579 |
582 |
583 |
586 |
589 | 592 |
595 | 19 596 |
597 | 600 |
601 |
604 |
605 |
608 |
611 | 614 |
617 | 20 618 |
619 | 622 |
623 |
626 |
627 |
630 |
633 | 636 |
639 | 21 640 |
641 | 644 |
645 |
648 |
649 |
652 |
655 | 658 |
661 | 22 662 |
663 | 666 |
667 |
670 |
671 |
674 |
677 | 680 |
683 | 23 684 |
685 | 688 |
689 |
692 |
693 |
694 |
697 |
700 |
703 | 706 |
709 | 24 710 |
711 | 714 |
715 |
718 |
719 |
722 |
725 | 728 |
731 | 25 732 |
733 | 736 |
737 |
740 |
741 |
744 |
747 | 750 |
753 | 26 754 |
755 | 758 |
759 |
762 |
763 |
766 |
769 | 772 |
775 | 27 776 |
777 | 780 |
781 |
784 |
785 |
788 |
791 | 794 |
797 | 28 798 |
799 | 802 |
803 |
806 |
807 |
810 |
813 | 816 |
819 | 29 820 |
821 | 824 |
825 |
828 |
829 |
832 |
835 | 838 |
841 | 30 842 |
843 | 846 |
847 |
850 |
851 |
852 |
853 |
854 |
857 |
860 | 2018年07月 861 |
862 |
865 |
868 |
871 |
874 | 877 |
880 | 1 881 |
882 | 885 |
886 |
889 |
890 |
893 |
896 | 899 |
902 | 2 903 |
904 | 907 |
908 |
911 |
912 |
915 |
918 | 921 |
924 | 3 925 |
926 | 929 |
930 |
933 |
934 |
937 |
940 | 943 |
946 | 4 947 |
948 | 951 |
952 |
955 |
956 |
959 |
962 | 965 |
968 | 5 969 |
970 | 973 |
974 |
977 |
978 |
981 |
984 | 987 |
990 | 6 991 |
992 | 995 |
996 |
999 |
1000 |
1003 |
1006 | 1009 |
1012 | 7 1013 |
1014 | 1017 |
1018 |
1021 |
1022 |
1023 |
1026 |
1029 |
1032 | 1035 |
1038 | 8 1039 |
1040 | 1043 |
1044 |
1047 |
1048 |
1051 |
1054 | 1057 |
1060 | 9 1061 |
1062 | 1065 |
1066 |
1069 |
1070 |
1073 |
1076 | 1079 |
1082 | 10 1083 |
1084 | 1087 |
1088 |
1091 |
1092 |
1095 |
1098 | 1101 |
1104 | 11 1105 |
1106 | 1109 |
1110 |
1113 |
1114 |
1117 |
1120 | 1123 |
1126 | 12 1127 |
1128 | 1131 |
1132 |
1135 |
1136 |
1139 |
1142 | 1145 |
1148 | 13 1149 |
1150 | 1153 |
1154 |
1157 |
1158 |
1161 |
1164 | 1167 |
1170 | 14 1171 |
1172 | 1175 |
1176 |
1179 |
1180 |
1181 |
1184 |
1187 |
1190 | 1193 |
1196 | 15 1197 |
1198 | 1201 |
1202 |
1205 |
1206 |
1209 |
1212 | 1215 |
1218 | 16 1219 |
1220 | 1223 |
1224 |
1227 |
1228 |
1231 |
1234 | 1237 |
1240 | 17 1241 |
1242 | 1245 |
1246 |
1249 |
1250 |
1253 |
1256 | 1259 |
1262 | 18 1263 |
1264 | 1267 |
1268 |
1271 |
1272 |
1275 |
1278 | 1281 |
1284 | 19 1285 |
1286 | 1289 |
1290 |
1293 |
1294 |
1297 |
1300 | 1303 |
1306 | 20 1307 |
1308 | 1311 |
1312 |
1315 |
1316 |
1319 |
1322 | 1325 |
1328 | 21 1329 |
1330 | 1333 |
1334 |
1337 |
1338 |
1339 |
1342 |
1345 |
1348 | 1351 |
1354 | 22 1355 |
1356 | 1359 |
1360 |
1363 |
1364 |
1367 |
1370 | 1373 |
1376 | 23 1377 |
1378 | 1381 |
1382 |
1385 |
1386 |
1389 |
1392 | 1395 |
1398 | 24 1399 |
1400 | 1403 |
1404 |
1407 |
1408 |
1411 |
1414 | 1417 |
1420 | 25 1421 |
1422 | 1425 |
1426 |
1429 |
1430 |
1433 |
1436 | 1439 |
1442 | 26 1443 |
1444 | 1447 |
1448 |
1451 |
1452 |
1455 |
1458 | 1461 |
1464 | 27 1465 |
1466 | 1469 |
1470 |
1473 |
1474 |
1477 |
1480 | 1483 |
1486 | 28 1487 |
1488 | 1491 |
1492 |
1495 |
1496 |
1497 |
1500 |
1503 |
1506 | 1509 |
1512 | 29 1513 |
1514 | 1517 |
1518 |
1521 |
1522 |
1525 |
1528 | 1531 |
1534 | 30 1535 |
1536 | 1539 |
1540 |
1543 |
1544 |
1547 |
1550 | 1553 |
1556 | 31 1557 |
1558 | 1561 |
1562 |
1565 |
1566 |
1567 |
1568 |
1569 |
1572 |
1575 | 2018年08月 1576 |
1577 |
1580 |
1583 |
1586 |
1589 | 1592 |
1595 | 1598 |
1599 |
1602 |
1603 |
1606 |
1609 | 1612 |
1615 | 1618 |
1619 |
1622 |
1623 |
1626 |
1629 | 1632 |
1635 | 1638 |
1639 |
1642 |
1643 |
1646 |
1649 | 1652 |
1655 | 1 1656 |
1657 | 1660 |
1661 |
1664 |
1665 |
1668 |
1671 | 1674 |
1677 | 2 1678 |
1679 | 1682 |
1683 |
1686 |
1687 |
1690 |
1693 | 1696 |
1699 | 3 1700 |
1701 | 1704 |
1705 |
1708 |
1709 |
1712 |
1715 | 1718 |
1721 | 4 1722 |
1723 | 1726 |
1727 |
1730 |
1731 |
1732 |
1735 |
1738 |
1741 | 1744 |
1747 | 5 1748 |
1749 | 1752 |
1753 |
1756 |
1757 |
1760 |
1763 | 1766 |
1769 | 6 1770 |
1771 | 1774 |
1775 |
1778 |
1779 |
1782 |
1785 | 1788 |
1791 | 7 1792 |
1793 | 1796 |
1797 |
1800 |
1801 |
1804 |
1807 | 1810 |
1813 | 8 1814 |
1815 | 1818 |
1819 |
1822 |
1823 |
1826 |
1829 | 1832 |
1835 | 9 1836 |
1837 | 1840 |
1841 |
1844 |
1845 |
1848 |
1851 | 1854 |
1857 | 10 1858 |
1859 | 1862 |
1863 |
1866 |
1867 |
1870 |
1873 | 1876 |
1879 | 11 1880 |
1881 | 1884 |
1885 |
1888 |
1889 |
1890 |
1893 |
1896 |
1899 | 1902 |
1905 | 12 1906 |
1907 | 1910 |
1911 |
1914 |
1915 |
1918 |
1921 | 1924 |
1927 | 13 1928 |
1929 | 1932 |
1933 |
1936 |
1937 |
1940 |
1943 | 1946 |
1949 | 14 1950 |
1951 | 1954 |
1955 |
1958 |
1959 |
1962 |
1965 | 1968 |
1971 | 15 1972 |
1973 | 1976 |
1977 |
1980 |
1981 |
1984 |
1987 | 1990 |
1993 | 16 1994 |
1995 | 1998 |
1999 |
2002 |
2003 |
2006 |
2009 | 2012 |
2015 | 17 2016 |
2017 | 2020 |
2021 |
2024 |
2025 |
2028 |
2031 | 2034 |
2037 | 18 2038 |
2039 | 2042 |
2043 |
2046 |
2047 |
2048 |
2051 |
2054 |
2057 | 2060 |
2063 | 19 2064 |
2065 | 2068 |
2069 |
2072 |
2073 |
2076 |
2079 | 2082 |
2085 | 20 2086 |
2087 | 2090 |
2091 |
2094 |
2095 |
2098 |
2101 | 2104 |
2107 | 21 2108 |
2109 | 2112 |
2113 |
2116 |
2117 |
2120 |
2123 | 2126 |
2129 | 22 2130 |
2131 | 2134 |
2135 |
2138 |
2139 |
2142 |
2145 | 2148 |
2151 | 23 2152 |
2153 | 2156 |
2157 |
2160 |
2161 |
2164 |
2167 | 2170 |
2173 | 24 2174 |
2175 | 2178 |
2179 |
2182 |
2183 |
2186 |
2189 | 2192 |
2195 | 25 2196 |
2197 | 2200 |
2201 |
2204 |
2205 |
2206 |
2209 |
2212 |
2215 | 2218 |
2221 | 26 2222 |
2223 | 2226 |
2227 |
2230 |
2231 |
2234 |
2237 | 2240 |
2243 | 27 2244 |
2245 | 2248 |
2249 |
2252 |
2253 |
2256 |
2259 | 2262 |
2265 | 28 2266 |
2267 | 2270 |
2271 |
2274 |
2275 |
2278 |
2281 | 2284 |
2287 | 29 2288 |
2289 | 2292 |
2293 |
2296 |
2297 |
2300 |
2303 | 2306 |
2309 | 30 2310 |
2311 | 2314 |
2315 |
2318 |
2319 |
2322 |
2325 | 2328 |
2331 | 31 2332 |
2333 | 2336 |
2337 |
2340 |
2341 |
2342 |
2343 |
2344 |
2347 |
2350 | 2018年09月 2351 |
2352 |
2355 |
2358 |
2361 |
2364 | 2367 |
2370 | 2373 |
2374 |
2377 |
2378 |
2381 |
2384 | 2387 |
2390 | 2393 |
2394 |
2397 |
2398 |
2401 |
2404 | 2407 |
2410 | 2413 |
2414 |
2417 |
2418 |
2421 |
2424 | 2427 |
2430 | 2433 |
2434 |
2437 |
2438 |
2441 |
2444 | 2447 |
2450 | 2453 |
2454 |
2457 |
2458 |
2461 |
2464 | 2467 |
2470 | 2473 |
2474 |
2477 |
2478 |
2481 |
2484 | 2487 |
2490 | 1 2491 |
2492 | 2495 |
2496 |
2499 |
2500 |
2501 |
2504 |
2507 |
2510 | 2513 |
2516 | 2 2517 |
2518 | 2521 |
2522 |
2525 |
2526 |
2529 |
2532 | 2535 |
2538 | 3 2539 |
2540 | 2543 |
2544 |
2547 |
2548 |
2551 |
2554 | 2557 |
2560 | 4 2561 |
2562 | 2565 |
2566 |
2569 |
2570 |
2573 |
2576 | 2579 |
2582 | 5 2583 |
2584 | 2587 |
2588 |
2591 |
2592 |
2595 |
2598 | 2601 |
2604 | 6 2605 |
2606 | 2609 |
2610 |
2613 |
2614 |
2617 |
2620 | 2623 |
2626 | 7 2627 |
2628 | 2631 |
2632 |
2635 |
2636 |
2639 |
2642 | 2645 |
2648 | 8 2649 |
2650 | 2653 |
2654 |
2657 |
2658 |
2659 |
2662 |
2665 |
2668 | 2671 |
2674 | 9 2675 |
2676 | 2679 |
2680 |
2683 |
2684 |
2687 |
2690 | 2693 |
2696 | 10 2697 |
2698 | 2701 |
2702 |
2705 |
2706 |
2709 |
2712 | 2715 |
2718 | 11 2719 |
2720 | 2723 |
2724 |
2727 |
2728 |
2731 |
2734 | 2737 |
2740 | 12 2741 |
2742 | 2745 |
2746 |
2749 |
2750 |
2753 |
2756 | 2759 |
2762 | 13 2763 |
2764 | 2767 |
2768 |
2771 |
2772 |
2775 |
2778 | 2781 |
2784 | 14 2785 |
2786 | 2789 |
2790 |
2793 |
2794 |
2797 |
2800 | 2803 |
2806 | 15 2807 |
2808 | 2811 |
2812 |
2815 |
2816 |
2817 |
2820 |
2823 |
2826 | 2829 |
2832 | 16 2833 |
2834 | 2837 |
2838 |
2841 |
2842 |
2845 |
2848 | 2851 |
2854 | 17 2855 |
2856 | 2859 |
2860 |
2863 |
2864 |
2867 |
2870 | 2873 |
2876 | 18 2877 |
2878 | 2881 |
2882 |
2885 |
2886 |
2889 |
2892 | 2895 |
2898 | 19 2899 |
2900 | 2903 |
2904 |
2907 |
2908 |
2911 |
2914 | 2917 |
2920 | 20 2921 |
2922 | 2925 |
2926 |
2929 |
2930 |
2933 |
2936 | 2939 |
2942 | 21 2943 |
2944 | 2947 |
2948 |
2951 |
2952 |
2955 |
2958 | 2961 |
2964 | 22 2965 |
2966 | 2969 |
2970 |
2973 |
2974 |
2975 |
2978 |
2981 |
2984 | 2987 |
2990 | 23 2991 |
2992 | 2995 |
2996 |
2999 |
3000 |
3003 |
3006 | 3009 |
3012 | 24 3013 |
3014 | 3017 |
3018 |
3021 |
3022 |
3025 |
3028 | 3031 |
3034 | 25 3035 |
3036 | 3039 |
3040 |
3043 |
3044 |
3047 |
3050 | 3053 |
3056 | 26 3057 |
3058 | 3061 |
3062 |
3065 |
3066 |
3069 |
3072 | 3075 |
3078 | 27 3079 |
3080 | 3083 |
3084 |
3087 |
3088 |
3091 |
3094 | 3097 |
3100 | 28 3101 |
3102 | 3105 |
3106 |
3109 |
3110 |
3113 |
3116 | 3119 |
3122 | 29 3123 |
3124 | 3127 |
3128 |
3131 |
3132 |
3133 |
3136 |
3139 |
3142 | 3145 |
3148 | 30 3149 |
3150 | 3153 |
3154 |
3157 |
3158 |
3159 |
3160 |
3161 |
3164 |
3167 | 2018年10月 3168 |
3169 |
3172 |
3175 |
3178 |
3181 | 3184 |
3187 | 3190 |
3191 |
3194 |
3195 |
3198 |
3201 | 3204 |
3207 | 1 3208 |
3209 | 3212 |
3213 |
3216 |
3217 |
3220 |
3223 | 3226 |
3229 | 2 3230 |
3231 | 3234 |
3235 |
3238 |
3239 |
3242 |
3245 | 3248 |
3251 | 3 3252 |
3253 | 3256 |
3257 |
3260 |
3261 |
3264 |
3267 | 3270 |
3273 | 4 3274 |
3275 | 3278 |
3279 |
3282 |
3283 |
3286 |
3289 | 3292 |
3295 | 5 3296 |
3297 | 3300 |
3301 |
3304 |
3305 |
3308 |
3311 | 3314 |
3317 | 6 3318 |
3319 | 3322 |
3323 |
3326 |
3327 |
3328 |
3331 |
3334 |
3337 | 3340 |
3343 | 7 3344 |
3345 | 3348 |
3349 |
3352 |
3353 |
3356 |
3359 | 3362 |
3365 | 8 3366 |
3367 | 3370 |
3371 |
3374 |
3375 |
3378 |
3381 | 3384 |
3387 | 9 3388 |
3389 | 3392 |
3393 |
3396 |
3397 |
3400 |
3403 | 3406 |
3409 | 10 3410 |
3411 | 3414 |
3415 |
3418 |
3419 |
3422 |
3425 | 3428 |
3431 | 11 3432 |
3433 | 3436 |
3437 |
3440 |
3441 |
3444 |
3447 | 3450 |
3453 | 12 3454 |
3455 | 3458 |
3459 |
3462 |
3463 |
3466 |
3469 | 3472 |
3475 | 13 3476 |
3477 | 3480 |
3481 |
3484 |
3485 |
3486 |
3489 |
3492 |
3495 | 3498 |
3501 | 14 3502 |
3503 | 3506 |
3507 |
3510 |
3511 |
3514 |
3517 | 3520 |
3523 | 15 3524 |
3525 | 3528 |
3529 |
3532 |
3533 |
3536 |
3539 | 3542 |
3545 | 16 3546 |
3547 | 3550 |
3551 |
3554 |
3555 |
3558 |
3561 | 3564 |
3567 | 17 3568 |
3569 | 3572 |
3573 |
3576 |
3577 |
3580 |
3583 | 3586 |
3589 | 18 3590 |
3591 | 3594 |
3595 |
3598 |
3599 |
3602 |
3605 | 3608 |
3611 | 19 3612 |
3613 | 3616 |
3617 |
3620 |
3621 |
3624 |
3627 | 3630 |
3633 | 20 3634 |
3635 | 3638 |
3639 |
3642 |
3643 |
3644 |
3647 |
3650 |
3653 | 3656 |
3659 | 21 3660 |
3661 | 3664 |
3665 |
3668 |
3669 |
3672 |
3675 | 3678 |
3681 | 22 3682 |
3683 | 3686 |
3687 |
3690 |
3691 |
3694 |
3697 | 3700 |
3703 | 23 3704 |
3705 | 3708 |
3709 |
3712 |
3713 |
3716 |
3719 | 3722 |
3725 | 24 3726 |
3727 | 3730 |
3731 |
3734 |
3735 |
3738 |
3741 | 3744 |
3747 | 25 3748 |
3749 | 3752 |
3753 |
3756 |
3757 |
3760 |
3763 | 3766 |
3769 | 26 3770 |
3771 | 3774 |
3775 |
3778 |
3779 |
3782 |
3785 | 3788 |
3791 | 27 3792 |
3793 | 3796 |
3797 |
3800 |
3801 |
3802 |
3805 |
3808 |
3811 | 3814 |
3817 | 28 3818 |
3819 | 3822 |
3823 |
3826 |
3827 |
3830 |
3833 | 3836 |
3839 | 29 3840 |
3841 | 3844 |
3845 |
3848 |
3849 |
3852 |
3855 | 3858 |
3861 | 30 3862 |
3863 | 3866 |
3867 |
3870 |
3871 |
3874 |
3877 | 3880 |
3883 | 31 3884 |
3885 | 3888 |
3889 |
3892 |
3893 |
3894 |
3895 |
3896 |
3899 |
3902 | 2018年11月 3903 |
3904 |
3907 |
3910 |
3913 |
3916 | 3919 |
3922 | 3925 |
3926 |
3929 |
3930 |
3933 |
3936 | 3939 |
3942 | 3945 |
3946 |
3949 |
3950 |
3953 |
3956 | 3959 |
3962 | 3965 |
3966 |
3969 |
3970 |
3973 |
3976 | 3979 |
3982 | 3985 |
3986 |
3989 |
3990 |
3993 |
3996 | 3999 |
4002 | 1 4003 |
4004 | 4007 |
4008 |
4011 |
4012 |
4015 |
4018 | 4021 |
4024 | 2 4025 |
4026 | 4029 |
4030 |
4033 |
4034 |
4037 |
4040 | 4043 |
4046 | 3 4047 |
4048 | 4051 |
4052 |
4055 |
4056 |
4057 |
4060 |
4063 |
4066 | 4069 |
4072 | 4 4073 |
4074 | 4077 |
4078 |
4081 |
4082 |
4085 |
4088 | 4091 |
4094 | 5 4095 |
4096 | 4099 |
4100 |
4103 |
4104 |
4107 |
4110 | 4113 |
4116 | 6 4117 |
4118 | 4121 |
4122 |
4125 |
4126 |
4129 |
4132 | 4135 |
4138 | 7 4139 |
4140 | 4143 |
4144 |
4147 |
4148 |
4151 |
4154 | 4157 |
4160 | 8 4161 |
4162 | 4165 |
4166 |
4169 |
4170 |
4173 |
4176 | 4179 |
4182 | 9 4183 |
4184 | 4187 |
4188 |
4191 |
4192 |
4195 |
4198 | 4201 |
4204 | 10 4205 |
4206 | 4209 |
4210 |
4213 |
4214 |
4215 |
4218 |
4221 |
4224 | 4227 |
4230 | 11 4231 |
4232 | 4235 |
4236 |
4239 |
4240 |
4243 |
4246 | 4249 |
4252 | 12 4253 |
4254 | 4257 |
4258 |
4261 |
4262 |
4265 |
4268 | 4271 |
4274 | 13 4275 |
4276 | 4279 |
4280 |
4283 |
4284 |
4287 |
4290 | 4293 |
4296 | 14 4297 |
4298 | 4301 |
4302 |
4305 |
4306 |
4309 |
4312 | 4315 |
4318 | 15 4319 |
4320 | 4323 |
4324 |
4327 |
4328 |
4331 |
4334 | 4337 |
4340 | 16 4341 |
4342 | 4345 |
4346 |
4349 |
4350 |
4353 |
4356 | 4359 |
4362 | 17 4363 |
4364 | 4367 |
4368 |
4371 |
4372 |
4373 |
4376 |
4379 |
4382 | 4385 |
4388 | 18 4389 |
4390 | 4393 |
4394 |
4397 |
4398 |
4401 |
4404 | 4407 |
4410 | 19 4411 |
4412 | 4415 |
4416 |
4419 |
4420 |
4423 |
4426 | 4429 |
4432 | 20 4433 |
4434 | 4437 |
4438 |
4441 |
4442 |
4445 |
4448 | 4451 |
4454 | 21 4455 |
4456 | 4459 |
4460 |
4463 |
4464 |
4467 |
4470 | 4473 |
4476 | 22 4477 |
4478 | 4481 |
4482 |
4485 |
4486 |
4489 |
4492 | 4495 |
4498 | 23 4499 |
4500 | 4503 |
4504 |
4507 |
4508 |
4511 |
4514 | 4517 |
4520 | 24 4521 |
4522 | 4525 |
4526 |
4529 |
4530 |
4531 |
4534 |
4537 |
4540 | 4543 |
4546 | 25 4547 |
4548 | 4551 |
4552 |
4555 |
4556 |
4559 |
4562 | 4565 |
4568 | 26 4569 |
4570 | 4573 |
4574 |
4577 |
4578 |
4581 |
4584 | 4587 |
4590 | 27 4591 |
4592 | 4595 |
4596 |
4599 |
4600 |
4603 |
4606 | 4609 |
4612 | 28 4613 |
4614 | 4617 |
4618 |
4621 |
4622 |
4625 |
4628 | 4631 |
4634 | 29 4635 |
4636 | 4639 |
4640 |
4643 |
4644 |
4647 |
4650 | 4653 |
4656 | 30 4657 |
4658 | 4661 |
4662 |
4665 |
4666 |
4667 |
4668 |
4669 |
4670 |
4671 |
4672 |
4673 | `; 4674 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strictNullChecks": true, 4 | "moduleResolution": "node", 5 | "jsx": "preserve", 6 | "allowSyntheticDefaultImports": true, 7 | "target": "es6", 8 | "noImplicitAny": true, 9 | "noUnusedLocals": true, 10 | "rootDir": "src", 11 | "outDir": "lib" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "class-name": true, 4 | "comment-format": [ 5 | true, 6 | "check-space" 7 | ], 8 | "indent": [ 9 | true, 10 | "spaces" 11 | ], 12 | "member-ordering": [ 13 | true, 14 | "public-before-private", 15 | "static-before-instance", 16 | "variables-before-functions" 17 | ], 18 | "no-conditional-assignment": true, 19 | "no-duplicate-variable": true, 20 | "no-eval": true, 21 | "no-internal-module": true, 22 | "no-trailing-whitespace": true, 23 | "no-unused-variable": false, 24 | "no-var-keyword": true, 25 | "one-line": [ 26 | true, 27 | "check-open-brace", 28 | "check-whitespace" 29 | ], 30 | "quotemark": [ 31 | true, 32 | "single", 33 | "jsx-double" 34 | ], 35 | "semicolon": [ 36 | true, 37 | "always" 38 | ], 39 | "typedef-whitespace": [ 40 | true, 41 | { 42 | "call-signature": "nospace", 43 | "index-signature": "nospace", 44 | "parameter": "nospace", 45 | "property-declaration": "nospace", 46 | "variable-declaration": "nospace" 47 | } 48 | ], 49 | "variable-name": [ 50 | true, 51 | "ban-keywords" 52 | ], 53 | "whitespace": [ 54 | true, 55 | "check-branch", 56 | "check-decl", 57 | "check-operator", 58 | "check-separator", 59 | "check-type" 60 | ] 61 | } 62 | } -------------------------------------------------------------------------------- /typings/models.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'rc-animate'; 2 | declare module 'zscroller/lib/DOMScroller'; 3 | --------------------------------------------------------------------------------