weekCalendar.tsx 10 KB

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