yearAndMonth.tsx 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. /* eslint-disable max-len */
  2. import React from 'react';
  3. import PropTypes from 'prop-types';
  4. import YearAndMonthFoundation, { MonthScrollItem, YearAndMonthAdapter, YearAndMonthFoundationProps, YearAndMonthFoundationState, YearScrollItem } from '@douyinfe/semi-foundation/datePicker/yearAndMonthFoundation';
  5. import BaseComponent, { BaseProps } from '../_base/baseComponent';
  6. import ScrollList from '../scrollList/index';
  7. import ScrollItem from '../scrollList/scrollItem';
  8. import { getYears } from '@douyinfe/semi-foundation/datePicker/_utils/index';
  9. import isNullOrUndefined from '@douyinfe/semi-foundation/utils/isNullOrUndefined';
  10. import IconButton from '../iconButton';
  11. import { IconChevronLeft } from '@douyinfe/semi-icons';
  12. import { BASE_CLASS_PREFIX } from '@douyinfe/semi-foundation/base/constants';
  13. import { noop, stubFalse } from 'lodash';
  14. import { setYear, setMonth } from 'date-fns';
  15. import { Locale } from '../locale/interface';
  16. const prefixCls = `${BASE_CLASS_PREFIX}-datepicker`;
  17. export interface YearAndMonthProps extends YearAndMonthFoundationProps, BaseProps {
  18. locale?: Locale['DatePicker'];
  19. }
  20. export type YearAndMonthState = YearAndMonthFoundationState;
  21. class YearAndMonth extends BaseComponent<YearAndMonthProps, YearAndMonthState> {
  22. static propTypes = {
  23. currentYear: PropTypes.number,
  24. currentMonth: PropTypes.number,
  25. onSelect: PropTypes.func,
  26. locale: PropTypes.object,
  27. localeCode: PropTypes.string,
  28. monthCycled: PropTypes.bool,
  29. yearCycled: PropTypes.bool,
  30. noBackBtn: PropTypes.bool,
  31. disabledDate: PropTypes.func,
  32. density: PropTypes.string,
  33. };
  34. static defaultProps = {
  35. disabledDate: stubFalse,
  36. monthCycled: false,
  37. yearCycled: false,
  38. noBackBtn: false,
  39. onSelect: noop,
  40. };
  41. foundation: YearAndMonthFoundation;
  42. yearRef: React.RefObject<ScrollItem<YearScrollItem>>;
  43. monthRef: React.RefObject<ScrollItem<MonthScrollItem>>;
  44. constructor(props: YearAndMonthProps) {
  45. super(props);
  46. const now = new Date();
  47. let { currentYear, currentMonth } = props;
  48. currentYear = currentYear || now.getFullYear();
  49. currentMonth = currentMonth || now.getMonth() + 1;
  50. this.state = {
  51. years: getYears().map(year => ({
  52. value: year,
  53. year,
  54. })),
  55. months: Array(12)
  56. .fill(0)
  57. .map((v, idx) => ({
  58. value: idx + 1,
  59. month: idx + 1,
  60. })),
  61. currentYear,
  62. currentMonth,
  63. };
  64. this.yearRef = React.createRef();
  65. this.monthRef = React.createRef();
  66. this.foundation = new YearAndMonthFoundation(this.adapter);
  67. }
  68. get adapter(): YearAndMonthAdapter {
  69. return {
  70. ...super.adapter,
  71. // updateYears: years => this.setState({ years }),
  72. // updateMonths: months => this.setState({ months }),
  73. setCurrentYear: currentYear => this.setState({ currentYear }),
  74. setCurrentMonth: currentMonth => this.setState({ currentMonth }),
  75. notifySelectYear: year =>
  76. this.props.onSelect({
  77. currentMonth: this.state.currentMonth,
  78. currentYear: year,
  79. }),
  80. notifySelectMonth: month =>
  81. this.props.onSelect({
  82. currentYear: this.state.currentYear,
  83. currentMonth: month,
  84. }),
  85. notifyBackToMain: () => this.props.onBackToMain(),
  86. };
  87. }
  88. static getDerivedStateFromProps(props: YearAndMonthProps, state: YearAndMonthState) {
  89. const willUpdateStates: Partial<YearAndMonthState> = {};
  90. const now = new Date();
  91. if (!isNullOrUndefined(props.currentMonth) && props.currentMonth !== state.currentMonth && props.currentMonth !== 0) {
  92. willUpdateStates.currentMonth = props.currentMonth || now.getMonth() + 1;
  93. }
  94. if (isNullOrUndefined(props.currentYear) && props.currentYear !== state.currentYear && props.currentYear !== 0) {
  95. willUpdateStates.currentYear = props.currentYear || now.getFullYear();
  96. }
  97. return willUpdateStates;
  98. }
  99. renderColYear() {
  100. const { years, currentYear, currentMonth } = this.state;
  101. const { disabledDate, localeCode, yearCycled } = this.props;
  102. const currentDate = setMonth(Date.now(), currentMonth - 1);
  103. const list: any[] = years.map(({ value, year }) => ({
  104. year,
  105. value, // Actual rendered text
  106. disabled: disabledDate(setYear(currentDate, year)),
  107. }));
  108. let transform = (val: string) => val;
  109. if (localeCode === 'zh-CN' || localeCode === 'zh-TW') {
  110. // Only Chinese needs to add [year] after the selected year
  111. transform = val => `${val }年`;
  112. }
  113. return (
  114. <ScrollItem
  115. ref={this.yearRef}
  116. cycled={yearCycled}
  117. list={list}
  118. transform={transform}
  119. selectedIndex={years.findIndex(item => item.value === currentYear)}
  120. type="year"
  121. onSelect={this.selectYear}
  122. />
  123. );
  124. }
  125. selectYear = (item: YearScrollItem) => {
  126. this.foundation.selectYear(item);
  127. };
  128. selectMonth = (item: MonthScrollItem) => {
  129. this.foundation.selectMonth(item);
  130. };
  131. reselect = () => {
  132. const refKeys = ['yearRef', 'monthRef'];
  133. refKeys.forEach(key => {
  134. const ref = this[key];
  135. if (ref && ref.current && ref.current.scrollToIndex) {
  136. ref.current.scrollToIndex();
  137. }
  138. });
  139. };
  140. renderColMonth() {
  141. const { months, currentMonth, currentYear } = this.state;
  142. const { locale, localeCode, monthCycled, disabledDate } = this.props;
  143. let transform = (val: string) => val;
  144. const currentDate = setYear(Date.now(), currentYear);
  145. if (localeCode === 'zh-CN' || localeCode === 'zh-TW') {
  146. // Only Chinese needs to add [month] after the selected month
  147. transform = val => `${val }月`;
  148. }
  149. // i18n
  150. const list: MonthScrollItem[] = months.map(({ value, month }) => ({
  151. month,
  152. disabled: disabledDate(setMonth(currentDate, month - 1)),
  153. value: locale.fullMonths[value], // Actual rendered text
  154. }));
  155. const selectedIndex = list.findIndex(item => item.month === currentMonth);
  156. return (
  157. <ScrollItem
  158. ref={this.monthRef}
  159. cycled={monthCycled}
  160. list={list}
  161. transform={transform}
  162. selectedIndex={selectedIndex}
  163. type="month"
  164. onSelect={this.selectMonth}
  165. />
  166. );
  167. }
  168. backToMain: React.MouseEventHandler<HTMLButtonElement> = e => {
  169. e.nativeEvent.stopImmediatePropagation();
  170. this.foundation.backToMain();
  171. };
  172. render() {
  173. const { locale, noBackBtn, density } = this.props;
  174. const prefix = `${prefixCls}-yearmonth-header`;
  175. // i18n
  176. const selectDateText = locale.selectDate;
  177. const iconSize = density === 'compact' ? 'default' : 'large';
  178. const buttonSize = density === 'compact' ? 'small' : 'default';
  179. return (
  180. <React.Fragment>
  181. {noBackBtn ? null : (
  182. <div className={prefix}>
  183. <IconButton
  184. noHorizontalPadding={false}
  185. icon={<IconChevronLeft size={iconSize} />}
  186. size={buttonSize}
  187. onClick={this.backToMain}
  188. >
  189. <span>{selectDateText}</span>
  190. </IconButton>
  191. </div>
  192. )}
  193. <ScrollList>
  194. {this.renderColYear()}
  195. {this.renderColMonth()}
  196. </ScrollList>
  197. </React.Fragment>
  198. );
  199. }
  200. }
  201. export default YearAndMonth;