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