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 { formatAssetPath } from 'utils/formatPath';
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 }) => ({
margin: theme.spacing(2, 0, 1, 0),
@ -165,25 +165,14 @@ export const NewInUnleash = ({
},
{
label: 'Release templates',
summary: 'Save time with release plans',
summary: 'Save time and optimize your process',
icon: <StyledReleaseManagementIcon />,
preview: <ReleaseManagementPreview />,
onCheckItOut: () => navigate('/release-templates'),
docsLink: 'https://docs.getunleash.io/reference/release-templates',
show: isEnterprise() && releasePlansEnabled,
beta: true,
longDescription: (
<>
<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>
</>
),
beta: false,
popout: true,
},
];
@ -229,6 +218,7 @@ export const NewInUnleash = ({
preview,
summary,
beta = false,
popout = false,
}) => (
<NewInUnleashItem
key={label}
@ -255,6 +245,7 @@ export const NewInUnleash = ({
docsLink={docsLink}
summary={summary}
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 {
IconButton,
ListItem,
ListItemButton,
styled,
Tooltip,
Typography,
} from '@mui/material';
import Close from '@mui/icons-material/Close';
import { ListItem } from '@mui/material';
import { NewInUnleashTooltip } from './NewInUnleashTooltip';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { Badge } from 'component/common/Badge/Badge';
import { Truncator } from 'component/common/Truncator/Truncator';
import { NewInUnleashDialog } from './NewInUnleashDialog';
import { NewInUnleashSideBarItem } from './NewInUnleashSideBarItem';
export type NewInUnleashItemDetails = {
label: string;
@ -21,41 +11,12 @@ export type NewInUnleashItemDetails = {
onCheckItOut?: () => void;
docsLink?: string;
show: boolean;
longDescription: ReactNode;
longDescription?: ReactNode;
preview?: ReactNode;
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
extends Omit<NewInUnleashItemDetails, 'show' | 'beta'> {
onClick: () => void;
@ -88,6 +49,7 @@ export const NewInUnleashItem = ({
preview,
summary,
beta,
popout,
}: INewInUnleashItemProps) => {
const { open, handleTooltipOpen, handleTooltipClose } = useTooltip();
@ -96,51 +58,55 @@ export const NewInUnleashItem = ({
onDismiss();
};
const onOpen = () => {
onClick();
handleTooltipOpen();
};
return (
<ListItem
disablePadding
onClick={() => {
onClick();
handleTooltipOpen();
}}
>
<NewInUnleashTooltip
open={open}
onClose={handleTooltipClose}
title={label}
longDescription={longDescription}
onCheckItOut={onCheckItOut}
docsLink={docsLink}
preview={preview}
beta={beta}
>
<StyledItemButton>
{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={onDismissClick}
size='small'
>
<Close fontSize='inherit' />
</StyledItemButtonClose>
</Tooltip>
</StyledItemButton>
</NewInUnleashTooltip>
<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
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>
)}
</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>
}
>
{children}
<div>{children}</div>
</HtmlTooltip>
);