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

feat: stateful banner (#2346)

Conditional banner for change request status
This commit is contained in:
Mateusz Kwasniewski 2022-11-08 12:56:28 +01:00 committed by GitHub
parent f76ac0edb3
commit 88a9e0cb9b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 109 additions and 79 deletions

View File

@ -1,4 +1,4 @@
import { VFC } from 'react'; import { FC, VFC } from 'react';
import { import {
Box, Box,
Button, Button,
@ -58,6 +58,15 @@ const BackButton = styled(Button)(({ theme }) => ({
marginLeft: 'auto', marginLeft: 'auto',
})); }));
const SubmitChangeRequestButton: FC<{ onClick: () => void; count: number }> = ({
onClick,
count,
}) => (
<Button sx={{ mt: 2, ml: 'auto' }} variant="contained" onClick={onClick}>
Submit change request ({count})
</Button>
);
export const ChangeRequestSidebar: VFC<IChangeRequestSidebarProps> = ({ export const ChangeRequestSidebar: VFC<IChangeRequestSidebarProps> = ({
open, open,
project, project,
@ -218,17 +227,18 @@ export const ChangeRequestSidebar: VFC<IChangeRequestSidebarProps> = ({
} }
show={ show={
<> <>
<Button <SubmitChangeRequestButton
sx={{ mt: 2, ml: 'auto' }}
variant="contained"
onClick={() => onClick={() =>
onReview( onReview(
environmentChangeRequest.id environmentChangeRequest.id
) )
} }
> count={
Request changes environmentChangeRequest
</Button> .features.length
}
/>
<Button <Button
sx={{ mt: 2, ml: 2 }} sx={{ mt: 2, ml: 2 }}
variant="outlined" variant="outlined"

View File

@ -1,74 +1,100 @@
import { useState, VFC } from 'react'; import { FC, useState, VFC } from 'react';
import { Box, Button, Typography } from '@mui/material'; import { Box, Button, styled, Typography } from '@mui/material';
import { useStyles as useAppStyles } from 'component/App.styles'; import { useStyles as useAppStyles } from 'component/App.styles';
import WarningAmberIcon from '@mui/icons-material/WarningAmber'; import WarningAmberIcon from '@mui/icons-material/WarningAmber';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { ChangeRequestSidebar } from '../ChangeRequestSidebar/ChangeRequestSidebar'; import { ChangeRequestSidebar } from '../ChangeRequestSidebar/ChangeRequestSidebar';
import { useChangeRequestOpen } from 'hooks/api/getters/useChangeRequestOpen/useChangeRequestOpen'; import { useChangeRequestOpen } from 'hooks/api/getters/useChangeRequestOpen/useChangeRequestOpen';
import { IChangeRequest } from '../changeRequest.types';
interface IDraftBannerProps { interface IDraftBannerProps {
project: string; project: string;
} }
export const DraftBanner: VFC<IDraftBannerProps> = ({ project }) => { const DraftBannerContentWrapper = styled(Box)(({ theme }) => ({
display: 'flex',
alignItems: 'center',
padding: theme.spacing(1, 1.5),
color: theme.palette.warning.main,
}));
const DraftBannerContent: FC<{
changeRequest: IChangeRequest;
onClick: () => void;
}> = ({ changeRequest, onClick }) => {
const { classes } = useAppStyles(); const { classes } = useAppStyles();
return (
<Box className={classes.content}>
<DraftBannerContentWrapper>
<WarningAmberIcon />
<Typography variant="body2" sx={{ ml: 1 }}>
<strong>Draft mode!</strong> You have changes{' '}
<ConditionallyRender
condition={Boolean(changeRequest.environment)}
show={
<>
in <strong>{changeRequest.environment} </strong>
</>
}
/>
<ConditionallyRender
condition={changeRequest.state === 'Draft'}
show={'that need to be reviewed'}
/>
<ConditionallyRender
condition={changeRequest.state === 'In review'}
show={'that are in review'}
/>
</Typography>
<Button
variant="contained"
onClick={onClick}
sx={{ ml: 'auto' }}
>
View changes ({changeRequest.features.length})
</Button>
<Button variant="text" onClick={() => {}} sx={{ ml: 1 }}>
Discard all
</Button>
</DraftBannerContentWrapper>
</Box>
);
};
const StickyBanner = styled(Box)(({ theme }) => ({
position: 'sticky',
top: -1,
zIndex: theme.zIndex.appBar,
borderTop: `1px solid ${theme.palette.warning.border}`,
borderBottom: `1px solid ${theme.palette.warning.border}`,
backgroundColor: theme.palette.warning.light,
}));
export const DraftBanner: VFC<IDraftBannerProps> = ({ project }) => {
const [isSidebarOpen, setIsSidebarOpen] = useState(false); const [isSidebarOpen, setIsSidebarOpen] = useState(false);
const { draft, loading } = useChangeRequestOpen(project); const { draft, loading } = useChangeRequestOpen(project);
const environment = '';
if ((!loading && !draft) || draft?.length === 0) { if ((!loading && !draft) || draft?.length === 0) {
return null; return null;
} }
return ( return (
<Box <StickyBanner>
sx={{ {draft &&
position: 'sticky', draft
top: -1, .filter(changeRequest =>
zIndex: theme => theme.zIndex.appBar, ['Draft', 'In review'].includes(changeRequest.state)
borderTop: theme => `1px solid ${theme.palette.warning.border}`, )
borderBottom: theme => .map(changeRequest => (
`1px solid ${theme.palette.warning.border}`, <DraftBannerContent
backgroundColor: theme => theme.palette.warning.light, changeRequest={changeRequest}
}}
>
<Box className={classes.content}>
<Box
sx={{
display: 'flex',
alignItems: 'center',
px: 1,
py: 1.5,
color: theme => theme.palette.warning.main,
}}
>
<WarningAmberIcon />
<Typography variant="body2" sx={{ ml: 1 }}>
<strong>Draft mode!</strong> You have changes{' '}
<ConditionallyRender
condition={Boolean(environment)}
show={
<>
in <strong>{environment} </strong>
</>
}
/>
that need to be reviewed
</Typography>
<Button
variant="contained"
onClick={() => { onClick={() => {
setIsSidebarOpen(true); setIsSidebarOpen(true);
}} }}
sx={{ ml: 'auto' }} />
> ))}
Review changes
</Button>
<Button variant="text" onClick={() => {}} sx={{ ml: 1 }}>
Discard all
</Button>
</Box>
</Box>
<ChangeRequestSidebar <ChangeRequestSidebar
project={project} project={project}
open={isSidebarOpen} open={isSidebarOpen}
@ -76,6 +102,6 @@ export const DraftBanner: VFC<IDraftBannerProps> = ({ project }) => {
setIsSidebarOpen(false); setIsSidebarOpen(false);
}} }}
/> />
</Box> </StickyBanner>
); );
}; };

View File

@ -1,15 +0,0 @@
import { makeStyles } from 'tss-react/mui';
export const useStyles = makeStyles()(() => ({
modal: {
position: 'absolute',
top: 0,
right: 0,
bottom: 0,
height: '100vh',
maxWidth: '98vw',
width: 1300,
overflow: 'auto',
boxShadow: '0 0 1rem rgba(0, 0, 0, 0.25)',
},
}));

View File

@ -1,7 +1,6 @@
import { ReactNode } from 'react'; import { ReactNode } from 'react';
import { Modal, Backdrop } from '@mui/material'; import { Modal, Backdrop, styled } from '@mui/material';
import Fade from '@mui/material/Fade'; import Fade from '@mui/material/Fade';
import { useStyles } from 'component/common/SidebarModal/SidebarModal.styles';
import { SIDEBAR_MODAL_ID } from 'utils/testIds'; import { SIDEBAR_MODAL_ID } from 'utils/testIds';
interface ISidebarModalProps { interface ISidebarModalProps {
@ -13,14 +12,24 @@ interface ISidebarModalProps {
const TRANSITION_DURATION = 250; const TRANSITION_DURATION = 250;
const ModalContentWrapper = styled('div')({
position: 'absolute',
top: 0,
right: 0,
bottom: 0,
height: '100vh',
maxWidth: '98vw',
width: 1300,
overflow: 'auto',
boxShadow: '0 0 1rem rgba(0, 0, 0, 0.25)',
});
export const SidebarModal = ({ export const SidebarModal = ({
open, open,
onClose, onClose,
label, label,
children, children,
}: ISidebarModalProps) => { }: ISidebarModalProps) => {
const { classes: styles } = useStyles();
return ( return (
<Modal <Modal
open={open} open={open}
@ -32,7 +41,7 @@ export const SidebarModal = ({
data-testid={SIDEBAR_MODAL_ID} data-testid={SIDEBAR_MODAL_ID}
> >
<Fade timeout={TRANSITION_DURATION} in={open}> <Fade timeout={TRANSITION_DURATION} in={open}>
<div className={styles.modal}>{children}</div> <ModalContentWrapper>{children}</ModalContentWrapper>
</Fade> </Fade>
</Modal> </Modal>
); );