From d372f0de1647195c701282727245a36f5864dc83 Mon Sep 17 00:00:00 2001
From: Alex Koehler <alexkoeh@gmail.com>
Date: Mon, 18 Jun 2018 09:01:22 -0700
Subject: [PATCH] feat: DnD support for custom DropWrapper components
 (dayWrapper and dateCellWrapper) (#843)

* Added DnD support for custom DateCellWrapper and DayWrapper components

* Code cleanup

Fixed all hardcoded `defaultView` prop strings to use the Views
constants

* Custom DropWrapper fixes

- Pass through range and value props to match non-dnd component props
- Added `customComponents` utility file for use with stories
- Clarified comment in `withDragAndDrop`
---
 examples/demos/customView.js              |  2 +-
 examples/demos/dnd.js                     |  2 +-
 examples/demos/rendering.js               |  2 +-
 examples/demos/resource.js                |  2 +-
 examples/demos/selectable.js              |  2 +-
 examples/demos/timeslots.js               |  2 +-
 src/addons/dragAndDrop/DropWrappers.js    | 35 ++++++++--
 src/addons/dragAndDrop/withDragAndDrop.js |  8 ++-
 stories/Calendar.js                       | 85 ++++++++++++++++++++---
 stories/customComponents.js               | 49 +++++++++++++
 10 files changed, 163 insertions(+), 26 deletions(-)
 create mode 100644 stories/customComponents.js

diff --git a/examples/demos/customView.js b/examples/demos/customView.js
index 4a6aa48420..876a550730 100644
--- a/examples/demos/customView.js
+++ b/examples/demos/customView.js
@@ -49,7 +49,7 @@ MyWeek.title = date => {
 let CustomView = () => (
   <BigCalendar
     events={events}
-    defaultView="week"
+    defaultView={BigCalendar.Views.WEEK}
     defaultDate={new Date(2015, 3, 1)}
     views={{ month: true, week: MyWeek }}
   />
diff --git a/examples/demos/dnd.js b/examples/demos/dnd.js
index 7d57749e8d..0b139c66eb 100644
--- a/examples/demos/dnd.js
+++ b/examples/demos/dnd.js
@@ -59,7 +59,7 @@ class Dnd extends React.Component {
         onEventDrop={this.moveEvent}
         resizable
         onEventResize={this.resizeEvent}
-        defaultView="week"
+        defaultView={BigCalendar.Views.WEEK}
         defaultDate={new Date(2015, 3, 12)}
       />
     )
diff --git a/examples/demos/rendering.js b/examples/demos/rendering.js
index 556f0dee35..a257f42df0 100644
--- a/examples/demos/rendering.js
+++ b/examples/demos/rendering.js
@@ -43,7 +43,7 @@ let Rendering = () => (
   <BigCalendar
     events={events}
     defaultDate={new Date(2015, 3, 1)}
-    defaultView="agenda"
+    defaultView={BigCalendar.Views.AGENDA}
     dayPropGetter={customDayPropGetter}
     slotPropGetter={customSlotPropGetter}
     components={{
diff --git a/examples/demos/resource.js b/examples/demos/resource.js
index fdf209274f..e946b9c776 100644
--- a/examples/demos/resource.js
+++ b/examples/demos/resource.js
@@ -43,7 +43,7 @@ const resourceMap = [
 let Resource = () => (
   <BigCalendar
     events={events}
-    defaultView="day"
+    defaultView={BigCalendar.Views.DAY}
     views={['day', 'work_week']}
     step={60}
     defaultDate={new Date(2018, 0, 29)}
diff --git a/examples/demos/selectable.js b/examples/demos/selectable.js
index 0d952ae3f2..43c1174a33 100644
--- a/examples/demos/selectable.js
+++ b/examples/demos/selectable.js
@@ -11,7 +11,7 @@ let Selectable = () => (
     <BigCalendar
       selectable
       events={events}
-      defaultView="week"
+      defaultView={BigCalendar.Views.WEEK}
       scrollToTime={new Date(1970, 1, 1, 6)}
       defaultDate={new Date(2015, 3, 12)}
       onSelectEvent={event => alert(event.title)}
diff --git a/examples/demos/timeslots.js b/examples/demos/timeslots.js
index 0cad219b1f..09c4a8b216 100644
--- a/examples/demos/timeslots.js
+++ b/examples/demos/timeslots.js
@@ -7,7 +7,7 @@ let Timeslots = () => (
     events={events}
     step={15}
     timeslots={8}
-    defaultView="week"
+    defaultView={BigCalendar.Views.WEEK}
     defaultDate={new Date(2015, 3, 12)}
   />
 )
diff --git a/src/addons/dragAndDrop/DropWrappers.js b/src/addons/dragAndDrop/DropWrappers.js
index 82e4cf66e5..210549a278 100644
--- a/src/addons/dragAndDrop/DropWrappers.js
+++ b/src/addons/dragAndDrop/DropWrappers.js
@@ -16,12 +16,12 @@ function getEventDropProps(start, end, dropDate, droppedInAllDay) {
   /*
    * If the event is dropped in a "Day" cell, preserve an event's start time by extracting the hours and minutes off
    * the original start date and add it to newDate.value
-   * 
+   *
    * note: this behavior remains for backward compatibility, but might be counter-intuitive to some:
    * dragging an event from the grid to the day header might more commonly mean "make this an allDay event
    * on that day" - but the behavior here implements "keep the times of the event, but move it to the
    * new day".
-   * 
+   *
    * To permit either interpretation, we embellish a new `allDay` parameter which determines whether the
    * event was dropped on the day header or not.
    */
@@ -39,13 +39,16 @@ function getEventDropProps(start, end, dropDate, droppedInAllDay) {
 class DropWrapper extends React.Component {
   static propTypes = {
     connectDropTarget: PropTypes.func.isRequired,
-    type: PropTypes.string,
     isOver: PropTypes.bool,
+    range: PropTypes.arrayOf(PropTypes.instanceOf(Date)),
+    type: PropTypes.string,
+    value: PropTypes.instanceOf(Date),
   }
 
   static contextTypes = {
     onEventDrop: PropTypes.func,
     onEventResize: PropTypes.func,
+    components: PropTypes.object,
     dragDropManager: PropTypes.object,
     startAccessor: accessor,
     endAccessor: accessor,
@@ -98,17 +101,35 @@ class DropWrapper extends React.Component {
   // };
 
   render() {
-    const { connectDropTarget, children, type, isOver } = this.props
-    const BackgroundWrapper = BigCalendar.components[type]
+    const {
+      connectDropTarget,
+      children,
+      isOver,
+      range,
+      type,
+      value,
+    } = this.props
+
+    // Check if wrapper component of this type was passed in, otherwise use library default
+    const { components } = this.context
+    const BackgroundWrapper = components[type] || BigCalendar.components[type]
+    const backgroundWrapperProps = {
+      value,
+    }
+
+    if (range) {
+      backgroundWrapperProps.range = range
+    }
 
     let resultingChildren = children
-    if (isOver)
+    if (isOver) {
       resultingChildren = React.cloneElement(children, {
         className: cn(children.props.className, 'rbc-addons-dnd-over'),
       })
+    }
 
     return (
-      <BackgroundWrapper>
+      <BackgroundWrapper {...backgroundWrapperProps}>
         {connectDropTarget(resultingChildren)}
       </BackgroundWrapper>
     )
diff --git a/src/addons/dragAndDrop/withDragAndDrop.js b/src/addons/dragAndDrop/withDragAndDrop.js
index fafff7b888..4d8b6faa47 100644
--- a/src/addons/dragAndDrop/withDragAndDrop.js
+++ b/src/addons/dragAndDrop/withDragAndDrop.js
@@ -52,8 +52,9 @@ try {
  * If you care about these corner cases, you can examine the `allDay` param suppled
  * in the callback to determine how the user dropped or resized the event.
  *
- * Note: you cannot use custom `EventWrapper`, `DayWrapper` or `DateCellWrapper`
- * components when using this HOC as they are overwritten here.
+ * Note: you cannot use custom `EventWrapper` components when using this HOC as it
+ * is overwritten here.
+ *
  *
  * @param {*} Calendar
  * @param {*} backend
@@ -79,6 +80,7 @@ export default function withDragAndDrop(
 
     static defaultProps = {
       // TODO: pick these up from Calendar.defaultProps
+      components: {},
       startAccessor: 'start',
       endAccessor: 'end',
       allDayAccessor: 'allDay',
@@ -94,6 +96,7 @@ export default function withDragAndDrop(
     static childContextTypes = {
       onEventDrop: PropTypes.func,
       onEventResize: PropTypes.func,
+      components: PropTypes.object,
       startAccessor: accessor,
       endAccessor: accessor,
       draggableAccessor: accessor,
@@ -105,6 +108,7 @@ export default function withDragAndDrop(
       return {
         onEventDrop: this.props.onEventDrop,
         onEventResize: this.props.onEventResize,
+        components: this.props.components,
         startAccessor: this.props.startAccessor,
         endAccessor: this.props.endAccessor,
         step: this.props.step,
diff --git a/stories/Calendar.js b/stories/Calendar.js
index 4c8085005e..505ecfbc07 100644
--- a/stories/Calendar.js
+++ b/stories/Calendar.js
@@ -8,6 +8,7 @@ import '../src/less/styles.less'
 import '../src/addons/dragAndDrop/styles.less'
 import demoEvents from '../examples/events'
 import createEvents from './createEvents'
+import customComponents from './customComponents'
 import resources from './resourceEvents'
 import withDragAndDrop from '../src/addons/dragAndDrop'
 
@@ -119,7 +120,7 @@ storiesOf('module.Calendar.week', module)
     return (
       <div style={{ height: 600 }}>
         <Calendar
-          defaultView="week"
+          defaultView={Calendar.Views.WEEK}
           min={moment('12:00am', 'h:mma').toDate()}
           max={moment('11:59pm', 'h:mma').toDate()}
           events={events}
@@ -133,7 +134,7 @@ storiesOf('module.Calendar.week', module)
     return (
       <div style={{ height: 600 }}>
         <DragableCalendar
-          defaultView="day"
+          defaultView={Calendar.Views.DAY}
           min={moment('12:00am', 'h:mma').toDate()}
           max={moment('11:59pm', 'h:mma').toDate()}
           events={[
@@ -181,7 +182,7 @@ storiesOf('module.Calendar.week', module)
     return (
       <div style={{ height: 600 }}>
         <Calendar
-          defaultView="week"
+          defaultView={Calendar.Views.WEEK}
           selectable
           min={moment('12:00am', 'h:mma').toDate()}
           max={moment('11:59pm', 'h:mma').toDate()}
@@ -197,7 +198,7 @@ storiesOf('module.Calendar.week', module)
     return (
       <div style={{ height: 600 }}>
         <Calendar
-          defaultView="week"
+          defaultView={Calendar.Views.WEEK}
           selectable
           timeslots={4}
           step={15}
@@ -215,7 +216,7 @@ storiesOf('module.Calendar.week', module)
     return (
       <div style={{ height: 600 }}>
         <Calendar
-          defaultView="week"
+          defaultView={Calendar.Views.WEEK}
           selectable
           timeslots={6}
           step={10}
@@ -233,7 +234,7 @@ storiesOf('module.Calendar.week', module)
     return (
       <div style={{ height: 600 }}>
         <Calendar
-          defaultView="week"
+          defaultView={Calendar.Views.WEEK}
           selectable
           timeslots={6}
           step={5}
@@ -251,7 +252,7 @@ storiesOf('module.Calendar.week', module)
     return (
       <div style={{ height: 600 }}>
         <Calendar
-          defaultView="week"
+          defaultView={Calendar.Views.WEEK}
           selectable
           timeslots={3}
           getNow={() => moment('9:30am', 'h:mma').toDate()}
@@ -372,6 +373,32 @@ storiesOf('module.Calendar.week', module)
       </div>
     )
   })
+  .add('add custom dateCellWrapper', () => {
+    return (
+      <div style={{ height: 600 }}>
+        <Calendar
+          defaultView={Calendar.Views.MONTH}
+          events={events}
+          components={{
+            dateCellWrapper: customComponents.dateCellWrapper,
+          }}
+        />
+      </div>
+    )
+  })
+  .add('add custom dayWrapper', () => {
+    return (
+      <div style={{ height: 600 }}>
+        <Calendar
+          defaultView={Calendar.Views.DAY}
+          events={events}
+          components={{
+            dayWrapper: customComponents.dayWrapper,
+          }}
+        />
+      </div>
+    )
+  })
   .add('no duration', () => {
     return (
       <div style={{ height: 600 }}>
@@ -505,7 +532,7 @@ storiesOf('module.Calendar.week', module)
     return (
       <div style={{ height: 600 }}>
         <Calendar
-          defaultView="week"
+          defaultView={Calendar.Views.WEEK}
           getNow={customNow}
           min={moment('12:00am', 'h:mma').toDate()}
           max={moment('11:59pm', 'h:mma').toDate()}
@@ -521,7 +548,7 @@ storiesOf('module.Calendar.week', module)
       <div style={{ height: 600 }}>
         <DragAndDropCalendar
           defaultDate={new Date()}
-          defaultView="week"
+          defaultView={Calendar.Views.WEEK}
           events={events}
           resizable
           onEventDrop={action('event dropped')}
@@ -535,7 +562,7 @@ storiesOf('module.Calendar.week', module)
       <div style={{ height: 600 }}>
         <DragAndDropCalendar
           defaultDate={new Date()}
-          defaultView="week"
+          defaultView={Calendar.Views.WEEK}
           events={events}
           resizable
           step={15}
@@ -551,7 +578,43 @@ storiesOf('module.Calendar.week', module)
       <div style={{ height: 600 }}>
         <DragAndDropCalendar
           defaultDate={new Date()}
-          defaultView="week"
+          defaultView={Calendar.Views.WEEK}
+          events={events}
+          resizable
+          showMultiDayTimes
+          onEventDrop={action('event dropped')}
+          onEventResize={action('event resized')}
+        />
+      </div>
+    )
+  })
+  .add('draggable and resizable with custom dateCellWrapper', () => {
+    return (
+      <div style={{ height: 600 }}>
+        <DragAndDropCalendar
+          components={{
+            dateCellWrapper: customComponents.dateCellWrapper,
+          }}
+          defaultDate={new Date()}
+          defaultView={Calendar.Views.MONTH}
+          events={events}
+          resizable
+          showMultiDayTimes
+          onEventDrop={action('event dropped')}
+          onEventResize={action('event resized')}
+        />
+      </div>
+    )
+  })
+  .add('draggable and resizable with custom dayWrapper', () => {
+    return (
+      <div style={{ height: 600 }}>
+        <DragAndDropCalendar
+          components={{
+            dayWrapper: customComponents.dayWrapper,
+          }}
+          defaultDate={new Date()}
+          defaultView={Calendar.Views.WEEK}
           events={events}
           resizable
           showMultiDayTimes
diff --git a/stories/customComponents.js b/stories/customComponents.js
new file mode 100644
index 0000000000..90f9508a5c
--- /dev/null
+++ b/stories/customComponents.js
@@ -0,0 +1,49 @@
+import React from 'react'
+import { action } from '@storybook/react'
+
+const customComponents = {
+  dateCellWrapper: dateCellWrapperProps => {
+    // Show 'click me' text in arbitrary places by using the range prop
+    const hasAlert = dateCellWrapperProps.range
+      ? dateCellWrapperProps.range.some(date => {
+          return date.getDate() % 12 === 0
+        })
+      : false
+
+    const style = {
+      display: 'flex',
+      flex: 1,
+      borderLeft: '1px solid #DDD',
+      backgroundColor: hasAlert ? '#f5f5dc' : '#fff',
+    }
+    return (
+      <div style={style}>
+        {hasAlert && (
+          <a onClick={action('custom dateCellWrapper component clicked')}>
+            Click me
+          </a>
+        )}
+        {dateCellWrapperProps.children}
+      </div>
+    )
+  },
+  dayWrapper: dayWrapperProps => {
+    // Show different styles at arbitrary time
+    const hasCustomInfo = dayWrapperProps.value
+      ? dayWrapperProps.value.getHours() === 4
+      : false
+    const style = {
+      display: 'flex',
+      flex: 1,
+      backgroundColor: hasCustomInfo ? '#f5f5dc' : '#fff',
+    }
+    return (
+      <div style={style}>
+        {hasCustomInfo && 'Custom Day Wrapper'}
+        {dayWrapperProps.children}
+      </div>
+    )
+  },
+}
+
+export default customComponents