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:
parent
b870333990
commit
f4556839c8
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user