-
-
Notifications
You must be signed in to change notification settings - Fork 741
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(1-3262): initial impl of new month/range picker #9122
Changes from all commits
ebc283f
368c303
80eee2b
15761ca
f020e27
2d3071f
097e3de
016387f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,230 @@ | ||
import { styled } from '@mui/material'; | ||
import { type FC, useState } from 'react'; | ||
|
||
export type Period = { | ||
key: string; | ||
dayCount: number; | ||
label: string; | ||
year: number; | ||
month: number; | ||
selectable: boolean; | ||
shortLabel: string; | ||
}; | ||
|
||
export const toSelectablePeriod = ( | ||
date: Date, | ||
label?: string, | ||
selectable = true, | ||
): Period => { | ||
const year = date.getFullYear(); | ||
const month = date.getMonth(); | ||
const period = `${year}-${(month + 1).toString().padStart(2, '0')}`; | ||
const dayCount = new Date(year, month + 1, 0).getDate(); | ||
return { | ||
key: period, | ||
year, | ||
month, | ||
dayCount, | ||
shortLabel: date.toLocaleString('en-US', { | ||
month: 'short', | ||
}), | ||
label: | ||
label || | ||
date.toLocaleString('en-US', { month: 'long', year: 'numeric' }), | ||
selectable, | ||
}; | ||
}; | ||
|
||
const currentDate = new Date(Date.now()); | ||
const currentPeriod = toSelectablePeriod(currentDate, 'Current month'); | ||
|
||
const getSelectablePeriods = (): Period[] => { | ||
const selectablePeriods = [currentPeriod]; | ||
for ( | ||
let subtractMonthCount = 1; | ||
subtractMonthCount < 12; | ||
subtractMonthCount++ | ||
) { | ||
Comment on lines
+43
to
+47
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seeing months in reverse order looks a bit off. I'm used to pickers having same natural order, and maybe even starting line aligned to January |
||
// JavaScript wraps around the year, so we don't need to handle that. | ||
const date = new Date( | ||
currentDate.getFullYear(), | ||
currentDate.getMonth() - subtractMonthCount, | ||
1, | ||
); | ||
selectablePeriods.push( | ||
toSelectablePeriod(date, undefined, date > new Date('2024-03-31')), | ||
); | ||
} | ||
return selectablePeriods; | ||
}; | ||
Comment on lines
+4
to
+59
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These functions are mostly copied from |
||
|
||
const Wrapper = styled('article')(({ theme }) => ({ | ||
borderRadius: theme.shape.borderRadiusLarge, | ||
border: `2px solid ${theme.palette.divider}`, | ||
padding: theme.spacing(3), | ||
display: 'flex', | ||
flexFlow: 'column', | ||
gap: theme.spacing(2), | ||
button: { | ||
cursor: 'pointer', | ||
border: 'none', | ||
background: 'none', | ||
fontSize: theme.typography.body1.fontSize, | ||
padding: theme.spacing(0.5), | ||
borderRadius: theme.shape.borderRadius, | ||
|
||
'&.selected': { | ||
backgroundColor: theme.palette.secondary.light, | ||
}, | ||
}, | ||
thomasheartman marked this conversation as resolved.
Show resolved
Hide resolved
|
||
'button:disabled': { | ||
cursor: 'default', | ||
}, | ||
})); | ||
|
||
const MonthSelector = styled('article')(({ theme }) => ({ | ||
border: 'none', | ||
hgroup: { | ||
h3: { | ||
margin: 0, | ||
fontSize: theme.typography.h3.fontSize, | ||
}, | ||
p: { | ||
color: theme.palette.text.secondary, | ||
fontSize: theme.typography.body2.fontSize, | ||
}, | ||
|
||
marginBottom: theme.spacing(1), | ||
}, | ||
})); | ||
|
||
const MonthGrid = styled('ul')(({ theme }) => ({ | ||
listStyle: 'none', | ||
padding: 0, | ||
display: 'grid', | ||
gridTemplateColumns: 'repeat(4, 1fr)', | ||
rowGap: theme.spacing(1), | ||
columnGap: theme.spacing(2), | ||
})); | ||
|
||
const RangeSelector = styled('article')(({ theme }) => ({ | ||
display: 'flex', | ||
flexFlow: 'column', | ||
gap: theme.spacing(0.5), | ||
h4: { | ||
fontSize: theme.typography.body2.fontSize, | ||
margin: 0, | ||
color: theme.palette.text.secondary, | ||
}, | ||
})); | ||
|
||
const RangeList = styled('ul')(({ theme }) => ({ | ||
listStyle: 'none', | ||
padding: 0, | ||
'li + li': { | ||
marginTop: theme.spacing(1), | ||
}, | ||
thomasheartman marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
button: { | ||
marginLeft: `-${theme.spacing(0.5)}`, | ||
}, | ||
})); | ||
|
||
type Selection = | ||
| { | ||
type: 'month'; | ||
value: string; | ||
} | ||
| { | ||
type: 'range'; | ||
monthsBack: number; | ||
}; | ||
|
||
type Props = { | ||
selectedPeriod: string; | ||
setPeriod: (period: string) => void; | ||
}; | ||
|
||
export const PeriodSelector: FC<Props> = ({ selectedPeriod, setPeriod }) => { | ||
const selectablePeriods = getSelectablePeriods(); | ||
|
||
// this is for dev purposes; only to show how the design will work when you select a range. | ||
const [tempOverride, setTempOverride] = useState<Selection | null>(); | ||
|
||
const select = (value: Selection) => { | ||
if (value.type === 'month') { | ||
setTempOverride(null); | ||
setPeriod(value.value); | ||
} else { | ||
setTempOverride(value); | ||
} | ||
}; | ||
|
||
const rangeOptions = [3, 6, 12].map((monthsBack) => ({ | ||
value: monthsBack, | ||
label: `Last ${monthsBack} months`, | ||
})); | ||
|
||
return ( | ||
<Wrapper> | ||
<MonthSelector> | ||
<hgroup> | ||
<h3>Select month</h3> | ||
<p>Last 12 months</p> | ||
</hgroup> | ||
<MonthGrid> | ||
{selectablePeriods.map((period, index) => ( | ||
<li key={period.label}> | ||
<button | ||
className={ | ||
!tempOverride && | ||
period.key === selectedPeriod | ||
? 'selected' | ||
: '' | ||
} | ||
Comment on lines
+179
to
+184
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doing this with classes + nested selector instead of props makes it harder to refactor. There a link between how this element looks like, and where it is placed. |
||
type='button' | ||
disabled={!period.selectable} | ||
onClick={() => { | ||
select({ | ||
type: 'month', | ||
value: period.key, | ||
}); | ||
}} | ||
> | ||
{period.shortLabel} | ||
</button> | ||
</li> | ||
))} | ||
</MonthGrid> | ||
</MonthSelector> | ||
<RangeSelector> | ||
<h4>Range</h4> | ||
|
||
<RangeList> | ||
{rangeOptions.map((option) => ( | ||
<li key={option.label}> | ||
<button | ||
className={ | ||
tempOverride && | ||
tempOverride.type === 'range' && | ||
option.value === tempOverride.monthsBack | ||
? 'selected' | ||
: '' | ||
} | ||
type='button' | ||
onClick={() => { | ||
select({ | ||
type: 'range', | ||
monthsBack: option.value, | ||
}); | ||
}} | ||
> | ||
Last {option.value} months | ||
</button> | ||
</li> | ||
))} | ||
</RangeList> | ||
</RangeSelector> | ||
</Wrapper> | ||
); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -48,8 +48,7 @@ const calculateTrafficDataCost = ( | |
return unitCount * trafficUnitCost; | ||
}; | ||
|
||
const padMonth = (month: number): string => | ||
month < 10 ? `0${month}` : `${month}`; | ||
const padMonth = (month: number): string => month.toString().padStart(2, '0'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Javascript can do this for us with a builtin instead of us checking the value |
||
|
||
export const toSelectablePeriod = ( | ||
date: Date, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For me
DateRange
is more intuitive, and easier to find in the code.