-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
22 changed files
with
626 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,300 @@ | ||
|
||
import {Component} from 'react'; | ||
import * as React from 'react'; | ||
import * as classNames from 'classnames'; | ||
import {IBaseComponent, TSize} from '../template/component'; | ||
import Input from '../Input'; | ||
import Icon from '../Icon'; | ||
import PopOver from '../PopOver'; | ||
import * as moment from 'moment'; | ||
|
||
export interface IDatePickerProps extends IBaseComponent { | ||
/** | ||
* 默认提示文本 | ||
*/ | ||
placeholder?: string; | ||
/** | ||
* 默认值 | ||
*/ | ||
defaultValue?: string; | ||
/** | ||
* 值 | ||
*/ | ||
value?: string; | ||
/** | ||
* 禁用 | ||
*/ | ||
disabled?: boolean; | ||
/** | ||
* 值变化回调 | ||
*/ | ||
onChange?: (v: string) => void; | ||
/** | ||
* 面板是否展开 - 受控 | ||
*/ | ||
open?: boolean; | ||
/** | ||
* 面板是否展开 - 非受控 | ||
*/ | ||
defaultOpen?: boolean; | ||
/** | ||
* 面板展开回调 | ||
*/ | ||
onOpenChange?: (v: boolean) => void; | ||
/** | ||
* 尺寸 | ||
*/ | ||
size?: TSize; | ||
/** | ||
* 展示时间格式 | ||
*/ | ||
format?: string; | ||
} | ||
|
||
export interface IDatePickerState { | ||
open: boolean; | ||
value: string; | ||
} | ||
|
||
/** | ||
* **组件中文名称**-组件描述。 | ||
*/ | ||
export class DatePicker extends Component<IDatePickerProps, IDatePickerState> { | ||
preCls = 'yoshino-datepicker'; | ||
|
||
static defaultProps = { | ||
size: 'default', | ||
placeholder: '请选择日期', | ||
defaultOpen: false, | ||
defaultValue: '', | ||
format: 'YYYY-MM-DD', | ||
}; | ||
|
||
state = { | ||
open: this.props.defaultOpen!, | ||
value: this.props.defaultValue!, | ||
}; | ||
|
||
getOpen = () => { | ||
const { open } = this.props; | ||
return open !== undefined ? open : this.state.open; | ||
} | ||
|
||
getValue = () => { | ||
const { value } = this.props; | ||
return value !== undefined ? value : this.state.value; | ||
} | ||
|
||
onOpenChange = (open: boolean) => { | ||
const { onOpenChange } = this.props; | ||
if (onOpenChange) { | ||
onOpenChange(open); | ||
} | ||
this.setState({ | ||
open, | ||
}); | ||
} | ||
|
||
onChange = (value: string) => { | ||
const { onChange } = this.props; | ||
if (onChange) { | ||
onChange(value); | ||
} | ||
this.setState({ | ||
value, | ||
}); | ||
} | ||
|
||
renderPop = () => { | ||
const valueR = this.getValue(); | ||
const preCls = this.preCls; | ||
const { format, placeholder } = this.props; | ||
const m = valueR ? moment(valueR, format) : moment(); | ||
const weekArr = '日一二三四五六'.split(''); | ||
const dayArr: Array<{ | ||
tag: '-1' | '0' | '1'; // -1上月 0 本月 1下个月 | ||
v: number; | ||
moment: string; | ||
}> = []; // 7 * 6 = 42; | ||
const currentMonthMax = moment([m.year(), m.month()]).endOf('month').date(); | ||
const currentMonthWeek = moment([m.year(), m.month(), 1]).weekday(); | ||
const preMonthMax = moment([m.year(), m.month()]).subtract(1, 'month').endOf('month').date(); | ||
// 填充跟上个月的末尾天 | ||
if (currentMonthWeek !== 7) { | ||
for (let i = 0; i < currentMonthWeek; i++) { | ||
const date = preMonthMax - i; | ||
dayArr.push({ | ||
v: date, | ||
tag: '-1', | ||
moment: moment([m.year(), m.month(), date]).subtract(1, 'month').format(format), | ||
}); | ||
} | ||
} | ||
|
||
// 本月天数填充 | ||
for (let i = 0; i < currentMonthMax; i++) { | ||
const date = 1 + i; | ||
dayArr.push({ | ||
v: 1 + i, | ||
tag: '0', | ||
moment: moment([m.year(), m.month(), date]).format(format), | ||
}); | ||
} | ||
|
||
// 填充下个月开始天数 | ||
for (let i = 0; dayArr.length < 42; i++) { | ||
const date = 1 + i; | ||
dayArr.push({ | ||
v: 1 + i, | ||
tag: '1', | ||
moment: moment([m.year(), m.month(), date]).add(1, 'month').format(format), | ||
}); | ||
} | ||
|
||
const renderDate = () => { | ||
let trKey = 0; | ||
let count = 0; | ||
const arr = []; | ||
let temp: JSX.Element[] = []; | ||
|
||
for (const day of dayArr) { | ||
const isCurrent = m.format(format) === day.moment; | ||
const cls = classNames( | ||
`${preCls}-table-cell`, | ||
{ | ||
[`${preCls}-not-current-month`]: day.tag !== '0', | ||
[`${preCls}-selected-day`]: isCurrent, | ||
} | ||
); | ||
temp.push( | ||
<td | ||
key={count++} | ||
onClick={() => { | ||
let newM = moment([m.year(), m.month(), m.date()]); | ||
const tagAction = { | ||
'-1': () => { | ||
newM = newM.subtract(1, 'month'); | ||
}, | ||
'0': () => -1, | ||
'1': () => { | ||
newM = newM.add(1, 'month'); | ||
}, | ||
}; | ||
tagAction[day.tag](); | ||
this.onChange(newM.date(day.v).format(format)); | ||
this.onOpenChange(false); | ||
}} | ||
> | ||
<div className={cls}>{day.v}</div> | ||
</td> | ||
); | ||
|
||
if (count === 7) { | ||
arr.push(<tr key={trKey++}>{temp}</tr>); | ||
count = 0; | ||
temp = []; | ||
} | ||
} | ||
|
||
return arr; | ||
}; | ||
return ( | ||
<div className={`${preCls}-panel`}> | ||
<div className={`${preCls}-input-wrap`}> | ||
<input | ||
placeholder={placeholder} | ||
readOnly | ||
value={valueR} | ||
/> | ||
<Icon | ||
type='ios-close-circle' | ||
onClick={this.onChange.bind(this, '')} | ||
/> | ||
</div> | ||
<div className={`${preCls}-header`}> | ||
<Icon | ||
type='ios-rewind' | ||
className={`${preCls}-year-pre`} | ||
onClick={() => { | ||
this.onChange(m.subtract(1, 'year').format(format)); | ||
}} | ||
/> | ||
<Icon | ||
type='md-arrow-dropleft' | ||
className={`${preCls}-month-pre`} | ||
onClick={() => { | ||
this.onChange(m.subtract(1, 'month').format(format)); | ||
}} | ||
/> | ||
<span className={`${preCls}-time`}> | ||
{m.format('YYYY年MM月')} | ||
</span> | ||
<Icon | ||
type='md-arrow-dropright' | ||
className={`${preCls}-month-next`} | ||
onClick={() => { | ||
this.onChange(m.add(1, 'month').format(format)); | ||
}} | ||
/> | ||
<Icon | ||
type='ios-fastforward' | ||
className={`${preCls}-year-next`} | ||
onClick={() => { | ||
this.onChange(m.add(1, 'year').format(format)); | ||
}} | ||
/> | ||
</div> | ||
<div className={`${preCls}-body`}> | ||
<table> | ||
<thead> | ||
<tr> | ||
{weekArr.map((item, key) => <th key={key}>{item}</th>)} | ||
</tr> | ||
</thead> | ||
<tbody> | ||
{renderDate()} | ||
</tbody> | ||
</table> | ||
</div> | ||
</div> | ||
); | ||
} | ||
|
||
render() { | ||
const { | ||
className, style, size, open, defaultOpen, | ||
value, defaultValue, onChange, onOpenChange, | ||
placeholder, disabled, format, children, | ||
...otherProps} = this.props; | ||
const preCls = this.preCls; | ||
const clsName = classNames( | ||
preCls, className, | ||
); | ||
const openR = this.getOpen(); | ||
const valueR = this.getValue(); | ||
|
||
return ( | ||
<PopOver | ||
open={openR} | ||
onOpenChange={this.onOpenChange} | ||
pop={this.renderPop()} | ||
> | ||
<div | ||
className={clsName} | ||
style={style} | ||
{...otherProps} | ||
> | ||
<Input | ||
size={size} | ||
placeholder={placeholder} | ||
value={valueR} | ||
readOnly | ||
/> | ||
<Icon className={`${preCls}-icon`} type='ios-calendar'/> | ||
</div> | ||
</PopOver> | ||
); | ||
} | ||
} | ||
|
||
export default DatePicker; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import * as React from 'react'; | ||
import {Simulate, renderIntoDocument, scryRenderedComponentsWithType} from 'react-dom/test-utils'; | ||
import {findDOMNode} from 'react-dom'; | ||
import DatePicker from '../index'; | ||
|
||
describe('多选', () => { | ||
test('点击其中一个选项后可通过 onChange 拿到最新的值', () => { | ||
const component = renderIntoDocument( | ||
<DatePicker/>, | ||
) as DatePicker; | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import * as renderer from 'react-test-renderer'; | ||
import * as React from 'react'; | ||
import DatePicker from '../index'; | ||
|
||
describe('Props', () => { | ||
|
||
test('默认', () => { | ||
const component = renderer.create( | ||
<DatePicker/>, | ||
); | ||
const tree = component.toJSON(); | ||
expect(tree).toMatchSnapshot(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import DatePicker from './DatePicker'; | ||
|
||
export default DatePicker; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
import '../../Icon/style/'; | ||
import '../../Input/style/'; | ||
import '../../PopOver/style/'; | ||
import './index.css'; |
Oops, something went wrong.