parent
2ad4054c27
commit
d7bc8ebafe
17 changed files with 1038 additions and 59 deletions
@ -0,0 +1,181 @@ |
|||||||
|
import React, { Component } from 'react'; |
||||||
|
import PropTypes from 'prop-types'; |
||||||
|
import DaysViewHeading from './DaysViewHeading'; |
||||||
|
import DaysOfWeek from './DaysOfWeek'; |
||||||
|
import MonthSelector from './MonthSelector'; |
||||||
|
import Day from './Day'; |
||||||
|
import { getDaysOfMonth } from '../utils/moment-helper'; |
||||||
|
import moment from 'moment-jalali'; |
||||||
|
import onClickOutside from 'react-onclickoutside'; |
||||||
|
|
||||||
|
// Load Persian localisation
|
||||||
|
moment.loadPersian(); |
||||||
|
|
||||||
|
export class DateTimeCalendar extends Component { |
||||||
|
static propTypes = { |
||||||
|
min: PropTypes.object, |
||||||
|
max: PropTypes.object, |
||||||
|
styles: PropTypes.object, |
||||||
|
selectedDay: PropTypes.object, |
||||||
|
defaultMonth: PropTypes.object, |
||||||
|
onSelect: PropTypes.func, |
||||||
|
onClickOutside: PropTypes.func, |
||||||
|
containerProps: PropTypes.object |
||||||
|
}; |
||||||
|
|
||||||
|
static childContextTypes = { |
||||||
|
nextMonth: PropTypes.func.isRequired, |
||||||
|
prevMonth: PropTypes.func.isRequired, |
||||||
|
setCalendarMode: PropTypes.func.isRequired, |
||||||
|
setMonth: PropTypes.func.isRequired |
||||||
|
}; |
||||||
|
|
||||||
|
static defaultProps = { |
||||||
|
styles: require('../styles/basic.css'), |
||||||
|
containerProps: {} |
||||||
|
}; |
||||||
|
|
||||||
|
state = { |
||||||
|
month: this.props.defaultMonth || this.props.selectedDay || moment(this.props.min), |
||||||
|
selectedDay: this.props.selectedDay || null, |
||||||
|
mode: 'days' |
||||||
|
}; |
||||||
|
|
||||||
|
getChildContext() { |
||||||
|
return { |
||||||
|
nextMonth: this.nextMonth.bind(this), |
||||||
|
prevMonth: this.prevMonth.bind(this), |
||||||
|
setCalendarMode: this.setMode.bind(this), |
||||||
|
setMonth: this.setMonth.bind(this) |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
componentWillReceiveProps({ selectedDay, defaultMonth, min }) { |
||||||
|
if (this.props.selectedDay !== selectedDay) { |
||||||
|
this.selectDay(selectedDay); |
||||||
|
} else if (defaultMonth && this.props.defaultMonth !== defaultMonth && this.state.month === this.props.defaultMonth) { |
||||||
|
this.setMonth(defaultMonth); |
||||||
|
} else if (min && this.props.min !== min && this.state.month.isSame(this.props.min)) { |
||||||
|
this.setMonth(min.clone()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
setMode(mode) { |
||||||
|
this.setState({ mode }); |
||||||
|
} |
||||||
|
|
||||||
|
setMonth(month) { |
||||||
|
this.setState({ month }); |
||||||
|
} |
||||||
|
|
||||||
|
nextMonth() { |
||||||
|
this.setState({ |
||||||
|
month: this.state.month.clone().add(1, 'jMonth') |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
prevMonth() { |
||||||
|
this.setState({ |
||||||
|
month: this.state.month.clone().subtract(1, 'jMonth') |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
selectDay(selectedDay) { |
||||||
|
const { month } = this.state; |
||||||
|
|
||||||
|
// Because there's no `m1.isSame(m2, 'jMonth')`
|
||||||
|
if (selectedDay.format('jYYYYjMM') !== month.format('jYYYYjMM')) { |
||||||
|
this.setState({ month: selectedDay }); |
||||||
|
} |
||||||
|
|
||||||
|
this.setState({ selectedDay }); |
||||||
|
} |
||||||
|
|
||||||
|
handleClickOnDay = selectedDay => { |
||||||
|
const { onSelect } = this.props; |
||||||
|
this.selectDay(selectedDay); |
||||||
|
if (onSelect) { |
||||||
|
onSelect(selectedDay); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
handleClickOutside(event) { |
||||||
|
if (this.props.onClickOutside) { |
||||||
|
this.props.onClickOutside(event); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
days = null; |
||||||
|
lastRenderedMonth = null; |
||||||
|
|
||||||
|
renderMonthSelector() { |
||||||
|
const { month } = this.state; |
||||||
|
const { styles } = this.props; |
||||||
|
return (<MonthSelector styles={styles} selectedMonth={month}/>); |
||||||
|
} |
||||||
|
|
||||||
|
renderDays() { |
||||||
|
const { month, selectedDay } = this.state; |
||||||
|
const { children, min, max, styles, outsideClickIgnoreClass } = this.props; |
||||||
|
|
||||||
|
let days; |
||||||
|
|
||||||
|
if (this.lastRenderedMonth === month) { |
||||||
|
days = this.days; |
||||||
|
} else { |
||||||
|
days = getDaysOfMonth(month); |
||||||
|
this.days = days; |
||||||
|
this.lastRenderedMonth = month; |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<div> |
||||||
|
{children} |
||||||
|
<DaysViewHeading styles={styles} month={month}/> |
||||||
|
<DaysOfWeek styles={styles}/> |
||||||
|
<div className={styles.dayPickerContainer}> |
||||||
|
{ |
||||||
|
days.map(day => { |
||||||
|
const isCurrentMonth = day.format('jMM') === month.format('jMM'); |
||||||
|
const disabled = (min ? day.isBefore(min) : false) || (max ? day.isAfter(max) : false); |
||||||
|
const selected = selectedDay ? selectedDay.isSame(day, 'day') : false; |
||||||
|
|
||||||
|
return ( |
||||||
|
<Day |
||||||
|
key={day.format('YYYYMMDD') } |
||||||
|
onClick={this.handleClickOnDay} |
||||||
|
day={day} |
||||||
|
disabled={disabled} |
||||||
|
selected={selected} |
||||||
|
isCurrentMonth={isCurrentMonth} |
||||||
|
styles={styles} |
||||||
|
/> |
||||||
|
); |
||||||
|
}) |
||||||
|
} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
render() { |
||||||
|
const { |
||||||
|
selectedDay, |
||||||
|
min, |
||||||
|
max, |
||||||
|
onClickOutside, |
||||||
|
outsideClickIgnoreClass, |
||||||
|
styles, |
||||||
|
className |
||||||
|
} = this.props; |
||||||
|
const { mode } = this.state; |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className={styles.calendarContainer + ' ' + className}> |
||||||
|
{ mode === 'monthSelector' ? this.renderMonthSelector() : this.renderDays() } |
||||||
|
</div> |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export default onClickOutside(Calendar); |
@ -0,0 +1,204 @@ |
|||||||
|
import React, { Component } from 'react'; |
||||||
|
import PropTypes from 'prop-types'; |
||||||
|
import moment from 'moment-jalali'; |
||||||
|
import TetherComponent from 'react-tether'; |
||||||
|
import Calendar from './Calendar'; |
||||||
|
import classnames from 'classnames'; |
||||||
|
|
||||||
|
export const outsideClickIgnoreClass = 'ignore--click--outside'; |
||||||
|
|
||||||
|
export default class DateTimePicker extends Component { |
||||||
|
static propTypes = { |
||||||
|
value: PropTypes.object, |
||||||
|
defaultValue: PropTypes.object, |
||||||
|
onChange: PropTypes.func, |
||||||
|
onFocus: PropTypes.func, |
||||||
|
onBlur: PropTypes.func, |
||||||
|
children: PropTypes.node, |
||||||
|
min: PropTypes.object, |
||||||
|
max: PropTypes.object, |
||||||
|
defaultMonth: PropTypes.object, |
||||||
|
inputFormat: PropTypes.string, |
||||||
|
removable: PropTypes.bool, |
||||||
|
timePickerComponent: PropTypes.func, |
||||||
|
calendarStyles: PropTypes.object, |
||||||
|
calendarContainerProps: PropTypes.object |
||||||
|
}; |
||||||
|
|
||||||
|
static defaultProps = { |
||||||
|
inputFormat: 'jYYYY/jM/jD', |
||||||
|
calendarStyles: require('../styles/basic.css'), |
||||||
|
calendarContainerProps: {} |
||||||
|
}; |
||||||
|
|
||||||
|
state = { |
||||||
|
isOpen: false, |
||||||
|
momentValue: this.props.defaultValue || null, |
||||||
|
inputValue: this.props.defaultValue ? |
||||||
|
this.props.defaultValue.format(this.props.inputFormat) : '' |
||||||
|
}; |
||||||
|
|
||||||
|
setOpen(isOpen) { |
||||||
|
this.setState({ isOpen }); |
||||||
|
} |
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps) { |
||||||
|
if ('value' in nextProps && nextProps.value !== this.props.value) { |
||||||
|
this.setMomentValue(nextProps.value); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
setMomentValue(momentValue) { |
||||||
|
const { inputFormat } = this.props; |
||||||
|
|
||||||
|
if (this.props.onChange) { |
||||||
|
this.props.onChange(momentValue); |
||||||
|
} |
||||||
|
|
||||||
|
let inputValue = ""; |
||||||
|
if (momentValue) |
||||||
|
inputValue = momentValue.format(inputFormat); |
||||||
|
this.setState({ momentValue, inputValue }); |
||||||
|
} |
||||||
|
|
||||||
|
handleFocus() { |
||||||
|
this.setOpen(true); |
||||||
|
} |
||||||
|
|
||||||
|
handleBlur(event) { |
||||||
|
const { onBlur, inputFormat } = this.props; |
||||||
|
const { isOpen, momentValue } = this.state; |
||||||
|
|
||||||
|
if (isOpen) { |
||||||
|
this.refs.input.focus(); |
||||||
|
} else if (onBlur) { |
||||||
|
onBlur(event); |
||||||
|
} |
||||||
|
|
||||||
|
if (momentValue) { |
||||||
|
const inputValue = momentValue.format(inputFormat); |
||||||
|
this.setState({ inputValue }); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
handleClickOutsideCalendar() { |
||||||
|
this.setOpen(false); |
||||||
|
} |
||||||
|
|
||||||
|
handleSelectDay(selectedDay) { |
||||||
|
const { momentValue: oldValue } = this.state; |
||||||
|
let momentValue = selectedDay.clone(); |
||||||
|
|
||||||
|
if (oldValue) { |
||||||
|
momentValue = momentValue |
||||||
|
.set({ |
||||||
|
hour: oldValue.hours(), |
||||||
|
minute: oldValue.minutes(), |
||||||
|
second: oldValue.seconds() |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
this.setMomentValue(momentValue); |
||||||
|
} |
||||||
|
|
||||||
|
handleInputChange(event) { |
||||||
|
const { inputFormat } = this.props; |
||||||
|
const inputValue = event.target.value; |
||||||
|
const momentValue = moment(inputValue, inputFormat); |
||||||
|
|
||||||
|
if (momentValue.isValid()) { |
||||||
|
this.setState({ momentValue }); |
||||||
|
} |
||||||
|
|
||||||
|
this.setState({ inputValue }); |
||||||
|
} |
||||||
|
|
||||||
|
handleInputClick() { |
||||||
|
if (!this.props.disabled) { |
||||||
|
this.setOpen(true) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
renderInput() { |
||||||
|
let { isOpen, inputValue } = this.state; |
||||||
|
|
||||||
|
if (this.props.value) { |
||||||
|
let value = this.props.value; |
||||||
|
let inputFormat = this.props.inputFormat; |
||||||
|
inputValue = value.format(inputFormat); |
||||||
|
} |
||||||
|
|
||||||
|
const className = classnames(this.props.className, { |
||||||
|
[outsideClickIgnoreClass]: isOpen |
||||||
|
}); |
||||||
|
|
||||||
|
return ( |
||||||
|
<div> |
||||||
|
<input |
||||||
|
className={className} |
||||||
|
type="text" |
||||||
|
ref="input" |
||||||
|
onFocus={this.handleFocus.bind(this) } |
||||||
|
onBlur={this.handleBlur.bind(this) } |
||||||
|
onChange={this.handleInputChange.bind(this) } |
||||||
|
onClick={this.handleInputClick.bind(this) } |
||||||
|
value={inputValue} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
renderCalendar() { |
||||||
|
const { momentValue } = this.state; |
||||||
|
const { timePickerComponent: TimePicker, onChange, min, max, defaultMonth, calendarStyles, calendarContainerProps } = this.props; |
||||||
|
|
||||||
|
return ( |
||||||
|
<div> |
||||||
|
<Calendar |
||||||
|
min={min} |
||||||
|
max={max} |
||||||
|
selectedDay={momentValue} |
||||||
|
defaultMonth={defaultMonth} |
||||||
|
onSelect={this.handleSelectDay.bind(this) } |
||||||
|
onClickOutside={this.handleClickOutsideCalendar.bind(this) } |
||||||
|
outsideClickIgnoreClass={outsideClickIgnoreClass} |
||||||
|
styles={calendarStyles} |
||||||
|
containerProps={calendarContainerProps} |
||||||
|
> |
||||||
|
{ |
||||||
|
TimePicker ? ( |
||||||
|
<TimePicker |
||||||
|
min={min} |
||||||
|
max={max} |
||||||
|
momentValue={momentValue} |
||||||
|
setMomentValue={this.setMomentValue.bind(this) } |
||||||
|
/> |
||||||
|
) : null |
||||||
|
} |
||||||
|
</Calendar> |
||||||
|
</div> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
removeDate() { |
||||||
|
const {onChange} = this.props; |
||||||
|
if (onChange) { |
||||||
|
onChange(''); |
||||||
|
} |
||||||
|
this.setState({ |
||||||
|
input: '', |
||||||
|
inputValue: '' |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
render() { |
||||||
|
const { isOpen } = this.state; |
||||||
|
|
||||||
|
return ( |
||||||
|
<TetherComponent attachment="top center"> |
||||||
|
{ this.renderInput() } |
||||||
|
{ isOpen ? this.renderCalendar() : null } |
||||||
|
</TetherComponent> |
||||||
|
); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,61 @@ |
|||||||
|
import React, { Component } from 'react'; |
||||||
|
import PropTypes from 'prop-types'; |
||||||
|
import classnames from 'classnames'; |
||||||
|
import { persianNumber } from '../utils/persian'; |
||||||
|
|
||||||
|
const styles = { |
||||||
|
wrapper: {}, |
||||||
|
button: { |
||||||
|
outline: 'none', |
||||||
|
cursor: 'pointer' |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
export default class Day extends Component { |
||||||
|
static propTypes = { |
||||||
|
day: PropTypes.object.isRequired, |
||||||
|
isCurrentMonth: PropTypes.bool, |
||||||
|
disabled: PropTypes.bool, |
||||||
|
selected: PropTypes.bool, |
||||||
|
onClick: PropTypes.func |
||||||
|
}; |
||||||
|
|
||||||
|
shouldComponentUpdate(nextProps) { |
||||||
|
return nextProps.selected !== this.props.selected || |
||||||
|
nextProps.disabled !== this.props.disabled || |
||||||
|
nextProps.isCurrentMonth !== this.props.isCurrentMonth; |
||||||
|
} |
||||||
|
|
||||||
|
handleClick(event) { |
||||||
|
event.preventDefault(); |
||||||
|
event.stopPropagation(); |
||||||
|
event.nativeEvent.stopImmediatePropagation(); |
||||||
|
const { onClick, day } = this.props; |
||||||
|
|
||||||
|
if (onClick) { |
||||||
|
onClick(day); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
render() { |
||||||
|
const { day, disabled, selected, isCurrentMonth, onClick, styles, ...rest } = this.props; |
||||||
|
|
||||||
|
const className = classnames(styles.dayWrapper, { |
||||||
|
[styles.selected]: selected, |
||||||
|
[styles.currentMonth]: isCurrentMonth |
||||||
|
}); |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className={className}> |
||||||
|
<button |
||||||
|
type="button" |
||||||
|
onClick={this.handleClick.bind(this) } |
||||||
|
disabled={disabled} |
||||||
|
{...rest} |
||||||
|
> |
||||||
|
{ persianNumber(day.format('jD')) } |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,21 @@ |
|||||||
|
import React, { Component } from 'react'; |
||||||
|
import PropTypes from 'prop-types'; |
||||||
|
|
||||||
|
// Day of week names for use in date-picker heading
|
||||||
|
const dayOfWeekNames = ['ش', 'ی', 'د', 'س', 'چ', 'پ', 'ج']; |
||||||
|
|
||||||
|
export default class DaysOfWeek extends Component { |
||||||
|
static propTypes = { |
||||||
|
styles: PropTypes.object |
||||||
|
}; |
||||||
|
|
||||||
|
render() { |
||||||
|
const { styles } = this.props; |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className={styles.daysOfWeek}> |
||||||
|
{ dayOfWeekNames.map((name, key) => <div key={key}>{name}</div>) } |
||||||
|
</div> |
||||||
|
); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,50 @@ |
|||||||
|
import React, { Component } from 'react'; |
||||||
|
import PropTypes from 'prop-types'; |
||||||
|
import { persianNumber } from '../utils/persian'; |
||||||
|
import { leftArrow, rightArrow } from '../utils/assets'; |
||||||
|
|
||||||
|
export default class Heading extends Component { |
||||||
|
static propTypes = { |
||||||
|
month: PropTypes.object.isRequired |
||||||
|
}; |
||||||
|
|
||||||
|
static contextTypes = { |
||||||
|
styles: PropTypes.object, |
||||||
|
nextMonth: PropTypes.func.isRequired, |
||||||
|
prevMonth: PropTypes.func.isRequired, |
||||||
|
setCalendarMode: PropTypes.func.isRequired |
||||||
|
}; |
||||||
|
|
||||||
|
handleMonthClick(event) { |
||||||
|
const { setCalendarMode } = this.context; |
||||||
|
event.preventDefault(); |
||||||
|
setCalendarMode('monthSelector'); |
||||||
|
} |
||||||
|
|
||||||
|
render() { |
||||||
|
const { nextMonth, prevMonth } = this.context; |
||||||
|
const { month, styles } = this.props; |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className={styles.heading}> |
||||||
|
<button className={styles.title} onClick={this.handleMonthClick.bind(this)}> |
||||||
|
{ persianNumber(month.format('jMMMM jYYYY')) } |
||||||
|
</button> |
||||||
|
<button |
||||||
|
type="button" |
||||||
|
title="ماه قبل" |
||||||
|
className={styles.prev} |
||||||
|
onClick={prevMonth} |
||||||
|
dangerouslySetInnerHTML={rightArrow} |
||||||
|
/> |
||||||
|
<button |
||||||
|
type="button" |
||||||
|
title="ماه بعد" |
||||||
|
className={styles.next} |
||||||
|
onClick={nextMonth} |
||||||
|
dangerouslySetInnerHTML={leftArrow} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,93 @@ |
|||||||
|
import React, { Component } from 'react'; |
||||||
|
import PropTypes from 'prop-types'; |
||||||
|
import moment from 'moment-jalali'; |
||||||
|
import classnames from 'classnames'; |
||||||
|
import MonthsViewHeading from './MonthsViewHeading'; |
||||||
|
import { persianNumber } from '../utils/persian'; |
||||||
|
import { leftArrow, rightArrow } from '../utils/assets'; |
||||||
|
|
||||||
|
// List of months
|
||||||
|
const months = [ |
||||||
|
'فروردین', |
||||||
|
'اردیبهشت', |
||||||
|
'خرداد', |
||||||
|
'تیر', |
||||||
|
'مرداد', |
||||||
|
'شهریور', |
||||||
|
'مهر', |
||||||
|
'آبان', |
||||||
|
'آذر', |
||||||
|
'دی', |
||||||
|
'بهمن', |
||||||
|
'اسفند' |
||||||
|
]; |
||||||
|
|
||||||
|
export default class MonthSelector extends Component { |
||||||
|
static propTypes = { |
||||||
|
styles: PropTypes.object, |
||||||
|
selectedMonth: PropTypes.object.isRequired |
||||||
|
}; |
||||||
|
|
||||||
|
static contextTypes = { |
||||||
|
setCalendarMode: PropTypes.func.isRequired, |
||||||
|
setMonth: PropTypes.func.isRequired |
||||||
|
}; |
||||||
|
|
||||||
|
state = { |
||||||
|
year: this.props.selectedMonth |
||||||
|
}; |
||||||
|
|
||||||
|
nextYear() { |
||||||
|
this.setState({ |
||||||
|
year: this.state.year.clone().add(1, 'year') |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
prevYear() { |
||||||
|
this.setState({ |
||||||
|
year: this.state.year.clone().subtract(1, 'year') |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
handleClick(key) { |
||||||
|
const { setMonth, setCalendarMode } = this.context; |
||||||
|
setMonth(moment(key, 'jM-jYYYY')); |
||||||
|
setCalendarMode('days'); |
||||||
|
} |
||||||
|
|
||||||
|
render() { |
||||||
|
const { year } = this.state; |
||||||
|
const { selectedMonth, styles } = this.props; |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="month-selector"> |
||||||
|
<MonthsViewHeading |
||||||
|
styles={styles} |
||||||
|
year={year} |
||||||
|
onNextYear={this.nextYear.bind(this) } |
||||||
|
onPrevYear={this.prevYear.bind(this) } |
||||||
|
/> |
||||||
|
<div className={styles.monthsList}> |
||||||
|
{ |
||||||
|
months.map((name, key) => { |
||||||
|
const buttonFingerprint = (key + 1) + '-' + year.format('jYYYY'); |
||||||
|
const selectedMonthFingerprint = selectedMonth.format('jM-jYYYY'); |
||||||
|
const isCurrent = selectedMonthFingerprint === buttonFingerprint; |
||||||
|
|
||||||
|
const className = classnames(styles.monthWrapper, { |
||||||
|
[styles.selected]: isCurrent |
||||||
|
}); |
||||||
|
|
||||||
|
return ( |
||||||
|
<div key={key} className={className}> |
||||||
|
<button onClick={this.handleClick.bind(this, buttonFingerprint)}> |
||||||
|
{name} |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}) |
||||||
|
} |
||||||
|
</div> |
||||||
|
</div>); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,44 @@ |
|||||||
|
import React, { Component } from 'react'; |
||||||
|
import PropTypes from 'prop-types'; |
||||||
|
import { persianNumber } from '../utils/persian'; |
||||||
|
import { leftArrow, rightArrow } from '../utils/assets'; |
||||||
|
|
||||||
|
export default class MonthsViewHeading extends Component { |
||||||
|
static propTypes = { |
||||||
|
year: PropTypes.object.isRequired, |
||||||
|
onNextYear: PropTypes.func.isRequired, |
||||||
|
onPrevYear: PropTypes.func.isRequired |
||||||
|
}; |
||||||
|
|
||||||
|
static contextTypes = { |
||||||
|
styles: PropTypes.object |
||||||
|
}; |
||||||
|
|
||||||
|
render() { |
||||||
|
const { year, styles } = this.props; |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className={styles.heading}> |
||||||
|
<span className={styles.title}> |
||||||
|
{ persianNumber(year.format('jYYYY')) } |
||||||
|
</span> |
||||||
|
<button |
||||||
|
type="button" |
||||||
|
title="سال قبل" |
||||||
|
style={styles.navButton} |
||||||
|
className={styles.prev} |
||||||
|
onClick={this.props.onPrevYear} |
||||||
|
dangerouslySetInnerHTML={rightArrow} |
||||||
|
/> |
||||||
|
<button |
||||||
|
type="button" |
||||||
|
title="سال بعد" |
||||||
|
style={styles.navButton} |
||||||
|
className={styles.next} |
||||||
|
onClick={this.props.onNextYear} |
||||||
|
dangerouslySetInnerHTML={leftArrow} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,3 @@ |
|||||||
|
// export {DateTimeCalendar as default} from './components/Calendar';
|
||||||
|
// export {DateTimePicker as default} from './components/DatePicker';
|
||||||
|
// export { outsideClickIgnoreClass as default } from './components/DatePicker'
|
@ -0,0 +1,156 @@ |
|||||||
|
.calendarContainer { |
||||||
|
border-radius: 3px; |
||||||
|
box-shadow: 0 3px 10px #dbdbdb; |
||||||
|
border: 1px solid #cccccc; |
||||||
|
width: 300px; |
||||||
|
margin: auto; |
||||||
|
text-align: center; |
||||||
|
padding: 10px; |
||||||
|
background-color: #fff; |
||||||
|
} |
||||||
|
|
||||||
|
.calendarContainer * { |
||||||
|
box-sizing: border-box; |
||||||
|
} |
||||||
|
|
||||||
|
.calendarContainer .dayPickerContainer:after, |
||||||
|
.calendarContainer .monthsList:after, |
||||||
|
.calendarContainer .daysOfWeek:after { |
||||||
|
content: ''; |
||||||
|
display: block; |
||||||
|
clear: both; |
||||||
|
} |
||||||
|
|
||||||
|
/* Heading */ |
||||||
|
.calendarContainer .heading { |
||||||
|
height: 42px; |
||||||
|
font-weight: bold; |
||||||
|
margin-bottom: 10px; |
||||||
|
} |
||||||
|
|
||||||
|
.calendarContainer .heading > button { |
||||||
|
border-radius: 3px; |
||||||
|
background: none; |
||||||
|
margin: 5px 0; |
||||||
|
border: 1px solid #F7F7F7; |
||||||
|
text-align: center; |
||||||
|
line-height: 30px; |
||||||
|
width: 36px; |
||||||
|
height: 32px; |
||||||
|
cursor: pointer; |
||||||
|
} |
||||||
|
|
||||||
|
.calendarContainer .heading > button:hover { |
||||||
|
background-color: #f2f2f2; |
||||||
|
} |
||||||
|
|
||||||
|
.calendarContainer .heading > span { |
||||||
|
line-height: 35px; |
||||||
|
} |
||||||
|
|
||||||
|
.calendarContainer .heading svg { |
||||||
|
width: 10px; |
||||||
|
} |
||||||
|
|
||||||
|
.calendarContainer .heading .prev { |
||||||
|
float: right; |
||||||
|
} |
||||||
|
|
||||||
|
.calendarContainer .heading .next { |
||||||
|
float: left; |
||||||
|
} |
||||||
|
|
||||||
|
.calendarContainer .heading .title { |
||||||
|
line-height: 32px; |
||||||
|
width: 120px; |
||||||
|
height: 32px; |
||||||
|
font-size: 1em; |
||||||
|
margin: 5px 0; |
||||||
|
border: 1px solid #F7F7F7; |
||||||
|
text-align: center; |
||||||
|
display: inline-block; |
||||||
|
font-weight: normal; |
||||||
|
} |
||||||
|
|
||||||
|
/* Day wrapper styles */ |
||||||
|
.calendarContainer .dayWrapper { |
||||||
|
padding: 5; |
||||||
|
float: right; |
||||||
|
width: 14.28571429%; |
||||||
|
} |
||||||
|
|
||||||
|
/* Day wrapper button styles */ |
||||||
|
.calendarContainer .dayWrapper button { |
||||||
|
border: none; |
||||||
|
background: none; |
||||||
|
outline: none; |
||||||
|
width: 100%; |
||||||
|
height: 36px; |
||||||
|
border-radius: 3px; |
||||||
|
cursor: pointer; |
||||||
|
} |
||||||
|
|
||||||
|
.calendarContainer .dayWrapper button:hover { |
||||||
|
background-color: #eeeeff; |
||||||
|
} |
||||||
|
|
||||||
|
.calendarContainer .dayWrapper button[disabled] { |
||||||
|
color: #aaa; |
||||||
|
cursor: not-allowed; |
||||||
|
background-color: #ebebeb; |
||||||
|
} |
||||||
|
|
||||||
|
.calendarContainer .dayWrapper button.selected { |
||||||
|
background-color: #337ab7; |
||||||
|
color: #ffffff; |
||||||
|
} |
||||||
|
|
||||||
|
.calendarContainer .dayWrapper:not(.currentMonth) button { |
||||||
|
opacity: .5; |
||||||
|
} |
||||||
|
|
||||||
|
/* Days of week row */ |
||||||
|
.calendarContainer .daysOfWeek { |
||||||
|
border-bottom: 1px solid #EEE; |
||||||
|
margin-bottom: 5px; |
||||||
|
padding-bottom: 5px; |
||||||
|
display: flex; |
||||||
|
width: 100%; |
||||||
|
} |
||||||
|
|
||||||
|
.calendarContainer .daysOfWeek > div { |
||||||
|
flex-grow: 1; |
||||||
|
justify-content: space-between; |
||||||
|
} |
||||||
|
|
||||||
|
/* Month selector portion */ |
||||||
|
.calendarContainer .monthsList { |
||||||
|
clear: both; |
||||||
|
width: 100%; |
||||||
|
} |
||||||
|
|
||||||
|
.calendarContainer .monthsList button { |
||||||
|
width: 33.33333332%; |
||||||
|
height: 25%; |
||||||
|
float: right; |
||||||
|
border: 1px solid #F9F9F9; |
||||||
|
outline: none; |
||||||
|
font-size: 1em; |
||||||
|
background: #fff; |
||||||
|
padding: 10px 0; |
||||||
|
cursor: pointer; |
||||||
|
} |
||||||
|
|
||||||
|
.calendarContainer .monthsList button:hover { |
||||||
|
background: #eeeeee; |
||||||
|
cursor: pointer; |
||||||
|
} |
||||||
|
|
||||||
|
/* Selected state of buttons */ |
||||||
|
.calendarContainer .selected button, |
||||||
|
.calendarContainer .selected button:hover, |
||||||
|
.calendarContainer .selected button:active, |
||||||
|
.calendarContainer .selected button:focus { |
||||||
|
background-color: #337ab7; |
||||||
|
color: #ffffff; |
||||||
|
} |
@ -0,0 +1,11 @@ |
|||||||
|
export const leftArrow = { |
||||||
|
__html: '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 314.5 314.5" style="enable-background:new 0 0 314.5 314.5;" xml:space="preserve"><g><path class="arrow-icon" d="M125,157.5l116-116c10-10,10-24,0-34s-25-10-35,0l-133,133c-5,5-7,10-7,17s2,12,7,17l133,133c5,5,11,7,17,7s13-2,18-7c10-10,10-24,0-34L125,157.5z"/></g></svg>' |
||||||
|
}; |
||||||
|
|
||||||
|
export const rightArrow = { |
||||||
|
__html: '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 315.5 315.5" style="enable-background:new 0 0 315.5 315.5;" xml:space="preserve"><g><path class="arrow-icon" d="M242,141L109,8c-5-5-12-8-18-8S79,3,74,8c-10,10-10,24,0,34l116,116L74,274c-10,10-10,24,0,34s25,10,35,0l133-133c5-5,7-11,7-17C249,151,247,146,242,141z"/></g></svg>' |
||||||
|
}; |
||||||
|
|
||||||
|
export const remove = { |
||||||
|
__html: '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 212.982 212.982" style="enable-background:new 0 0 212.982 212.982;" xml:space="preserve"><g><path style="fill-rule:evenodd;clip-rule:evenodd;" d="M131.804,106.491l75.936-75.936c6.99-6.99,6.99-18.323,0-25.312 c-6.99-6.99-18.322-6.99-25.312,0l-75.937,75.937L30.554,5.242c-6.99-6.99-18.322-6.99-25.312,0c-6.989,6.99-6.989,18.323,0,25.312 l75.937,75.936L5.242,182.427c-6.989,6.99-6.989,18.323,0,25.312c6.99,6.99,18.322,6.99,25.312,0l75.937-75.937l75.937,75.937 c6.989,6.99,18.322,6.99,25.312,0c6.99-6.99,6.99-18.322,0-25.312L131.804,106.491z" fill="#555555"/></g></svg>' |
||||||
|
}; |
@ -0,0 +1,25 @@ |
|||||||
|
/** |
||||||
|
* Get days of a month that should be shown on a month page |
||||||
|
* |
||||||
|
* @param month A moment object |
||||||
|
* @returns {Array} |
||||||
|
*/ |
||||||
|
export function getDaysOfMonth(month) { |
||||||
|
const days = []; |
||||||
|
|
||||||
|
const current = month.clone().startOf('jMonth'); |
||||||
|
const end = month.clone().endOf('jMonth'); |
||||||
|
|
||||||
|
// Set start to the first day of week in the last month
|
||||||
|
current.subtract((current.day() + 1) % 7, 'days'); |
||||||
|
|
||||||
|
// Set end to the last day of week in the next month
|
||||||
|
end.add(6 - (end.day() + 1) % 7, 'days'); |
||||||
|
|
||||||
|
while (current.isBefore(end)) { |
||||||
|
days.push(current.clone()); |
||||||
|
current.add(1, 'days'); |
||||||
|
} |
||||||
|
|
||||||
|
return days; |
||||||
|
} |
@ -0,0 +1,29 @@ |
|||||||
|
const latinToPersianMap = ['۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹', '۰']; |
||||||
|
const latinNumbers = [/1/g, /2/g, /3/g, /4/g, /5/g, /6/g, /7/g, /8/g, /9/g, /0/g]; |
||||||
|
|
||||||
|
function prepareNumber(input) { |
||||||
|
let string; |
||||||
|
if (typeof input === 'number') { |
||||||
|
string = input.toString(); |
||||||
|
} else if (typeof input === 'undefined') { |
||||||
|
string = ''; |
||||||
|
} else { |
||||||
|
string = input; |
||||||
|
} |
||||||
|
|
||||||
|
return string; |
||||||
|
} |
||||||
|
|
||||||
|
function latinToPersian(string) { |
||||||
|
let result = string; |
||||||
|
|
||||||
|
for (let index = 0; index < 10; index++) { |
||||||
|
result = result.replace(latinNumbers[index], latinToPersianMap[index]); |
||||||
|
} |
||||||
|
|
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
export function persianNumber(input) { |
||||||
|
return latinToPersian(prepareNumber(input)); |
||||||
|
} |
Loading…
Reference in new issue