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