toastIndicator.tsx 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. import styled from '@emotion/styled';
  2. import classNames from 'classnames';
  3. import {motion} from 'framer-motion';
  4. import {Indicator} from 'sentry/actionCreators/indicator';
  5. import LoadingIndicator from 'sentry/components/loadingIndicator';
  6. import {IconCheckmark, IconClose} from 'sentry/icons';
  7. import {t} from 'sentry/locale';
  8. import space from 'sentry/styles/space';
  9. import testableTransition from 'sentry/utils/testableTransition';
  10. type Props = {
  11. indicator: Indicator;
  12. onDismiss: (indicator: Indicator, event: React.MouseEvent) => void;
  13. className?: string;
  14. };
  15. function ToastIndicator({indicator, onDismiss, className, ...props}: Props) {
  16. let icon: React.ReactNode;
  17. const {options, message, type} = indicator;
  18. const {undo, disableDismiss} = options || {};
  19. const showUndo = typeof undo === 'function';
  20. const handleClick = (e: React.MouseEvent) => {
  21. if (disableDismiss) {
  22. return;
  23. }
  24. if (typeof onDismiss === 'function') {
  25. onDismiss(indicator, e);
  26. }
  27. };
  28. if (type === 'success') {
  29. icon = <IconCheckmark size="lg" isCircled />;
  30. } else if (type === 'error') {
  31. icon = <IconClose size="lg" isCircled />;
  32. }
  33. // TODO(billy): Remove ref- className after removing usage from getsentry
  34. return (
  35. <Toast
  36. onClick={handleClick}
  37. data-test-id={type ? `toast-${type}` : 'toast'}
  38. className={classNames(className, 'ref-toast', `ref-${type}`)}
  39. {...props}
  40. >
  41. {type === 'loading' ? (
  42. <StyledLoadingIndicator mini />
  43. ) : (
  44. <Icon type={type}>{icon}</Icon>
  45. )}
  46. <Message>{message}</Message>
  47. {showUndo && <Undo onClick={undo}>{t('Undo')}</Undo>}
  48. </Toast>
  49. );
  50. }
  51. const Toast = styled(motion.div)`
  52. display: flex;
  53. align-items: center;
  54. height: 40px;
  55. padding: 0 15px 0 10px;
  56. margin-top: 15px;
  57. background: ${p => p.theme.inverted.background};
  58. color: ${p => p.theme.inverted.textColor};
  59. border-radius: 44px 7px 7px 44px;
  60. box-shadow: ${p => p.theme.dropShadowHeavy};
  61. position: relative;
  62. `;
  63. Toast.defaultProps = {
  64. initial: {
  65. opacity: 0,
  66. y: 70,
  67. },
  68. animate: {
  69. opacity: 1,
  70. y: 0,
  71. },
  72. exit: {
  73. opacity: 0,
  74. y: 70,
  75. },
  76. transition: testableTransition({
  77. type: 'spring',
  78. stiffness: 450,
  79. damping: 25,
  80. }),
  81. };
  82. const Icon = styled('div', {shouldForwardProp: p => p !== 'type'})<{type: string}>`
  83. margin-right: ${space(0.75)};
  84. svg {
  85. display: block;
  86. }
  87. color: ${p => (p.type === 'success' ? p.theme.green300 : p.theme.red300)};
  88. `;
  89. const Message = styled('div')`
  90. flex: 1;
  91. `;
  92. const Undo = styled('div')`
  93. display: inline-block;
  94. color: ${p => p.theme.linkColor};
  95. padding-left: ${space(1)};
  96. margin-left: ${space(1)};
  97. cursor: pointer;
  98. &:hover {
  99. color: ${p => p.theme.linkHoverColor};
  100. }
  101. `;
  102. const StyledLoadingIndicator = styled(LoadingIndicator)`
  103. .loading-indicator {
  104. border-color: ${p => p.theme.inverted.border};
  105. border-left-color: ${p => p.theme.inverted.purple300};
  106. }
  107. `;
  108. export default ToastIndicator;