Created
April 23, 2019 15:15
-
-
Save dy/918653e771b9d82a9dc4c2d5be5f00c4 to your computer and use it in GitHub Desktop.
Infinite-scroll-react
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useState, useRef, useMemo } from 'react'; | |
import Typography from '@material-ui/core/Typography'; | |
import Box from '@material-ui/core/Box'; | |
import Tooltip from '@material-ui/core/Tooltip'; | |
import Paper from '@material-ui/core/Paper'; | |
import MenuList from '@material-ui/core/MenuList'; | |
import MenuItem from '@material-ui/core/MenuItem'; | |
import ListItemText from '@material-ui/core/ListItemText'; | |
import IconButton, { IconButtonProps } from '@material-ui/core/IconButton'; | |
import ArrowLeftIcon from '@material-ui/icons/ArrowLeft'; | |
import ArrowRightIcon from '@material-ui/icons/ArrowRight'; | |
import { makeStyles } from '@material-ui/core/styles'; | |
import { Grid } from 'react-virtualized' | |
import 'react-virtualized/styles.css' | |
import clsx from 'clsx' | |
import Dates from 'date-math' | |
import format from 'dateformat' | |
import measureScrollbar from 'measure-scrollbar' | |
export default function DateRange ({ | |
from=Dates.year.floor(Dates.year.shift(new Date, -1)), | |
to=Dates.month.ceil(new Date), | |
...props | |
}) { | |
let [today] = useState(new Date) | |
let [headerDate, setHeaderDate] = useState(today) | |
let [scrollbar] = useState(() => measureScrollbar()) | |
let [selected, setSelected] = useState(new Set([dateId(today)])) | |
let gridRef = useRef() | |
let options = useMemo(() => [ | |
{ | |
label: `Today`, | |
from: Dates.day.floor(today), to: Dates.day.ceil(today) | |
}, | |
{ | |
label: `This week`, | |
from: Dates.week.floor(today), to: Dates.week.ceil(today) | |
}, | |
{ | |
label: `This month`, | |
from: Dates.month.floor(today), to: Dates.month.ceil(today) | |
}, | |
{ | |
label: `Last month`, | |
from: Dates.month.floor(Dates.month.shift(today, -1)), to: Dates.month.ceil(Dates.month.shift(today, -1)) | |
}, | |
{ | |
label: `Last 90 days`, | |
from: Dates.day.shift(today, -90), to: Dates.day.ceil(today) | |
}, | |
{ | |
label: `This year`, | |
from: Dates.year.floor(today), to: Dates.year.ceil(today) | |
}, | |
{ | |
label: `Last year`, | |
from: Dates.year.floor(Dates.year.shift(today, -1)), to: Dates.year.ceil(Dates.year.shift(today, -1)) | |
}, | |
].map(option => { | |
let range = [] | |
option.range = range | |
let day = option.from | |
let toId = dateId(option.to) | |
while (dateId(day) != toId) { | |
range.push(dateId(day)) | |
day = Dates.day.shift(day, 1) | |
} | |
option.range = new Set(range) | |
return option | |
}), []) | |
return <Box display="flex" flexDirection="row"> | |
<Box> | |
<CalendarHeader date={headerDate}/> | |
<Grid | |
ref={gridRef} | |
cellRenderer={cellRenderer} | |
selected={selected} | |
onSelect={date => { | |
!selected.has(dateId(date)) ? | |
selected.add(dateId(date)) : | |
selected.delete(dateId(date)) | |
setSelected(new Set(selected)) | |
}} | |
columnCount={7} | |
rowCount={100} | |
columnWidth={40} | |
height={320} | |
rowHeight={40} | |
autoContainerWidth | |
width={280 + scrollbar} | |
startDate={from} | |
endDate={to} | |
onSectionRendered={state => { | |
// update current month corresponding to the row start idex | |
let {rowStartIndex} = state | |
setHeaderDate(Dates.week.shift(Dates.week.floor(from), rowStartIndex + 1)) | |
}} | |
/> | |
</Box> | |
<Box mt={5}> | |
<MenuList id="options-menu"> | |
{ | |
options.map(({label, range}) => ( | |
<MenuItem key={label} onClick={() => { | |
setSelected(range) | |
}} button> | |
<ListItemText primary={label} primaryTypographyProps={{ variant: 'body2' }}/> | |
</MenuItem> | |
) | |
)} | |
</MenuList> | |
</Box> | |
</Box> | |
} | |
function cellRenderer ({ columnIndex, key, rowIndex, style, parent, ...props }) { | |
let startDay = Dates.week.floor(parent.props.startDate) | |
let currentDay = Dates.day.shift(startDay, columnIndex + rowIndex * 7) | |
let prevDay = Dates.day.shift(currentDay, -1) | |
let nextDay = Dates.day.shift(currentDay, 1) | |
let isFirstWeek = Dates.week.shift(currentDay, -1).getMonth() !== currentDay.getMonth() | |
let isFirstDay = currentDay.getDate() === 1 | |
style = {...style} | |
let shadow = [] | |
if (isFirstWeek) shadow.push('0 -1px 0 rgba(0,0,0,.13)') | |
if (isFirstDay) shadow.push('-1px 0 0 rgba(0,0,0,.13)') | |
style.boxShadow = shadow.join(',') | |
let isWeekend = currentDay.getDay() === 0 || currentDay.getDay() === 6 | |
let today = Dates.day.floor(new Date) | |
let isSelected = parent.props.selected.has(dateId(currentDay)) | |
let isNextSelected = parent.props.selected.has(dateId(nextDay)) | |
let isPrevSelected = parent.props.selected.has(dateId(prevDay)) | |
return ( | |
<div key={key} style={style}> | |
<Day | |
title={format(currentDay, 'fullDate')} | |
onClick={e => { | |
parent.props.onSelect(currentDay) | |
}} | |
current={dateId(today) === dateId(currentDay)} | |
weekend={isWeekend} | |
selected={parent.props.selected.has(dateId(currentDay))} | |
prevSelected={isPrevSelected} | |
nextSelected={isNextSelected} | |
> | |
{format(currentDay, 'd')} | |
</Day> | |
</div> | |
) | |
} | |
const useDayStyles = makeStyles( | |
(theme) => ({ | |
day: { | |
width: 40, | |
height: 40, | |
fontSize: theme.typography.caption.fontSize, | |
margin: '0 2px', | |
color: theme.palette.text.primary, | |
fontWeight: theme.typography.fontWeightMedium, | |
padding: 0, | |
}, | |
hidden: { | |
opacity: 0, | |
pointerEvents: 'none', | |
}, | |
weekend: { | |
color: theme.palette.text.secondary | |
}, | |
current: { | |
color: theme.palette.primary.main, | |
fontWeight: 600, | |
}, | |
isSelected: { | |
color: theme.palette.primary.contrastText, | |
backgroundColor: theme.palette.primary.main, | |
fontWeight: theme.typography.fontWeightMedium, | |
'&:hover': { | |
backgroundColor: theme.palette.primary.main, | |
}, | |
}, | |
isPrevSelected: { | |
borderTopLeftRadius: 0, | |
borderBottomLeftRadius: 0 | |
}, | |
isNextSelected: { | |
borderTopRightRadius: 0, | |
borderBottomRightRadius: 0 | |
}, | |
isDisabled: { | |
pointerEvents: 'none', | |
color: theme.palette.text.hint, | |
}, | |
}), | |
{ name: 'MuiPickersDay' } | |
); | |
function Day ({ children, disabled, hidden, current, selected, prevSelected, nextSelected, title, weekend, ...other }) { | |
const classes = useDayStyles(); | |
const className = clsx(classes.day, { | |
[classes.hidden]: hidden, | |
[classes.weekend]: weekend, | |
[classes.current]: current, | |
[classes.isSelected]: selected, | |
[classes.isPrevSelected]: selected && prevSelected, | |
[classes.isNextSelected]: selected && nextSelected, | |
[classes.isDisabled]: disabled, | |
}); | |
return <IconButton title={title} className={className} tabIndex={hidden || disabled ? -1 : 0} {...other}> | |
<Typography variant="body2" color="inherit"> | |
{children} | |
</Typography> | |
</IconButton> | |
}; | |
const useCalendarHeaderStyles = makeStyles((theme) => | |
({ | |
switchHeader: { | |
display: 'flex', | |
justifyContent: 'space-between', | |
alignItems: 'center', | |
marginTop: theme.spacing(0.5), | |
marginBottom: theme.spacing(1), | |
}, | |
transitionContainer: { | |
width: '100%', | |
height: 20, | |
}, | |
iconButton: { | |
zIndex: 2, | |
backgroundColor: theme.palette.background.paper, | |
'& > *': { | |
// label | |
backgroundColor: theme.palette.background.paper, | |
'& > *': { | |
// icon | |
zIndex: 1, | |
overflow: 'visible', | |
}, | |
}, | |
}, | |
daysHeader: { | |
display: 'flex', | |
justifyContent: 'space-evenly', | |
alignItems: 'center', | |
maxHeight: 16, | |
}, | |
dayLabel: { | |
textIndent: 3, | |
width: 40, | |
margin: 0, | |
textAlign: 'center', | |
color: theme.palette.text.hint, | |
}, | |
}), | |
{ | |
withTheme: true, | |
name: 'MuiPickersCalendarHeader', | |
} | |
) | |
function CalendarHeader ({ | |
leftArrowIcon=<ArrowLeftIcon />, | |
rightArrowIcon=<ArrowRightIcon />, | |
disablePrevMonth=false, | |
disableNextMonth=false, | |
date=new Date, | |
onMonthChange, | |
leftArrowButtonProps, | |
rightArrowButtonProps | |
}) { | |
const classes = useCalendarHeaderStyles() | |
let [scrollbar] = useState(() => { | |
return measureScrollbar() | |
}) | |
const selectNextMonth = () => onMonthChange(Dates.month.shift(date, 1), 'left'); | |
const selectPreviousMonth = () => onMonthChange(Dates.month.shift(date, -1), 'right'); | |
return ( | |
<Box mr={scrollbar + 'px'}> | |
<Box className={classes.switchHeader}> | |
<IconButton | |
{...leftArrowButtonProps} | |
disabled={disablePrevMonth} | |
onClick={selectPreviousMonth} | |
className={classes.iconButton} | |
> | |
{leftArrowIcon} | |
</IconButton> | |
<Typography align="center" variant="body1"> | |
{format(date, 'mmmm, yyyy')} | |
</Typography> | |
<IconButton | |
{...rightArrowButtonProps} | |
disabled={disableNextMonth} | |
onClick={selectNextMonth} | |
className={classes.iconButton} | |
> | |
{rightArrowIcon} | |
</IconButton> | |
</Box> | |
<Box className={classes.daysHeader}> | |
{[0,1,2,3,4,5,6].map((day, index) => { | |
let startDay = Dates.week.floor(date) | |
let currentDay = Dates.day.shift(startDay, index) | |
return ( | |
<Typography | |
key={index} | |
variant="caption" | |
className={classes.dayLabel} | |
> | |
{ | |
format(currentDay, 'ddd') | |
} | |
</Typography> | |
)})} | |
</Box> | |
</Box> | |
); | |
}; | |
// return date identifier | |
function dateId(date) { | |
return +Dates.day.floor(date) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment