1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-25 00:07:47 +01:00

refactor: use css grid for flags and no content grid (#8347)

This PR uses the new CSS grid layout for the flag grid and the no
content grid.

In doing so, it also improves how you use the grid item (giving them a
`gridArea` prop) and extracts the breakpoint handling so that all
sections that use breakpoints use the same breakpoints.

As with the previous PR, here's screenies of the same screen width, but
with open and closed sidebar:
Open:

![image](https://github.com/user-attachments/assets/2d41d412-5072-4c66-9a48-e7aa0d8cff45)

Closed:

![image](https://github.com/user-attachments/assets/994e3f2c-6f4c-4db1-9f10-e1d1a4d96540)
This commit is contained in:
Thomas Heartman 2024-10-03 09:54:27 +02:00 committed by GitHub
parent 401425e35c
commit f5c78605ed
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 206 additions and 221 deletions

View File

@ -1,18 +1,18 @@
import { Grid, Typography, styled } from '@mui/material';
import { Typography, styled } from '@mui/material';
import { AvatarGroupFromOwners } from 'component/common/AvatarGroupFromOwners/AvatarGroupFromOwners';
import { UserAvatar } from 'component/common/UserAvatar/UserAvatar';
import type { PersonalDashboardSchemaAdminsItem } from 'openapi/models/personalDashboardSchemaAdminsItem';
import type { PersonalDashboardSchemaProjectOwnersItem } from 'openapi/models/personalDashboardSchemaProjectOwnersItem';
import { Link } from 'react-router-dom';
import {
ContentGridContainer,
EmptyGridItem,
ProjectGrid,
SpacedGridItem,
} from './Grid';
const ContentGrid = styled(Grid)(({ theme }) => ({
backgroundColor: theme.palette.background.paper,
borderRadius: `${theme.shape.borderRadiusLarge}px`,
}));
const SpacedGridItem = styled(Grid)(({ theme }) => ({
const PaddedEmptyGridItem = styled(EmptyGridItem)(({ theme }) => ({
padding: theme.spacing(4),
border: `0.5px solid ${theme.palette.divider}`,
}));
const TitleContainer = styled('div')(({ theme }) => ({
@ -68,96 +68,99 @@ type Props = {
export const ContentGridNoProjects: React.FC<Props> = ({ owners, admins }) => {
return (
<ContentGrid container columns={{ lg: 12, md: 1 }}>
<SpacedGridItem item lg={4} md={1}>
<Typography variant='h3'>My projects</Typography>
</SpacedGridItem>
<SpacedGridItem item lg={8} md={1}>
<Typography>Potential next steps</Typography>
</SpacedGridItem>
<SpacedGridItem item lg={4} md={1}>
<GridContent>
<Typography>
You don't currently have access to any projects in the
system.
</Typography>
<Typography>
To get started, you can{' '}
<Link to='/projects?create=true'>
create your own project
</Link>
. Alternatively, you can review the available projects
in the system and ask the owner for access.
</Typography>
</GridContent>
</SpacedGridItem>
<SpacedGridItem item lg={4} md={1}>
<GridContent>
<TitleContainer>
<NeutralCircleContainer>1</NeutralCircleContainer>
Contact Unleash admin
</TitleContainer>
<BoxMainContent>
{admins.length ? (
<>
<ContentGridContainer>
<ProjectGrid>
<SpacedGridItem gridArea='title'>
<Typography variant='h3'>My projects</Typography>
</SpacedGridItem>
<SpacedGridItem gridArea='onboarding'>
<Typography>Potential next steps</Typography>
</SpacedGridItem>
<SpacedGridItem gridArea='projects'>
<GridContent>
<Typography>
You don't currently have access to any projects in
the system.
</Typography>
<Typography>
To get started, you can{' '}
<Link to='/projects?create=true'>
create your own project
</Link>
. Alternatively, you can review the available
projects in the system and ask the owner for access.
</Typography>
</GridContent>
</SpacedGridItem>
<SpacedGridItem gridArea='box1'>
<GridContent>
<TitleContainer>
<NeutralCircleContainer>1</NeutralCircleContainer>
Contact Unleash admin
</TitleContainer>
<BoxMainContent>
{admins.length ? (
<>
<p>
Your Unleash administrator
{admins.length > 1 ? 's are' : ' is'}:
</p>
<AdminList>
{admins.map((admin) => {
return (
<AdminListItem key={admin.id}>
<UserAvatar
sx={{
margin: 0,
}}
user={admin}
/>
<Typography>
{admin.name ||
admin.username ||
admin.email}
</Typography>
</AdminListItem>
);
})}
</AdminList>
</>
) : (
<p>
Your Unleash administrator
{admins.length > 1 ? 's are' : ' is'}:
You have no Unleash administrators to
contact.
</p>
<AdminList>
{admins.map((admin) => {
return (
<AdminListItem key={admin.id}>
<UserAvatar
sx={{
margin: 0,
}}
user={admin}
/>
<Typography>
{admin.name ||
admin.username ||
admin.email}
</Typography>
</AdminListItem>
);
})}
</AdminList>
</>
) : (
<p>
You have no Unleash administrators to contact.
</p>
)}
</BoxMainContent>
</GridContent>
</SpacedGridItem>
<SpacedGridItem item lg={4} md={1}>
<GridContent>
<TitleContainer>
<NeutralCircleContainer>2</NeutralCircleContainer>
Ask a project owner to add you to their project
</TitleContainer>
<BoxMainContent>
{owners.length ? (
<>
<p>Project owners in Unleash:</p>
<AvatarGroupFromOwners
users={owners}
avatarLimit={9}
/>
</>
) : (
<p>
There are no project owners in Unleash to ask
for access.
</p>
)}
</BoxMainContent>
</GridContent>
</SpacedGridItem>
<SpacedGridItem item lg={4} md={1} />
<SpacedGridItem item lg={8} md={1} />
</ContentGrid>
)}
</BoxMainContent>
</GridContent>
</SpacedGridItem>
<SpacedGridItem gridArea='box2'>
<GridContent>
<TitleContainer>
<NeutralCircleContainer>2</NeutralCircleContainer>
Ask a project owner to add you to their project
</TitleContainer>
<BoxMainContent>
{owners.length ? (
<>
<p>Project owners in Unleash:</p>
<AvatarGroupFromOwners
users={owners}
avatarLimit={9}
/>
</>
) : (
<p>
There are no project owners in Unleash to
ask for access.
</p>
)}
</BoxMainContent>
</GridContent>
</SpacedGridItem>
<EmptyGridItem />
<PaddedEmptyGridItem gridArea='owners' />
</ProjectGrid>
</ContentGridContainer>
);
};

View File

@ -1,11 +1,11 @@
import { Box, Grid, styled } from '@mui/material';
import { Box, styled } from '@mui/material';
import type { Theme } from '@mui/material/styles/createTheme';
export const ContentGridContainer = styled('div')({
containerType: 'inline-size',
});
const ContentGrid2 = styled('article')(({ theme }) => {
const ContentGrid = styled('article')(({ theme }) => {
return {
backgroundColor: theme.palette.divider,
borderRadius: `${theme.shape.borderRadiusLarge}px`,
@ -21,8 +21,23 @@ const ContentGrid2 = styled('article')(({ theme }) => {
};
});
export const ProjectGrid = styled(ContentGrid2)(({ theme }) => ({
'@container (min-width: 1000px)': {
const onWideContainer =
(css: object) =>
({ theme }: { theme: Theme }) => {
const containerBreakpoint = '1000px';
const screenBreakpoint = theme.breakpoints.up('lg');
return {
[`@container (min-width: ${containerBreakpoint})`]: css,
'@supports not (container-type: inline-size)': {
[screenBreakpoint]: css,
},
};
};
export const ProjectGrid = styled(ContentGrid)(
onWideContainer({
gridTemplateColumns: '1fr 1fr 1fr',
display: 'grid',
gridTemplateAreas: `
@ -30,47 +45,36 @@ export const ProjectGrid = styled(ContentGrid2)(({ theme }) => ({
"projects box1 box2"
". owners owners"
`,
},
}),
);
'@supports not (container-type: inline-size)': {
[theme.breakpoints.up('lg')]: {
gridTemplateColumns: '1fr 1fr 1fr',
display: 'grid',
gridTemplateAreas: `
"title onboarding onboarding"
"projects box1 box2"
". owners owners"
export const FlagGrid = styled(ContentGrid)(
onWideContainer({
gridTemplateColumns: '1fr 1fr 1fr',
display: 'grid',
gridTemplateAreas: `
"title lifecycle lifecycle"
"flags chart chart"
`,
},
},
}));
}),
);
export const SpacedGridItem2 = styled('div')(({ theme }) => ({
export const SpacedGridItem = styled('div', {
shouldForwardProp: (prop) => prop !== 'gridArea',
})<{ gridArea: string }>(({ theme, gridArea }) => ({
padding: theme.spacing(4),
gridArea,
}));
export const EmptyGridItem = styled('div')(({ theme }) => ({
export const EmptyGridItem = styled('div', {
shouldForwardProp: (prop) => prop !== 'gridArea',
})<{ gridArea?: string }>(({ theme, gridArea }) => ({
display: 'none',
gridArea,
'@container (min-width: 1000px)': {
...onWideContainer({
display: 'block',
},
'@supports not (container-type: inline-size)': {
[theme.breakpoints.up('lg')]: {
display: 'block',
},
},
}));
export const ContentGrid = styled(Grid)(({ theme }) => ({
backgroundColor: theme.palette.background.paper,
borderRadius: `${theme.shape.borderRadiusLarge}px`,
}));
export const SpacedGridItem = styled(Grid)(({ theme }) => ({
padding: theme.spacing(4),
border: `0.5px solid ${theme.palette.divider}`,
})({ theme }),
}));
export const ListItemBox = styled(Box)(({ theme }) => ({

View File

@ -26,7 +26,7 @@ import {
ListItemBox,
listItemStyle,
ProjectGrid,
SpacedGridItem2,
SpacedGridItem,
} from './Grid';
const ActiveProjectDetails: FC<{
@ -82,29 +82,15 @@ export const MyProjects: FC<{
return (
<ContentGridContainer>
<ProjectGrid>
<SpacedGridItem2
sx={{
gridArea: 'title',
}}
>
<SpacedGridItem gridArea='title'>
<Typography variant='h3'>My projects</Typography>
</SpacedGridItem2>
<SpacedGridItem2
sx={{
gridArea: 'onboarding',
display: 'flex',
justifyContent: 'flex-end',
}}
>
</SpacedGridItem>
<SpacedGridItem gridArea='onboarding'>
{setupIncomplete ? (
<Badge color='warning'>Setup incomplete</Badge>
) : null}
</SpacedGridItem2>
<SpacedGridItem2
sx={{
gridArea: 'projects',
}}
>
</SpacedGridItem>
<SpacedGridItem gridArea='projects'>
<List
disablePadding={true}
sx={{ maxHeight: '400px', overflow: 'auto' }}
@ -149,12 +135,8 @@ export const MyProjects: FC<{
);
})}
</List>
</SpacedGridItem2>
<SpacedGridItem2
sx={{
gridArea: 'box1',
}}
>
</SpacedGridItem>
<SpacedGridItem gridArea='box1'>
{activeProjectStage === 'onboarded' &&
personalDashboardProjectDetails ? (
<ProjectSetupComplete
@ -169,12 +151,8 @@ export const MyProjects: FC<{
{activeProjectStage === 'first-flag-created' ? (
<ExistingFlag project={activeProject} />
) : null}
</SpacedGridItem2>
<SpacedGridItem2
sx={{
gridArea: 'box2',
}}
>
</SpacedGridItem>
<SpacedGridItem gridArea='box2'>
{activeProjectStage === 'onboarded' &&
personalDashboardProjectDetails ? (
<LatestProjectEvents
@ -186,13 +164,9 @@ export const MyProjects: FC<{
{setupIncomplete || activeProjectStage === 'loading' ? (
<ConnectSDK project={activeProject} />
) : null}
</SpacedGridItem2>
</SpacedGridItem>
<EmptyGridItem />
<SpacedGridItem2
sx={{
gridArea: 'owners',
}}
>
<SpacedGridItem gridArea='owners'>
{personalDashboardProjectDetails ? (
<RoleAndOwnerInfo
roles={personalDashboardProjectDetails.roles.map(
@ -201,7 +175,7 @@ export const MyProjects: FC<{
owners={personalDashboardProjectDetails.owners}
/>
) : null}
</SpacedGridItem2>
</SpacedGridItem>
</ProjectGrid>
</ContentGridContainer>
);

View File

@ -24,7 +24,8 @@ import HelpOutline from '@mui/icons-material/HelpOutline';
import useLoading from '../../hooks/useLoading';
import { MyProjects } from './MyProjects';
import {
ContentGrid,
ContentGridContainer,
FlagGrid,
ListItemBox,
listItemStyle,
SpacedGridItem,
@ -180,55 +181,58 @@ export const PersonalDashboard = () => {
/>
)}
<ContentGrid container columns={{ lg: 12, md: 1 }} sx={{ mt: 2 }}>
<SpacedGridItem item lg={4} md={1}>
<Typography variant='h3'>My feature flags</Typography>
</SpacedGridItem>
<SpacedGridItem
item
lg={8}
md={1}
sx={{ display: 'flex', justifyContent: 'flex-end' }}
>
{activeFlag ? (
<FlagExposure
project={activeFlag.project}
flagName={activeFlag.name}
onArchive={refetchDashboard}
/>
) : null}
</SpacedGridItem>
<SpacedGridItem item lg={4} md={1}>
{personalDashboard && personalDashboard.flags.length > 0 ? (
<List
disablePadding={true}
sx={{ maxHeight: '400px', overflow: 'auto' }}
>
{personalDashboard.flags.map((flag) => (
<FlagListItem
key={flag.name}
flag={flag}
selected={flag.name === activeFlag?.name}
onClick={() => setActiveFlag(flag)}
/>
))}
</List>
) : (
<Typography>
You have not created or favorited any feature flags.
Once you do, they will show up here.
</Typography>
)}
</SpacedGridItem>
<ContentGridContainer>
<FlagGrid sx={{ mt: 2 }}>
<SpacedGridItem gridArea='title'>
<Typography variant='h3'>My feature flags</Typography>
</SpacedGridItem>
<SpacedGridItem
gridArea='lifecycle'
sx={{ display: 'flex', justifyContent: 'flex-end' }}
>
{activeFlag ? (
<FlagExposure
project={activeFlag.project}
flagName={activeFlag.name}
onArchive={refetchDashboard}
/>
) : null}
</SpacedGridItem>
<SpacedGridItem gridArea='flags'>
{personalDashboard &&
personalDashboard.flags.length > 0 ? (
<List
disablePadding={true}
sx={{ maxHeight: '400px', overflow: 'auto' }}
>
{personalDashboard.flags.map((flag) => (
<FlagListItem
key={flag.name}
flag={flag}
selected={
flag.name === activeFlag?.name
}
onClick={() => setActiveFlag(flag)}
/>
))}
</List>
) : (
<Typography>
You have not created or favorited any feature
flags. Once you do, they will show up here.
</Typography>
)}
</SpacedGridItem>
<SpacedGridItem item lg={8} md={1}>
{activeFlag ? (
<FlagMetricsChart flag={activeFlag} />
) : (
<PlaceholderFlagMetricsChart />
)}
</SpacedGridItem>
</ContentGrid>
<SpacedGridItem gridArea='chart'>
{activeFlag ? (
<FlagMetricsChart flag={activeFlag} />
) : (
<PlaceholderFlagMetricsChart />
)}
</SpacedGridItem>
</FlagGrid>
</ContentGridContainer>
<WelcomeDialog
open={welcomeDialog === 'open'}
onClose={() => setWelcomeDialog('closed')}