Skip to content

Instantly share code, notes, and snippets.

@dy
Created April 23, 2019 15:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dy/918653e771b9d82a9dc4c2d5be5f00c4 to your computer and use it in GitHub Desktop.
Save dy/918653e771b9d82a9dc4c2d5be5f00c4 to your computer and use it in GitHub Desktop.
Infinite-scroll-react
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