@navinc/base-react-components
Version:
Nav's Pattern Library
297 lines (267 loc) • 8.1 kB
JavaScript
/* eslint-disable @typescript-eslint/no-use-before-define */
import React, { useState } from 'react'
import propTypes from 'prop-types'
import styled, { keyframes, css } from 'styled-components'
import Cookies from 'js-cookie'
import { global } from '@navinc/utils'
import Button from './button.js'
import Copy from './copy.js'
import { InteractiveIcon } from './icon.js'
import isRebrand from './is-rebrand.js'
const { CLIENT_COOKIE_DOMAIN } = global?.process?.env ?? ''
const slideInFromSide = keyframes`
0% {
transform: translateX(50%);
opacity: 0;
}
100% {
transform: translateX(0);
opacity: 1;
}
`
const slideInFromBottom = keyframes`
0% {
transform: translateY(50%);
opacity: 0;
}
100% {
transform: translateX(0);
opacity: 1;
}
`
const slideOutAnimation = keyframes`
0% {
opacity: 1;
transform: translateY(0);
}
10% {
transform: translateY(calc(100% - 4px));
opacity: 1;
border-top: 4px solid ${({ theme }) => (isRebrand(theme) ? theme.navNeutral300 : theme.border)};
}
50% {
opacity: 1;
border-top: 4px solid ${({ theme }) => (isRebrand(theme) ? theme.navStatusPositive : theme.seaturtleGreen200)};
}
60% {
opacity: 1;
border-top: 4px solid ${({ theme }) => (isRebrand(theme) ? theme.navStatusPositive : theme.seaturtleGreen200)};
}
80% {
transform: translateY(calc(100% - 4px)) translateX(0);
opacity: 1;
}
100% {
transform: translateY(calc(100% - 4px)) translateX(100%);
opacity: 0;
}
`
const slideOut = css`
opacity: 0;
transform: translateY(100%);
animation: ${slideOutAnimation} 2s ease-in-out;
`
export const UserFeedbackContainer = styled.div`
position: relative;
display: flex;
height: 52px;
max-height: 52px;
min-width: 240px;
margin-left: auto;
opacity: 1;
overflow: hidden;
transition: opacity 0.2s linear 15s, height 0.2s linear 15s, max-height 0.2s linear;
${({ isTouched }) =>
isTouched &&
`
height: 0;
opacity: 0;
& > ${UserFeedbackPrompt} {
opacity: 0;
}
`}
${({ shouldHideImmediately }) =>
shouldHideImmediately &&
`
max-height: 0;
`}
`
const UserFeedbackPrompt = styled.div`
display: flex;
width: 100%;
align-items: center;
justify-content: flex-end;
margin-top: 16px;
opacity: 1;
transition: opacity 0.2s linear;
`
export const ThankYouMessage = styled.div`
position: absolute;
top: 16px;
right: 0;
padding-left: 8px;
overflow: hidden;
animation: ${slideInFromSide} 0.4s ease-in-out;
&::after {
position: absolute;
content: '';
top: 0;
left: 0;
width: 2px;
height: 100%;
border-radius: 2px;
background: ${({ theme }) => (isRebrand(theme) ? theme.navPrimary400 : theme.bubbleBlue400)};
z-index: 5;
}
`
const IconButton = styled(InteractiveIcon)`
height: 16px;
color: ${({ theme }) => (isRebrand(theme) ? theme.navNeutral400 : theme.neutral400)};
transition: color 0.2s linear;
cursor: pointer;
`
const IconButtonPositive = styled(IconButton)`
&:hover {
color: ${({ theme }) => (isRebrand(theme) ? theme.navStatusPositive500 : theme.mermaidGreen500)};
}
`
const IconButtonNegative = styled(IconButton)`
&:hover {
color: ${({ theme }) => (isRebrand(theme) ? theme.navStatusPositive500 : theme.mermaidGreen500)};
}
`
const MoreFeedbackLink = styled(Copy).attrs(() => ({
size: 'xs',
as: 'a',
}))`
color: ${({ theme }) => theme.navPrimary};
align-content: center;
cursor: pointer;
`
export const AdditionalFeedbackContainer = styled.div`
position: fixed;
bottom: 0;
left: 0;
width: 100%;
border-top: 4px solid ${({ theme }) => (isRebrand(theme) ? theme.navNeutral300 : theme.border)};
background-color: ${({ theme }) => (isRebrand(theme) ? theme.navNeutralLight : theme.white)};
opacity: 1;
transform: translateY(0);
z-index: 60;
animation: ${slideInFromBottom} 0.4s ease-in-out;
${({ shouldHide }) => shouldHide && slideOut}
`
const Form = styled.div`
display: flex;
flex-wrap: wrap;
justify-content: flex-end;
max-width: ${({ theme }) => theme.contentWidth}px;
margin: 0 auto;
padding: 16px;
${Copy} {
width: 100%;
max-width: 100%;
text-align: center;
}
${Button} {
margin-left: 16px;
}
`
const Textarea = styled.textarea`
width: 100%;
margin: 8px 0 16px;
border: 1px solid ${({ theme }) => (isRebrand(theme) ? theme.navNeutral300 : theme.border)};
border-radius: 4px;
`
export const UserFeedback = ({
trackingLabel = '',
className,
initialPrompt = 'Was this useful?',
thumbsUpThankYouText = `We're glad this helps.`,
thumbsDownThankYouText = `We're sorry this isn't helpful.`,
thumbsUpAdditionalFeedbackPrompt = 'Tell us more',
thumbsDownAdditionalFeedbackPrompt = 'Tell us how we can improve',
additionalFeedbackQuestion = 'What can we do to improve your experience?',
}) => {
const [isTouched, setIsTouched] = useState(false)
const [showAdditionalDialogue, setShowAdditionalDialogue] = useState(false)
const [userReaction, setUserReaction] = useState('')
const [userFeedback, setUserFeedback] = useState('')
const trackClick = (newUserReaction) => () => {
setIsTouched(true)
setUserReaction(newUserReaction)
Cookies.set(trackingLabel, true, {
domain: CLIENT_COOKIE_DOMAIN,
path: '/',
expires: 30,
})
}
const handleShowMoreFeedback = () => {
setShowAdditionalDialogue(true)
}
const handleHideMoreFeedback = () => setShowAdditionalDialogue(false)
const handleSubmitMoreFeedback = (event) => {
event.preventDefault()
setShowAdditionalDialogue(false)
}
const handleTextChange = ({ target }) => setUserFeedback(target.value)
if (!isTouched && !showAdditionalDialogue && Cookies.get(trackingLabel)) return null
return (
<>
<UserFeedbackContainer
isTouched={isTouched}
shouldHideImmediately={!!userFeedback}
className={className}
data-id={`user-feedback-${trackingLabel}`}
>
<UserFeedbackPrompt>
<Copy size="sm" light>
{initialPrompt}
</Copy>
<IconButtonPositive
onClick={trackClick('Thumbs Up')}
name="feedback/thumbs-up"
data-testid="user-feedback:button-positive"
/>
<IconButtonNegative
onClick={trackClick('Thumbs Down')}
name="feedback/thumbs-down"
data-testid="user-feedback:button-negative"
/>
</UserFeedbackPrompt>
{isTouched && (
<ThankYouMessage>
<Copy size="xs">{userReaction === 'Thumbs Up' ? thumbsUpThankYouText : thumbsDownThankYouText}</Copy>
<MoreFeedbackLink onClick={handleShowMoreFeedback} data-testid="user-feedback:tell-us-more-link">
{userReaction === 'Thumbs Up' ? thumbsUpAdditionalFeedbackPrompt : thumbsDownAdditionalFeedbackPrompt}
</MoreFeedbackLink>
</ThankYouMessage>
)}
</UserFeedbackContainer>
{(showAdditionalDialogue || userFeedback) && (
<AdditionalFeedbackContainer shouldHide={!showAdditionalDialogue}>
<Form>
<Copy bold>{additionalFeedbackQuestion}</Copy>
<Textarea onChange={handleTextChange} data-testid="user-feedback:text-area" />
<Button onClick={handleHideMoreFeedback} variation="outline" data-testid="user-feedback:cancel">
Cancel
</Button>
<Button onClick={handleSubmitMoreFeedback} disabled={!userFeedback} data-testid="user-feedback:submit">
Submit
</Button>
</Form>
</AdditionalFeedbackContainer>
)}
</>
)
}
UserFeedback.propTypes = {
trackingLabel: propTypes.string,
initialPrompt: propTypes.string,
thumbsUpThankYouText: propTypes.string,
thumbsDownThankYouText: propTypes.string,
thumbsUpAdditionalFeedbackPrompt: propTypes.string,
thumbsDownAdditionalFeedbackPrompt: propTypes.string,
additionalFeedbackQuestion: propTypes.string,
}
export default styled(UserFeedback)``