rangeCalendar.tsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. import React from 'react';
  2. import cls from 'classnames';
  3. import PropTypes from 'prop-types';
  4. // eslint-disable-next-line max-len
  5. import CalendarFoundation, { ParsedEvents, CalendarAdapter, RangeData, ParsedRangeEvent, ParsedEventsType } from '@douyinfe/semi-foundation/calendar/foundation';
  6. import LocaleConsumer from '../locale/localeConsumer';
  7. import localeContext from '../locale/context';
  8. import { cssClasses } from '@douyinfe/semi-foundation/calendar/constants';
  9. import BaseComponent from '../_base/baseComponent';
  10. import DayCol from './dayCol';
  11. import TimeCol from './timeCol';
  12. import { isEqual } from 'lodash';
  13. import { calcRowHeight } from '@douyinfe/semi-foundation/calendar/eventUtil';
  14. import '@douyinfe/semi-foundation/calendar/calendar.scss';
  15. import { RangeCalendarProps } from './interface';
  16. import { Locale } from '../locale/interface';
  17. const toPercent = (num: number) => {
  18. const res = num < 1 ? num * 100 : 100;
  19. return `${res}%`;
  20. };
  21. const prefixCls = `${cssClasses.PREFIX}-week`;
  22. const allDayCls = `${cssClasses.PREFIX}-all-day`;
  23. export interface RangeCalendarState {
  24. scrollHeight: number;
  25. parsedEvents: ParsedEvents;
  26. cachedKeys: Array<string>;
  27. }
  28. export default class RangeCalendar extends BaseComponent<RangeCalendarProps, RangeCalendarState> {
  29. static propTypes = {
  30. // displayValue: PropTypes.instanceOf(Date),
  31. range: PropTypes.array,
  32. header: PropTypes.node,
  33. events: PropTypes.array,
  34. mode: PropTypes.string,
  35. showCurrTime: PropTypes.bool,
  36. markWeekend: PropTypes.bool,
  37. scrollTop: PropTypes.number,
  38. renderTimeDisplay: PropTypes.func,
  39. dateGridRender: PropTypes.func,
  40. width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  41. height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  42. style: PropTypes.object,
  43. className: PropTypes.string,
  44. };
  45. static defaultProps = {
  46. events: [] as ParsedEvents[],
  47. mode: 'range',
  48. };
  49. static contextType = localeContext;
  50. dom: React.RefObject<HTMLDivElement>;
  51. scrollDom: React.RefObject<HTMLDivElement>;
  52. isWeekend: boolean;
  53. allDayRowHeight: number;
  54. RangeData: RangeData;
  55. foundation: CalendarFoundation;
  56. constructor(props: RangeCalendarProps) {
  57. super(props);
  58. this.state = {
  59. scrollHeight: 0,
  60. parsedEvents: {
  61. day: new Map(),
  62. allDay: new Map()
  63. },
  64. cachedKeys: [],
  65. };
  66. this.foundation = new CalendarFoundation(this.adapter);
  67. this.dom = React.createRef();
  68. this.scrollDom = React.createRef();
  69. this.handleClick = this.handleClick.bind(this);
  70. this.allDayRowHeight = 1;
  71. }
  72. get adapter(): CalendarAdapter<RangeCalendarProps, RangeCalendarState> {
  73. return {
  74. ...super.adapter,
  75. setRangeData: data => {
  76. this.RangeData = data;
  77. },
  78. getRangeData: () => this.RangeData,
  79. updateScrollHeight: scrollHeight => {
  80. this.setState({ scrollHeight });
  81. },
  82. setParsedEvents: (parsedEvents: ParsedEventsType) => {
  83. this.setState({ parsedEvents: parsedEvents as ParsedEvents });
  84. },
  85. cacheEventKeys: cachedKeys => {
  86. this.setState({ cachedKeys });
  87. }
  88. };
  89. }
  90. componentDidMount() {
  91. this.foundation.init();
  92. const { scrollHeight } = this.scrollDom.current;
  93. this.dom.current.scrollTop = this.props.scrollTop;
  94. this.foundation.notifyScrollHeight(scrollHeight);
  95. this.foundation.parseRangeEvents();
  96. }
  97. componentDidUpdate(prevProps: RangeCalendarProps, prevState: RangeCalendarState) {
  98. const prevEventKeys = prevState.cachedKeys;
  99. const nowEventKeys = this.props.events.map(event => event.key);
  100. if (!isEqual(prevEventKeys, nowEventKeys)) {
  101. this.foundation.parseRangeEvents();
  102. }
  103. }
  104. componentWillUnmount() {
  105. this.foundation.destroy();
  106. }
  107. handleClick = (e: React.MouseEvent, val: [Date, number, number, number]) => {
  108. const { onClick } = this.props;
  109. const value = this.foundation.formatCbValue(val);
  110. onClick && onClick(e, value);
  111. };
  112. renderDayGrid = () => {
  113. const { parsedEvents } = this.state;
  114. const events = parsedEvents.day;
  115. const { week } = this.RangeData;
  116. const { markWeekend, dateGridRender } = this.props;
  117. const inner = week.map(day => {
  118. const dateString = day.date.toString();
  119. const dayEvents = events.has(dateString) ? events.get(dateString) : [];
  120. const parsed = this.foundation.getParseDailyEvents(dayEvents, day.date);
  121. return (
  122. <DayCol
  123. key={`${dateString}-weekday`}
  124. displayValue={day.date}
  125. scrollHeight={this.state.scrollHeight}
  126. handleClick={this.handleClick}
  127. events={parsed.day}
  128. showCurrTime={this.props.showCurrTime}
  129. isWeekend={markWeekend && day.isWeekend}
  130. dateGridRender={dateGridRender}
  131. />
  132. );
  133. });
  134. return inner;
  135. };
  136. renderHeader = (dateFnsLocale: Locale['dateFnsLocale']) => {
  137. const { markWeekend, range } = this.props;
  138. const { month, week } = this.foundation.getRangeData(range[0], dateFnsLocale);
  139. return (
  140. <div className={`${prefixCls}-header`}>
  141. <ul className={`${cssClasses.PREFIX}-tag ${prefixCls}-tag ${prefixCls}-sticky-left`}>
  142. <span>{month}</span>
  143. </ul>
  144. <div role="gridcell" className={`${prefixCls}-grid`}>
  145. <ul className={`${prefixCls}-grid-row`}>
  146. {week.map(day => {
  147. const { date, dayString, weekday, isToday } = day;
  148. const listCls = cls({
  149. [`${cssClasses.PREFIX}-today`]: isToday,
  150. [`${cssClasses.PREFIX}-weekend`]: markWeekend && day.isWeekend,
  151. });
  152. return (
  153. <li key={`${date.toString()}-weekheader`} className={listCls}>
  154. <span className={`${cssClasses.PREFIX}-today-date`}>{dayString}</span>
  155. <span>{weekday}</span>
  156. </li>
  157. );
  158. })}
  159. </ul>
  160. </div>
  161. </div>
  162. );
  163. };
  164. renderAllDayEvents = (events: Array<ParsedRangeEvent>) => {
  165. const list = events.map((event, ind) => {
  166. const { leftPos, width, topInd, children } = event;
  167. const top = `${topInd}em`;
  168. const style = {
  169. left: toPercent(leftPos),
  170. width: toPercent(width),
  171. top,
  172. };
  173. return (
  174. <li
  175. className={`${cssClasses.PREFIX}-event-item ${cssClasses.PREFIX}-event-allday`}
  176. key={`allDay-${ind}`}
  177. style={style}
  178. >
  179. {children}
  180. </li>
  181. );
  182. });
  183. return list;
  184. };
  185. renderAllDay = (locale: Locale['Calendar']) => {
  186. const { allDay } = this.state.parsedEvents;
  187. const parsed = this.foundation.parseRangeAllDayEvents(allDay);
  188. const maxRowHeight = calcRowHeight(parsed);
  189. const style = {
  190. height: `${maxRowHeight}em`
  191. };
  192. const { markWeekend } = this.props;
  193. const { week } = this.RangeData;
  194. return (
  195. <div className={`${allDayCls}`} style={style}>
  196. <ul className={`${cssClasses.PREFIX}-tag ${allDayCls}-tag ${prefixCls}-sticky-left`}>
  197. <span>{locale.allDay}</span>
  198. </ul>
  199. <div role="gridcell" className={`${cssClasses.PREFIX}-content ${allDayCls}-content`}>
  200. <ul className={`${allDayCls}-skeleton`}>
  201. {Object.keys(week).map((date, ind) => {
  202. const listCls = cls({
  203. [`${cssClasses.PREFIX}-weekend`]: markWeekend && week[date].isWeekend,
  204. });
  205. return (
  206. <li key={`${date}-weekgrid`} className={listCls} />
  207. );
  208. })}
  209. </ul>
  210. <ul className={`${cssClasses.PREFIX}-event-items`}>
  211. {this.renderAllDayEvents(parsed)}
  212. </ul>
  213. </div>
  214. </div>
  215. );
  216. };
  217. render() {
  218. const { renderTimeDisplay, className, height, width, style, header } = this.props;
  219. const weekCls = cls(prefixCls, className);
  220. const weekStyle = {
  221. height,
  222. width,
  223. ...style,
  224. };
  225. return (
  226. <LocaleConsumer componentName="Calendar">
  227. {(locale: Locale['Calendar'], localeCode: string, dateFnsLocale: Locale['dateFnsLocale']) => (
  228. <div className={weekCls} style={weekStyle} ref={this.dom}>
  229. <div className={`${prefixCls}-sticky-top`}>
  230. {header}
  231. {this.renderHeader(dateFnsLocale)}
  232. {this.renderAllDay(locale)}
  233. </div>
  234. <div className={`${prefixCls}-scroll-wrapper`}>
  235. <div className={`${prefixCls}-scroll`} ref={this.scrollDom}>
  236. <TimeCol
  237. className={`${prefixCls}-sticky-left`}
  238. renderTimeDisplay={renderTimeDisplay}
  239. />
  240. {this.renderDayGrid()}
  241. </div>
  242. </div>
  243. </div>
  244. )}
  245. </LocaleConsumer>
  246. );
  247. }
  248. }