From 0bc72976df6b730abad6e8e1a94e7fc86e9f46d4 Mon Sep 17 00:00:00 2001 From: zzy Date: Mon, 21 Aug 2017 12:09:06 +0800 Subject: [PATCH] init --- .editorconfig | 9 ++ .gitignore | 37 +++++ .stylelintrc | 27 ++++ .travis.yml | 36 +++++ HISTORY.md | 6 + README.md | 140 +++++++++++++++++ assets/common/Animation.less | 65 ++++++++ assets/common/Calendar.less | 55 +++++++ assets/common/ConfirmPanel.less | 36 +++++ assets/common/DatePicker.less | 28 ++++ assets/common/SingleMonth.less | 99 ++++++++++++ assets/common/TimePicker.less | 70 +++++++++ assets/common/WeekPanel.less | 19 +++ assets/common/index.less | 20 +++ assets/index.less | 4 + examples/basic.html | 1 + examples/basic.tsx | 74 +++++++++ index.android.js | 1 + index.ios.js | 12 ++ index.js | 3 + package.json | 70 +++++++++ src/Calendar.tsx | 229 +++++++++++++++++++++++++++ src/DatePicker.base.tsx | 267 ++++++++++++++++++++++++++++++++ src/DatePicker.tsx | 140 +++++++++++++++++ src/TimePicker.tsx | 78 ++++++++++ src/calendar/AnimateWrapper.tsx | 23 +++ src/calendar/ConfirmPanel.tsx | 55 +++++++ src/calendar/Header.tsx | 55 +++++++ src/calendar/ShortcutPanel.tsx | 14 ++ src/date/DataTypes.ts | 43 +++++ src/date/SingleMonth.tsx | 172 ++++++++++++++++++++ src/date/WeekPanel.tsx | 17 ++ src/locale/zh_CN.ts | 7 + src/typings.d.ts | 13 ++ src/util/index.ts | 0 tsconfig.json | 10 ++ tslint.json | 62 ++++++++ 37 files changed, 1997 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 .stylelintrc create mode 100644 .travis.yml create mode 100644 HISTORY.md create mode 100644 README.md create mode 100644 assets/common/Animation.less create mode 100644 assets/common/Calendar.less create mode 100644 assets/common/ConfirmPanel.less create mode 100644 assets/common/DatePicker.less create mode 100644 assets/common/SingleMonth.less create mode 100644 assets/common/TimePicker.less create mode 100644 assets/common/WeekPanel.less create mode 100644 assets/common/index.less create mode 100644 assets/index.less create mode 100644 examples/basic.html create mode 100644 examples/basic.tsx create mode 100644 index.android.js create mode 100644 index.ios.js create mode 100644 index.js create mode 100644 package.json create mode 100644 src/Calendar.tsx create mode 100644 src/DatePicker.base.tsx create mode 100644 src/DatePicker.tsx create mode 100644 src/TimePicker.tsx create mode 100644 src/calendar/AnimateWrapper.tsx create mode 100644 src/calendar/ConfirmPanel.tsx create mode 100644 src/calendar/Header.tsx create mode 100644 src/calendar/ShortcutPanel.tsx create mode 100644 src/date/DataTypes.ts create mode 100644 src/date/SingleMonth.tsx create mode 100644 src/date/WeekPanel.tsx create mode 100644 src/locale/zh_CN.ts create mode 100644 src/typings.d.ts create mode 100644 src/util/index.ts create mode 100644 tsconfig.json create mode 100644 tslint.json diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..604c94e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*.{js,css}] +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9cf7faa --- /dev/null +++ b/.gitignore @@ -0,0 +1,37 @@ +*.iml +*.log +*.log.* +.idea +.ipr +.iws +*~ +~* +*.diff +*.patch +*.bak +.DS_Store +Thumbs.db +.project +.*proj +.svn +*.swp +*.swo +*.pyc +*.pyo +node_modules +.cache +*.css +build +lib +es +coverage +*.js +*.jsx +*.map +!tests/index.js +!/index*.js +ios/ +android/ +xcuserdata +yarn.lock +_ts2js \ No newline at end of file diff --git a/.stylelintrc b/.stylelintrc new file mode 100644 index 0000000..0ceab43 --- /dev/null +++ b/.stylelintrc @@ -0,0 +1,27 @@ +{ + "extends": "stylelint-config-standard", + "rules": { + at-rule-empty-line-before: null, + at-rule-name-space-after: null, + at-rule-no-unknown: null, + comment-empty-line-before: null, + declaration-bang-space-before: null, + declaration-empty-line-before: null, + function-comma-newline-after: null, + function-name-case: null, + function-parentheses-newline-inside: null, + function-max-empty-lines: null, + function-whitespace-after: null, + indentation: null, + number-leading-zero: null, + number-no-trailing-zeros: null, + rule-empty-line-before: null, + selector-combinator-space-after: null, + selector-list-comma-newline-after: null, + selector-pseudo-element-colon-notation: null, + unit-no-unknown: null, + value-list-max-empty-lines: null, + unit-case: null, + color-hex-case: null, + } +} diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..7788a92 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,36 @@ +language: node_js + +sudo: false + +notifications: + email: + - yiminghe@gmail.com + +node_js: +- 6.9.1 + +before_install: +- | + if ! git diff --name-only $TRAVIS_COMMIT_RANGE | grep -qvE '(\.md$)|(^(docs|examples))/' + then + echo "Only docs were updated, stopping build process." + exit + fi + phantomjs --version +script: +- | + if [ "$TEST_TYPE" = test ]; then + npm test + else + npm run $TEST_TYPE + fi +env: + matrix: + - TEST_TYPE=lint + - TEST_TYPE=test + - TEST_TYPE=coverage + + +matrix: + allow_failures: + - env: "TEST_TYPE=saucelabs" \ No newline at end of file diff --git a/HISTORY.md b/HISTORY.md new file mode 100644 index 0000000..ca02dce --- /dev/null +++ b/HISTORY.md @@ -0,0 +1,6 @@ +# History +---- + +## 0.0.1 / 2017-08-10 + +- TODO \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..bb5174d --- /dev/null +++ b/README.md @@ -0,0 +1,140 @@ +# rmc-calendar +--- + +React Mobile Calendar Component (web and react-native) + + +[![NPM version][npm-image]][npm-url] +![react-native](https://img.shields.io/badge/react--native-%3E%3D_0.30.0-green.svg) +![react](https://img.shields.io/badge/react-%3E%3D_15.2.0-green.svg) +[![build status][travis-image]][travis-url] +[![Test coverage][coveralls-image]][coveralls-url] +[![gemnasium deps][gemnasium-image]][gemnasium-url] +[![npm download][download-image]][download-url] + +[npm-image]: http://img.shields.io/npm/v/rmc-calendar.svg?style=flat-square +[npm-url]: http://npmjs.org/package/rmc-calendar +[travis-image]: https://img.shields.io/travis/react-component/m-calendar.svg?style=flat-square +[travis-url]: https://travis-ci.org/react-component/m-calendar +[coveralls-image]: https://img.shields.io/coveralls/react-component/m-calendar.svg?style=flat-square +[coveralls-url]: https://coveralls.io/r/react-component/m-calendar?branch=master +[gemnasium-image]: http://img.shields.io/gemnasium/react-component/m-calendar.svg?style=flat-square +[gemnasium-url]: https://gemnasium.com/react-component/m-calendar +[node-image]: https://img.shields.io/badge/node.js-%3E=_0.10-green.svg?style=flat-square +[node-url]: http://nodejs.org/download/ +[download-image]: https://img.shields.io/npm/dm/rmc-calendar.svg?style=flat-square +[download-url]: https://npmjs.org/package/rmc-calendar + +## Screenshots + + + + +## Development + +``` +npm i +npm start +``` + +## Example + +http://localhost:8000/examples/ + +online example: http://react-component.github.io/m-calendar/ + +## react-native + +``` +./node_modules/rc-tools run react-native-init +npm run watch-tsc +react-native start +react-native run-ios +``` + +## install + +[![rmc-calendar](https://nodei.co/npm/rmc-calendar.png)](https://npmjs.org/package/rmc-calendar) + + +# 4.x beta docs + +## Usage +```jsx + + + one + two + three + four + five + six + seven + eight + + + eleven + twelve + thirteen + fourteen + fifteen + sixteen + seventeen + eighteen + + +``` + +## API + +### MultiPicker props + +| name | description | type | default | +|----------|----------------|----------|--------------| +|className(web) | additional css class of root dom node | String | | +|prefixCls(web) | prefix class | String | '' | +|defaultSelectedValue(web) | default selected values | string[]/number[] | | +|selectedValue | current selected values | string[]/number[] | | +|onValueChange | fire when picker change | Function(value) | | + + +### Picker props + +| name | description | type | default | +|----------|----------------|----------|--------------| +|className(web) | additional css class of root dom node | String | | +|prefixCls(web) | prefix class | String | '' | +|defaultSelectedValue(web) | default selected values | string/number | | +|selectedValue | current selected values | string/number | | +|onValueChange | fire when picker change | Function(value) | | +|disabled | whether picker is disabled | bool | false +|indicatorClassName | className of indicator | String | +|indicatorStyle | style of indicator | object | + +### Picker.Item props +| name | description | type | default | +|----------|----------------|----------|--------------| +|className(web) | additional css class of root dom node | String | | +|value | value of item | String | | + +## Test Case + +``` +npm test +npm run chrome-test +``` + +## Coverage + +``` +npm run coverage +``` + +open coverage/ dir + +## License + +rmc-calendar is released under the MIT license. diff --git a/assets/common/Animation.less b/assets/common/Animation.less new file mode 100644 index 0000000..44c8cdf --- /dev/null +++ b/assets/common/Animation.less @@ -0,0 +1,65 @@ +.@{prefixClass} { + + .animate { + animation-duration: .3s; + animation-fill-mode: both; + } + + @keyframes fadeIn { + 0% { + opacity: 0; + } + + to { + opacity: 1; + } + } + + @keyframes fadeOut { + 0% { + opacity: 1; + } + + to { + opacity: 0; + } + } + + .fade-enter { + animation-name: fadeIn; + } + + .fade-leave { + animation-name: fadeOut; + } + + @keyframes slideInDown { + 0% { + transform: translateZ(0); + visibility: visible; + } + + to { + transform: translate3d(0, 100%, 0); + } + } + + @keyframes slideInUp { + 0% { + transform: translate3d(0, 100%, 0); + visibility: visible; + } + + to { + transform: translateZ(0); + } + } + + .slide-enter { + animation-name: slideInUp; + } + + .slide-leave { + animation-name: slideInDown; + } +} diff --git a/assets/common/Calendar.less b/assets/common/Calendar.less new file mode 100644 index 0000000..235c963 --- /dev/null +++ b/assets/common/Calendar.less @@ -0,0 +1,55 @@ +.@{prefixClass}.calendar { + .mask { + position: fixed; + width: 100%; + height: 100%; + top: 0; + left: 0; + z-index: 1; + background: rgba(0, 0, 0, .5); + } + + .content { + position: fixed; + display: flex; + flex-direction: column; + width: 100%; + height: 100%; + top: 0; + left: 0; + z-index: 1; + background: #fff; + } + + .header { + margin: 5px; + display: flex; + flex-shrink: 0; + align-items: center; + + .close { + padding: 0 8px; + } + + .title { + text-align: center; + width: 100%; + } + + .left { + position: absolute; + left: 5px; + top: 5px; + } + + .right { + position: absolute; + right: 5px; + top: 5px; + } + } + + .timePicker { + border-top: 1px #ccc solid; + } +} diff --git a/assets/common/ConfirmPanel.less b/assets/common/ConfirmPanel.less new file mode 100644 index 0000000..a8271d2 --- /dev/null +++ b/assets/common/ConfirmPanel.less @@ -0,0 +1,36 @@ +.@{prefixClass} .confirm-panel { + display: flex; + flex-shrink: 0; + justify-content: center; + align-items: center; + width: 100%; + background: #eee; + padding: 5px; + + .info { + font-size: 12px; + + p { + margin: 0; + } + + .grey { + color: #999; + } + } + + .button { + padding: 5px 20px; + border-radius: 5px; + align-self: center; + margin: 5px 0 5px auto; + color: #fff; + font-size: 14px; + background: rgb(26, 123, 230); + } + + .button-disable { + color: #aaa; + background: #d5d5d5; + } +} diff --git a/assets/common/DatePicker.less b/assets/common/DatePicker.less new file mode 100644 index 0000000..b655707 --- /dev/null +++ b/assets/common/DatePicker.less @@ -0,0 +1,28 @@ +.@{prefixClass}.data-picker { + display: flex; + flex-direction: column; + background: #eee; + max-height: 600px; + + .wrapper { + height: auto; + position: relative; + } + + .months { + background: #fff; + } + + .load-tip { + position: absolute; + display: flex; + justify-content: center; + align-items: flex-end; + height: 40px; + left: 0; + right: 0; + padding: 10px 0; + top: -40px; + color: #bbb; + } +} diff --git a/assets/common/SingleMonth.less b/assets/common/SingleMonth.less new file mode 100644 index 0000000..a0ef842 --- /dev/null +++ b/assets/common/SingleMonth.less @@ -0,0 +1,99 @@ +.@{prefixClass} .single-month { + padding: 10px 2px 0; + + .header { + margin-left: 15px; + } + + .row { + display: flex; + margin: 5px 0; + + .cell { + display: flex; + flex-direction: column; + height: 50px; + width: 100/7%; + justify-content: center; + align-items: center; + + .date-wrapper { + display: flex; + height: 30px; + width: 100%; + justify-content: center; + align-items: center; + + .disable { + color: #aaa; + background: #eee; + border: none; + border-radius: 100%; + } + + .date { + display: flex; + justify-content: center; + align-items: center; + width: 30px; + height: 30px; + flex-shrink: 0; + } + + .grey { + color: #999; + } + + .important { + border: 1px #999 solid; + border-radius: 100%; + } + + .left, + .right { + border: none; + width: 100%; + height: 100%; + } + + .date-selected { + border: none; + background: rgb(26, 123, 230); + color: #fff; + } + + .selected-start { + border-radius: 100% 0 0 100%; + } + + .selected-single { + border-radius: 100%; + } + + .selected-middle { + border-radius: 0; + } + + .selected-end { + border-radius: 0 100% 100% 0; + } + } + + .info { + height: 15px; + width: 100%; + padding: 0 5px; + font-size: 12px; + color: #999; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + text-align: center; + } + + .date-selected { + color: rgb(26, 123, 230); + } + } + } +} diff --git a/assets/common/TimePicker.less b/assets/common/TimePicker.less new file mode 100644 index 0000000..6d35454 --- /dev/null +++ b/assets/common/TimePicker.less @@ -0,0 +1,70 @@ +.time-picker { + text-align: center; + .title { + font-size: 13px; + padding: 5px 0; + border-bottom: 1px #ccc solid; + } +} + +// .rmc-picker, +// .rmc-multi-picker { +// height: 238px; +// /*34*7*/ +// } +// .rmc-multi-picker { +// display: flex; +// align-items: center; +// } +// .rmc-picker-item { +// font-size: 16px; +// height: 34px; +// line-height: 34px; +// padding: 0 10px; +// white-space: nowrap; +// position: relative; +// overflow: hidden; +// text-overflow: ellipsis; +// color: #9b9b9b; +// width: 100%; +// box-sizing: border-box; +// } +// .rmc-picker { +// display: block; +// position: relative; +// overflow: hidden; +// width: 100%; +// flex: 1; +// text-align: center; +// } +// .rmc-picker-mask { +// position: absolute; +// left: 0; +// top: 0; +// height: 100%; +// margin: 0 auto; +// width: 100%; +// z-index: 3; +// background-image: linear-gradient(to bottom, rgba(255, 255, 255, 0.95), rgba(255, 255, 255, 0.6)), linear-gradient(to top, rgba(255, 255, 255, 0.95), rgba(255, 255, 255, 0.6)); +// background-position: top, bottom; +// background-size: 100% 204px; +// background-repeat: no-repeat; +// } +// .rmc-picker-content { +// position: absolute; +// left: 0; +// top: 0; +// width: 100%; +// z-index: 1; +// } +// .rmc-picker-indicator { +// box-sizing: border-box; +// width: 100%; +// height: 34px; +// position: absolute; +// left: 0; +// top: 102px; +// z-index: 3; +// border-top: 1PX solid #ddd; +// border-bottom: 1PX solid #ddd; +// } diff --git a/assets/common/WeekPanel.less b/assets/common/WeekPanel.less new file mode 100644 index 0000000..7f81108 --- /dev/null +++ b/assets/common/WeekPanel.less @@ -0,0 +1,19 @@ +.@{prefixClass} .week-panel { + background: #fff; + display: flex; + flex-shrink: 0; + padding: 0 2px; + border-bottom: 1px #ccc solid; + + .cell { + height: 30px; + display: flex; + width: 100/7%; + justify-content: center; + align-items: center; + } + + .cell-grey { + color: #999; + } +} diff --git a/assets/common/index.less b/assets/common/index.less new file mode 100644 index 0000000..e6c3a9d --- /dev/null +++ b/assets/common/index.less @@ -0,0 +1,20 @@ +@prefixClass: rmc-calendar; +.@{prefixClass} { + font-family: Arial, "Hiragino Sans GB", "Microsoft Yahei", "Microsoft Sans Serif", "WenQuanYi Micro Hei", sans-serif; + box-sizing: border-box; + * { + box-sizing: border-box; + } +} + +.@{prefixClass}-hidden { + display: none; +} + +@import "./Animation.less"; +@import "./Calendar.less"; +@import "./WeekPanel.less"; +@import "./DatePicker.less"; +@import "./ConfirmPanel.less"; +@import "./TimePicker.less"; +@import "./SingleMonth.less"; diff --git a/assets/index.less b/assets/index.less new file mode 100644 index 0000000..d32314c --- /dev/null +++ b/assets/index.less @@ -0,0 +1,4 @@ +@import './common/index.less'; + +// @import '../node_modules/rmc-picker/assets/index.css'; +// @import '../node_modules/rmc-date-picker/assets/index.css'; diff --git a/examples/basic.html b/examples/basic.html new file mode 100644 index 0000000..b3a4252 --- /dev/null +++ b/examples/basic.html @@ -0,0 +1 @@ +placeholder \ No newline at end of file diff --git a/examples/basic.tsx b/examples/basic.tsx new file mode 100644 index 0000000..76c3483 --- /dev/null +++ b/examples/basic.tsx @@ -0,0 +1,74 @@ +/* tslint:disable:no-console */ + +import 'rmc-picker/assets/index.css'; +import 'rmc-calendar/assets/index.less'; + +import React from 'react'; +import ReactDOM from 'react-dom'; +import Calendar, { ExtraData } from '../src/Calendar'; + +const extra: { [key: string]: ExtraData } = { + 1501516800000: { info: '建军节' }, + '2017/08/11': { info: '周报!' }, + '2017/08/14': { info: '百阿', disable: true }, + '2017/08/15': { info: '百阿', disable: true }, + '2017/08/16': { info: '百阿', disable: true }, + '2017/08/17': { info: '百阿', disable: true }, + '2017/08/18': { info: '百阿', disable: true }, +}; +for (let key in extra) { + if (extra.hasOwnProperty(key)) { + let info = extra[key]; + const date = new Date(key); + if (!Number.isNaN(+date) && !extra[+date]) { + extra[+date] = info; + } + } +} + +class BasicDemo extends React.Component { + originbodyScrollY = document.getElementsByTagName('body')[0].style.overflowY; + + constructor(props: any) { + super(props); + this.state = { + show: false, + }; + } + + render() { + return ( +
+
{ + document.getElementsByTagName('body')[0].style.overflowY = 'hidden'; + this.setState({ + show: true, + }); + }}> + 选择日期时间 +
+ { + document.getElementsByTagName('body')[0].style.overflowY = this.originbodyScrollY; + this.setState({ show: false }); + }} + getDateExtra={(date) => { + return extra[+date]; + }} + minDate={new Date(+new Date - 60 * 24 * 3600 * 1000)} + maxDate={new Date(+new Date + 365 * 24 * 3600 * 1000)} + /> +
+ ); + } +} + +ReactDOM.render(, document.getElementById('__react-content')); + +const ip = (document.body.children[3] as HTMLScriptElement).innerText.split('/')[2].split(':')[0]; +const elm = document.createElement('script'); +elm.src = `http://${ip}:1337/vorlon.js`; +document.body.appendChild(elm); \ No newline at end of file diff --git a/index.android.js b/index.android.js new file mode 100644 index 0000000..8288d7a --- /dev/null +++ b/index.android.js @@ -0,0 +1 @@ +module.exports = require('./index.ios'); diff --git a/index.ios.js b/index.ios.js new file mode 100644 index 0000000..73bb256 --- /dev/null +++ b/index.ios.js @@ -0,0 +1,12 @@ +// only for react-native examples + +import getList from 'react-native-index-page'; + +getList({ + demos: [ + require('./_ts2js/examples/picker'), + require('./_ts2js/examples/multi-picker'), + require('./_ts2js/examples/popup'), + ], + title: require('./package.json').name, +}); diff --git a/index.js b/index.js new file mode 100644 index 0000000..c81af34 --- /dev/null +++ b/index.js @@ -0,0 +1,3 @@ +// export this package's api +import Picker from './src/'; +export default Picker; diff --git a/package.json b/package.json new file mode 100644 index 0000000..cb56456 --- /dev/null +++ b/package.json @@ -0,0 +1,70 @@ +{ + "name": "rmc-calendar", + "version": "0.0.1", + "description": "React Mobile Calendar Component(web and react-native)", + "keywords": [ + "react", + "react-component", + "react-m-calendar", + "m-calendar" + ], + "homepage": "https://github.com/react-component/m-calendar", + "repository": { + "type": "git", + "url": "https://github.com/react-component/m-calendar.git" + }, + "bugs": { + "url": "https://github.com/react-component/m-calendar/issues" + }, + "files": [ + "lib", + "es", + "assets/*.css" + ], + "licenses": "MIT", + "main": "./lib/index", + "module": "./es/index", + "config": { + "port": 8021 + }, + "scripts": { + "watch-tsc": "rc-tools run watch-tsc", + "compile": "rc-tools run compile --babel-runtime", + "build": "rc-tools run build", + "gh-pages": "rc-tools run gh-pages", + "start": "rc-tools run server", + "pub": "rc-tools run pub --babel-runtime", + "lint": "rc-tools run lint --no-js-lint", + "karma": "rc-test run karma", + "saucelabs": "rc-test run saucelabs", + "test": "rc-test run test", + "chrome-test": "rc-test run chrome-test", + "coverage": "rc-test run coverage", + "rn-start": "node node_modules/react-native/local-cli/cli.js start" + }, + "dependencies": { + "babel-runtime": "6.x", + "rc-animate": "^2.4.1", + "zscroller": "^0.3.1" + }, + "devDependencies": { + "@types/mocha": "~2.2.32", + "@types/react": "^15.0.27", + "@types/react-dom": "^15.5.0", + "array-tree-filter": "1.x", + "expect.js": "0.3.x", + "fastclick": "^1.0.6", + "pre-commit": "1.x", + "rc-test": "6.x", + "rc-tools": "6.x", + "react": "15.5.x", + "react-dom": "15.5.x", + "react-native": "0.41.x", + "react-native-index-page": "~0.2.0", + "stylelint-config-standard": "^17.0.0" + }, + "typings": "./lib/index.d.ts", + "pre-commit": [ + "lint" + ] +} \ No newline at end of file diff --git a/src/Calendar.tsx b/src/Calendar.tsx new file mode 100644 index 0000000..87c9aef --- /dev/null +++ b/src/Calendar.tsx @@ -0,0 +1,229 @@ +import React from 'react'; +import Animate from 'rc-animate'; +import TimePicker from './TimePicker'; + +import DatePicker from './DatePicker'; +import ConfirmPanel from './calendar/ConfirmPanel'; +import AnimateWrapper from './calendar/AnimateWrapper'; +import zhCN from './locale/zh_CN'; +import WeekPanel from './date/WeekPanel'; +import Header from './calendar/Header'; +import { Models as DateModels } from './date/DataTypes'; + +export type ExtraData = DateModels.ExtraData; + +export interface PropsType { + /** (web only) 样式前缀,default: rmc-calendar */ + prefixCls?: string; + /** 是否展示头部,default: true */ + showHeader?: boolean; + /** 选择类型,default: range,one: 单日,range: 日期区间 */ + type?: 'one' | 'range'; + /** 选择时间,default: false */ + pickTime?: boolean; + /** 是否展示,default: false */ + visible: boolean; + /** 本地化 */ + locale?: Models.Locale; + /** 值变化时回调 */ + onValueChange?: (startDateTime?: Date, endDateTime?: Date) => void; + /** 关闭时回调 */ + onCancel?: () => void; + /** 确认时回调 */ + onConfirm?: (startDateTime?: Date, endDateTime?: Date) => void; + + // DatePicker + /** 无限滚动,default: true */ + infinite?: boolean; + /** 无限滚动优化(大范围选择),default: false */ + infiniteOpt?: boolean; + /** 初始化月个数,default: 6 */ + initalMonths?: number; + /** 开始日期,default: today */ + defaultDate?: Date; + /** 最小日期 */ + minDate?: Date; + /** 最大日期 */ + maxDate?: Date; + /** 日期扩展数据 */ + getDateExtra?: (date: Date) => ExtraData; +} +export class StateType { + showTimePicker: boolean = false; + timePickerTitle?: string; + startDate?: Date; + endDate?: Date; + startTime?: number; + endTime?: number; + disConfirm?: boolean = true; + defaultTime?: number; +} +export default class Calendar extends React.Component { + static defaultProps = { + visible: false, + showHeader: true, + locale: zhCN, + pickTime: false, + prefixCls: 'rmc-calendar', + type: 'range', + } as PropsType; + + constructor(props: PropsType) { + super(props); + + this.state = new StateType; + } + + onSelectedDate = (date: Date) => { + const { type, pickTime } = this.props; + const { startDate, endDate } = this.state; + + switch (type) { + case 'one': + this.setState({ + startDate: date, + }); + if (pickTime) { + this.setState({ + showTimePicker: true, + timePickerTitle: '选择时间', + defaultTime: 8 * 60, + }); + } + return; + + case 'range': + if (!startDate || endDate) { + this.setState({ + startDate: date, + endDate: undefined, + disConfirm: true, + }); + if (pickTime) { + this.setState({ + showTimePicker: true, + timePickerTitle: '选择开始时间', + }); + } + } else { + if (+date >= +startDate) { + this.setState({ + endDate: date, + }); + } else { + this.setState({ + startDate: date, + endDate: startDate, + }); + } + this.setState({ + disConfirm: false, + }); + } + return; + + default: + return; + } + } + + onClose = () => { + this.setState(new StateType); + } + + onCancel = () => { + this.onClose(); + this.props.onCancel && this.props.onCancel(); + } + + onConfirm = () => { + this.onClose(); + this.props.onConfirm && this.props.onConfirm(); + } + + onTimeChange = (time: any) => { + const { startDate, endDate } = this.state; + if (endDate) { + this.setState({ + endTime: time, + }); + } else if (startDate) { + this.setState({ + startTime: time, + }); + } + } + + onClear = () => { + this.setState({ + startDate: undefined, + endDate: undefined, + showTimePicker: false, + }); + } + + render() { + const { + locale, prefixCls, visible, showHeader, onConfirm, pickTime, + infinite, infiniteOpt, initalMonths, defaultDate, minDate, maxDate, getDateExtra, + } = this.props; + const { + showTimePicker, timePickerTitle, + startDate, endDate, startTime, endTime, + disConfirm, defaultTime + } = this.state; + + return ( +
+ + + + + + + { + showHeader && +
+ } + + { + showTimePicker && + + } + { + startDate && + + } + + +
+ ); + } +} diff --git a/src/DatePicker.base.tsx b/src/DatePicker.base.tsx new file mode 100644 index 0000000..d771aa0 --- /dev/null +++ b/src/DatePicker.base.tsx @@ -0,0 +1,267 @@ +import * as React from 'react'; +import { Models } from './date/DataTypes'; + +export interface PropsType { + /** (web only) 样式前缀 */ + prefixCls?: string; + /** 无限滚动,default: true */ + infinite?: boolean; + /** 无限滚动优化(大范围选择),default: false */ + infiniteOpt?: boolean; + /** 初始化月个数,default: 6 */ + initalMonths?: number; + /** 默认日期,default: today */ + defaultDate?: Date; + /** 最小日期 */ + minDate?: Date; + /** 最大日期 */ + maxDate?: Date; + /** 选择值 */ + value?: { + startDate?: Date; + endDate?: Date; + }; + /** 日期扩展数据 */ + getDateExtra?: (date: Date) => Models.ExtraData; + /** 日期点击回调 */ + onCellClick?: (date: Date) => void; +} +export interface StateType { + months: Models.MonthData[]; +} +export default abstract class DatePicker extends React.PureComponent { + static defaultProps = { + prefixCls: 'rmc-calendar', + infinite: true, + infiniteOpt: false, + defaultDate: new Date, + initalMonths: 6, + } as PropsType; + + visibleMonth: Models.MonthData[] = []; + abstract genMonthComponent: (data: Models.MonthData) => React.ReactNode; + + constructor(props: PropsType) { + super(props); + + this.state = { + months: [], + }; + } + + componentWillReceiveProps(nextProps: PropsType) { + if (this.props.value !== nextProps.value) { + const oldValue = this.props.value; + if (oldValue && oldValue.startDate) { + this.selectDateRange(oldValue.startDate, oldValue.endDate, true); + } + if (nextProps.value && nextProps.value.startDate) { + this.selectDateRange(nextProps.value.startDate, nextProps.value.endDate); + } + } + } + + componentWillMount() { + const { initalMonths = 6, defaultDate } = this.props; + for (let i = 0; i < initalMonths; i++) { + this.canLoadNext() && this.genMonthData(defaultDate, i); + } + this.visibleMonth = [...this.state.months]; + this.forceUpdate(); + } + + getMonthDate(date = new Date, addMonth = 0) { + const y = date.getFullYear(), m = date.getMonth(); + return { + firstDate: new Date(y, m + addMonth, 1), + lastDate: new Date(y, m + 1 + addMonth, 0), + }; + } + + canLoadPrev() { + const { minDate } = this.props; + return !minDate || this.state.months.length <= 0 || +this.getMonthDate(minDate).firstDate < +this.state.months[0].firstDate; + } + + canLoadNext() { + const { maxDate } = this.props; + return !maxDate || this.state.months.length <= 0 || +this.getMonthDate(maxDate).firstDate > +this.state.months[this.state.months.length - 1].firstDate; + } + + genWeekData = (firstDate: Date) => { + const minDateTime = +(this.props.minDate || 0); + const maxDateTime = +(this.props.maxDate || Number.POSITIVE_INFINITY); + + const weeks: Models.CellData[][] = []; + const nextMonth = this.getMonthDate(firstDate, 1).firstDate; + let currentDay = firstDate; + let currentWeek: Models.CellData[] = []; + weeks.push(currentWeek); + + let startWeekday = currentDay.getDay(); + if (startWeekday > 0) { + for (let i = 0; i < startWeekday; i++) { + currentWeek.push({} as Models.CellData); + } + } + while (currentDay < nextMonth) { + if (currentWeek.length === 7) { + currentWeek = []; + weeks.push(currentWeek); + } + const dayOfMonth = currentDay.getDate(); + const tick = +currentDay; + currentWeek.push({ + tick, + dayOfMonth, + selected: Models.SelectType.None, + isFirstOfMonth: dayOfMonth === 1, + isLastOfMonth: false, + outOfDate: tick < minDateTime || tick > maxDateTime, + }); + currentDay = new Date(currentDay.getTime() + 3600 * 24 * 1000); + } + currentWeek[currentWeek.length - 1].isLastOfMonth = true; + return weeks; + } + + genMonthData(date?: Date, addMonth: number = 0) { + if (!date) { + date = addMonth >= 0 ? this.state.months[this.state.months.length - 1].firstDate : this.state.months[0].firstDate; + } + if (!date) { + date = new Date; + } + const { firstDate, lastDate } = this.getMonthDate(date, addMonth); + + const weeks = this.genWeekData(firstDate); + const title = `${firstDate.getFullYear()}年${firstDate.getMonth() + 1}月`; + const data = { + title, + firstDate, + lastDate, + weeks, + } as Models.MonthData; + data.component = this.genMonthComponent(data); + if (addMonth >= 0) { + this.state.months.push(data); + } else { + this.state.months.unshift(data); + } + return data; + } + + inDate(date: number, tick: number) { + return date <= tick && tick < date + 24 * 3600000; + } + + selectDateRange = (startDate: Date, endDate?: Date, clear = false) => { + const { getDateExtra } = this.props; + const time1 = +startDate, time2 = endDate ? +endDate : 0; + const startDateTick = !time2 || time1 < time2 ? time1 : time2; + const endDateTick = time2 && time1 > time2 ? time1 : time2; + + const startMonthDate = this.getMonthDate(new Date(startDateTick)).firstDate; + const endMonthDate = endDateTick ? new Date(endDateTick) : this.getMonthDate(new Date(startDateTick)).lastDate; + + let goback = false, needUpdate = false; + this.state.months + .filter(m => { + return m.firstDate >= startMonthDate && m.firstDate <= endMonthDate; + }) + .forEach(m => { + m.weeks.forEach(w => w + .filter(d => { + if (!endDateTick) { + return d.tick && this.inDate(startDateTick, d.tick); + } else { + return d.tick && d.tick >= startDateTick && d.tick <= endDateTick; + } + }) + .forEach(d => { + const oldValue = d.selected; + if (clear) { + d.selected = Models.SelectType.None; + } else { + const info = getDateExtra && getDateExtra(new Date(d.tick)) || {}; + if (goback || d.outOfDate || info.disable) { + goback = true; + return; + } + if (this.inDate(startDateTick, d.tick)) { + if (!endDateTick) { + d.selected = Models.SelectType.Only; + } else if (startDateTick !== endDateTick) { + d.selected = Models.SelectType.Start; + } else { + d.selected = Models.SelectType.Single; + } + } else if (this.inDate(endDateTick, d.tick)) { + d.selected = Models.SelectType.End; + } else { + d.selected = Models.SelectType.Middle; + } + } + needUpdate = needUpdate || d.selected !== oldValue; + }) + ); + if (!goback && needUpdate && m.componentRef) { + m.componentRef.updateWeeks(); + m.componentRef.forceUpdate(); + }; + }); + if (goback) { + this.selectDateRange(startDate, endDate, true); + } + } + + computeVisible = (fullHeight: number, clientHeight: number, scrollTop: number) => { + const canScrollHeight = fullHeight - clientHeight; + const toBottom = canScrollHeight - scrollTop; + + let needUpdate = false; + const MAX_VIEW_PORT = clientHeight * 2; + const MIN_VIEW_PORT = clientHeight; + + // 大缓冲区外过滤规则 + const filterFunc = (vm: Models.MonthData) => vm.y && vm.height && (vm.y + vm.height > scrollTop - MAX_VIEW_PORT && vm.y < scrollTop + clientHeight + MAX_VIEW_PORT); + + if (this.props.infiniteOpt && this.visibleMonth.length > 12) { + this.visibleMonth = this.visibleMonth.filter(filterFunc).sort((a, b) => +a.firstDate - +b.firstDate); + } + + // 当小缓冲区不满时填充 + if (this.visibleMonth.length > 0) { + const last = this.visibleMonth[this.visibleMonth.length - 1]; + if (last.y !== undefined && last.height && last.y + last.height < scrollTop + clientHeight + MIN_VIEW_PORT) { + const lastIndex = this.state.months.indexOf(last); + for (let i = 1; i <= 2; i++) { + const index = lastIndex + i; + if (index < this.state.months.length && this.visibleMonth.indexOf(this.state.months[index]) < 0) { + this.visibleMonth.push(this.state.months[index]); + } else { + this.canLoadNext() && this.genMonthData(undefined, 1); + } + } + needUpdate = true; + } + + const first = this.visibleMonth[0]; + if (first.y !== undefined && first.height && first.y > scrollTop - MIN_VIEW_PORT) { + const firstIndex = this.state.months.indexOf(first); + for (let i = 1; i <= 2; i++) { + const index = firstIndex - i; + if (index >= 0 && this.visibleMonth.indexOf(this.state.months[index]) < 0) { + this.visibleMonth.unshift(this.state.months[index]); + needUpdate = true; + } + } + } + } else if (this.state.months.length > 0) { + this.visibleMonth = this.state.months.filter(filterFunc); + needUpdate = true; + } + + return needUpdate; + } +} \ No newline at end of file diff --git a/src/DatePicker.tsx b/src/DatePicker.tsx new file mode 100644 index 0000000..6d77e7e --- /dev/null +++ b/src/DatePicker.tsx @@ -0,0 +1,140 @@ +import * as React from 'react'; +import Component, { PropsType, StateType } from './DatePicker.base'; +import WeekPanel from './date/WeekPanel'; +import SingleMonth from './date/SingleMonth'; +import { Models } from './date/DataTypes'; +import DOMScroller from 'zscroller/lib/DOMScroller'; + +export { PropsType } +export default class DatePicker extends Component { + + scroller: any; + + genMonthComponent = (data?: Models.MonthData) => { + if (!data) return; + + return { + data.componentRef = dom || undefined; + data.updateLayout = () => { + this.computeHeight(data, dom); + }; + data.updateLayout(); + }} + />; + } + + computeHeight = (data: Models.MonthData, singleMonth: SingleMonth | null) => { + if (singleMonth && singleMonth.wrapperDivDOM) { + data.height = singleMonth.wrapperDivDOM.clientHeight; + data.y = singleMonth.wrapperDivDOM.offsetTop; + } + } + + onCellClick = (single: SingleMonth, day: Models.CellData) => { + if (!day.tick) return; + + this.props.onCellClick && this.props.onCellClick(new Date(day.tick)); + } + + createOnScroll = () => { + let timer: any; + let fullHeight = 0, clientHeight = 0, scrollTop = 0; + + return (data: { full: number, client: number, top: number }) => { + const { full, client, top } = data; + fullHeight = full; + clientHeight = client; + scrollTop = top; + + if (timer) { + return; + } + + timer = setTimeout(() => { + timer = undefined; + const time1 = +new Date; + if (this.computeVisible(fullHeight, clientHeight, scrollTop)) { + const time2 = +new Date; + this.forceUpdate(); + // console.log('forceUpdate', +new Date - time2); + } + // console.log('all', +new Date - time1); + }, 64); + }; + } + + setLayout = (dom: HTMLDivElement) => { + if (!this.scroller) { + const scrollHandler = this.createOnScroll(); + if (this.props.infinite) { + this.scroller = new DOMScroller(dom.children[0], { + scrollingX: false, + onScroll: () => scrollHandler({ + client: dom.clientHeight, + full: (dom.children[0] as HTMLDivElement).clientHeight, + top: this.scroller.getValues().top, + }) + }).scroller; + + this.scroller.activatePullToRefresh(40, function () { + }, function () { + }, () => { + this.canLoadPrev() && this.genMonthData(this.state.months[0].firstDate, -1); + + this.visibleMonth = this.visibleMonth.slice(0, this.props.initalMonths); + + this.state.months.forEach((m, index) => { + m.updateLayout && m.updateLayout(); + }); + + this.scroller.finishPullToRefresh(); + }); + } else { + this.scroller = true; + dom.onscroll = (evt) => { + scrollHandler({ + client: dom.clientHeight, + full: (evt.target as HTMLDivElement).clientHeight, + top: (evt.target as HTMLDivElement).scrollTop, + }); + }; + } + } + } + + render() { + const { infinite, prefixCls = '' } = this.props; + + return ( +
+ +
+
+ { + this.canLoadPrev() &&
加载上一个月
+ } +
+ { + this.state.months.map((m, index) => { + const hidden = m.height && this.visibleMonth.indexOf(m) < 0; + if (hidden) { + return
; + } + return m.component; + + {/* return ; */} + }) + } +
+
+
+
+ ); + } +} \ No newline at end of file diff --git a/src/TimePicker.tsx b/src/TimePicker.tsx new file mode 100644 index 0000000..143f45f --- /dev/null +++ b/src/TimePicker.tsx @@ -0,0 +1,78 @@ +import React from 'react'; +import Picker from 'rmc-picker'; +import MultiPicker from 'rmc-picker/lib/MultiPicker'; + +export interface PropsType { + locale?: Object; + title?: string; + /** unit: minutes, default: 8am */ + defaultValue?: number; + /** unit: minutes */ + value?: number; + onValueChange?: (time: number) => void; +} +export interface StateType { + value: string[]; +} +export default class TimePicker extends React.PureComponent { + static defaultProps = { + // locale: zhCN, + defaultValue: 8 * 60, + }; + + constructor(props: PropsType) { + super(props); + this.state = { + value: [], + }; + } + + onValueChange = (data: string[]) => { + this.setState({ value: data }); + const isPM = data[0] === 'pm'; + const hours = isPM ? +data[1] + 12 : +data[1]; + const minutes = +data[2]; + this.props.onValueChange && this.props.onValueChange((hours * 60 + minutes) * 60000); + } + + render() { + const { title, defaultValue, value } = this.props; + let useValue = value || defaultValue || 0; + const showValue = []; + if (useValue >= 12 * 60) { + showValue.push('pm'); + useValue - 12 * 60; + } else { + showValue.push('am'); + } + showValue.push((useValue / 60).toFixed(0)); + + const hourPickers = []; + for (let i = 1; i <= 12; i++) { + hourPickers.push({i + ''}); + } + const minutePickers = []; + for (let i = 0; i <= 59; i++) { + const value = i < 10 ? '0' + i : i + ''; + minutePickers.push({value}); + } + + return ( +
+
{title}
+ + + 上午 + 下午 + + + {hourPickers} + + + {minutePickers} + + +
+ ); + } +} \ No newline at end of file diff --git a/src/calendar/AnimateWrapper.tsx b/src/calendar/AnimateWrapper.tsx new file mode 100644 index 0000000..d8cdbdb --- /dev/null +++ b/src/calendar/AnimateWrapper.tsx @@ -0,0 +1,23 @@ +import React from 'react'; + +interface PropsType { + visible: boolean; + className?: string; + displayType?: string; +} +export default class AnimateWrapper extends React.PureComponent { + static defaultProps = { + className: '', + displayType: 'flex', + } as PropsType; + + render() { + const { className, displayType, visible } = this.props; + + return
+ {visible && this.props.children} +
; + } +} \ No newline at end of file diff --git a/src/calendar/ConfirmPanel.tsx b/src/calendar/ConfirmPanel.tsx new file mode 100644 index 0000000..d1cfb8f --- /dev/null +++ b/src/calendar/ConfirmPanel.tsx @@ -0,0 +1,55 @@ +import * as React from 'react'; + +export interface ConfirmPanelPropsType { + disableBtn?: boolean; + startDateTime?: Date; + endDateTime?: Date; + formatStr?: string; + onConfirm: () => void; +} +export default class ConfirmPanel extends React.PureComponent { + static defaultProps = { + formatStr: 'yyyy-MM-dd hh:mm' + } as ConfirmPanelPropsType; + + formatDate(date: Date, format: string) { + let o: { [key: string]: any } = { + 'M+': date.getMonth() + 1, + 'd+': date.getDate(), + 'h+': date.getHours(), + 'm+': date.getMinutes(), + 's+': date.getSeconds(), + 'q+': Math.floor((date.getMonth() + 3) / 3), + 'S': date.getMilliseconds() + }; + if (/(y+)/.test(format)) format = format.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length)); + for (let k in o) { + if (new RegExp('(' + k + ')').test(format)) { + format = format.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length)); + } + } + return format; + } + + onConfirm = () => { + this.props.onConfirm(); + } + + render() { + const { startDateTime, endDateTime, formatStr = '', disableBtn } = this.props; + let startTimeStr = startDateTime ? this.formatDate(startDateTime, formatStr) : '未选择'; + let endTimeStr = endDateTime ? this.formatDate(endDateTime, formatStr) : '未选择'; + + return ( +
+
+

开始:{startTimeStr}

+

结束:{endTimeStr}

+
+
+ 确定 +
+
+ ); + } +} \ No newline at end of file diff --git a/src/calendar/Header.tsx b/src/calendar/Header.tsx new file mode 100644 index 0000000..2a76314 --- /dev/null +++ b/src/calendar/Header.tsx @@ -0,0 +1,55 @@ +import * as React from 'react'; + +export default class Header extends React.PureComponent<{ + locale?: Models.Locale; + showClear?: boolean; + onCancel?: () => void; + onClear?: () => void; +}, {}> { + fps: HTMLDivElement; + + setFPS = (dom: HTMLDivElement) => { + // console.log('setFPS'); + // if (!this.fps) { + // this.fps = dom; + // let time = +new Date; + // let count = 0; + // const raf = () => requestAnimationFrame(() => { + // const now = +new Date; + // if (now - time > 1 * 1000) { + // dom.innerText = `JS FPS: ${count}`; + // count = 0; + // time = now; + // console.log(dom.innerText); + // } else { + // count++; + // } + // raf(); + // }); + // raf(); + // } + } + + render() { + const { + locale = {} as Models.Locale, + onCancel, + onClear, + showClear + } = this.props; + + return ( +
+ onCancel && onCancel()}>X + {locale.title} + { + showClear && + onClear && onClear()} + >清除 + } +
+ ); + } +} \ No newline at end of file diff --git a/src/calendar/ShortcutPanel.tsx b/src/calendar/ShortcutPanel.tsx new file mode 100644 index 0000000..e905dae --- /dev/null +++ b/src/calendar/ShortcutPanel.tsx @@ -0,0 +1,14 @@ +import * as React from 'react'; + +export default class ShortcutPanel extends React.PureComponent<{}, {}> { + render() { + return ( +
+
今天
+
昨天
+
近一周
+
近一个月
+
+ ); + } +} \ No newline at end of file diff --git a/src/date/DataTypes.ts b/src/date/DataTypes.ts new file mode 100644 index 0000000..7d9459a --- /dev/null +++ b/src/date/DataTypes.ts @@ -0,0 +1,43 @@ +import SingleMonth from './SingleMonth'; + +export namespace Models { + export enum SelectType { + None, + Single, + Only, + Start, + Middle, + End, + } + + export interface CellData { + tick: number; + dayOfMonth: number; + selected: SelectType; + isFirstOfMonth: boolean; + isLastOfMonth: boolean; + outOfDate: boolean; + } + + export interface ExtraData { + /** 扩展信息 */ + info?: string; + /** 是否禁止选择 */ + disable?: boolean; + /** (web only) 附加cell样式 className */ + cellCls?: any; + cellRender?: (date: Date) => React.ReactNode; + } + + export interface MonthData { + title: string; + firstDate: Date; + lastDate: Date; + weeks: Models.CellData[][]; + component?: React.ReactNode; + height?: number; + y?: number; + updateLayout?: Function; + componentRef?: SingleMonth; + } +} \ No newline at end of file diff --git a/src/date/SingleMonth.tsx b/src/date/SingleMonth.tsx new file mode 100644 index 0000000..860dbe9 --- /dev/null +++ b/src/date/SingleMonth.tsx @@ -0,0 +1,172 @@ +import * as React from 'react'; +import { Models } from './DataTypes'; + +export interface PropsType { + monthData: Models.MonthData; + getDateExtra?: (date: Date) => Models.ExtraData; + onCellClick?: (singleMonth: SingleMonth, data: Models.CellData) => void; +} +export default class SingleMonth extends React.PureComponent { + static defaultProps = { + }; + + public wrapperDivDOM: HTMLDivElement | null; + + constructor(props: PropsType) { + super(props); + + this.state = { + weekComponents: [], + }; + } + + componentWillMount() { + this.props.monthData.weeks.forEach((week, index) => { + this.genWeek(week, index); + }); + } + + genWeek = (weeksData: Models.CellData[], index: number) => { + const { getDateExtra } = this.props; + this.state.weekComponents[index] = ( +
+ { + weeksData.map((day, dayOfWeek) => { + const extra = (getDateExtra && getDateExtra(new Date(day.tick))) || {}; + let info = extra.info; + const disable = extra.disable || day.outOfDate; + + let cls = 'date'; + let lCls = 'left'; + let rCls = 'right'; + let infoCls = 'info'; + + if (dayOfWeek === 0 || dayOfWeek === 6) { + cls += ' grey'; + } + + if (disable) { + cls += ' disable'; + } else if (info) { + cls += ' important'; + } + + if (day.selected) { + cls += ' date-selected'; + let styleType = day.selected; + switch (styleType) { + case Models.SelectType.Single: + info = '起/至'; + infoCls += ' date-selected'; + break; + + case Models.SelectType.Start: + info = '起'; + infoCls += ' date-selected'; + if (dayOfWeek === 6 || day.isLastOfMonth) { + styleType = Models.SelectType.Single; + } + break; + case Models.SelectType.Middle: + if (dayOfWeek === 0 || day.isFirstOfMonth) { + if (day.isLastOfMonth || dayOfWeek === 6) { + styleType = Models.SelectType.Single; + } else { + styleType = Models.SelectType.Start; + } + } else if (dayOfWeek === 6 || day.isLastOfMonth) { + styleType = Models.SelectType.End; + } + break; + case Models.SelectType.End: + info = '至'; + infoCls += ' date-selected'; + if (dayOfWeek === 0 || day.isFirstOfMonth) { + styleType = Models.SelectType.Single; + } + break; + } + + switch (styleType) { + case Models.SelectType.Only: + case Models.SelectType.Single: + cls += ' selected-single'; + break; + case Models.SelectType.Start: + cls += ' selected-start'; + rCls += ' date-selected'; + break; + case Models.SelectType.Middle: + cls += ' selected-middle'; + lCls += ' date-selected'; + rCls += ' date-selected'; + break; + case Models.SelectType.End: + cls += ' selected-end'; + lCls += ' date-selected'; + break; + } + } + + const defaultContent = [ +
+ +
+ {day.dayOfMonth} +
+ +
+ , +
{info}
+ ]; + + return ( +
{ + !disable && this.props.onCellClick && this.props.onCellClick(this, day); + }}> + { + extra.cellRender ? + extra.cellRender(new Date(day.tick)) + : + defaultContent + } +
+ ); + }) + } +
+ ); + } + + updateWeeks = (monthData?: Models.MonthData) => { + (monthData || this.props.monthData).weeks.forEach((week, index) => { + this.genWeek(week, index); + }); + } + + componentWillReceiveProps(nextProps: PropsType) { + if (this.props.monthData !== nextProps.monthData) { + this.updateWeeks(nextProps.monthData); + } + } + + render() { + const { title, weeks, firstDate } = this.props.monthData; + const { weekComponents } = this.state; + const todayDate = new Date(); + const today = firstDate.getMonth() === todayDate.getMonth() && todayDate.getDate(); + + return ( +
this.wrapperDivDOM = dom}> +
+ {title} +
+
+ {weekComponents} +
+
+ ); + } +} \ No newline at end of file diff --git a/src/date/WeekPanel.tsx b/src/date/WeekPanel.tsx new file mode 100644 index 0000000..f4a9c6a --- /dev/null +++ b/src/date/WeekPanel.tsx @@ -0,0 +1,17 @@ +import * as React from 'react'; + +export default class WeekPanel extends React.PureComponent<{}, {}> { + render() { + return ( +
+
+
+
+
+
+
+
+
+ ); + } +} \ No newline at end of file diff --git a/src/locale/zh_CN.ts b/src/locale/zh_CN.ts new file mode 100644 index 0000000..ec9143b --- /dev/null +++ b/src/locale/zh_CN.ts @@ -0,0 +1,7 @@ +const locale: Models.Locale = { + title: '日期选择', + today: '今天', + month: '月', + year: '年', +}; +export default locale; \ No newline at end of file diff --git a/src/typings.d.ts b/src/typings.d.ts new file mode 100644 index 0000000..fd16165 --- /dev/null +++ b/src/typings.d.ts @@ -0,0 +1,13 @@ +declare module 'rc-animate'; +declare module 'zscroller/lib/DOMScroller'; + +declare namespace Models { + + interface Locale { + /** 标题 */ + title: string, + today: string, + month: string, + year: string, + } +} \ No newline at end of file diff --git a/src/util/index.ts b/src/util/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..86b61cf --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "strictNullChecks": true, + "moduleResolution": "node", + "jsx": "preserve", + "allowSyntheticDefaultImports": true, + "target": "es6", + "noImplicitAny": true + } +} \ No newline at end of file diff --git a/tslint.json b/tslint.json new file mode 100644 index 0000000..3df5744 --- /dev/null +++ b/tslint.json @@ -0,0 +1,62 @@ +{ + "rules": { + "class-name": true, + "comment-format": [ + true, + "check-space" + ], + "indent": [ + true, + "spaces" + ], + "member-ordering": [ + true, + "public-before-private", + "static-before-instance", + "variables-before-functions" + ], + "no-conditional-assignment": true, + "no-duplicate-variable": true, + "no-eval": true, + "no-internal-module": true, + "no-trailing-whitespace": true, + "no-unused-variable": false, + "no-var-keyword": true, + "one-line": [ + true, + "check-open-brace", + "check-whitespace" + ], + "quotemark": [ + true, + "single", + "jsx-double" + ], + "semicolon": [ + true, + "always" + ], + "typedef-whitespace": [ + true, + { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + } + ], + "variable-name": [ + true, + "ban-keywords" + ], + "whitespace": [ + true, + "check-branch", + "check-decl", + "check-operator", + "check-separator", + "check-type" + ] + } +} \ No newline at end of file