Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/docs/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -162,12 +162,13 @@ const App = () => {
minTime={minTime}
maxTime={maxTime}
numDays={numDays}
startDate={new Date(startDate)}
startDate={startDate}
selection={schedule}
onChange={setSchedule}
hourlyChunks={hourlyChunks}
timeFormat="h:mma"
selectionScheme={selectionScheme}
blockedTimes={[new Date('2020-11-22T23:00:00.000Z'), new Date('2020-11-22T24:00:00.000Z')]}
/>
</ScheduleSelectorCard>
<Links>
Expand Down
119 changes: 72 additions & 47 deletions src/lib/ScheduleSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,37 @@ export const GridCell = styled.div`
touch-action: none;
`

const DateCell = styled.div<{
type DateCellProps = {
blocked: boolean
selected: boolean
selectedColor: string
unselectedColor: string
blockedColor: string
hoveredColor: string
}>`
}
const getDateCellColor = (props: DateCellProps) => {
if (props.blocked) {
return props.blockedColor
} else if (props.selected) {
return props.selectedColor
} else {
return props.unselectedColor
}
}

const DateCell = styled.div<DateCellProps>`
width: 100%;
height: 25px;
background-color: ${props => (props.selected ? props.selectedColor : props.unselectedColor)};
background-color: ${getDateCellColor};

&:hover {
background-color: ${props => props.hoveredColor};
}
${props =>
!props.blocked
? `
&:hover {
background-color: ${props.hoveredColor};
}
`
: ''}
`

const DateLabel = styled(Subtitle)`
Expand All @@ -66,6 +84,14 @@ const TimeText = styled(Text)`
margin-right: 4px;
`

interface DateCellRendererProps {
datetime: Date
// Indicates whether the specified time is prevented from being selectable
blocked: boolean
selected: boolean
refSetter: (dateCellElement: HTMLElement) => void
}

type PropsType = {
selection: Array<Date>
selectionScheme: SelectionSchemeType
Expand All @@ -81,8 +107,10 @@ type PropsType = {
rowGap: string
unselectedColor: string
selectedColor: string
blockedColor: string
hoveredColor: string
renderDateCell?: (datetime: Date, selected: boolean, refSetter: (dateCellElement: HTMLElement) => void) => JSX.Element
blockedTimes: Array<Date>
renderCell?: (props: DateCellRendererProps) => JSX.Element
renderTimeLabel?: (time: Date) => JSX.Element
renderDateLabel?: (date: Date) => JSX.Element
}
Expand All @@ -102,16 +130,7 @@ export const preventScroll = (e: TouchEvent) => {
}

export default class ScheduleSelector extends React.Component<PropsType, StateType> {
selectionSchemeHandlers: { [key: string]: (startDate: Date, endDate: Date, foo: Array<Array<Date>>) => Date[] }
cellToDate: Map<Element, Date> = new Map()
// documentMouseUpHandler: () => void = () => {}
// endSelection: () => void = () => {}
// handleTouchMoveEvent: (event: React.SyntheticTouchEvent<*>) => void
// handleTouchEndEvent: () => void
// handleMouseUpEvent: (date: Date) => void
// handleMouseEnterEvent: (date: Date) => void
// handleSelectionStartEvent: (date: Date) => void
gridRef: HTMLElement | null = null

static defaultProps: Partial<PropsType> = {
selection: [],
Expand All @@ -127,7 +146,9 @@ export default class ScheduleSelector extends React.Component<PropsType, StateTy
rowGap: '4px',
selectedColor: colors.blue,
unselectedColor: colors.paleBlue,
blockedColor: colors.paleRed,
hoveredColor: colors.lightBlue,
blockedTimes: [],
onChange: () => {}
}

Expand Down Expand Up @@ -169,11 +190,6 @@ export default class ScheduleSelector extends React.Component<PropsType, StateTy
dates: ScheduleSelector.computeDatesMatrix(props)
}

this.selectionSchemeHandlers = {
linear: selectionSchemes.linear,
square: selectionSchemes.square
}

this.endSelection = this.endSelection.bind(this)
this.handleMouseUpEvent = this.handleMouseUpEvent.bind(this)
this.handleMouseEnterEvent = this.handleMouseEnterEvent.bind(this)
Expand Down Expand Up @@ -241,11 +257,11 @@ export default class ScheduleSelector extends React.Component<PropsType, StateTy

let newSelection: Array<Date> = []
if (selectionStart && selectionEnd && selectionType) {
newSelection = this.selectionSchemeHandlers[this.props.selectionScheme](
newSelection = selectionSchemes[this.props.selectionScheme](
selectionStart,
selectionEnd,
this.state.dates
)
).filter(selectedTime => !this.isTimeBlocked(selectedTime))
}

let nextDraft = [...this.props.selection]
Expand All @@ -258,6 +274,10 @@ export default class ScheduleSelector extends React.Component<PropsType, StateTy
this.setState({ selectionDraft: nextDraft }, callback)
}

isTimeBlocked(time: Date) {
return this.props.blockedTimes.find(blockedTime => blockedTime.toISOString() === time.toISOString()) !== undefined
}

// Isomorphic (mouse and touch) handler since starting a selection works the same way for both classes of user input
handleSelectionStartEvent(startTime: Date) {
// Check if the startTime cell is selected/unselected to determine if this drag-select should
Expand Down Expand Up @@ -303,54 +323,62 @@ export default class ScheduleSelector extends React.Component<PropsType, StateTy
this.setState({ isTouchDragging: false })
}

renderDateCellWrapper = (time: Date): JSX.Element => {
renderCellWrapper = (time: Date): JSX.Element => {
const startHandler = () => {
this.handleSelectionStartEvent(time)
}

const selected = Boolean(this.state.selectionDraft.find(a => isSameMinute(a, time)))
const blocked = this.isTimeBlocked(time)

const unblockedCellProps = {
// Mouse handlers
onMouseDown: startHandler,
onMouseEnter: () => {
this.handleMouseEnterEvent(time)
},
onMouseUp: () => {
this.handleMouseUpEvent(time)
},
// Touch handlers
// Since touch events fire on the event where the touch-drag started, there's no point in passing
// in the time parameter, instead these handlers will do their job using the default Event
// parameters
onTouchStart: startHandler,
onTouchMove: this.handleTouchMoveEvent,
onTouchEnd: this.handleTouchEndEvent
}

return (
<GridCell
className="rgdp__grid-cell"
role="presentation"
key={time.toISOString()}
// Mouse handlers
onMouseDown={startHandler}
onMouseEnter={() => {
this.handleMouseEnterEvent(time)
}}
onMouseUp={() => {
this.handleMouseUpEvent(time)
}}
// Touch handlers
// Since touch events fire on the event where the touch-drag started, there's no point in passing
// in the time parameter, instead these handlers will do their job using the default Event
// parameters
onTouchStart={startHandler}
onTouchMove={this.handleTouchMoveEvent}
onTouchEnd={this.handleTouchEndEvent}
{...(!blocked ? unblockedCellProps : {})}
>
{this.renderDateCell(time, selected)}
{this.renderCell(time, selected, blocked)}
</GridCell>
)
}

renderDateCell = (time: Date, selected: boolean): JSX.Element => {
renderCell = (time: Date, selected: boolean, blocked: boolean): JSX.Element => {
const refSetter = (dateCell: HTMLElement | null) => {
if (dateCell) {
this.cellToDate.set(dateCell, time)
}
}
if (this.props.renderDateCell) {
return this.props.renderDateCell(time, selected, refSetter)

if (this.props.renderCell) {
return this.props.renderCell({ datetime: time, blocked, selected, refSetter })
} else {
return (
<DateCell
blocked={blocked}
selected={selected}
ref={refSetter}
selectedColor={this.props.selectedColor}
unselectedColor={this.props.unselectedColor}
blockedColor={this.props.blockedColor}
hoveredColor={this.props.hoveredColor}
/>
)
Expand Down Expand Up @@ -382,7 +410,7 @@ export default class ScheduleSelector extends React.Component<PropsType, StateTy
flattenedDates.push(this.state.dates[i][j])
}
}
const dateGridElements = flattenedDates.map(this.renderDateCellWrapper)
const dateGridElements = flattenedDates.map(this.renderCellWrapper)
for (let i = 0; i < numTimes; i += 1) {
const index = i * numDays
const time = this.state.dates[0][i]
Expand All @@ -409,9 +437,6 @@ export default class ScheduleSelector extends React.Component<PropsType, StateTy
rows={this.state.dates[0].length}
columnGap={this.props.columnGap}
rowGap={this.props.rowGap}
ref={el => {
this.gridRef = el
}}
>
{this.renderFullDateGrid()}
</Grid>
Expand Down
3 changes: 2 additions & 1 deletion src/lib/colors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ const colors = {
white: 'rgba(255, 255, 255, 1)',
black: 'rgba(79, 79, 79, 1)',
grey: 'rgba(79, 79, 79, 0.87)',
paleBlue: '#dbedff'
paleBlue: '#dbedff',
paleRed: '#B91600'
}

export default colors