1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-02-09 00:18:00 +01:00

feat(1-3281): wraps the new datepicker in a dropdown (#9169)

Wraps the datepicker in a popover, making it function largely the same
as a dropdown list.

The dropdown displays one of:
- "current month" if you've selected the current month
- "<month> <year>" (e.g. "December 2024") if you've selected a month
that isn't the current month
- "Last n months" (e.g. "Last 3 months") if you have selected a range

Additionally, the range selections have been updated to span the whole
row, aligning with the look of generic dropdown lists.


![image](https://github.com/user-attachments/assets/d356aec5-d51b-42fa-9591-60e2b5038a8e)

Like with the rest of this file (`PeriodSelector`), the code is rough
and not according to Unleash standards. However, I'm prioritizing fast
changes so UX can have a look before I clean up the code to switch to
using styled components etc later. It's still behind a flag, so I'm not
very worried about it.
This commit is contained in:
Thomas Heartman 2025-01-29 15:29:30 +01:00 committed by GitHub
parent b870333990
commit f4556839c8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -1,6 +1,12 @@
import { styled } from '@mui/material'; import { styled, Button, Popover, Box, type Theme } from '@mui/material';
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
import ArrowDropUpIcon from '@mui/icons-material/ArrowDropUp';
import type { ChartDataSelection } from 'hooks/api/getters/useInstanceTrafficMetrics/useInstanceTrafficMetrics'; import type { ChartDataSelection } from 'hooks/api/getters/useInstanceTrafficMetrics/useInstanceTrafficMetrics';
import type { FC } from 'react'; import { useRef, useState, type FC } from 'react';
import { format } from 'date-fns';
const dropdownWidth = '15rem';
const dropdownInlinePadding = (theme: Theme) => theme.spacing(3);
export type Period = { export type Period = {
key: string; key: string;
@ -60,9 +66,8 @@ const getSelectablePeriods = (): Period[] => {
}; };
const Wrapper = styled('article')(({ theme }) => ({ const Wrapper = styled('article')(({ theme }) => ({
borderRadius: theme.shape.borderRadiusLarge, width: dropdownWidth,
border: `2px solid ${theme.palette.divider}`, paddingBlock: theme.spacing(2),
padding: theme.spacing(3),
display: 'flex', display: 'flex',
flexFlow: 'column', flexFlow: 'column',
gap: theme.spacing(2), gap: theme.spacing(2),
@ -84,13 +89,17 @@ const Wrapper = styled('article')(({ theme }) => ({
cursor: 'default', cursor: 'default',
color: theme.palette.text.disabled, color: theme.palette.text.disabled,
}, },
'button:hover': { 'button:hover:not(:disabled)': {
backgroundColor: theme.palette.action.hover, backgroundColor: theme.palette.action.hover,
}, },
'button:focus': {
outline: `2px solid ${theme.palette.primary.main}`,
},
})); }));
const MonthSelector = styled('article')(({ theme }) => ({ const MonthSelector = styled('article')(({ theme }) => ({
border: 'none', border: 'none',
paddingInline: dropdownInlinePadding(theme),
hgroup: { hgroup: {
h3: { h3: {
margin: 0, margin: 0,
@ -116,9 +125,11 @@ const MonthGrid = styled('ul')(({ theme }) => ({
const RangeSelector = styled('article')(({ theme }) => ({ const RangeSelector = styled('article')(({ theme }) => ({
display: 'flex', display: 'flex',
width: '100%',
flexFlow: 'column', flexFlow: 'column',
gap: theme.spacing(0.5), gap: theme.spacing(0),
h4: { h4: {
paddingInline: dropdownInlinePadding(theme),
fontSize: theme.typography.body2.fontSize, fontSize: theme.typography.body2.fontSize,
margin: 0, margin: 0,
color: theme.palette.text.secondary, color: theme.palette.text.secondary,
@ -127,31 +138,34 @@ const RangeSelector = styled('article')(({ theme }) => ({
const RangeList = styled('ul')(({ theme }) => ({ const RangeList = styled('ul')(({ theme }) => ({
listStyle: 'none', listStyle: 'none',
margin: 0,
padding: 0, padding: 0,
'li + li': { width: '100%',
marginTop: theme.spacing(1), li: {
width: '100%',
}, },
button: { button: {
marginLeft: `-${theme.spacing(0.5)}`, width: '100%',
paddingBlock: theme.spacing(1),
textAlign: 'left',
borderRadius: 0,
paddingInline: dropdownInlinePadding(theme),
}, },
})); }));
type Selection =
| {
type: 'month';
value: string;
}
| {
type: 'range';
monthsBack: number;
};
type Props = { type Props = {
selectedPeriod: ChartDataSelection; selectedPeriod: ChartDataSelection;
setPeriod: (period: ChartDataSelection) => void; setPeriod: (period: ChartDataSelection) => void;
}; };
const StyledPopover = styled(Popover)(({ theme }) => ({
'& .MuiPaper-root': {
borderRadius: theme.shape.borderRadiusLarge,
border: `1px solid ${theme.palette.divider}`,
},
}));
export const PeriodSelector: FC<Props> = ({ selectedPeriod, setPeriod }) => { export const PeriodSelector: FC<Props> = ({ selectedPeriod, setPeriod }) => {
const selectablePeriods = getSelectablePeriods(); const selectablePeriods = getSelectablePeriods();
@ -160,7 +174,62 @@ export const PeriodSelector: FC<Props> = ({ selectedPeriod, setPeriod }) => {
label: `Last ${monthsBack} months`, label: `Last ${monthsBack} months`,
})); }));
const [open, setOpen] = useState(false);
const ref = useRef<HTMLDivElement>(null);
const selectPeriod = (period: ChartDataSelection) => {
setPeriod(period);
setOpen(false);
};
const buttonText =
selectedPeriod.grouping === 'daily'
? selectedPeriod.month === format(new Date(), 'yyyy-MM')
? 'Current month'
: new Date(selectedPeriod.month).toLocaleDateString('en-US', {
month: 'long',
year: 'numeric',
})
: `Last ${selectedPeriod.monthsBack} months`;
return ( return (
<Box ref={ref}>
<Button
endIcon={open ? <ArrowDropUpIcon /> : <ArrowDropDownIcon />}
sx={(theme) => ({
whiteSpace: 'nowrap',
width: dropdownWidth,
justifyContent: 'space-between',
fontWeight: 'normal',
color: theme.palette.text.primary,
borderColor: theme.palette.divider,
borderWidth: '2px',
':focus-within': {
borderColor: theme.palette.primary.main,
},
':hover': {
borderWidth: '2px', // Prevent the border from changing width on hover
},
})}
variant='outlined'
disableRipple
onClick={() => setOpen(true)}
>
{buttonText}
</Button>
<StyledPopover
open={open}
anchorEl={ref.current}
onClose={() => setOpen(false)}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'center',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'center',
}}
>
<Wrapper> <Wrapper>
<MonthSelector> <MonthSelector>
<hgroup> <hgroup>
@ -172,7 +241,8 @@ export const PeriodSelector: FC<Props> = ({ selectedPeriod, setPeriod }) => {
<li key={period.label}> <li key={period.label}>
<button <button
className={ className={
selectedPeriod.grouping === 'daily' && selectedPeriod.grouping ===
'daily' &&
period.key === selectedPeriod.month period.key === selectedPeriod.month
? 'selected' ? 'selected'
: '' : ''
@ -180,7 +250,7 @@ export const PeriodSelector: FC<Props> = ({ selectedPeriod, setPeriod }) => {
type='button' type='button'
disabled={!period.selectable} disabled={!period.selectable}
onClick={() => { onClick={() => {
setPeriod({ selectPeriod({
grouping: 'daily', grouping: 'daily',
month: period.key, month: period.key,
}); });
@ -200,14 +270,16 @@ export const PeriodSelector: FC<Props> = ({ selectedPeriod, setPeriod }) => {
<li key={option.label}> <li key={option.label}>
<button <button
className={ className={
selectedPeriod.grouping === 'monthly' && selectedPeriod.grouping ===
option.value === selectedPeriod.monthsBack 'monthly' &&
option.value ===
selectedPeriod.monthsBack
? 'selected' ? 'selected'
: '' : ''
} }
type='button' type='button'
onClick={() => { onClick={() => {
setPeriod({ selectPeriod({
grouping: 'monthly', grouping: 'monthly',
monthsBack: option.value, monthsBack: option.value,
}); });
@ -219,6 +291,8 @@ export const PeriodSelector: FC<Props> = ({ selectedPeriod, setPeriod }) => {
))} ))}
</RangeList> </RangeList>
</RangeSelector> </RangeSelector>
</Wrapper> </Wrapper>{' '}
</StyledPopover>
</Box>
); );
}; };