1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-07-26 13:48:33 +02:00

chore(unl-204): make toasts smaller (#8935)

This PR makes toasts smaller and less intrusive, and gives them a new
color scheme.

Changes include:
- new color scheme
- no description, only title
- new padding
- removes confetti code (even when rendered, they're invisible; UX also
says to cut it)
- use warning triangle for error messages 

I've also set a max height on the container and made it scrollable if
it's too tall to deal with super long messages.

I'll remove the description and confetti props in a separate PR to keep
this one cleaner.

Light mode:

![image](https://github.com/user-attachments/assets/05666259-bc40-4c87-8e51-9900bc67310e)

![image](https://github.com/user-attachments/assets/25dfca09-af5f-4a2a-8490-1169f6d8accb)


Dark mode:

![image](https://github.com/user-attachments/assets/8fa199aa-3cb5-47b4-acaa-1b0fcfd668eb)

![image](https://github.com/user-attachments/assets/eca7d26e-f695-43f9-b281-a64315544212)


With line break (min-width):

![image](https://github.com/user-attachments/assets/2ebd9117-a7c2-4a96-8b4b-c217ba12993b)

With line break (max-width):

![image](https://github.com/user-attachments/assets/8015c761-fc1e-4ff9-992d-a0e9ec27a4f9)

With very long message on phone in landscape mode:


![image](https://github.com/user-attachments/assets/7dc34d25-026c-46c3-9906-dc1348daf208)
This commit is contained in:
Thomas Heartman 2024-12-10 14:10:04 +01:00 committed by GitHub
parent 7ff6a9c5c8
commit 2f7beceb21
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 59 additions and 148 deletions

View File

@ -1,5 +1,5 @@
import Check from '@mui/icons-material/Check'; import Check from '@mui/icons-material/CheckCircle';
import Close from '@mui/icons-material/Close'; import Warning from '@mui/icons-material/Warning';
import { styled } from '@mui/material'; import { styled } from '@mui/material';
interface ICheckMarkBadgeProps { interface ICheckMarkBadgeProps {
@ -7,40 +7,25 @@ interface ICheckMarkBadgeProps {
type?: string; type?: string;
} }
const StyledBatch = styled('div')(({ theme }) => ({
backgroundColor: theme.palette.background.alternative,
width: '75px',
height: '75px',
borderRadius: '50px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
[theme.breakpoints.down('sm')]: {
width: '50px',
height: '50px',
},
}));
const StyledClose = styled(Close)(({ theme }) => ({
color: theme.palette.common.white,
width: '35px',
height: '35px',
}));
const StyledCheck = styled(Check)(({ theme }) => ({ const StyledCheck = styled(Check)(({ theme }) => ({
color: theme.palette.common.white, color:
width: '35px', theme.mode === 'light'
height: '35px', ? theme.palette.secondary.border
: theme.palette.primary.main,
})); }));
const CheckMarkBadge = ({ type, className }: ICheckMarkBadgeProps) => { const StyledCancel = styled(Warning)(({ theme }) => ({
return ( color:
<StyledBatch className={className}> theme.mode === 'light'
{type === 'error' ? ( ? theme.palette.warning.border
<StyledClose titleAccess='Error' /> : theme.palette.warning.main,
) : ( }));
<StyledCheck />
)} const CheckMarkBadge = ({ type }: ICheckMarkBadgeProps) => {
</StyledBatch> return type === 'error' ? (
<StyledCancel titleAccess='Error' />
) : (
<StyledCheck />
); );
}; };

View File

@ -2,56 +2,49 @@ import { makeStyles } from 'tss-react/mui';
export const useStyles = makeStyles()((theme) => ({ export const useStyles = makeStyles()((theme) => ({
container: { container: {
maxWidth: '450px', alignItems: 'center',
background: theme.palette.background.paper, background:
// the background color for this doesn't exist in the theme yet and it's not synchronized across dark/light modes yet.
theme.mode === 'light' ? '#201E42' : theme.palette.background.paper,
borderRadius: theme.shape.borderRadiusMedium,
boxShadow: theme.boxShadows.popup, boxShadow: theme.boxShadows.popup,
zIndex: 500, display: 'flex',
flexDirection: 'row',
gap: theme.spacing(2),
margin: '0 0.8rem', margin: '0 0.8rem',
borderRadius: '12.5px', maxWidth: '450px',
padding: '2rem', padding: theme.spacing(1),
}, paddingLeft: theme.spacing(2),
innerContainer: { zIndex: theme.zIndex.snackbar,
position: 'relative', color: theme.palette.common.white,
}, },
starting: { starting: {
opacity: 0, opacity: 0,
}, },
headerContainer: {
display: 'flex',
alignItems: 'center',
},
confettiContainer: {
position: 'relative',
maxWidth: '600px',
margin: '0 auto',
display: 'flex',
},
textContainer: {
marginLeft: '1rem',
wordBreak: 'break-word',
},
headerStyles: { headerStyles: {
fontSize: theme.typography.body1.fontSize,
fontWeight: 'normal', fontWeight: 'normal',
margin: 0, margin: 0,
marginBottom: '0.5rem', maxHeight: '75vh',
}, overflowY: 'auto',
createdContainer: { '&::first-letter': {
display: 'flex', textTransform: 'uppercase',
alignItems: 'center', },
flexDirection: 'column',
}, },
anim: { anim: {
animation: `$drop 10s 3s`, animation: `$drop 10s 3s`,
}, },
checkMark: { checkMark: {
width: '65px', marginLeft: theme.spacing(1),
height: '65px',
}, },
buttonStyle: { buttonStyle: {
position: 'absolute', color: theme.palette.common.white,
top: '-33px', svg: {
right: '-33px', fontSize: '1em',
},
}, },
'@keyframes drop': { '@keyframes drop': {
'0%': { '0%': {
opacity: '0%', opacity: '0%',

View File

@ -4,52 +4,13 @@ import { useContext } from 'react';
import { IconButton, Tooltip } from '@mui/material'; import { IconButton, Tooltip } from '@mui/material';
import CheckMarkBadge from 'component/common/CheckmarkBadge/CheckMarkBadge'; import CheckMarkBadge from 'component/common/CheckmarkBadge/CheckMarkBadge';
import UIContext from 'contexts/UIContext'; import UIContext from 'contexts/UIContext';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import Close from '@mui/icons-material/Close'; import Close from '@mui/icons-material/Close';
import type { IToast } from 'interfaces/toast'; import type { IToast } from 'interfaces/toast';
import { TOAST_TEXT } from 'utils/testIds';
const Toast = ({ title, text, type, confetti }: IToast) => { const Toast = ({ title, type }: IToast) => {
const { setToast } = useContext(UIContext); const { setToast } = useContext(UIContext);
const { classes: styles } = useStyles(); const { classes: styles } = useStyles();
const confettiColors = ['#d13447', '#ffbf00', '#263672'];
const confettiAmount = 200;
const getRandomNumber = (input: number) => {
return Math.floor(Math.random() * input) + 1;
};
const renderConfetti = () => {
const elements = new Array(confettiAmount).fill(1);
const styledElements = elements.map((el, index) => {
const width = getRandomNumber(8);
const length = getRandomNumber(100);
const style = {
position: 'absolute' as const,
width: `${width}px`,
height: `${width * 0.4}px`,
backgroundColor: confettiColors[getRandomNumber(2)],
left: `${length}%`,
transform: `rotate(${getRandomNumber(101)}deg)`,
animationDelay: `${getRandomNumber(5)}s`,
animationDuration: `${getRandomNumber(3)}s`,
animationEase: `${getRandomNumber(2)}s`,
};
return (
<div
key={index}
style={style}
className={classnames(styles.starting, styles.anim)}
/>
);
});
return styledElements;
};
const hide = () => { const hide = () => {
setToast((prev: IToast) => ({ ...prev, show: false })); setToast((prev: IToast) => ({ ...prev, show: false }));
@ -57,40 +18,19 @@ const Toast = ({ title, text, type, confetti }: IToast) => {
return ( return (
<div className={classnames(styles.container, 'dropdown-outline')}> <div className={classnames(styles.container, 'dropdown-outline')}>
<div className={styles.innerContainer}> <CheckMarkBadge type={type} className={styles.checkMark} />
<div className={styles.confettiContainer}>
{confetti && renderConfetti()}
<div className={styles.createdContainer}>
<div className={styles.headerContainer}>
<div>
<CheckMarkBadge
type={type}
className={styles.checkMark}
/>
</div>
<div className={styles.textContainer}>
<h3 className={styles.headerStyles}>{title}</h3>
<ConditionallyRender <h3 className={styles.headerStyles}>{title}</h3>
condition={Boolean(text)}
show={ <Tooltip title='Close' arrow>
<p data-testid={TOAST_TEXT}>{text}</p> <IconButton
} onClick={hide}
/> className={styles.buttonStyle}
</div> size='small'
</div> >
<Tooltip title='Close' arrow> <Close />
<IconButton </IconButton>
onClick={hide} </Tooltip>
className={styles.buttonStyle}
size='large'
>
<Close />
</IconButton>
</Tooltip>
</div>
</div>
</div>
</div> </div>
); );
}; };

View File

@ -22,7 +22,6 @@ const ToastRenderer = () => {
const timeout = setTimeout(() => { const timeout = setTimeout(() => {
hide(); hide();
}, toastData.autoHideDuration); }, toastData.autoHideDuration);
return () => { return () => {
clearTimeout(timeout); clearTimeout(timeout);
}; };
@ -36,7 +35,7 @@ const ToastRenderer = () => {
right: 0, right: 0,
left: 0, left: 0,
margin: '0 auto', margin: '0 auto',
maxWidth: '450px', width: 'fit-content',
}, },
enter: fadeInBottomEnter, enter: fadeInBottomEnter,
leave: fadeInBottomLeave, leave: fadeInBottomLeave,

View File

@ -47,9 +47,6 @@ test('should show deleted stale sessions info for Password Auth', async () => {
button.click(); button.click();
await screen.findByText('Maximum Session Limit Reached'); await screen.findByText('Maximum Session Limit Reached');
await screen.findByText(
'You can have up to 3 active sessions at a time. To enhance your account security, weve ended 1 session(s) on other browsers.',
);
}); });
test('should show deleted stale sessions info for Hosted Auth', async () => { test('should show deleted stale sessions info for Hosted Auth', async () => {
@ -76,7 +73,4 @@ test('should show deleted stale sessions info for Hosted Auth', async () => {
button.click(); button.click();
await screen.findByText('Maximum Session Limit Reached'); await screen.findByText('Maximum Session Limit Reached');
await screen.findByText(
'You can have up to 3 active sessions at a time. To enhance your account security, weve ended 1 session(s) on other browsers.',
);
}); });

View File

@ -100,6 +100,6 @@ test('handle error', async () => {
checkbox.click(); checkbox.click();
await screen.findByText('user error'); await screen.findByText('Something went wrong');
expect(changed).toBe(true); expect(changed).toBe(true);
}); });