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:   Dark mode:   With line break (min-width):  With line break (max-width):  With very long message on phone in landscape mode: 
This commit is contained in:
parent
7ff6a9c5c8
commit
2f7beceb21
@ -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 />
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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%',
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -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,
|
||||||
|
@ -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, we’ve 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, we’ve ended 1 session(s) on other browsers.',
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
@ -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);
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user