All files / client/src/components DismissibleBanner.tsx

90% Statements 9/10
100% Branches 0/0
100% Functions 5/5
90% Lines 9/10

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105                                              520x                                                             1x                     1x       372x 372x         1x                 1x                         478x       478x      
import { IconName } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { ReactElement, ReactNode } from "react";
import styled from "styled-components";
import { RaisedCard } from "client/components/RaisedCard";
import {
  HighlightStyle,
  getHighlightColor,
} from "client/components/componentStyles";
 
interface Props {
  children: ReactNode;
  onDismiss: () => void;
  dismissAriaLabel: string;
  icon: IconName;
  highlightStyle: HighlightStyle;
  "data-testid"?: string;
}
 
// Shared shell for app-level notification banners: a leading icon, the
// message, and a dismiss button on the right. Built on the same highlighted
// card the app uses for its other notices so the banners read as part of the
// same system. Stickiness comes from the wrapper the app-level bars render in
export const DismissibleBanner = ({
  children,
  onDismiss,
  dismissAriaLabel,
  icon,
  highlightStyle,
  "data-testid": dataTestId,
}: Props): ReactElement => {
  return (
    <Banner
      isHighlighted={true}
      highlightStyle={highlightStyle}
      data-testid={dataTestId}
    >
      <BannerIcon
        icon={icon}
        $highlightStyle={highlightStyle}
        aria-hidden={true}
      />
      <Content>{children}</Content>
      <CloseButton
        type="button"
        onClick={onDismiss}
        aria-label={dismissAriaLabel}
      >
        <FontAwesomeIcon icon="xmark" />
      </CloseButton>
    </Banner>
  );
};
 
const Banner = styled(RaisedCard)`
  display: flex;
  align-items: center;
  gap: 12px;
 
  /* Same box metrics as the other app-level bars so their dismiss icons
     line up when several are stacked */
  margin: 4px 0;
  padding: 10px;
`;
 
const BannerIcon = styled(FontAwesomeIcon)<{
  $highlightStyle: HighlightStyle;
}>`
  flex-shrink: 0;
  font-size: ${(props) => props.theme.iconSizeNormal};
  color: ${(props) => getHighlightColor(props.theme, props.$highlightStyle)};
`;
 
// Lays out the message and any action the banner offers, wrapping onto a
// second row on narrow screens instead of squeezing the text
const Content = styled.div`
  display: flex;
  flex: 1;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
  justify-content: space-between;
`;
 
const CloseButton = styled.button`
  flex-shrink: 0;
 
  /* Centers the glyph in the button box: as inline content it would sit off
     centre by the font's descender, which shows next to a taller message */
  display: flex;
  align-items: center;
 
  border: none;
  background: none;
  padding: 0;
  font-size: 18px;
  cursor: pointer;
  color: ${(props) => props.theme.textLighter};
 
  &:hover,
  &:focus {
    color: ${(props) => props.theme.textMain};
  }
`;