@navinc/base-react-components
Version:
Nav's Pattern Library
278 lines (259 loc) • 7.21 kB
JavaScript
import React, { useState } from 'react'
import styled from 'styled-components'
import Button from './button'
import Copy from './copy'
import Header from './header.js'
import Icon from './icon.js'
import Text from './text'
import CDNIllustration from './cdn-illustration.js'
const DetailsDrawer = styled(Button).attrs(() => ({ variation: 'noOutline' }))`
display: flex;
justify-content: center;
align-items: center;
& > ${Copy} {
padding-right: 8px;
}
& > ${Icon} {
fill: ${({ theme }) => theme.neutral400};
}
`
const DisclaimerAsterisk = styled(Text)`
font-size: 12px;
position: absolute;
`
const Flag = styled.div`
display: inline-block;
background-color: ${({ theme }) => theme.azure};
padding: 4px 8px;
border-radius: 0 0 10px 10px;
width: 128px;
height: 32px;
& > ${Copy} {
color: ${({ theme }) => theme.white};
text-align: center;
}
`
const PlanPrice = styled(Copy)`
position: relative;
& > ${DisclaimerAsterisk} {
margin-left: 2px;
}
`
const StyledButton = styled(Button).attrs(() => ({ variation: 'outline', size: 'large' }))``
const SelectPlanButton = styled.div`
width: min-content;
margin: auto;
`
const NoPaddingCard = styled.div`
background-color: ${({ theme }) => theme.white};
opacity: ${({ isSelected }) => (isSelected ? 0.5 : 1)};
border-radius: 4px;
width: 100%;
box-shadow: 0 0 3px 0 ${({ theme }) => theme.neutral300}, 0 1px 2px 0 ${({ theme }) => theme.neutral300};
`
const StripedRow = styled.div`
display: flex;
&::before {
content: '•';
padding-right: 8px;
}
`
const StripedRowContent = styled(Copy).attrs(() => ({ size: 'sm' }))`
display: inline-block;
`
const StripedContentWrapper = styled.div`
& > ${StripedRow} {
padding: 16px 24px;
}
& > ${StripedRow}:nth-child(odd) {
background-color: ${({ theme }) => theme.neutral100};
}
`
const MobileStripedContentWrapper = styled(StripedContentWrapper)`
@media (${({ theme }) => theme.forLargerThanPhone}) {
display: none;
}
`
const DesktopStripedContentWrapper = styled(StripedContentWrapper)`
display: none;
@media (${({ theme }) => theme.forLargerThanPhone}) {
display: block;
}
`
const PlanHeadline = styled.div`
width: 100%;
padding-top: 16px;
`
const Illustration = styled.img`
width: auto;
height: auto;
max-height: 100px;
@media (${({ theme }) => theme.forLargerThanPhone}) {
max-height: 200px;
}
`
const StyledCDNIllustration = styled(CDNIllustration)`
width: auto;
height: auto;
max-height: 100px;
@media (${({ theme }) => theme.forLargerThanPhone}) {
max-height: 200px;
}
`
const PricingCardContent = styled.div`
display: grid;
position: relative;
padding: 24px;
grid-gap: 16px;
grid-template:
'image plan plan'
'image plan plan'
'image description description'
'button button button'
'details details details' / auto auto auto;
& > ${StyledCDNIllustration}, ${Illustration} {
grid-area: image;
margin: auto;
padding-top: 24px;
}
& > ${PlanHeadline} {
grid-area: plan;
padding-bottom: ${({ shouldBeSelectable }) => (shouldBeSelectable ? '0px' : '24px')};
}
& > ${SelectPlanButton} {
grid-area: button;
}
& > ${Copy} {
grid-area: description;
}
& > ${DetailsDrawer} {
grid-area: details;
&:hover {
background-color: ${({ theme }) => theme.white};
}
}
& > ${Flag} {
position: absolute;
transform: rotateZ(-90deg);
top: 44%;
left: -48px;
}
@media (${({ theme }) => theme.forLargerThanPhone}) {
grid-template-areas:
'image image image'
'plan plan plan'
'button button button'
'description description description';
& > ${PlanHeadline}, & > ${Copy} {
text-align: center;
}
& > ${Copy} {
grid-area: description;
padding: 0 36px;
}
& > ${DetailsDrawer} {
display: none;
}
& > ${Flag} {
transform: none;
top: 0;
left: calc(50% - 64px);
}
}
`
export default ({
actionHref,
actionTarget,
altIllustration,
altIllustrationText,
description,
disclaimerReference,
features = [],
illustration,
isMostPopular,
isSelected,
planCode,
planName,
planPrice,
shouldBeSelectable,
}) => {
const [isOpen, setIsOpen] = useState(false)
const renderFeatures = () =>
features.map((feature, i) => (
<StripedRow key={`${feature}${i}`}>
<StripedRowContent>
<Text>{feature}</Text>
</StripedRowContent>
</StripedRow>
))
return (
<NoPaddingCard isSelected={isSelected}>
<PricingCardContent>
{isMostPopular && (
<Flag data-testid="pricing-card:is_most_popular">
<Copy>Most popular</Copy>
</Flag>
)}
{altIllustration ? (
<Illustration src={altIllustration} alt={altIllustrationText} />
) : (
!!illustration && <StyledCDNIllustration filename={illustration} data-testid="pricing-card:illustration" />
)}
<PlanHeadline>
<Header size="lg" data-testid="pricing-card:plan_name">
{planName}
</Header>
<PlanPrice data-testid="pricing-card:plan_price">
<strong>{planPrice}</strong>
<DisclaimerAsterisk>{'*'.repeat(disclaimerReference)}</DisclaimerAsterisk>
</PlanPrice>
</PlanHeadline>
{actionHref && shouldBeSelectable && (
<SelectPlanButton>
{isSelected ? (
<StyledButton disabled data-testid="pricing-card:is_selected">
Current Plan
</StyledButton>
) : (
<StyledButton
href={actionHref}
target={actionTarget}
trackingContext={{
type: 'interaction_upgrade_page',
payload: { category: 'upgrade_page_click', label: `select_${planCode}`, name: `select_${planCode}` },
}}
data-testid={`pricing-card:select_this_plan_${planCode}`}
>
Select this plan
</StyledButton>
)}
</SelectPlanButton>
)}
<Copy bold>{description}</Copy>
{!!features.length && (
<DetailsDrawer
trackingContext={{
type: 'interaction_upgrade_page',
payload: { category: 'upgrade_page_click', label: `select_${planCode}`, name: `select_${planCode}` },
}}
onClick={() => setIsOpen((isOpen) => !isOpen)}
data-testid="pricing-card:mobile_see_more_details"
>
<Copy size="sm" light>
See more details
</Copy>
<Icon name={isOpen ? 'actions/carrot-up' : 'actions/carrot-down'} />
</DetailsDrawer>
)}
</PricingCardContent>
{isOpen && (
<MobileStripedContentWrapper data-testid="pricing-card-mobile-features">
{renderFeatures()}
</MobileStripedContentWrapper>
)}
<DesktopStripedContentWrapper data-testid="pricing-card-desktop-features">
{renderFeatures()}
</DesktopStripedContentWrapper>
</NoPaddingCard>
)
}