mirror of
https://github.com/Unleash/unleash.git
synced 2025-10-18 11:14:57 +02:00
Fixes a bug where the dashboard would scroll you down from the top of the page on load if your window was too short too see both the selected flag and the selected project. This solves it by immediately scrolling to the top of the page after scrolling your selected element into view. Because this hook only runs on page load, it shouldn't be safe. (At least I couldn't make this misbehave with manual testing). It also changes the list scroll behavior to scroll your selected item to the top of the list instead of to the bottom (effectively). During testing, that seems like a better solution to me. ## Background (or why do we auto-scroll here?) The dashboard's flag and projects panels stores your last selection, so that when you return to the page you'll be shown what you were looking at last. This is especially useful if you have a lot of flags but you're focusing on one in particular. However, if you **do** have a lot of flags, then it's also quite likely that your selection will be "below the fold" of the panel, and you won't see your selected flag/project immediately in the list (without scrolling). It seemed like a nice UI affordance to automatically bring your selected item into view (especially because without it, there's no way to see what flag/project) you're looking at, so I added the [`scrollIntoView`](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView) hook. What I didn't realize, however, is that it scrolls all scrollable ancestor containers, which means that if your screen is too short, it'll scroll you down the page. From my reading of the docs and some local testing, I don't think there is a way to limit the scrolling to only the nearest ancestor, so the easiest way to ensure that we're always at the top seemed to be to just scroll to the immediately after.
181 lines
5.9 KiB
TypeScript
181 lines
5.9 KiB
TypeScript
import { type FC, useEffect, useRef } from 'react';
|
|
import {
|
|
ContentGridContainer,
|
|
FlagGrid,
|
|
ListItemBox,
|
|
SpacedGridItem,
|
|
StyledCardTitle,
|
|
StyledList,
|
|
listItemStyle,
|
|
} from './SharedComponents';
|
|
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
|
import { getFeatureTypeIcons } from 'utils/getFeatureTypeIcons';
|
|
import {
|
|
Alert,
|
|
IconButton,
|
|
Link,
|
|
ListItem,
|
|
ListItemButton,
|
|
Typography,
|
|
styled,
|
|
} from '@mui/material';
|
|
import LinkIcon from '@mui/icons-material/ArrowForward';
|
|
import React from 'react';
|
|
import type { PersonalDashboardSchemaFlagsItem } from 'openapi';
|
|
|
|
const NoActiveFlagsInfo = styled('div')(({ theme }) => ({
|
|
display: 'flex',
|
|
flexFlow: 'column',
|
|
gap: theme.spacing(2),
|
|
}));
|
|
|
|
const FlagListItem: FC<{
|
|
flag: { name: string; project: string; type: string };
|
|
selected: boolean;
|
|
onClick: () => void;
|
|
}> = ({ flag, selected, onClick }) => {
|
|
const activeFlagRef = useRef<HTMLLIElement>(null);
|
|
const { trackEvent } = usePlausibleTracker();
|
|
|
|
useEffect(() => {
|
|
if (activeFlagRef.current) {
|
|
activeFlagRef.current.scrollIntoView({
|
|
block: 'start',
|
|
inline: 'start',
|
|
});
|
|
window.scrollTo({ top: 0 });
|
|
}
|
|
}, []);
|
|
const IconComponent = getFeatureTypeIcons(flag.type);
|
|
const flagLink = `projects/${flag.project}/features/${flag.name}`;
|
|
return (
|
|
<ListItem
|
|
key={flag.name}
|
|
disablePadding={true}
|
|
sx={{ mb: 1 }}
|
|
ref={selected ? activeFlagRef : null}
|
|
>
|
|
<ListItemButton
|
|
sx={listItemStyle}
|
|
selected={selected}
|
|
onClick={onClick}
|
|
>
|
|
<ListItemBox>
|
|
<IconComponent color='primary' />
|
|
<StyledCardTitle>{flag.name}</StyledCardTitle>
|
|
<IconButton
|
|
component={Link}
|
|
href={flagLink}
|
|
onClick={() => {
|
|
trackEvent('personal-dashboard', {
|
|
props: {
|
|
eventType: `Go to flag from list`,
|
|
},
|
|
});
|
|
}}
|
|
size='small'
|
|
sx={{ ml: 'auto' }}
|
|
>
|
|
<LinkIcon titleAccess={flagLink} />
|
|
</IconButton>
|
|
</ListItemBox>
|
|
</ListItemButton>
|
|
</ListItem>
|
|
);
|
|
};
|
|
|
|
type FlagData =
|
|
| {
|
|
state: 'flags';
|
|
flags: PersonalDashboardSchemaFlagsItem[];
|
|
activeFlag?: PersonalDashboardSchemaFlagsItem;
|
|
}
|
|
| {
|
|
state: 'no flags';
|
|
};
|
|
|
|
type Props = {
|
|
hasProjects: boolean;
|
|
flagData: FlagData;
|
|
setActiveFlag: (flag: PersonalDashboardSchemaFlagsItem) => void;
|
|
refetchDashboard: () => void;
|
|
};
|
|
|
|
export const MyFlags: FC<Props> = ({
|
|
hasProjects,
|
|
flagData,
|
|
setActiveFlag,
|
|
refetchDashboard,
|
|
}) => {
|
|
return (
|
|
<ContentGridContainer>
|
|
<FlagGrid>
|
|
<SpacedGridItem gridArea='flags'>
|
|
{flagData.state === 'flags' ? (
|
|
<StyledList
|
|
disablePadding={true}
|
|
sx={{
|
|
height: '100%',
|
|
}}
|
|
>
|
|
{flagData.flags.map((flag) => (
|
|
<FlagListItem
|
|
key={flag.name}
|
|
flag={flag}
|
|
selected={
|
|
flag.name === flagData.activeFlag?.name
|
|
}
|
|
onClick={() => setActiveFlag(flag)}
|
|
/>
|
|
))}
|
|
</StyledList>
|
|
) : hasProjects ? (
|
|
<NoActiveFlagsInfo>
|
|
<Typography>
|
|
You have not created or favorited any feature
|
|
flags. Once you do, they will show up here.
|
|
</Typography>
|
|
<Typography>
|
|
To create a new flag, go to one of your
|
|
projects.
|
|
</Typography>
|
|
</NoActiveFlagsInfo>
|
|
) : (
|
|
<Alert severity='info'>
|
|
You need to create or join a project to be able to
|
|
add a flag, or you must be given the rights by your
|
|
admin to add feature flags.
|
|
</Alert>
|
|
)}
|
|
</SpacedGridItem>
|
|
|
|
<SpacedGridItem gridArea='chart'>
|
|
{flagData.state === 'flags' && flagData.activeFlag ? (
|
|
<FlagMetricsChart
|
|
flag={flagData.activeFlag}
|
|
onArchive={refetchDashboard}
|
|
/>
|
|
) : (
|
|
<PlaceholderFlagMetricsChart
|
|
label={
|
|
'Metrics for your feature flags will be shown here'
|
|
}
|
|
/>
|
|
)}
|
|
</SpacedGridItem>
|
|
</FlagGrid>
|
|
</ContentGridContainer>
|
|
);
|
|
};
|
|
|
|
const FlagMetricsChart = React.lazy(() =>
|
|
import('./FlagMetricsChart').then((module) => ({
|
|
default: module.FlagMetricsChart,
|
|
})),
|
|
);
|
|
const PlaceholderFlagMetricsChart = React.lazy(() =>
|
|
import('./FlagMetricsChart').then((module) => ({
|
|
default: module.PlaceholderFlagMetricsChartWithWrapper,
|
|
})),
|
|
);
|