tuya-panel-kit
Version:
a functional component library for developing tuya device panels!
586 lines (556 loc) • 16.9 kB
JavaScript
/* eslint-disable react/require-default-props */
import React from 'react';
import { View, ViewPropTypes, StyleSheet } from 'react-native';
import PropTypes from 'prop-types';
import defaultLocale from './locale/en_US';
import cnLocale from './locale/zh_CN';
import Picker from '../picker-view';
import { RatioUtils, ThemeUtils } from '../../utils';
const { convertX: cx } = RatioUtils;
const { getPropsFromStyle } = ThemeUtils;
const DATETIME = 'datetime';
const DATE = 'date';
const TIME = 'time';
const MONTH = 'month';
const YEAR = 'year';
const ONEDAY = 24 * 60 * 60 * 1000;
function plusZero(n) {
return n < 10 ? `0${n}` : `${n}`;
}
const capitalized = str => str.charAt(0).toUpperCase() + str.slice(1); // 首字母大写
const sortColumnsAndValue = (dateSortKeys, cols, value) => {
if (!dateSortKeys || !Array.isArray(dateSortKeys) || dateSortKeys.length !== 3) {
dateSortKeys &&
console.warn(
`dateSortKeys: ${JSON.stringify(
dateSortKeys
)} 不合法,必须为长度为3的数组,且值必须为'year' || 'month' || 'day`
);
return { cols, value };
}
const sortedCols = [];
const sortedValue = [];
dateSortKeys.forEach(k => {
const colIndex = cols.findIndex(col => col.key === k);
colIndex !== -1 && sortedCols.push(cols[colIndex]);
colIndex !== -1 && sortedValue.push(value[colIndex]);
});
return { cols: sortedCols, value: sortedValue };
};
const formatColArray = (arrLength, min, labelLocal, isPlusZero) => {
return Array.from(Array(arrLength), (v, k) => {
const finalLabelLocal = `${labelLocal || ''}`;
const finalLabelMain = isPlusZero ? `${plusZero(k + min)}` : `${k + min}`;
const label = `${finalLabelMain}${finalLabelLocal}`;
return { value: `${k + min}`, label };
});
};
// get how many days
const getDaysInMonth = date => new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate();
const setMonth = (date, month) => {
const days = getDaysInMonth(new Date(date.getFullYear(), month));
date.setDate(Math.min(date.getDate(), days));
date.setMonth(month);
};
class DatePicker extends React.Component {
static propTypes = {
/**
* 测试标志
*/
accessibilityLabel: PropTypes.string,
/**
* 多语言设置
*/
locale: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
/**
* 选择器的选择类型
*/
mode: PropTypes.string,
/**
* 选择picker是否可循环
*/
loop: PropTypes.bool,
/**
* 是否为12小时制
*/
use12Hours: PropTypes.bool,
/**
* 月、日、时、分,是否补0显示
*/
isPlusZero: PropTypes.bool,
/**
* 设置最小可选择的值
*/
minDate: PropTypes.object,
/**
* 设置最大可选择的值
*/
maxDate: PropTypes.object,
/**
* 某一项被选中时执行此回调
*/
onDateChange: PropTypes.func,
/**
* 某一项被选中时执行此回调
*/
onValueChange: PropTypes.func,
/**
* `AM / PM` Picker项是否位于 `小时`及`分钟` 之前
*/
isAmpmFirst: PropTypes.bool,
/**
* `小时`及`分钟` Picker项是否位于 `年` `月` `日` 之前
*/
isTimeFirst: PropTypes.bool,
/**
* 当前选中的值,设置了该属性即为受控组件
*/
date: PropTypes.object,
/**
* 默认选中的值
*/
defaultDate: PropTypes.object,
/**
* 容器样式
*/
style: ViewPropTypes.style,
/**
* picker里字体颜色
*/
pickerFontColor: PropTypes.string,
/**
* `年` `月` `日` 排序规则,若不提供则默认为年月日
*/
dateSortKeys: PropTypes.array,
};
static defaultProps = {
accessibilityLabel: 'DatePicker',
mode: DATE,
loop: false,
use12Hours: false,
isPlusZero: true,
isAmpmFirst: false,
isTimeFirst: false,
locale: 'en',
minDate: new Date(2000, 0, 1, 0, 0, 0),
maxDate: new Date(2030, 11, 31, 23, 59, 59),
onDateChange: () => {},
onValueChange: () => {},
pickerFontColor: '#333',
dateSortKeys: null,
// disabled: false,
};
constructor(props) {
super(props);
this.state = {
date: props.date || props.defaultDate,
};
this.i18n(props.locale);
}
componentWillReceiveProps(nextProps) {
if ('date' in nextProps) {
this.setState({ date: nextProps.date || nextProps.defaultDate });
}
this.i18n(nextProps.locale);
}
onValueChange = (value, index, key) => {
const newValue = this.getNewDate(value, index, key);
if (!('date' in this.props)) {
this.setState({ date: newValue });
}
if (this.props.onDateChange) {
this.props.onDateChange(newValue);
}
if (this.props.onValueChange) {
this.props.onValueChange(value, index);
}
};
// get now date
getDate() {
return this.getRealDate(this.state.date || this.props.minDate);
}
getRealHour(hour) {
if (this.props.use12Hours) {
let resultHours = hour;
if (hour === 0) {
resultHours = 12;
}
if (hour > 12) {
resultHours -= 12;
}
return resultHours;
}
return hour;
}
setHours(date, hour) {
if (this.props.use12Hours) {
const dh = date.getHours();
let nhour = hour;
nhour = dh >= 12 ? hour + 12 : hour;
nhour = nhour >= 24 ? 0 : nhour;
date.setHours(nhour);
} else {
date.setHours(hour);
}
}
setAmPm(date, index) {
if (index === 0) {
date.setTime(+date - ONEDAY / 2);
} else {
date.setTime(+date + ONEDAY / 2);
}
}
getNewDate = (values, index, key) => {
const value = parseInt(values, 10);
const { mode } = this.props;
const newValue = new Date(this.getDate());
if (mode === DATETIME || mode === DATE || mode === YEAR || mode === MONTH) {
switch (key) {
case 'year':
newValue.setFullYear(value);
break;
case 'month':
setMonth(newValue, value - 1);
break;
case 'day':
newValue.setDate(value);
break;
case 'hour':
this.setHours(newValue, value);
break;
case 'minute':
newValue.setMinutes(value);
break;
case 'ampm':
this.setAmPm(newValue, value);
break;
default:
break;
}
} else if (mode === TIME) {
switch (key) {
case 'hour':
this.setHours(newValue, value);
break;
case 'minute':
newValue.setMinutes(value);
break;
case 'ampm':
this.setAmPm(newValue, value);
break;
default:
break;
}
}
return this.getRealDate(newValue);
};
// get time data
getTimeColsData = date => {
let minMinute = 0;
let maxMinute = 59;
let minHour = 0;
let maxHour = 23;
const { mode, use12Hours, isPlusZero, isAmpmFirst, minDate, maxDate } = this.props;
const { locale } = this;
const minDateMinute = minDate.getMinutes();
const maxDateMinute = maxDate.getMinutes();
const minDateHour = minDate.getHours();
const maxDateHour = maxDate.getHours();
const nowHour = date.getHours();
if (mode === DATETIME) {
const year = date.getFullYear();
const month = date.getMonth();
const day = date.getDate();
const minDateYear = minDate.getFullYear();
const maxDateYear = maxDate.getFullYear();
const minDateMonth = minDate.getMonth();
const maxDateMonth = maxDate.getMonth();
const minDateDay = minDate.getDate();
const maxDateDay = maxDate.getDate();
if (minDateYear === year && minDateMonth === month && minDateDay === day) {
minHour = minDateHour;
if (minDateHour === nowHour) {
minMinute = minDateMinute;
}
}
if (maxDateYear === year && maxDateMonth === month && maxDateDay === day) {
maxHour = maxDateHour;
if (maxDateHour === nowHour) {
maxMinute = maxDateMinute;
}
}
} else {
minHour = minDateHour;
if (minDateHour === nowHour) {
minMinute = minDateMinute;
}
maxHour = maxDateHour;
if (maxDateHour === nowHour) {
maxMinute = maxDateMinute;
}
}
let ampmCols = [];
// todo: minDate and maxDate
if (use12Hours) {
// let ampm = [];
// if (minHour > 12 && maxHour > 12) {
// ampm.push({ value: '1', label: locale.pm })
// } else if (minHour <= 12 && maxHour <= 12) {
// ampm.push({ value: '0', label: locale.am })
// } else {
// ampm = [{ value: '0', label: locale.am }, { value: '1', label: locale.pm }];
// }
const ampm = [
{ value: '0', label: locale.am },
{ value: '1', label: locale.pm },
];
ampmCols = [{ key: 'ampm', values: ampm }];
}
let hour = [];
if ((minHour === 0 && maxHour === 0) || (minHour !== 0 && maxHour !== 0)) {
minHour = this.getRealHour(minHour);
} else if (minHour === 0 && use12Hours) {
minHour = 1;
hour.push({ value: '0', label: locale.hour ? `12${locale.hour}` : '12' });
}
maxHour = this.getRealHour(maxHour);
const hours = formatColArray(maxHour - minHour + 1, minHour, locale.hour || '', isPlusZero);
hour = hour.concat(hours);
const hourCols = { key: 'hour', values: hour };
const nowMinute = date.getMinutes();
const minute = formatColArray(
maxMinute - minMinute + 1,
minMinute,
locale.minute || '',
isPlusZero
);
const minuteCols = { key: 'minute', values: minute };
const cols = !isAmpmFirst
? [hourCols, minuteCols].concat(ampmCols)
: ampmCols.concat([hourCols, minuteCols]);
return { cols, nowMinute, nowHour };
};
// get the correct date for pciker
getRealDate = date => {
const { mode, minDate, maxDate } = this.props;
switch (mode) {
case DATETIME:
if (date < minDate) {
return new Date(+minDate);
}
if (date > maxDate) {
return new Date(+maxDate);
}
break;
case TIME:
{
const maxHour = maxDate.getHours();
const maxMinutes = maxDate.getMinutes();
const minHour = minDate.getHours();
const minMinutes = minDate.getMinutes();
const hour = date.getHours();
const minutes = date.getMinutes();
if (hour < minHour || (hour === minHour && minutes < minMinutes)) {
return new Date(+minDate);
}
if (hour > maxHour || (hour === maxHour && minMinutes > maxMinutes)) {
return new Date(+maxDate);
}
}
break;
default:
if (date >= +maxDate + ONEDAY) {
return new Date(+maxDate);
}
if (+date + ONEDAY <= minDate) {
return new Date(+minDate);
}
break;
}
return date;
};
// get col data
getDateColsData = () => {
const { mode, maxDate, minDate, isPlusZero } = this.props;
const { locale } = this;
const date = this.getDate();
const nowYear = date.getFullYear();
const nowMonth = date.getMonth();
const maxDateYear = maxDate.getFullYear();
const minDateYear = minDate.getFullYear();
const minDateMonth = minDate.getMonth();
const maxDateMonth = maxDate.getMonth();
const minDateDay = minDate.getDate();
const maxDateDay = maxDate.getDate();
const year = formatColArray(maxDateYear - minDateYear + 1, minDateYear, locale.year);
const yearCol = { key: 'year', values: year };
if (mode === YEAR) {
return [yearCol];
}
let minMonth = 0;
let maxMonth = 11;
if (minDateYear === nowYear) {
minMonth = minDateMonth;
}
if (maxDateYear === nowYear) {
maxMonth = maxDateMonth;
}
const month = formatColArray(maxMonth - minMonth + 1, minMonth + 1, locale.month, isPlusZero);
const monthCol = { key: 'month', values: month };
if (mode === MONTH) {
return [yearCol, monthCol];
}
let minDay = 1;
let maxDay = getDaysInMonth(date);
if (minDateYear === nowYear && minDateMonth === nowMonth) {
minDay = minDateDay;
}
if (maxDateYear === nowYear && maxDateMonth === nowMonth) {
maxDay = maxDateDay;
}
const day = formatColArray(maxDay - minDay + 1, minDay, locale.day, isPlusZero);
const dayCol = { key: 'day', values: day };
return [yearCol, monthCol, dayCol];
};
// get picker selectItems and currentValue
getIndexAndCols = () => {
const { mode, use12Hours, isAmpmFirst, isTimeFirst, dateSortKeys } = this.props;
const date = this.getDate();
const cols = [];
const value = [];
if (mode === YEAR) {
return {
cols: this.getDateColsData(),
value: [`${date.getFullYear()}`],
};
}
if (mode === MONTH) {
const unSortDateCols = this.getDateColsData();
const unSortDateValue = [`${date.getFullYear()}`, `${date.getMonth() + 1}`];
return sortColumnsAndValue(dateSortKeys, unSortDateCols, unSortDateValue);
}
if (mode === DATE) {
const unSortDateCols = this.getDateColsData();
const unSortDateValue = [
`${date.getFullYear()}`,
`${date.getMonth() + 1}`,
`${date.getDate()}`,
];
return sortColumnsAndValue(dateSortKeys, unSortDateCols, unSortDateValue);
}
const time = this.getTimeColsData(date);
let realhour = time.nowHour;
const timeValue = [`${realhour}`, `${time.nowMinute}`];
if (use12Hours) {
realhour = time.nowHour === 0 ? 12 : time.nowHour > 12 ? time.nowHour - 12 : time.nowHour;
timeValue[0] = `${realhour}`;
const ampmStr = `${time.nowHour >= 12 ? 1 : 0}`;
if (isAmpmFirst) {
timeValue.splice(0, 0, ampmStr);
} else {
timeValue.push(ampmStr);
}
}
if (mode === DATETIME) {
const unSortDateCols = this.getDateColsData();
const unSortDateValue = [
`${date.getFullYear()}`,
`${date.getMonth() + 1}`,
`${date.getDate()}`,
];
const { cols: sortDateCols, value: sortDateValue } = sortColumnsAndValue(
dateSortKeys,
unSortDateCols,
unSortDateValue
);
return {
cols: isTimeFirst ? [...time.cols, ...sortDateCols] : [...sortDateCols, ...time.cols],
value: isTimeFirst ? [...timeValue, ...sortDateValue] : [...sortDateValue, ...timeValue],
};
}
if (mode === TIME) {
return {
cols: time.cols,
value: [...timeValue],
};
}
return {
cols,
value,
};
};
i18n = locale => {
if (typeof locale === 'string') {
this.locale = locale === 'cn' ? cnLocale : defaultLocale;
} else if (typeof locale === 'object') {
this.locale = Object.assign({}, defaultLocale, locale);
} else {
this.locale = defaultLocale;
}
};
render() {
const { value, cols } = this.getIndexAndCols();
let pickerFontSize = cx(30);
if (cols.length < 3) {
pickerFontSize = cx(30);
} else if (cols.length === 3) {
pickerFontSize = cx(27);
} else {
pickerFontSize = cx(24);
}
const {
locale,
mode,
use12Hours,
minDate,
maxDate,
onDateChange,
onValueChange,
isAmpmFirst,
date,
defaultDate,
style,
loop,
pickerFontColor,
accessibilityLabel,
...PickerProps
} = this.props;
const multiStyle = {
flexDirection: 'row',
alignItems: 'center',
paddingLeft: 5,
paddingRight: 5,
backgroundColor: '#fff',
height: 216,
};
const bgColor = getPropsFromStyle('backgroundColor', style);
return (
<View style={[multiStyle, style]}>
{cols.map((pItem, pindex) => (
<Picker
theme={{ fontColor: pickerFontColor, fontSize: pickerFontSize }}
{...PickerProps}
style={{ flex: 1, backgroundColor: bgColor || '#fff' }}
key={pItem.key}
accessibilityLabel={`${accessibilityLabel}_${capitalized(pItem.key)}`}
// disabled={disabled}
loop={pItem.key !== 'ampm' && loop}
selectedItemTextColor={pickerFontColor}
itemStyle={StyleSheet.flatten([{ color: pickerFontColor }, PickerProps.itemStyle])}
selectedValue={value[pindex]}
onValueChange={dateValue => this.onValueChange(dateValue, pindex, pItem.key)}
>
{pItem.values.map(item => (
<Picker.Item
key={`${pItem.key}_${item.value}`}
value={item.value}
label={item.label}
/>
))}
</Picker>
))}
</View>
);
}
}
export default DatePicker;