diff --git a/packages/discuz-design/README.md b/packages/discuz-design/README.md index a84123b98c9605db70464e3124915300b4a29dcd..891b6d45c53473c5d86131a390f102d55c55441d 100644 --- a/packages/discuz-design/README.md +++ b/packages/discuz-design/README.md @@ -96,6 +96,7 @@ npm run new poster 海报 - [x] [Slider](./components/slider) - 滑动条 - [x] [AudioRecorder](./components/audio-record) - 录音 - [x] [WebPicker](./components/web-picker) - 选择器(H5) + - [x] [DatePicker](./components/datepicker) - 日期选择器(PC) - 交互 - [x] [Animation](./components/animation) - 动画组件 - [x] [BackToTop](./components/back-to-top) - 返回顶部 diff --git a/packages/discuz-design/components/button/adapters/mini.ts b/packages/discuz-design/components/button/adapters/mini.ts index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..ee7f04cf4ea50b2754358dd92ed2d3ce56341a8b 100644 --- a/packages/discuz-design/components/button/adapters/mini.ts +++ b/packages/discuz-design/components/button/adapters/mini.ts @@ -0,0 +1 @@ +export const ButtonMiniAdapter = {}; diff --git a/packages/discuz-design/components/button/adapters/web.ts b/packages/discuz-design/components/button/adapters/web.ts index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..b70f58ea8961b58dba4b2cd08734f12089a7feaa 100644 --- a/packages/discuz-design/components/button/adapters/web.ts +++ b/packages/discuz-design/components/button/adapters/web.ts @@ -0,0 +1 @@ +export const ButtonWebAdapter = {}; diff --git a/packages/discuz-design/components/datepicker/README.md b/packages/discuz-design/components/datepicker/README.md new file mode 100644 index 0000000000000000000000000000000000000000..4e51b61c622ad49aa5ddc083974c1e6e5487f247 --- /dev/null +++ b/packages/discuz-design/components/datepicker/README.md @@ -0,0 +1,21 @@ +# Datepicker 组件 + +## 组件说明 + +基础Datepicker组件 + +## 示例 + +### WEB 示例 + +### 基础用法 + +[Example: 基础用法](./__examples__/web/index.tsx) + +### 小程序示例 + + + +## API 参数 + +[Interface: DatepickerProps](./interface.ts) diff --git a/packages/discuz-design/components/datepicker/__examples__/mini/index.tsx b/packages/discuz-design/components/datepicker/__examples__/mini/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..9564881fd266d620ff12b54e9c81bafe370b8a22 --- /dev/null +++ b/packages/discuz-design/components/datepicker/__examples__/mini/index.tsx @@ -0,0 +1,7 @@ +import React from 'react'; +import { View } from '@tarojs/components'; +import Datepicker from '../../index'; + +export default function DatepickerExample() { + return Datepicker; +} diff --git a/packages/discuz-design/components/datepicker/__examples__/web/index.tsx b/packages/discuz-design/components/datepicker/__examples__/web/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..af2dab74c1b770dcf823904f87c56284e6d3e589 --- /dev/null +++ b/packages/discuz-design/components/datepicker/__examples__/web/index.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import Datepicker from '../../index'; + +import YearPicker from '../../layouts/web/year-picker'; +import MonthPicker from '../../layouts/web/month-picker'; + + +function Title(props) { + return
{props.children}
; +} + + +export default function DatepickerExample() { + return
+ 基础用法 + + + 年份选择 + + + 月份选择 + +
; +} diff --git a/packages/discuz-design/components/datepicker/adapters/index.ts b/packages/discuz-design/components/datepicker/adapters/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..cb07f54f957752dbe02551757a2104bf079ef1eb --- /dev/null +++ b/packages/discuz-design/components/datepicker/adapters/index.ts @@ -0,0 +1,14 @@ +import { baseAdapterFactory } from '../../../extends/baseAdapter'; + +export const DatepickerLogicalAdapter = baseAdapterFactory({ + defaultAdapter: {}, + adapterImplement() { + if (process.env.DISCUZ_ENV === 'web') { + return require('./web').DatepickerWebAdapter; + } + + if (process.env.DISCUZ_ENV === 'mini') { + return require('./mini').DatepickerMiniAdapter; + } + }, +}); diff --git a/packages/discuz-design/components/datepicker/adapters/mini.ts b/packages/discuz-design/components/datepicker/adapters/mini.ts new file mode 100644 index 0000000000000000000000000000000000000000..892b62d39a76c5d38a38dd48607e66980e4cfbe4 --- /dev/null +++ b/packages/discuz-design/components/datepicker/adapters/mini.ts @@ -0,0 +1 @@ +export const DatepickerMiniAdapter = {}; diff --git a/packages/discuz-design/components/datepicker/adapters/web.ts b/packages/discuz-design/components/datepicker/adapters/web.ts new file mode 100644 index 0000000000000000000000000000000000000000..38097b11955d876c32629d761781047e458c04a4 --- /dev/null +++ b/packages/discuz-design/components/datepicker/adapters/web.ts @@ -0,0 +1 @@ +export const DatepickerWebAdapter = {}; diff --git a/packages/discuz-design/components/datepicker/index.tsx b/packages/discuz-design/components/datepicker/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..25225d7018199ebad82e38c34a777bf30dd3c0e3 --- /dev/null +++ b/packages/discuz-design/components/datepicker/index.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { baseComponentFactory } from '../../extends/baseComponent'; +import { DatepickerViewAdapter } from './layouts/index'; +import { DatepickerLogicalAdapter } from './adapters/index'; +import { DatepickerProps } from './interface'; + +interface DatepickerState {} + +interface DatepickerLayoutProps {} + +interface DatepickerAdapter {} + +export default class Datepicker extends baseComponentFactory< + DatepickerProps, + DatepickerState, + DatepickerLayoutProps, + DatepickerAdapter +>({ + viewAdapter: DatepickerViewAdapter, + logicalAdapter: DatepickerLogicalAdapter, +}) { + static defaultProps = {}; + + render() { + const { RenderComponent } = this; + return {this.props.children}; + } +} diff --git a/packages/discuz-design/components/datepicker/interface.ts b/packages/discuz-design/components/datepicker/interface.ts new file mode 100644 index 0000000000000000000000000000000000000000..95685169482b0d893caed276b7097b0e773d13c1 --- /dev/null +++ b/packages/discuz-design/components/datepicker/interface.ts @@ -0,0 +1,6 @@ +import { StyledProps } from 'utils/_type/StyledProps'; + +/** + * Datepicker 组件支持的属性 + */ +export interface DatepickerProps extends StyledProps {} diff --git a/packages/discuz-design/components/datepicker/layouts/index.ts b/packages/discuz-design/components/datepicker/layouts/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..8330b87cbce9bee35850660c49421517a611d288 --- /dev/null +++ b/packages/discuz-design/components/datepicker/layouts/index.ts @@ -0,0 +1,13 @@ +import { baseLayoutFactory } from '../../../extends/baseLayout'; + +export const DatepickerViewAdapter = baseLayoutFactory({ + layoutImplement() { + if (process.env.DISCUZ_ENV === 'web') { + return require('./web/index').DatepickerWebLayout; + } + + if (process.env.DISCUZ_ENV === 'mini') { + return require('./mini/index').DatepickerMiniLayout; + } + }, +}); diff --git a/packages/discuz-design/components/datepicker/layouts/mini/index.jsx b/packages/discuz-design/components/datepicker/layouts/mini/index.jsx new file mode 100644 index 0000000000000000000000000000000000000000..4e8661f64280aaacc5342a1da69203477a58eaa8 --- /dev/null +++ b/packages/discuz-design/components/datepicker/layouts/mini/index.jsx @@ -0,0 +1,11 @@ +import React from 'react'; +import { View } from '@tarojs/components'; +import classNames from 'classnames'; +import { DatepickerProps } from '../../interface'; +import { useConfig } from '../../../../extends/configContext'; + +export const DatepickerMiniLayout = ({ ...props }) => { + const { clsPrefix } = useConfig(); + + return Datepicker; +}; diff --git a/packages/discuz-design/components/datepicker/layouts/web/index.tsx b/packages/discuz-design/components/datepicker/layouts/web/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..623499b53df8b540dcfdcec0d065b3e7ed195cfd --- /dev/null +++ b/packages/discuz-design/components/datepicker/layouts/web/index.tsx @@ -0,0 +1,120 @@ +import React, { useState, forwardRef } from 'react'; +import classNames from 'classnames'; +import DatePicker, { registerLocale } from './lib/react-datepicker'; +import { DatepickerProps } from '../../interface'; +import { useConfig } from '../../../../extends/configContext'; +import { getYear, getMonth } from 'date-fns'; + +import Input from '../../../input'; +import Icon from '../../../icon'; +import Button from '../../../button'; + +import zhCN from 'date-fns/locale/zh-CN'; +registerLocale('zhCN', zhCN); + +/** + * 时间选择器 可参考 https://reactdatepicker.com/# + */ +export const DatepickerWebLayout = (props) => { + const { clsPrefix } = useConfig(); + + const [startDate, setStartDate] = useState(new Date()); + + const onNowClick = ({ changeYear, changeMonth }) => { + changeYear(getYear(Date.now())); + changeMonth(getMonth(Date.now())); + }; + + const ExampleCustomInput = forwardRef(({ value, isYear, onClick }, ref) => ( + + )); + + const renderCustomHeader = ({ + date, + changeYear, + changeMonth, + decreaseMonth, + increaseMonth, + prevMonthButtonDisabled, + nextMonthButtonDisabled, + }) => ( +
+
+ changeYear(getYear(date))} + showYearPicker + dateFormat="yyyy" + locale="zhCN" + customInput={} + /> + changeMonth(getMonth(date))} + dateFormat="MM" + showMonthYearPicker + locale="zhCN" + renderCustomHeader={() =>
选择月份
} + customInput={} + /> +
+ +
+ + + +
+
+ ); + + return ( + setStartDate(date)} + customInput={} + renderCustomHeader={renderCustomHeader} + locale="zhCN" + dateFormat="yyyy-MM-dd HH:mm" + showTimeSelect + timeFormat="HH:mm:ss" + showPopperArrow={false} + timeCaption="时间" + /> + ); +}; diff --git a/packages/discuz-design/components/datepicker/layouts/web/jumper.jsx b/packages/discuz-design/components/datepicker/layouts/web/jumper.jsx new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/packages/discuz-design/components/datepicker/layouts/web/lib/react-datepicker/calendar.jsx b/packages/discuz-design/components/datepicker/layouts/web/lib/react-datepicker/calendar.jsx new file mode 100644 index 0000000000000000000000000000000000000000..a9ab861907b054ae5235898c2c06941e22da94d0 --- /dev/null +++ b/packages/discuz-design/components/datepicker/layouts/web/lib/react-datepicker/calendar.jsx @@ -0,0 +1,973 @@ +import YearDropdown from './year_dropdown'; +import MonthDropdown from './month_dropdown'; +import MonthYearDropdown from './month_year_dropdown'; +import Month from './month'; +import Time from './time'; +import Year from './year'; +import InputTime from './inputTime'; +import React from 'react'; +import PropTypes from 'prop-types'; +import classnames from 'classnames'; +import CalendarContainer from './calendar_container'; +import { + newDate, + setMonth, + getMonth, + addMonths, + subMonths, + getStartOfWeek, + getStartOfToday, + addDays, + formatDate, + setYear, + getYear, + isBefore, + addYears, + subYears, + isAfter, + getFormattedWeekdayInLocale, + getWeekdayShortInLocale, + getWeekdayMinInLocale, + isSameDay, + monthDisabledBefore, + monthDisabledAfter, + yearDisabledBefore, + yearDisabledAfter, + yearsDisabledAfter, + yearsDisabledBefore, + getEffectiveMinDate, + getEffectiveMaxDate, + addZero, + isValid, + getYearsPeriod, + DEFAULT_YEAR_ITEM_NUMBER, +} from './date_utils'; + +const DROPDOWN_FOCUS_CLASSNAMES = [ + 'react-datepicker__year-select', + 'react-datepicker__month-select', + 'react-datepicker__month-year-select', +]; + +const isDropdownSelect = (element = {}) => { + const classNames = (element.className || '').split(/\s+/); + return DROPDOWN_FOCUS_CLASSNAMES.some(testClassname => classNames.indexOf(testClassname) >= 0); +}; + +export default class Calendar extends React.Component { + static get defaultProps() { + return { + onDropdownFocus: () => {}, + monthsShown: 1, + monthSelectedIn: 0, + forceShowMonthNavigation: false, + timeCaption: 'Time', + previousYearButtonLabel: 'Previous Year', + nextYearButtonLabel: 'Next Year', + previousMonthButtonLabel: 'Previous Month', + nextMonthButtonLabel: 'Next Month', + customTimeInput: null, + yearItemNumber: DEFAULT_YEAR_ITEM_NUMBER, + }; + } + + static propTypes = { + adjustDateOnChange: PropTypes.bool, + arrowProps: PropTypes.object, + chooseDayAriaLabelPrefix: PropTypes.string, + className: PropTypes.string, + children: PropTypes.node, + container: PropTypes.func, + dateFormat: PropTypes.oneOfType([PropTypes.string, PropTypes.array]) + .isRequired, + dayClassName: PropTypes.func, + weekDayClassName: PropTypes.func, + disabledDayAriaLabelPrefix: PropTypes.string, + monthClassName: PropTypes.func, + timeClassName: PropTypes.func, + disabledKeyboardNavigation: PropTypes.bool, + calendarStartDay: PropTypes.number, + dropdownMode: PropTypes.oneOf(['scroll', 'select']), + endDate: PropTypes.instanceOf(Date), + excludeDates: PropTypes.array, + filterDate: PropTypes.func, + fixedHeight: PropTypes.bool, + formatWeekNumber: PropTypes.func, + highlightDates: PropTypes.instanceOf(Map), + includeDates: PropTypes.array, + includeTimes: PropTypes.array, + injectTimes: PropTypes.array, + inline: PropTypes.bool, + shouldFocusDayInline: PropTypes.bool, + locale: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.shape({ locale: PropTypes.object }), + ]), + maxDate: PropTypes.instanceOf(Date), + minDate: PropTypes.instanceOf(Date), + monthsShown: PropTypes.number, + monthSelectedIn: PropTypes.number, + nextMonthAriaLabel: PropTypes.string, + nextYearAriaLabel: PropTypes.string, + onClickOutside: PropTypes.func.isRequired, + onMonthChange: PropTypes.func, + onYearChange: PropTypes.func, + forceShowMonthNavigation: PropTypes.bool, + onDropdownFocus: PropTypes.func, + onSelect: PropTypes.func.isRequired, + onWeekSelect: PropTypes.func, + showTimeSelect: PropTypes.bool, + showTimeInput: PropTypes.bool, + showMonthYearPicker: PropTypes.bool, + showFullMonthYearPicker: PropTypes.bool, + showTwoColumnMonthYearPicker: PropTypes.bool, + showFourColumnMonthYearPicker: PropTypes.bool, + showYearPicker: PropTypes.bool, + showQuarterYearPicker: PropTypes.bool, + showTimeSelectOnly: PropTypes.bool, + timeFormat: PropTypes.string, + timeIntervals: PropTypes.number, + onTimeChange: PropTypes.func, + timeInputLabel: PropTypes.string, + minTime: PropTypes.instanceOf(Date), + maxTime: PropTypes.instanceOf(Date), + excludeTimes: PropTypes.array, + filterTime: PropTypes.func, + timeCaption: PropTypes.string, + openToDate: PropTypes.instanceOf(Date), + peekNextMonth: PropTypes.bool, + previousMonthAriaLabel: PropTypes.string, + previousYearAriaLabel: PropTypes.string, + scrollableYearDropdown: PropTypes.bool, + scrollableMonthYearDropdown: PropTypes.bool, + preSelection: PropTypes.instanceOf(Date), + selected: PropTypes.instanceOf(Date), + selectsEnd: PropTypes.bool, + selectsStart: PropTypes.bool, + selectsRange: PropTypes.bool, + showMonthDropdown: PropTypes.bool, + showPreviousMonths: PropTypes.bool, + showMonthYearDropdown: PropTypes.bool, + showWeekNumbers: PropTypes.bool, + showYearDropdown: PropTypes.bool, + startDate: PropTypes.instanceOf(Date), + todayButton: PropTypes.string, + useWeekdaysShort: PropTypes.bool, + formatWeekDay: PropTypes.func, + withPortal: PropTypes.bool, + weekLabel: PropTypes.string, + yearItemNumber: PropTypes.number, + yearDropdownItemNumber: PropTypes.number, + setOpen: PropTypes.func, + shouldCloseOnSelect: PropTypes.bool, + useShortMonthInDropdown: PropTypes.bool, + showDisabledMonthNavigation: PropTypes.bool, + previousMonthButtonLabel: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.node, + ]), + nextMonthButtonLabel: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.node, + ]), + previousYearButtonLabel: PropTypes.string, + nextYearButtonLabel: PropTypes.string, + renderCustomHeader: PropTypes.func, + renderDayContents: PropTypes.func, + onDayMouseEnter: PropTypes.func, + onMonthMouseLeave: PropTypes.func, + showPopperArrow: PropTypes.bool, + handleOnKeyDown: PropTypes.func, + handleOnDayKeyDown: PropTypes.func, + isInputFocused: PropTypes.bool, + customTimeInput: PropTypes.element, + weekAriaLabelPrefix: PropTypes.string, + setPreSelection: PropTypes.func, + }; + + constructor(props) { + super(props); + + this.containerRef = React.createRef(); + + this.state = { + date: this.getDateInView(), + selectingDate: null, + monthContainer: null, + }; + } + + componentDidMount() { + // monthContainer height is needed in time component + // to determine the height for the ul in the time component + // setState here so height is given after final component + // layout is rendered + if (this.props.showTimeSelect) { + this.assignMonthContainer = (() => { + this.setState({ monthContainer: this.monthContainer }); + })(); + } + } + + componentDidUpdate(prevProps) { + if ( + this.props.preSelection + && !isSameDay(this.props.preSelection, prevProps.preSelection) + ) { + this.setState({ + date: this.props.preSelection, + }); + } else if ( + this.props.openToDate + && !isSameDay(this.props.openToDate, prevProps.openToDate) + ) { + this.setState({ + date: this.props.openToDate, + }); + } + } + + handleClickOutside = (event) => { + this.props.onClickOutside(event); + }; + + setClickOutsideRef = () => this.containerRef.current; + + handleDropdownFocus = (event) => { + if (isDropdownSelect(event.target)) { + this.props.onDropdownFocus(); + } + }; + + getDateInView = () => { + const { preSelection, selected, openToDate } = this.props; + const minDate = getEffectiveMinDate(this.props); + const maxDate = getEffectiveMaxDate(this.props); + const current = newDate(); + const initialDate = openToDate || selected || preSelection; + if (initialDate) { + return initialDate; + } + if (minDate && isBefore(current, minDate)) { + return minDate; + } if (maxDate && isAfter(current, maxDate)) { + return maxDate; + } + + return current; + }; + + increaseMonth = () => { + this.setState( + ({ date }) => ({ + date: addMonths(date, 1), + }), + () => this.handleMonthChange(this.state.date), + ); + }; + + decreaseMonth = () => { + this.setState( + ({ date }) => ({ + date: subMonths(date, 1), + }), + () => this.handleMonthChange(this.state.date), + ); + }; + + handleDayClick = (day, event, monthSelectedIn) => { + this.props.onSelect(day, event, monthSelectedIn); + this.props.setPreSelection && this.props.setPreSelection(day); + }; + + handleDayMouseEnter = (day) => { + this.setState({ selectingDate: day }); + this.props.onDayMouseEnter && this.props.onDayMouseEnter(day); + }; + + handleMonthMouseLeave = () => { + this.setState({ selectingDate: null }); + this.props.onMonthMouseLeave && this.props.onMonthMouseLeave(); + }; + + handleYearChange = (date) => { + if (this.props.onYearChange) { + this.props.onYearChange(date); + } + if (this.props.adjustDateOnChange) { + if (this.props.onSelect) { + this.props.onSelect(date); + } + if (this.props.setOpen) { + this.props.setOpen(true); + } + } + + this.props.setPreSelection && this.props.setPreSelection(date); + }; + + handleMonthChange = (date) => { + if (this.props.onMonthChange) { + this.props.onMonthChange(date); + } + if (this.props.adjustDateOnChange) { + if (this.props.onSelect) { + this.props.onSelect(date); + } + if (this.props.setOpen) { + this.props.setOpen(true); + } + } + + this.props.setPreSelection && this.props.setPreSelection(date); + }; + + handleMonthYearChange = (date) => { + this.handleYearChange(date); + this.handleMonthChange(date); + }; + + changeYear = (year) => { + this.setState( + ({ date }) => ({ + date: setYear(date, year), + }), + () => this.handleYearChange(this.state.date), + ); + }; + + changeMonth = (month) => { + this.setState( + ({ date }) => ({ + date: setMonth(date, month), + }), + () => this.handleMonthChange(this.state.date), + ); + }; + + changeMonthYear = (monthYear) => { + this.setState( + ({ date }) => ({ + date: setYear(setMonth(date, getMonth(monthYear)), getYear(monthYear)), + }), + () => this.handleMonthYearChange(this.state.date), + ); + }; + + header = (date = this.state.date) => { + const startOfWeek = getStartOfWeek( + date, + this.props.locale, + this.props.calendarStartDay, + ); + + const dayNames = []; + if (this.props.showWeekNumbers) { + dayNames.push(
+ {this.props.weekLabel || '#'} +
); + } + return dayNames.concat([0, 1, 2, 3, 4, 5, 6].map((offset) => { + const day = addDays(startOfWeek, offset); + const weekDayName = this.formatWeekday(day, this.props.locale); + + const weekDayClassName = this.props.weekDayClassName + ? this.props.weekDayClassName(day) + : undefined; + + return ( +
+ {weekDayName} +
+ ); + })); + }; + + formatWeekday = (day, locale) => { + if (this.props.formatWeekDay) { + return getFormattedWeekdayInLocale(day, this.props.formatWeekDay, locale); + } + return this.props.useWeekdaysShort + ? getWeekdayShortInLocale(day, locale) + : getWeekdayMinInLocale(day, locale); + }; + + decreaseYear = () => { + this.setState( + ({ date }) => ({ + date: subYears( + date, + this.props.showYearPicker ? this.props.yearItemNumber : 1, + ), + }), + () => this.handleYearChange(this.state.date), + ); + }; + + renderPreviousButton = () => { + if (this.props.renderCustomHeader) { + return; + } + + let allPrevDaysDisabled; + switch (true) { + case this.props.showMonthYearPicker: + allPrevDaysDisabled = yearDisabledBefore(this.state.date, this.props); + break; + case this.props.showYearPicker: + allPrevDaysDisabled = yearsDisabledBefore(this.state.date, this.props); + break; + default: + allPrevDaysDisabled = monthDisabledBefore(this.state.date, this.props); + break; + } + + if ( + (!this.props.forceShowMonthNavigation + && !this.props.showDisabledMonthNavigation + && allPrevDaysDisabled) + || this.props.showTimeSelectOnly + ) { + return; + } + + const iconClasses = [ + 'react-datepicker__navigation-icon', + 'react-datepicker__navigation-icon--previous', + ]; + + const classes = [ + 'react-datepicker__navigation', + 'react-datepicker__navigation--previous', + ]; + + let clickHandler = this.decreaseMonth; + + if ( + this.props.showMonthYearPicker + || this.props.showQuarterYearPicker + || this.props.showYearPicker + ) { + clickHandler = this.decreaseYear; + } + + if (allPrevDaysDisabled && this.props.showDisabledMonthNavigation) { + classes.push('react-datepicker__navigation--previous--disabled'); + clickHandler = null; + } + + const isForYear = this.props.showMonthYearPicker + || this.props.showQuarterYearPicker + || this.props.showYearPicker; + + const { previousMonthButtonLabel, previousYearButtonLabel } = this.props; + + const { + previousMonthAriaLabel = typeof previousMonthButtonLabel === 'string' + ? previousMonthButtonLabel + : 'Previous Month', + previousYearAriaLabel = typeof previousYearButtonLabel === 'string' + ? previousYearButtonLabel + : 'Previous Year', + } = this.props; + + return ( + + ); + }; + + increaseYear = () => { + this.setState( + ({ date }) => ({ + date: addYears( + date, + this.props.showYearPicker ? this.props.yearItemNumber : 1, + ), + }), + () => this.handleYearChange(this.state.date), + ); + }; + + renderNextButton = () => { + if (this.props.renderCustomHeader) { + return; + } + + let allNextDaysDisabled; + switch (true) { + case this.props.showMonthYearPicker: + allNextDaysDisabled = yearDisabledAfter(this.state.date, this.props); + break; + case this.props.showYearPicker: + allNextDaysDisabled = yearsDisabledAfter(this.state.date, this.props); + break; + default: + allNextDaysDisabled = monthDisabledAfter(this.state.date, this.props); + break; + } + + if ( + (!this.props.forceShowMonthNavigation + && !this.props.showDisabledMonthNavigation + && allNextDaysDisabled) + || this.props.showTimeSelectOnly + ) { + return; + } + + const classes = [ + 'react-datepicker__navigation', + 'react-datepicker__navigation--next', + ]; + const iconClasses = [ + 'react-datepicker__navigation-icon', + 'react-datepicker__navigation-icon--next', + ]; + if (this.props.showTimeSelect) { + classes.push('react-datepicker__navigation--next--with-time'); + } + if (this.props.todayButton) { + classes.push('react-datepicker__navigation--next--with-today-button'); + } + + let clickHandler = this.increaseMonth; + + if ( + this.props.showMonthYearPicker + || this.props.showQuarterYearPicker + || this.props.showYearPicker + ) { + clickHandler = this.increaseYear; + } + + if (allNextDaysDisabled && this.props.showDisabledMonthNavigation) { + classes.push('react-datepicker__navigation--next--disabled'); + clickHandler = null; + } + + const isForYear = this.props.showMonthYearPicker + || this.props.showQuarterYearPicker + || this.props.showYearPicker; + + const { nextMonthButtonLabel, nextYearButtonLabel } = this.props; + const { + nextMonthAriaLabel = typeof nextMonthButtonLabel === 'string' + ? nextMonthButtonLabel + : 'Next Month', + nextYearAriaLabel = typeof nextYearButtonLabel === 'string' + ? nextYearButtonLabel + : 'Next Year', + } = this.props; + + return ( + + ); + }; + + renderCurrentMonth = (date = this.state.date) => { + const classes = ['react-datepicker__current-month']; + + if (this.props.showYearDropdown) { + classes.push('react-datepicker__current-month--hasYearDropdown'); + } + if (this.props.showMonthDropdown) { + classes.push('react-datepicker__current-month--hasMonthDropdown'); + } + if (this.props.showMonthYearDropdown) { + classes.push('react-datepicker__current-month--hasMonthYearDropdown'); + } + return ( +
+ {formatDate(date, this.props.dateFormat, this.props.locale)} +
+ ); + }; + + renderYearDropdown = (overrideHide = false) => { + if (!this.props.showYearDropdown || overrideHide) { + return; + } + return ( + + ); + }; + + renderMonthDropdown = (overrideHide = false) => { + if (!this.props.showMonthDropdown || overrideHide) { + return; + } + return ( + + ); + }; + + renderMonthYearDropdown = (overrideHide = false) => { + if (!this.props.showMonthYearDropdown || overrideHide) { + return; + } + return ( + + ); + }; + + renderTodayButton = () => { + if (!this.props.todayButton || this.props.showTimeSelectOnly) { + return; + } + return ( +
this.props.onSelect(getStartOfToday(), e)} + > + {this.props.todayButton} +
+ ); + }; + + renderDefaultHeader = ({ monthDate, i }) => ( +
+ {this.renderCurrentMonth(monthDate)} +
+ {this.renderMonthDropdown(i !== 0)} + {this.renderMonthYearDropdown(i !== 0)} + {this.renderYearDropdown(i !== 0)} +
+
+ {this.header(monthDate)} +
+
+ ); + + renderCustomHeader = (headerArgs = {}) => { + const { monthDate, i } = headerArgs; + + if ( + (this.props.showTimeSelect && !this.state.monthContainer) + || this.props.showTimeSelectOnly + ) { + return null; + } + + const prevMonthButtonDisabled = monthDisabledBefore( + this.state.date, + this.props, + ); + + const nextMonthButtonDisabled = monthDisabledAfter( + this.state.date, + this.props, + ); + + const prevYearButtonDisabled = yearDisabledBefore( + this.state.date, + this.props, + ); + + const nextYearButtonDisabled = yearDisabledAfter( + this.state.date, + this.props, + ); + + const showDayNames = !this.props.showMonthYearPicker + && !this.props.showQuarterYearPicker + && !this.props.showYearPicker; + + return ( +
+ {this.props.renderCustomHeader({ + ...this.state, + customHeaderCount: i, + monthDate, + changeMonth: this.changeMonth, + changeYear: this.changeYear, + decreaseMonth: this.decreaseMonth, + increaseMonth: this.increaseMonth, + decreaseYear: this.decreaseYear, + increaseYear: this.increaseYear, + prevMonthButtonDisabled, + nextMonthButtonDisabled, + prevYearButtonDisabled, + nextYearButtonDisabled, + })} + {showDayNames && ( +
+ {this.header(monthDate)} +
+ )} +
+ ); + }; + + renderYearHeader = () => { + const { date } = this.state; + const { showYearPicker, yearItemNumber } = this.props; + const { startPeriod, endPeriod } = getYearsPeriod(date, yearItemNumber); + return ( +
+ {showYearPicker ? `${startPeriod} - ${endPeriod}` : getYear(date)} +
+ ); + }; + + renderHeader = (headerArgs) => { + switch (true) { + case this.props.renderCustomHeader !== undefined: + return this.renderCustomHeader(headerArgs); + case this.props.showMonthYearPicker + || this.props.showQuarterYearPicker + || this.props.showYearPicker: + return this.renderYearHeader(headerArgs); + default: + return this.renderDefaultHeader(headerArgs); + } + }; + + renderMonths = () => { + if (this.props.showTimeSelectOnly || this.props.showYearPicker) { + return; + } + + const monthList = []; + const monthsToSubtract = this.props.showPreviousMonths + ? this.props.monthsShown - 1 + : 0; + const fromMonthDate = subMonths(this.state.date, monthsToSubtract); + for (let i = 0; i < this.props.monthsShown; ++i) { + const monthsToAdd = i - this.props.monthSelectedIn; + const monthDate = addMonths(fromMonthDate, monthsToAdd); + const monthKey = `month-${i}`; + const monthShowsDuplicateDaysEnd = i < this.props.monthsShown - 1; + const monthShowsDuplicateDaysStart = i > 0; + monthList.push(
{ + this.monthContainer = div; + }} + className="react-datepicker__month-container" + > + {this.renderHeader({ monthDate, i })} + +
); + } + return monthList; + }; + + renderYears = () => { + if (this.props.showTimeSelectOnly) { + return; + } + if (this.props.showYearPicker) { + return ( +
+ {this.renderHeader()} + +
+ ); + } + }; + + renderTimeSection = () => { + if ( + this.props.showTimeSelect + && (this.state.monthContainer || this.props.showTimeSelectOnly) + ) { + return ( +