@navinc/base-react-components
Version:
Nav's Pattern Library
266 lines (237 loc) • 6.2 kB
JavaScript
import React from 'react'
import styled from 'styled-components'
import propTypes from 'prop-types'
import Button from './button'
import Icon from './icon.js'
import Banner from './banner.js'
import Copy from './copy'
import LoadingDots from './loading-dots.js'
import { Header } from './header.js'
import CDNIllustration from './cdn-illustration.js'
export const StyledCDNIllustration = styled(CDNIllustration)`
margin-top: 16px;
height: 180px;
width: 180px;
`
const Content = styled.div`
align-items: center;
display: flex;
flex-direction: column;
${({ hasImage }) =>
!hasImage &&
`
height: 400px;
justify-content: center;
`}
& > ${LoadingDots} {
color: ${({ theme }) => theme.azure};
}
`
const LoadingContent = ({ imageFilename, title, ...props }) => (
<>
{!!title && <Header>{title}</Header>}
<Content hasImage={!!imageFilename} {...props}>
<LoadingDots />
{imageFilename && <StyledCDNIllustration filename={imageFilename} data-testid="card:loading-image" />}
</Content>
</>
)
LoadingContent.propTypes = {
className: propTypes.string,
imageFilename: propTypes.string,
title: propTypes.string,
}
LoadingContent.displayName = 'LoadingContent'
const BackButton = styled(({ onClick, href, ...props }) =>
!(onClick || href) ? null : (
<StyledBackButton onClick={onClick} href={href} {...props}>
<Icon name="actions/arrow-back" />
</StyledBackButton>
)
)``
BackButton.displayName = 'BackButton'
BackButton.propTypes = {
onClick: propTypes.func,
href: propTypes.string,
}
const StyledHeader = styled.div`
position: relative;
padding-bottom: 16px;
& > *:not(:first-child) {
margin-top: 4px;
}
`
const Label = styled(Copy).attrs(() => ({ size: 'sm' }))`
color: ${({ theme }) => theme.neutral400};
`
const Title = styled(Header).attrs(() => ({ size: 'md' }))``
const StyledFooter = styled.div`
position: relative;
display: flex;
justify-content: flex-end;
align-items: center;
margin-top: 24px;
padding-top: 16px;
background: ${({ theme }) => theme.white};
&::before {
content: '';
width: 100%;
height: 4px;
position: absolute;
top: 0;
background: ${({ theme }) => theme.neutral200};
border-radius: 14px;
}
/* For button spacing when there are multiple buttons */
& > *:not(${BackButton}) {
margin: 0 8px;
}
& > *:first-child:not(${BackButton}) {
margin-left: 0;
}
& > *:last-child:not(${BackButton}) {
margin-right: 0;
}
`
export const Svg = styled.svg`
display: block;
`
export const StyledBackButton = styled(Button).attrs(() => ({
variation: 'noOutline',
}))`
margin-right: auto;
padding: 8px;
color: ${({ theme }) => theme.neutral400};
`
export const CardHeader = styled(({ children, label, title, ...remainingProps }) => (
<StyledHeader {...remainingProps}>
{label && <Label>{label}</Label>}
{title && <Title>{title}</Title>}
{children}
</StyledHeader>
))``
CardHeader.propTypes = {
label: propTypes.string,
title: propTypes.string,
}
CardHeader.displayName = 'Header'
export const CardFooter = styled(
({
actionText = 'Next',
actionForm,
actionButtonType,
actionDataTestId,
onAction,
actionHref,
isActionDisabled,
isLoading,
onBack,
backHref,
actionTrackingContext,
actionTarget,
children,
...remainingProps
}) => (
<StyledFooter {...remainingProps}>
{(!!onBack || !!backHref) && (
<BackButton data-testid="card:back" type="button" onClick={onBack} href={backHref} />
)}
{children}
{(!!onAction || !!actionHref || !!actionForm) && (
<Button
data-testid={actionDataTestId || 'card:next'}
onClick={onAction}
form={actionForm}
href={actionHref}
isLoading={isLoading}
disabled={isLoading || isActionDisabled}
size="cardButton"
variation="noOutline"
trackingContext={actionTrackingContext}
target={actionTarget}
{...(actionButtonType && { type: actionButtonType })}
>
{actionText}
</Button>
)}
</StyledFooter>
)
)``
CardFooter.displayName = 'Footer'
CardFooter.propTypes = {
actionText: propTypes.node,
actionForm: propTypes.string,
actionDataTestId: propTypes.string,
actionHref: propTypes.string,
actionButtonType: propTypes.string,
onAction: propTypes.func,
isActionDisabled: propTypes.bool,
isLoading: propTypes.bool,
onBack: propTypes.func,
backHref: propTypes.string,
actionTrackingContext: propTypes.shape({
type: propTypes.string,
context: propTypes.string,
category: propTypes.string,
payload: propTypes.shape({
category: propTypes.string,
label: propTypes.string,
name: propTypes.string,
}),
options: propTypes.shape({
integrations: propTypes.shape({
Salesforce: propTypes.bool,
}),
}),
}),
actionTarget: propTypes.string,
}
const CARD_PADDING = 24
const focusedCSS = `
text-align: center;
width: 100%;
& ${CardHeader} {
display: flex;
justify-content: center;
padding-bottom: 8px;
text-align: center;
}
& ${Title} {
max-width: 400px;
}
`
export const Card = styled.div`
position: relative;
padding: ${CARD_PADDING}px;
border-radius: 20px;
background-color: ${({ theme }) => theme.white};
box-shadow: 0 24px 16px -16px rgba(0, 0, 0, 0.05);
overflow: hidden;
& ${Banner} {
margin: -${CARD_PADDING}px -${CARD_PADDING}px 16px;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
${({ isFocused }) => isFocused && focusedCSS}
`
Card.displayName = 'StandardCard'
const FocusedContent = styled.div`
align-items: center;
display: flex;
flex-direction: column;
& > * {
max-width: 400px;
}
`
const TermsFooter = styled.section`
padding: 16px 32px;
margin: 24px -24px -24px -24px;
background-color: ${({ theme }) => theme.neutral100};
`
Card.Header = CardHeader
Card.Footer = CardFooter
Card.BackButton = BackButton
Card.FocusedContent = FocusedContent
Card.LoadingContent = LoadingContent
Card.TermsFooter = TermsFooter
export default Card