yearAndMonth.tsx 9.5 KB

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