import * as React from 'react'; import { useEffect } from 'react'; import styled, { css } from 'styled-components'; import type { JSX } from 'react'; import type { OptionalEmailSettings, ReasonsSettingsSchema } from '@redocly/config'; import type { ReasonsProps } from '@redocly/theme/components/Feedback/Reasons'; import { Reasons } from '@redocly/theme/components/Feedback/Reasons'; import { useThemeHooks } from '@redocly/theme/core/hooks'; import { RadioCheckButtonIcon } from '@redocly/theme/icons/RadioCheckButtonIcon/RadioCheckButtonIcon'; import { Comment } from '@redocly/theme/components/Feedback/Comment'; import { Button } from '@redocly/theme/components/Button/Button'; import { FaceDissatisfiedIcon } from '@redocly/theme/icons/FaceDissatisfiedIcon/FaceDissatisfiedIcon'; import { FaceSatisfiedIcon } from '@redocly/theme/icons/FaceSatisfiedIcon/FaceSatisfiedIcon'; import { FaceNeutralIcon } from '@redocly/theme/icons/FaceNeutralIcon/FaceNeutralIcon'; import { MAX_EMAIL_LENGTH } from '@redocly/theme/core/constants'; export enum MOOD_STATES { SATISFIED = 'satisfied', NEUTRAL = 'neutral', DISSATISFIED = 'dissatisfied', } export type MoodProps = { onSubmit: (value: { score: number; comment?: string; reasons?: string[]; email?: string; }) => void; settings?: { label?: string; submitText?: string; comment?: { hide?: boolean; satisfiedLabel?: string; neutralLabel?: string; dissatisfiedLabel?: string; }; reasons?: ReasonsSettingsSchema & { satisfied?: ReasonsSettingsSchema; neutral?: ReasonsSettingsSchema; dissatisfied?: ReasonsSettingsSchema; }; optionalEmail?: OptionalEmailSettings; }; className?: string; }; export function Mood({ settings, onSubmit, className }: MoodProps): JSX.Element { const { label, submitText, comment: commentSettings, reasons: reasonsSettings, optionalEmail: optionalEmailSettings, } = settings || {}; const [score, setScore] = React.useState(''); const [isSubmitted, setIsSubmitted] = React.useState(false); const [comment, setComment] = React.useState(''); const [reasons, setReasons] = React.useState([] as ReasonsProps['settings']['items']); const [email, setEmail] = React.useState(); const { useTranslate, useUserMenu } = useThemeHooks(); const { userData } = useUserMenu(); const { translate } = useTranslate(); const onEmailChange = (e: React.ChangeEvent) => { const value = e.target.value; setEmail(value || undefined); }; const checkIfShouldDisplayReasons = (score: string) => { if (!score || !reasonsSettings) { return false; } const scoreSpecificReasonsSettings = reasonsSettings[score as MOOD_STATES]; if (typeof scoreSpecificReasonsSettings?.hide === 'undefined') { return !reasonsSettings.hide; } return !scoreSpecificReasonsSettings?.hide; }; const renderCommentLabel = (score: string) => { if (!commentSettings || commentSettings?.hide === true) return ''; switch (score) { case MOOD_STATES.SATISFIED: return ( commentSettings.satisfiedLabel || translate('feedback.settings.comment.satisfiedLabel', 'What was most helpful?') ); case MOOD_STATES.NEUTRAL: return ( commentSettings.neutralLabel || translate('feedback.settings.comment.neutralLabel', 'What can we improve?') ); case MOOD_STATES.DISSATISFIED: return ( commentSettings.dissatisfiedLabel || translate('feedback.settings.comment.dissatisfiedLabel', 'What can we improve?') ); default: return translate('feedback.settings.comment.satisfiedLabel', 'What can we improve?'); } }; const renderReasonsLabel = (score: string) => { if (!reasonsSettings || reasonsSettings?.hide === true) return ''; return reasonsSettings[score as MOOD_STATES]?.label; }; const resolveReasonsItems = (score: string) => { if (!reasonsSettings) { return []; } const defaultReasonsItems = reasonsSettings?.items || []; return defaultReasonsItems.concat(reasonsSettings[score as MOOD_STATES]?.items ?? []); }; const resolveReasonsComponent = (score: string) => { if (!reasonsSettings || reasonsSettings?.hide === true) return 'checkbox'; return reasonsSettings[score as MOOD_STATES]?.component; }; const displayReasons = checkIfShouldDisplayReasons(score); const displayComment = !!(score && !commentSettings?.hide); const displaySubmitBnt = !!(score && (displayReasons || displayComment)); const displayFeedbackEmail = !!score && !optionalEmailSettings?.hide && !userData.isAuthenticated; const onSubmitMoodForm = () => { const trimmedComment = comment?.trim() || undefined; const trimmedEmail = email?.trim() || undefined; onSubmit({ score: remapScore(score), comment: trimmedComment, reasons, email: trimmedEmail, }); setIsSubmitted(true); }; const onCancelMoodForm = () => { setScore(''); setComment(''); setReasons([]); setEmail(undefined); }; useEffect(() => { if (score && !displayComment && !displayReasons && !displayFeedbackEmail) { onSubmitMoodForm(); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [score, displayComment, displayReasons, displayFeedbackEmail]); if (isSubmitted) { return ( ); } return ( )} ); } const remapScore = (score: string): number => { switch (score) { case MOOD_STATES.SATISFIED: return 1; case MOOD_STATES.NEUTRAL: return 0; case MOOD_STATES.DISSATISFIED: return -1; default: return 0; } }; const MoodWrapper = styled.div` font-family: var(--feedback-font-family); display: flex; justify-content: space-between; align-items: center; `; const Label = styled.h4<{ standAlone?: boolean }>` font-family: var(--feedback-font-family); font-weight: var(--font-weight-regular); font-size: var(--feedback-font-size); line-height: var(--feedback-line-height); color: var(--feedback-text-color); ${({ standAlone = false }) => standAlone && css` font-size: var(--feedback-header-font-size); line-height: var(--feedback-header-line-height); color: var(--feedback-header-text-color); `} margin: 0; `; const InputLabel = styled.h4` font-weight: var(--font-weight-regular); font-size: var(--feedback-font-size); line-height: var(--feedback-line-height); margin: 0; `; const ButtonsContainer = styled.div` display: flex; justify-content: end; gap: var(--spacing-xxs); `; const StyledForm = styled.form` width: 100%; gap: var(--spacing-sm); display: flex; flex-direction: column; `; const StyledFormOptionalFields = styled.div` display: flex; flex-flow: column; `; const StyledFormMandatoryFields = styled.div` display: flex; justify-content: space-between; align-items: center; gap: var(--spacing-xs); > svg { margin: var(--button-icon-padding) 0; /* prevent hight of the parent jumping */ } `; const StyledMandatoryFieldContainer = styled.div` display: flex; align-items: center; gap: var(--spacing-xxs); `; const EmailInput = styled.input` background-color: var(--bg-color); border-radius: var(--border-radius-lg); border: var(--input-border); outline: none; color: var(--feedback-text-color); font-family: var(--feedback-font-family); padding: 10px; `;