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

chore: whats new dialog (#9622)

Adds a new dialog option for whats in new in Unleash items. This can be
tiggerred by setting `popout` to true when configuring the items.

To do this without setting fire to the code, I've also needed to
refactor the NewInUnleash components:

- NewInUnleashItem becomes a dumb item that decides if a dialog or
tooltip should be rendered and controls that render state
- The child item in NewInUnleashItem has been moved out into
NewInUnleashSideBarItem, which feels a bit better since that is a
distinct UI element from the popup
- NewInUnleashDialog now exists, which is a dialog version of the popup.
Meaningfully different to ask for a new component

## Screenshots

![image](https://github.com/user-attachments/assets/33d3e7f5-9178-4d2d-9355-866814e58164)
This commit is contained in:
Simon Hornby 2025-03-27 11:30:24 +02:00 committed by GitHub
parent 1bd328f4e1
commit 01f3af4bda
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 466 additions and 127 deletions

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 60 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 509 KiB

View File

@ -23,7 +23,7 @@ import MonitorHeartIcon from '@mui/icons-material/MonitorHeartOutlined';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { formatAssetPath } from 'utils/formatPath'; import { formatAssetPath } from 'utils/formatPath';
import FactCheckOutlinedIcon from '@mui/icons-material/FactCheckOutlined'; import FactCheckOutlinedIcon from '@mui/icons-material/FactCheckOutlined';
import { ReactComponent as ReleaseManagementPreview } from 'assets/img/releaseManagementPreview.svg'; import { ReactComponent as ReleaseManagementPreview } from 'assets/img/releaseTemplatePreview.svg';
const StyledNewInUnleash = styled('div')(({ theme }) => ({ const StyledNewInUnleash = styled('div')(({ theme }) => ({
margin: theme.spacing(2, 0, 1, 0), margin: theme.spacing(2, 0, 1, 0),
@ -165,25 +165,14 @@ export const NewInUnleash = ({
}, },
{ {
label: 'Release templates', label: 'Release templates',
summary: 'Save time with release plans', summary: 'Save time and optimize your process',
icon: <StyledReleaseManagementIcon />, icon: <StyledReleaseManagementIcon />,
preview: <ReleaseManagementPreview />, preview: <ReleaseManagementPreview />,
onCheckItOut: () => navigate('/release-templates'), onCheckItOut: () => navigate('/release-templates'),
docsLink: 'https://docs.getunleash.io/reference/release-templates',
show: isEnterprise() && releasePlansEnabled, show: isEnterprise() && releasePlansEnabled,
beta: true, beta: false,
longDescription: ( popout: true,
<>
<p>
Instead of having to set up the same strategies again
and again, you can now create templates with milestones
of how you want to rollout features to your users.
</p>
<p>
Once you have set it up, just apply your release plan to
a flag, and you are ready to rollout!
</p>
</>
),
}, },
]; ];
@ -229,6 +218,7 @@ export const NewInUnleash = ({
preview, preview,
summary, summary,
beta = false, beta = false,
popout = false,
}) => ( }) => (
<NewInUnleashItem <NewInUnleashItem
key={label} key={label}
@ -255,6 +245,7 @@ export const NewInUnleash = ({
docsLink={docsLink} docsLink={docsLink}
summary={summary} summary={summary}
beta={beta} beta={beta}
popout={popout}
/> />
), ),
)} )}

View File

@ -0,0 +1,222 @@
import OpenInNew from '@mui/icons-material/OpenInNew';
import {
Badge,
Box,
Button,
ClickAwayListener,
Dialog,
IconButton,
Link,
styled,
Tooltip,
Typography,
} from '@mui/material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import type { Link as RouterLink } from 'react-router-dom';
import type { FC, ReactNode } from 'react';
import Close from '@mui/icons-material/Close';
const StyledLargeHeader = styled(Typography)(({ theme }) => ({
fontFamily: theme.typography.fontFamily,
fontSize: theme.typography.h1.fontSize,
fontWeight: theme.typography.fontWeightLight,
color: theme.palette.text.primary,
margin: 0,
}));
const StyledPreHeader = styled('div')(({ theme }) => ({
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
gap: theme.spacing(1),
color: theme.palette.neutral.main,
fontFamily: theme.typography.fontFamily,
fontSize: theme.typography.h3.fontSize,
fontWeight: theme.typography.fontWeightBold,
padding: 0,
marginBottom: theme.spacing(0.5),
}));
const StyledMainTitle = styled('div')(({ theme }) => ({
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
gap: theme.spacing(1),
padding: 0,
lineHeight: 1.2,
}));
const StyledLink = styled(Link<typeof RouterLink | 'a'>)(({ theme }) => ({
display: 'flex',
alignItems: 'center',
gap: theme.spacing(1),
padding: 0,
color: theme.palette.links,
fontWeight: theme.typography.fontWeightBold,
'&:hover, &:focus': {
textDecoration: 'underline',
},
}));
const StyledOpenInNew = styled(OpenInNew)(({ theme }) => ({
fontSize: theme.spacing(2.25),
}));
const CenteredPreview = styled(Box)(({ theme }) => ({
padding: theme.spacing(3),
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
'> svg': { display: 'block', width: '100%', height: 'auto' },
}));
const LongDescription = styled(Box)(({ theme }) => ({
display: 'flex',
flexDirection: 'column',
gap: theme.spacing(1.5),
ul: {
margin: 0,
paddingLeft: theme.spacing(2),
},
}));
const ReadMore = styled(Box)(({ theme }) => ({
paddingTop: theme.spacing(3),
paddingBottom: theme.spacing(1),
}));
const StyledCheckItOutButton = styled(Button)(({ theme }) => ({
marginTop: theme.spacing(2),
}));
const DialogCard = styled(Box)(({ theme }) => ({
backgroundColor: theme.palette.background.paper,
borderRadius: theme.shape.borderRadiusLarge,
padding: theme.spacing(4),
margin: theme.spacing(2),
display: 'flex',
flexDirection: 'column',
flex: 1,
boxShadow: theme.shadows[5],
maxWidth: theme.spacing(150),
width: '100%',
height: '100%',
overflow: 'auto',
}));
const StyledDialog = styled(Dialog)(() => ({
'& .MuiDialog-paper': {
backgroundColor: 'transparent',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
boxShadow: 'none',
overflow: 'visible',
},
}));
const BottomActions = styled(Box)(({ theme }) => ({
display: 'flex',
justifyContent: 'flex-end',
alignItems: 'center',
paddingRight: theme.spacing(3),
gap: theme.spacing(2),
flexWrap: 'wrap',
}));
const StyledItemButtonClose = styled(IconButton)(({ theme }) => ({
padding: theme.spacing(0.25),
}));
export const NewInUnleashDialog: FC<{
title: string;
longDescription: ReactNode;
docsLink?: string;
onCheckItOut?: () => void;
open: boolean;
preview?: ReactNode;
onClose: () => void;
beta: boolean;
}> = ({
title,
longDescription,
onCheckItOut,
docsLink,
preview,
open,
onClose,
beta,
}) => (
<StyledDialog open={open} onClose={onClose} maxWidth='lg' fullWidth>
<ClickAwayListener onClickAway={onClose}>
<DialogCard>
<Tooltip title='Dismiss' arrow sx={{ marginLeft: 'auto' }}>
<StyledItemButtonClose
aria-label='dismiss'
onClick={(e) => {
onClose();
e.stopPropagation();
}}
size='small'
>
<Close fontSize='inherit' />
</StyledItemButtonClose>
</Tooltip>
<StyledPreHeader>New in Unleash</StyledPreHeader>
<StyledMainTitle>
<StyledLargeHeader>{title}</StyledLargeHeader>
<ConditionallyRender
condition={beta}
show={<Badge color='secondary'>Beta</Badge>}
/>
</StyledMainTitle>
<LongDescription>{longDescription}</LongDescription>
<ConditionallyRender
condition={Boolean(preview)}
show={<CenteredPreview>{preview}</CenteredPreview>}
/>
<BottomActions>
<ConditionallyRender
condition={Boolean(docsLink)}
show={
<ReadMore>
<StyledLink
component='a'
href={docsLink}
underline='hover'
rel='noopener noreferrer'
target='_blank'
>
<StyledOpenInNew />
Read more in our documentation
</StyledLink>
</ReadMore>
}
/>
<ConditionallyRender
condition={Boolean(onCheckItOut)}
show={
<StyledCheckItOutButton
variant='contained'
color='primary'
onClick={(event) => {
event.stopPropagation();
onClose();
onCheckItOut?.();
}}
>
Get Started
</StyledCheckItOutButton>
}
/>
</BottomActions>
</DialogCard>
</ClickAwayListener>
</StyledDialog>
);

View File

@ -1,18 +1,8 @@
import type * as React from 'react';
import { type ReactNode, useState } from 'react'; import { type ReactNode, useState } from 'react';
import { import { ListItem } from '@mui/material';
IconButton,
ListItem,
ListItemButton,
styled,
Tooltip,
Typography,
} from '@mui/material';
import Close from '@mui/icons-material/Close';
import { NewInUnleashTooltip } from './NewInUnleashTooltip'; import { NewInUnleashTooltip } from './NewInUnleashTooltip';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { NewInUnleashDialog } from './NewInUnleashDialog';
import { Badge } from 'component/common/Badge/Badge'; import { NewInUnleashSideBarItem } from './NewInUnleashSideBarItem';
import { Truncator } from 'component/common/Truncator/Truncator';
export type NewInUnleashItemDetails = { export type NewInUnleashItemDetails = {
label: string; label: string;
@ -21,41 +11,12 @@ export type NewInUnleashItemDetails = {
onCheckItOut?: () => void; onCheckItOut?: () => void;
docsLink?: string; docsLink?: string;
show: boolean; show: boolean;
longDescription: ReactNode; longDescription?: ReactNode;
preview?: ReactNode; preview?: ReactNode;
beta?: boolean; beta?: boolean;
popout?: boolean;
}; };
const StyledItemButton = styled(ListItemButton)(({ theme }) => ({
outline: `1px solid ${theme.palette.divider}`,
borderRadius: theme.shape.borderRadiusMedium,
padding: theme.spacing(1),
width: '100%',
display: 'flex',
alignItems: 'start',
gap: theme.spacing(1),
fontSize: theme.fontSizes.smallBody,
'& > svg': {
width: theme.spacing(3),
height: theme.spacing(3),
},
}));
const LabelWithSummary = styled('div')(({ theme }) => ({
flex: 1,
}));
const StyledItemTitle = styled('div')(({ theme }) => ({
display: 'flex',
gap: theme.spacing(1),
alignItems: 'center',
height: theme.spacing(3),
}));
const StyledItemButtonClose = styled(IconButton)(({ theme }) => ({
padding: theme.spacing(0.25),
}));
interface INewInUnleashItemProps interface INewInUnleashItemProps
extends Omit<NewInUnleashItemDetails, 'show' | 'beta'> { extends Omit<NewInUnleashItemDetails, 'show' | 'beta'> {
onClick: () => void; onClick: () => void;
@ -88,6 +49,7 @@ export const NewInUnleashItem = ({
preview, preview,
summary, summary,
beta, beta,
popout,
}: INewInUnleashItemProps) => { }: INewInUnleashItemProps) => {
const { open, handleTooltipOpen, handleTooltipClose } = useTooltip(); const { open, handleTooltipOpen, handleTooltipClose } = useTooltip();
@ -96,14 +58,35 @@ export const NewInUnleashItem = ({
onDismiss(); onDismiss();
}; };
return ( const onOpen = () => {
<ListItem
disablePadding
onClick={() => {
onClick(); onClick();
handleTooltipOpen(); handleTooltipOpen();
}} };
>
return (
<ListItem disablePadding>
{popout ? (
<>
<NewInUnleashDialog
open={open}
onClose={handleTooltipClose}
title={label}
longDescription={longDescription}
onCheckItOut={onCheckItOut}
docsLink={docsLink}
preview={preview}
beta={beta}
/>
<NewInUnleashSideBarItem
label={label}
summary={summary}
icon={icon}
beta={beta}
onClick={onOpen}
onDismiss={onDismissClick}
/>
</>
) : (
<NewInUnleashTooltip <NewInUnleashTooltip
open={open} open={open}
onClose={handleTooltipClose} onClose={handleTooltipClose}
@ -114,33 +97,16 @@ export const NewInUnleashItem = ({
preview={preview} preview={preview}
beta={beta} beta={beta}
> >
<StyledItemButton> <NewInUnleashSideBarItem
{icon} label={label}
<LabelWithSummary> summary={summary}
<StyledItemTitle> icon={icon}
<Typography fontWeight='bold' fontSize='small'> beta={beta}
<Truncator title={label} arrow> onClick={onOpen}
{label} onDismiss={onDismissClick}
</Truncator>
</Typography>
<ConditionallyRender
condition={beta}
show={<Badge color='secondary'>Beta</Badge>}
/> />
</StyledItemTitle>
<Typography fontSize='small'>{summary}</Typography>
</LabelWithSummary>
<Tooltip title='Dismiss' arrow sx={{ marginLeft: 'auto' }}>
<StyledItemButtonClose
aria-label='dismiss'
onClick={onDismissClick}
size='small'
>
<Close fontSize='inherit' />
</StyledItemButtonClose>
</Tooltip>
</StyledItemButton>
</NewInUnleashTooltip> </NewInUnleashTooltip>
)}
</ListItem> </ListItem>
); );
}; };

View File

@ -0,0 +1,90 @@
import type * as React from 'react';
import type { ReactNode } from 'react';
import {
IconButton,
ListItemButton,
styled,
Tooltip,
Typography,
} from '@mui/material';
import Close from '@mui/icons-material/Close';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { Badge } from 'component/common/Badge/Badge';
import { Truncator } from 'component/common/Truncator/Truncator';
const StyledItemButton = styled(ListItemButton)(({ theme }) => ({
outline: `1px solid ${theme.palette.divider}`,
borderRadius: theme.shape.borderRadiusMedium,
padding: theme.spacing(1),
width: '100%',
display: 'flex',
alignItems: 'start',
gap: theme.spacing(1),
fontSize: theme.fontSizes.smallBody,
'& > svg': {
width: theme.spacing(3),
height: theme.spacing(3),
},
}));
const LabelWithSummary = styled('div')(({ theme }) => ({
flex: 1,
}));
const StyledItemButtonClose = styled(IconButton)(({ theme }) => ({
padding: theme.spacing(0.25),
}));
const StyledItemTitle = styled('div')(({ theme }) => ({
display: 'flex',
gap: theme.spacing(1),
alignItems: 'center',
height: theme.spacing(3),
}));
interface NewInUnleashSideBarItemProps {
label: string;
summary: string;
icon: ReactNode;
beta?: boolean;
onDismiss: (e: React.MouseEvent) => void;
onClick: (e: React.MouseEvent) => void;
}
export const NewInUnleashSideBarItem = ({
icon,
label,
summary,
beta = false,
onDismiss,
onClick,
}: NewInUnleashSideBarItemProps) => {
return (
<StyledItemButton onClick={onClick}>
{icon}
<LabelWithSummary>
<StyledItemTitle>
<Typography fontWeight='bold' fontSize='small'>
<Truncator title={label} arrow>
{label}
</Truncator>
</Typography>
<ConditionallyRender
condition={beta}
show={<Badge color='secondary'>Beta</Badge>}
/>
</StyledItemTitle>
<Typography fontSize='small'>{summary}</Typography>
</LabelWithSummary>
<Tooltip title='Dismiss' arrow sx={{ marginLeft: 'auto' }}>
<StyledItemButtonClose
aria-label='dismiss'
onClick={onDismiss}
size='small'
>
<Close fontSize='inherit' />
</StyledItemButtonClose>
</Tooltip>
</StyledItemButton>
);
};

View File

@ -179,6 +179,6 @@ export const NewInUnleashTooltip: FC<{
</ClickAwayListener> </ClickAwayListener>
} }
> >
{children} <div>{children}</div>
</HtmlTooltip> </HtmlTooltip>
); );