UNPKG

@navinc/base-react-components

Version:
297 lines (267 loc) 8.1 kB
/* 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)``