@navinc/base-react-components
Version:
Nav's Pattern Library
231 lines (214 loc) • 6.04 kB
JavaScript
import React from 'react'
import propTypes from 'prop-types'
import styled from 'styled-components'
import Copy from './copy'
import Icon from './icon.js'
import Link from './link.js'
import CDNIllustration from './cdn-illustration.js'
const styles = {
missingInfoAction: {
primaryColor: 'paleGold500',
secondaryColor: 'paleGold100',
defaultIcon: 'system/search',
defultActionIcon: 'actions/carrot-right',
},
improveAction: {
primaryColor: 'rose500',
secondaryColor: 'rose100',
defaultIcon: 'actions/circle-info',
defultActionIcon: 'actions/carrot-right',
},
positiveAction: {
primaryColor: 'greenSheen500',
secondaryColor: 'greenSheen100',
defaultIcon: 'feedback/thumbs-up',
defultActionIcon: 'actions/carrot-right',
},
neutralAction: {
primaryColor: 'lightBlue400',
secondaryColor: 'lightBlue100',
defaultIcon: 'actions/circle-info',
defultActionIcon: 'actions/carrot-right',
},
warning: {
primaryColor: 'tuscan200',
secondaryColor: 'tuscan100',
defaultIcon: 'actions/circle-warning',
defultActionIcon: 'actions/close',
},
error: {
primaryColor: 'copperRed200',
secondaryColor: 'copperRed100',
defaultIcon: 'actions/circle-warning',
defultActionIcon: 'actions/close',
},
}
export const StyledBanner = styled.aside`
box-shadow: 0 10px 11px -8px rgba(0, 0, 0, 0.12);
position: relative;
display: grid;
grid-template-rows: auto 1fr;
text-align: center;
padding: 16px;
border-radius: 12px;
overflow: hidden;
background-color: ${({ currentStyles, theme }) => theme[currentStyles.secondaryColor]};
cursor: ${({ hasLabel, onDismiss, hasAction }) => hasAction && !hasLabel && !onDismiss && 'pointer'};
&::before {
${({ currentStyles, shouldHideBorder, theme }) =>
!shouldHideBorder &&
`content: '';
width: 100%;
height: 8px;
position: absolute;
left: 0;
top: 0;
background: ${theme[currentStyles.primaryColor]};
border-radius: 14px 14px 0 0;`}
}
@media (${({ theme }) => theme.forLargerThanPhone}) {
grid-template-rows: auto;
grid-template-columns: auto 1fr;
padding-right: ${({ hasLabel }) => !hasLabel && '64px'};
text-align: left;
}
`
export const StyledBannerAsLink = styled(StyledBanner).attrs(() => ({
as: 'a',
}))`
text-decoration: none;
`
const BannerContainer = ({
currentStyles,
action = () => {},
actionHref,
hasLabel,
children,
className,
shouldHideBorder,
'data-testid': dataTestId,
}) => {
const sharedProps = {
className,
currentStyles,
hasLabel,
shouldHideBorder,
'data-testid': dataTestId,
}
if (!hasLabel) {
return actionHref ? (
<StyledBannerAsLink {...sharedProps} href={actionHref}>
{children}
</StyledBannerAsLink>
) : (
<StyledBanner {...sharedProps} onClick={action}>
{children}
</StyledBanner>
)
} else return <StyledBanner {...sharedProps}>{children}</StyledBanner>
}
export const TitleCopy = styled(Copy)`
@media (${({ theme }) => theme.forLargerThanPhone}) {
margin-right: 28px; /* need to give space for the (X) */
}
`
const IconContainer = styled.div`
flex: 0 0 24px;
height: auto;
@media (${({ theme }) => theme.forLargerThanPhone}) {
margin-right: 16px;
}
`
const Content = styled.div`
flex: 1 1 auto;
& > ${Copy}, & > ${Link} {
flex: 1 1 100%;
}
`
export const ActionIcon = styled(Icon)`
cursor: pointer;
position: absolute;
right: 16px;
top: 16px;
width: auto;
height: 24px;
color: ${({ theme, defaultcolor = theme.neutral500, name }) => (name === 'close' ? theme.neutral500 : defaultcolor)};
`
const ChildrenWrapper = styled.div`
width: '100%';
`
const StyledIcon = styled(Icon)`
color: ${({ theme, color }) => theme[color]};
`
export const Banner = ({
action,
actionHref,
actionLabel,
actionIcon,
actionTarget = '',
actionTrackingContext = {},
children,
className,
copy,
expandedStyles = {},
icon,
onDismiss,
shouldHideBorder,
CDNIllustrationIcon,
title,
type = 'neutralAction',
}) => {
const hasLabel = !!actionLabel
const hasAction = !!action || !!actionHref
const currentStyles = { ...styles, ...expandedStyles }[type] ?? styles.neutralAction
const { primaryColor, defaultIcon, defultActionIcon } = currentStyles
return (
<BannerContainer
currentStyles={currentStyles}
action={action}
actionHref={actionHref}
hasLabel={hasLabel}
shouldHideBorder={shouldHideBorder}
onDismiss={onDismiss}
className={className}
data-testid={`banner:${type}`}
>
<IconContainer>
{CDNIllustrationIcon ? (
<CDNIllustration filename={CDNIllustrationIcon} />
) : (
<StyledIcon name={icon || defaultIcon} color={primaryColor} data-testid="banner-icon" />
)}
</IconContainer>
<Content>
{title && <TitleCopy bold>{title}</TitleCopy>}
{copy && <Copy>{copy}</Copy>}
{!!children && <ChildrenWrapper>{children}</ChildrenWrapper>}
{hasLabel && hasAction && (
<Link bold href={actionHref} onClick={action} target={actionTarget} trackingContext={actionTrackingContext}>
{actionLabel}
</Link>
)}
</Content>
{!onDismiss && !hasLabel && hasAction && (
<ActionIcon name={actionIcon || defultActionIcon} defaultcolor={primaryColor} data-testid="action-icon" />
)}
{onDismiss && <ActionIcon onClick={onDismiss} data-testid="banner-dismiss" name="actions/close" />}
</BannerContainer>
)
}
Banner.propTypes = {
action: propTypes.func,
actionHref: propTypes.string,
actionLabel: propTypes.node,
type: propTypes.string,
icon: propTypes.string,
actionIcon: propTypes.string,
title: propTypes.node,
copy: propTypes.node,
onDismiss: propTypes.func,
expandedStyles: propTypes.object,
shouldHideBorder: propTypes.bool,
}
const StyledBannerExp = styled(Banner)``
export default StyledBannerExp