alert.tsx 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. import React, {useState} from 'react';
  2. import {css} from '@emotion/react';
  3. import styled from '@emotion/styled';
  4. import classNames from 'classnames';
  5. import {IconChevron} from 'app/icons';
  6. import space from 'app/styles/space';
  7. import {Theme} from 'app/utils/theme';
  8. type Props = {
  9. type?: keyof Theme['alert'];
  10. icon?: React.ReactNode;
  11. system?: boolean;
  12. expand?: React.ReactNode[];
  13. expandIcon?: React.ReactNode;
  14. onExpandIconClick?: () => void;
  15. };
  16. type AlertProps = Omit<React.HTMLProps<HTMLDivElement>, keyof Props> & Props;
  17. type AlertThemeProps = {
  18. backgroundLight: string;
  19. border: string;
  20. iconColor: string;
  21. };
  22. const DEFAULT_TYPE = 'info';
  23. const IconWrapper = styled('span')`
  24. display: flex;
  25. margin-right: ${space(1)};
  26. /* Give the wrapper an explicit height so icons are line height with the
  27. * (common) line height. */
  28. height: 22px;
  29. align-items: center;
  30. `;
  31. const getAlertColorStyles = ({
  32. backgroundLight,
  33. border,
  34. iconColor,
  35. }: AlertThemeProps) => css`
  36. background: ${backgroundLight};
  37. border: 1px solid ${border};
  38. ${IconWrapper} {
  39. color: ${iconColor};
  40. }
  41. `;
  42. const getSystemAlertColorStyles = ({
  43. backgroundLight,
  44. border,
  45. iconColor,
  46. }: AlertThemeProps) => css`
  47. background: ${backgroundLight};
  48. border: 0;
  49. border-radius: 0;
  50. border-bottom: 1px solid ${border};
  51. ${IconWrapper} {
  52. color: ${iconColor};
  53. }
  54. `;
  55. const alertStyles = ({theme, type = DEFAULT_TYPE, system}: Props & {theme: Theme}) => css`
  56. display: flex;
  57. flex-direction: column;
  58. margin: 0 0 ${space(3)};
  59. padding: ${space(1.5)} ${space(2)};
  60. font-size: 15px;
  61. box-shadow: ${theme.dropShadowLight};
  62. border-radius: ${theme.borderRadius};
  63. background: ${theme.backgroundSecondary};
  64. border: 1px solid ${theme.border};
  65. a:not([role='button']) {
  66. color: ${theme.textColor};
  67. border-bottom: 1px dotted ${theme.textColor};
  68. }
  69. ${getAlertColorStyles(theme.alert[type])};
  70. ${system && getSystemAlertColorStyles(theme.alert[type])};
  71. `;
  72. const StyledTextBlock = styled('span')`
  73. line-height: 1.5;
  74. position: relative;
  75. margin-right: auto;
  76. `;
  77. const MessageContainer = styled('div')`
  78. display: flex;
  79. width: 100%;
  80. `;
  81. const ExpandContainer = styled('div')`
  82. display: grid;
  83. grid-template-columns: minmax(${space(4)}, 1fr) 30fr 1fr;
  84. grid-template-areas: '. details details';
  85. padding: ${space(1.5)} 0;
  86. `;
  87. const DetailsContainer = styled('div')`
  88. grid-area: details;
  89. `;
  90. const ExpandIcon = styled(props => (
  91. <IconWrapper {...props}>{<IconChevron size="md" />}</IconWrapper>
  92. ))`
  93. transform: ${props => (props.isExpanded ? 'rotate(0deg)' : 'rotate(180deg)')};
  94. cursor: pointer;
  95. justify-self: flex-end;
  96. `;
  97. const Alert = styled(
  98. ({
  99. type,
  100. icon,
  101. children,
  102. className,
  103. expand,
  104. expandIcon,
  105. onExpandIconClick,
  106. system: _system, // don't forward to `div`
  107. ...props
  108. }: AlertProps) => {
  109. const [isExpanded, setIsExpanded] = useState(false);
  110. const showExpand = expand && expand.length;
  111. const showExpandItems = showExpand && isExpanded;
  112. const handleOnExpandIconClick = onExpandIconClick ? onExpandIconClick : setIsExpanded;
  113. return (
  114. <div className={classNames(type ? `ref-${type}` : '', className)} {...props}>
  115. <MessageContainer>
  116. {icon && <IconWrapper>{icon}</IconWrapper>}
  117. <StyledTextBlock>{children}</StyledTextBlock>
  118. {showExpand && (
  119. <div onClick={() => handleOnExpandIconClick(!isExpanded)}>
  120. {expandIcon || <ExpandIcon isExpanded={isExpanded} />}
  121. </div>
  122. )}
  123. </MessageContainer>
  124. {showExpandItems && (
  125. <ExpandContainer>
  126. <DetailsContainer>{(expand || []).map(item => item)}</DetailsContainer>
  127. </ExpandContainer>
  128. )}
  129. </div>
  130. );
  131. }
  132. )<AlertProps>`
  133. ${alertStyles}
  134. `;
  135. Alert.defaultProps = {
  136. type: DEFAULT_TYPE,
  137. };
  138. export {alertStyles};
  139. export default Alert;