toastIndicator.tsx 2.9 KB

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