diff --git a/examples/basic/index.js b/examples/basic/index.js index 05c87e6..f390123 100644 --- a/examples/basic/index.js +++ b/examples/basic/index.js @@ -25,21 +25,6 @@ import DatePicker from '../../lib/index'; } render() { - const monthMap = { - '01': 'Jan', - '02': 'Feb', - '03': 'Mar', - '04': 'Apr', - '05': 'May', - '06': 'Jun', - '07': 'Jul', - '08': 'Aug', - '09': 'Sep', - '10': 'Oct', - '11': 'Nov', - '12': 'Dec', - }; - return (

@@ -74,11 +59,27 @@ import DatePicker from '../../lib/index';

monthMap[month]], 'DD']} theme={this.state.theme} isOpen={this.state.isOpen} + showCaption + dateConfig={{ + 'year': { + format: 'YYYY', + caption: '年', + step: 1, + }, + 'month': { + format: 'M', + caption: '月', + step: 1, + }, + 'date': { + format: 'D', + caption: '日', + step: 1, + }, + }} onSelect={this.handleSelect} onCancel={this.handleToggle(false)} /> diff --git a/lib/DatePicker.js b/lib/DatePicker.js index ade5d8a..49883e2 100644 --- a/lib/DatePicker.js +++ b/lib/DatePicker.js @@ -6,6 +6,7 @@ import React, { Component } from 'react'; import DatePickerItem from './DatePickerItem.js'; import PureRender from './pureRender.js'; import { convertDate, nextDate } from './time.js'; +import { dateConfigMap } from './dataSource'; type Props = { theme: string, @@ -14,9 +15,9 @@ type Props = { max: Object, customHeader?: React.Element<*>, showHeader: boolean, - dateFormat: Array<*>, - dateSteps: Array<*>, - showFormat: string, + showCaption: boolean, + dateConfig: Object | Array, + headerFormat: string, confirmText: string, cancelText: string, onSelect: Function, @@ -27,6 +28,18 @@ type State = { value: Date, } +/** + * 大写首字母 + * @param {String} 字符串 + */ +const capitalize = ([first, ...rest]) => first.toUpperCase() + rest.join(''); + +/** + * 判断数组 + * @param {any} val + */ +const isArray = val => Object.prototype.toString.apply(val) === '[object Array]'; + /** * Class DatePicker Component Class * @extends Component @@ -38,6 +51,18 @@ class DatePicker extends Component { value: nextDate(this.props.value), }; + if ('dateFormat' in props) { + console.warn('dateFormat已经被弃用, 请使用dateConfig属性配置'); + } + + if ('dateSteps' in props) { + console.warn('dateSteps已经被弃用, 请使用dateConfig属性配置'); + } + + if ('showFormat' in props) { + console.warn('headerFormat, 请使用dateConfig属性'); + } + this.handleFinishBtnClick = this.handleFinishBtnClick.bind(this); this.handleDateSelect = this.handleDateSelect.bind(this); } @@ -96,31 +121,79 @@ class DatePicker extends Component { this.setState({ value }); } + /** + * 格式化dateConfig + * @param {*} dataConfig dateConfig属性 + */ + normalizeDateConfig(dataConfig) { + const configList = []; + if (isArray(dataConfig)) { + for (let i = 0; i < dataConfig.length; i++) { + const value = dataConfig[i]; + if (typeof value === 'string') { + const lowerCaseKey = value.toLocaleLowerCase(); + configList.push({ + ...dateConfigMap[lowerCaseKey], + type: capitalize(lowerCaseKey), + }); + } + } + } else { + for (const key in dataConfig) { + if (dataConfig.hasOwnProperty(key)) { + const lowerCaseKey = key.toLocaleLowerCase(); + if (dateConfigMap.hasOwnProperty(lowerCaseKey)) { + configList.push({ + ...dateConfigMap[lowerCaseKey], + ...dataConfig[key], + type: capitalize(lowerCaseKey), + }); + } + } + } + } + + return configList; + } + /** * render函数 * @return {Object} JSX对象 */ render() { - const { min, max, theme, dateFormat, confirmText, cancelText, showFormat, showHeader, customHeader, dateSteps } = this.props; + const { min, max, theme, dateConfig, confirmText, cancelText, headerFormat, showHeader, customHeader, showCaption } = this.props; const value = this.state.value; const themeClassName = ['default', 'dark', 'ios', 'android', 'android-dark'].indexOf(theme) === -1 ? 'default' : theme; - + + const dataConfigList = this.normalizeDateConfig(dateConfig); + return (
- {showHeader && -
{customHeader || convertDate(value, showFormat)}
} + {showHeader && ( +
+ {customHeader || convertDate(value, headerFormat)} +
+ )} + {showCaption && ( +
+ {dataConfigList.map((item, index) => ( +
{item.caption}
+ ))} +
+ )}
- {dateFormat.map((format, index) => ( + {dataConfigList.map((item, index) => ( ))}
diff --git a/lib/DatePickerItem.js b/lib/DatePickerItem.js index 372a35c..89078b2 100644 --- a/lib/DatePickerItem.js +++ b/lib/DatePickerItem.js @@ -13,35 +13,8 @@ const MIDDLE_INDEX = Math.floor(DATE_LENGTH / 2); // 日期数组中间值 const MIDDLE_Y = - DATE_HEIGHT * MIDDLE_INDEX; // translateY值 const isUndefined = val => typeof val === 'undefined'; -const isArray = val => Object.prototype.toString.apply(val) === '[object Array]'; const isFunction = val => Object.prototype.toString.apply(val) === '[object Function]'; -/** - * 根据格式获取时间滑动的类别 - * @param {String} format 格式 - * @return {string} 类别名称 - */ -const getTimeType = format => { - const typeMap = { - Y: 'Year', - M: 'Month', - D: 'Date', - h: 'Hour', - m: 'Minute', - s: 'Second', - }; - - for (const key in typeMap) { - if (typeMap.hasOwnProperty(key)) { - if (~format.indexOf(key)) { - return typeMap[key] - } - } - } - - throw new Error('时间格式必须包含 Y, M, D, h, m 或 s字母'); -} - type Props = { value: Object, min: Object, @@ -74,20 +47,6 @@ class DatePickerItem extends Component { marginTop: (this.currentIndex - MIDDLE_INDEX) * DATE_HEIGHT, }; - // 设置时间选择器单元的类别 - if (isArray(props.format)) { - this.typeName = getTimeType(props.format[0]); - this.format = props.format[0]; - if (isFunction(props.format[1])) { - this.formatTransform = props.format[1]; - } - } - - else { - this.format = props.format; - this.typeName = getTimeType(props.format); - } - this.renderDatepickerItem = this.renderDatepickerItem.bind(this); this.handleContentTouch = this.handleContentTouch.bind(this); this.handleContentMouseDown = this.handleContentMouseDown.bind(this); @@ -141,7 +100,7 @@ class DatePickerItem extends Component { } _iniDates(date) { - const typeName = this.typeName; + const typeName = this.props.type; const dates = Array(...Array(DATE_LENGTH)) .map((value, index) => TimeUtil[`next${typeName}`](date, (index - MIDDLE_INDEX) * this.props.step)); @@ -149,7 +108,7 @@ class DatePickerItem extends Component { } _updateDates(direction) { - const typeName = this.typeName; + const typeName = this.props.type; const { dates } = this.state; if (direction === 1) { this.currentIndex ++; @@ -325,9 +284,11 @@ class DatePickerItem extends Component { (date < this.props.min || date > this.props.max) ? 'disabled' : ''; - let formatDate = TimeUtil.convertDate(date, this.format); - if (this.formatTransform) { - formatDate = this.formatTransform(formatDate); + let formatDate; + if (isFunction(this.props.format)) { + formatDate = this.props.format(date); + } else { + formatDate = TimeUtil.convertDate(date, this.props.format); } return ( diff --git a/lib/dataSource.js b/lib/dataSource.js new file mode 100644 index 0000000..d9a1f85 --- /dev/null +++ b/lib/dataSource.js @@ -0,0 +1,75 @@ + +/** + * 默认属性 + */ +export const defaultProps = { + isPopup: true, + isOpen: false, + theme: 'default', + value: new Date(), + min: new Date(1970, 0, 1), + max: new Date(2050, 0, 1), + showHeader: true, + showCaption: false, + dateConfig: { + 'year': { + format: 'YYYY', + caption: 'Year', + step: 1, + }, + 'month': { + format: 'M', + caption: 'Mon', + step: 1, + }, + 'date': { + format: 'D', + caption: 'Day', + step: 1, + }, + }, + headerFormat: 'YYYY/MM/DD', + confirmText: '完成', + cancelText: '取消', + onSelect: () => {}, + onCancel: () => {}, +}; + +/** + * 日期配置 + */ +export const dateConfigMap = { + 'year': { + format: 'YYYY', + caption: 'Year', + step: 1, + }, + 'month': { + format: 'M', + caption: 'Mon', + step: 1, + }, + 'date': { + format: 'D', + caption: 'Day', + step: 1, + }, + 'hour': { + format: 'hh', + caption: 'Hour', + step: 1, + }, + 'minute': { + format: 'mm', + caption: 'Min', + step: 1, + }, + 'second': { + format: 'hh', + caption: 'Sec', + step: 1, + }, +}; + + + diff --git a/lib/index.css b/lib/index.css index 7682fa8..b8cfe8f 100644 --- a/lib/index.css +++ b/lib/index.css @@ -70,6 +70,19 @@ cursor: pointer; } + .datepicker-caption { + display: flex; + padding: .5em .25em; + } + + .datepicker-caption-item { + flex: 1; + margin: 0 .25em; + height: 40px; + line-height: 40px; + font-size: 1.2em; + } + .datepicker-content { display: flex; padding: .5em .25em; @@ -129,6 +142,9 @@ border-top: 1px solid var(--default-theme); border-bottom: 1px solid var(--default-theme); } + .datepicker-caption-item { + color: var(--default-color); + } .datepicker-scroll { li { color: var(--default-color); @@ -154,6 +170,9 @@ border-top: 1px solid var(--dark-theme); border-bottom: 1px solid var(--dark-theme); } + .datepicker-caption-item { + color: var(--dark-color); + } .datepicker-scroll { li { color: var(--dark-color); @@ -186,6 +205,9 @@ border-top: 1px solid var(--ios-theme); border-bottom: 1px solid var(--ios-theme); } + .datepicker-caption-item { + color: var(--ios-color); + } .datepicker-scroll { li { color: var(--ios-color); @@ -219,6 +241,10 @@ .datepicker-header + .datepicker-content { padding-top: 0; } + + .datepicker-caption + .datepicker-content { + padding-top: 0; + } } @@ -240,7 +266,9 @@ border-top: 2px solid var(--android-theme); border-bottom: 2px solid var(--android-theme); } - + .datepicker-caption-item { + color: var(--android-color); + } .datepicker-scroll { li { font-size: 1.125em; @@ -275,6 +303,9 @@ background-image: linear-gradient(#282828,rgba(40,40,40,0)52%,rgba(40,40,40,0)48%,#282828); } } + .datepicker-caption-item { + color: var(--android-dark-color); + } .datepicker-scroll { li { color: var(--android-dark-color); diff --git a/lib/index.js b/lib/index.js index bc09e47..b67cba2 100644 --- a/lib/index.js +++ b/lib/index.js @@ -2,6 +2,7 @@ import './index.css'; import React from 'react'; import DatePicker from './DatePicker.js'; import Modal from './Modal.js'; +import {defaultProps} from './dataSource'; type EnhanceDatePickerProps = T & { isOpen: boolean, @@ -40,21 +41,6 @@ function ModalDatePicker({ isPopup, ...props }: ModalDatePickerProps) { ); } -ModalDatePicker.defaultProps = { - isPopup: true, - isOpen: false, - theme: 'default', - value: new Date(), - min: new Date(1970, 0, 1), - max: new Date(2050, 0, 1), - showHeader: true, - dateFormat: ['YYYY', 'M', 'D'], - dateSteps: [1, 1, 1], - showFormat: 'YYYY/MM/DD', - confirmText: '完成', - cancelText: '取消', - onSelect: () => {}, - onCancel: () => {}, -}; +ModalDatePicker.defaultProps = defaultProps; export default ModalDatePicker; diff --git a/test/functional/DatePickerItem_spec.js b/test/functional/DatePickerItem_spec.js index 1a9779b..f9c6ada 100644 --- a/test/functional/DatePickerItem_spec.js +++ b/test/functional/DatePickerItem_spec.js @@ -23,7 +23,7 @@ describe('DatePickerItem.js', () => { it('should call componentWillMount and initialize dates of state', () => { const spyFunction = sinon.spy(DatePickerItem.prototype, 'componentWillMount'); const datePicker = mount( - + ); const dates = datePicker.state('dates'); sinon.assert.calledOnce(spyFunction); @@ -38,7 +38,7 @@ describe('DatePickerItem.js', () => { it('componentWillReceiveProps', () => { const spyFunction = sinon.spy(DatePickerItem.prototype, 'componentWillReceiveProps'); const datePicker = mount( - + ); datePicker.setProps({ date: new Date(2010, 3, 10) }); const dates = datePicker.state('dates'); @@ -54,7 +54,7 @@ describe('DatePickerItem.js', () => { it('shouldComponentUpdate', () => { const spyFunction = sinon.spy(DatePickerItem.prototype, 'shouldComponentUpdate'); const datePicker = mount( - + ); datePicker.setProps({ value: new Date(2010, 3, 10) }); @@ -68,7 +68,7 @@ describe('DatePickerItem.js', () => { it('should call handleContent three times after touching', () => { const spyFunction = sinon.spy(DatePickerItem.prototype, 'handleContentTouch'); const datePicker = mount( - + ); const touchstartEvent = { @@ -96,7 +96,7 @@ describe('DatePickerItem.js', () => { it('should analyzing the right direction', () => { const spyFunction = sinon.spy(DatePickerItem.prototype, '_moveToNext'); const datePicker = mount( - + ); const touchstartEvent = { targetTouches: [{ pageY: 0 }], @@ -113,7 +113,7 @@ describe('DatePickerItem.js', () => { expect(spyFunction.getCall(0).args[0]).to.equal(-1); const datePicker2 = mount( - + ); const touchstartEvent2 = { targetTouches: [{ pageY: 0 }], @@ -135,7 +135,7 @@ describe('DatePickerItem.js', () => { it('should update dates of state, When the sliding more than 20', () => { const spyFunction = sinon.spy(DatePickerItem.prototype, '_updateDates'); const datePicker = mount( - + ); const touchstartEvent = { targetTouches: [{ pageY: 0 }], @@ -164,7 +164,7 @@ describe('DatePickerItem.js', () => { const spyFunction = sinon.spy(DatePickerItem.prototype, '_moveTo'); const datePicker = mount( - + ); const touchstartEvent = { diff --git a/test/functional/DatePicker_spec.js b/test/functional/DatePicker_spec.js index 03c3616..bfdbe55 100644 --- a/test/functional/DatePicker_spec.js +++ b/test/functional/DatePicker_spec.js @@ -15,8 +15,23 @@ const DEFAULT_PROPS = { value: new Date(2016, 8, 16), min: new Date(2015, 10, 1), max: new Date(2020, 10, 1), - dateFormat: ['YYYY', 'M', 'D'], - dateSteps: [1, 1, 1], + dateConfig: { + 'year': { + format: 'YYYY', + caption: 'Year', + step: 1, + }, + 'month': { + format: 'M', + caption: 'Mon', + step: 1, + }, + 'date': { + format: 'D', + caption: 'Day', + step: 1, + }, + }, isOpen: true, } @@ -287,13 +302,22 @@ describe('渲染正确的DatepicketItem子组件', () => { beforeEach(() => { props = { value: new Date(2016, 8, 16), - dateSteps: [1, 1, 1] }; mountedDatepicker = undefined; }); it('当dateFormat等于[YYYY, MM, DD]', () => { - props.dateFormat = ['YYYY', 'MM', 'DD']; + props.dateConfig = { + 'year': { + format: 'YYYY', + }, + 'month': { + format: 'MM', + }, + 'date': { + format: 'DD', + }, + }; const datePickerItems = datePicker().find(DatePickerItem); expect(datePickerItems.length).to.equals(3); expect(datePickerItems.at(0).props().format).to.equals('YYYY'); @@ -302,7 +326,26 @@ describe('渲染正确的DatepicketItem子组件', () => { }); it('当dateFormat等于[YYYY, MM, DD, hh, mm, ss]', () => { - props.dateFormat = ['YYYY', 'MM', 'DD', 'hh', 'mm', 'ss']; + props.dateConfig = { + 'year': { + format: 'YYYY', + }, + 'month': { + format: 'MM', + }, + 'date': { + format: 'DD', + }, + 'hour': { + format: 'hh', + }, + 'minute': { + format: 'mm', + }, + 'second': { + format: 'ss', + }, + }; const datePickerItems = datePicker().find(DatePickerItem); expect(datePickerItems.length).to.equals(6); expect(datePickerItems.at(0).props().format).to.equals('YYYY'); @@ -314,7 +357,17 @@ describe('渲染正确的DatepicketItem子组件', () => { }); it('当dateFormat等于[hh, mm, ss]', () => { - props.dateFormat = ['hh', 'mm', 'ss']; + props.dateConfig = { + 'hour': { + format: 'hh', + }, + 'minute': { + format: 'mm', + }, + 'second': { + format: 'ss', + }, + }; const datePickerItems = datePicker().find(DatePickerItem); expect(datePickerItems.length).to.equals(3); expect(datePickerItems.at(0).props().format).to.equals('hh'); @@ -323,7 +376,7 @@ describe('渲染正确的DatepicketItem子组件', () => { }); }); -describe('测试dateSteps属性', () => { +describe('测试step', () => { let props; let mountedDatepicker; let yearPicker, monthPicker, dayPicker; @@ -357,8 +410,20 @@ describe('测试dateSteps属性', () => { it ('当datesteps等于[5, 5, 5], dateFormart等于[hh, mm, ss], 当前时间为8:20:57,向上滑动秒,分钟应该为23', () => { - props.dateFormat = ['hh', 'mm', 'ss']; - props.dateSteps = [1, 1, 5]; + props.dateConfig = { + 'hour': { + format: 'hh', + step: 1, + }, + 'minute': { + format: 'mm', + step: 1, + }, + 'second': { + format: 'ss', + step: 5, + }, + }; const datePickerItems = datePicker().find(DatePickerItem); const second = dayPicker.find('.datepicker-viewport').instance(); @@ -385,8 +450,21 @@ describe('测试dateSteps属性', () => { it ('当datesteps等于[5, 5, 5], dateFormart等于[hh, mm, ss], 当前时间为8:20:57,向上滑动秒,最大时间是8:20:59, 分钟应该为22', () => { - props.dateFormat = ['hh', 'mm', 'ss']; - props.dateSteps = [1, 1, 5]; + props.dateConfig = { + 'hour': { + format: 'hh', + step: 1, + }, + 'minute': { + format: 'mm', + step: 1, + }, + 'second': { + format: 'ss', + step: 5, + }, + }; + props.max = new Date(2016, 8, 16, 8, 22, 59); const datePickerItems = datePicker().find(DatePickerItem); @@ -412,3 +490,55 @@ describe('测试dateSteps属性', () => { }) }); }); + + +describe('测试showCaption属性', () => { + let props; + let mountedDatepicker; + + const datePicker = () => { + if (!mountedDatepicker) { + mountedDatepicker = mount( + + ); + } + + return mountedDatepicker; + } + + beforeEach(() => { + props = { + value: new Date(2016, 8, 16, 8, 22, 57), + isOpen: true, + }; + mountedDatepicker = undefined; + }); + + it ('不显示说明', () => { + const wrapper = datePicker(); + expect(wrapper.find('.datepicker-caption')) + expect(wrapper.find('.datepicker-caption')).to.have.lengthOf(0); + }); + it ('显示说明', () => { + props.showCaption = true; + props.dateConfig = { + 'month': { + format: 'M', + caption: 'Month', + step: 1, + }, + 'date': { + format: 'D', + caption: 'Day', + step: 1, + }, + 'hour': { + format: 'hh', + caption: 'Hour', + step: 1, + }, + }; + const wrapper = datePicker(); + expect(wrapper.find('.datepicker-caption').text()).to.be.equal('MonthDayHour'); + }); +}) \ No newline at end of file