feat: Add colors to rating (#2795)

Co-authored-by: Matti Nannt <mail@matthiasnannt.com>
Co-authored-by: Johannes <72809645+jobenjada@users.noreply.github.com>
This commit is contained in:
Piyush Gupta
2024-06-26 13:38:23 +05:30
committed by GitHub
parent cea5716c48
commit e00bdf2f79
14 changed files with 249 additions and 139 deletions

View File

@@ -4,6 +4,7 @@ import { PlusIcon } from "lucide-react";
import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/utils";
import { TAttributeClass } from "@formbricks/types/attributeClasses";
import { TSurvey, TSurveyNPSQuestion } from "@formbricks/types/surveys";
import { AdvancedOptionToggle } from "@formbricks/ui/AdvancedOptionToggle";
import { Button } from "@formbricks/ui/Button";
import { QuestionFormInput } from "@formbricks/ui/QuestionFormInput";
@@ -132,6 +133,16 @@ export const NPSQuestionForm = ({
/>
</div>
)}
<AdvancedOptionToggle
isChecked={question.isColorCodingEnabled}
onToggle={() => updateQuestion(questionIdx, { isColorCodingEnabled: !question.isColorCodingEnabled })}
htmlId="isColorCodingEnabled"
title="Add color coding"
description="Add red, orange and green color codes to the options."
childBorder
customContainerClass="p-0 mt-4"
/>
</form>
);
};

View File

@@ -2,6 +2,7 @@ import { HashIcon, PlusIcon, SmileIcon, StarIcon } from "lucide-react";
import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/utils";
import { TAttributeClass } from "@formbricks/types/attributeClasses";
import { TSurvey, TSurveyRatingQuestion } from "@formbricks/types/surveys";
import { AdvancedOptionToggle } from "@formbricks/ui/AdvancedOptionToggle";
import { Button } from "@formbricks/ui/Button";
import { Label } from "@formbricks/ui/Label";
import { QuestionFormInput } from "@formbricks/ui/QuestionFormInput";
@@ -93,7 +94,13 @@ export const RatingQuestionForm = ({
{ label: "Smiley", value: "smiley", icon: SmileIcon },
]}
defaultValue={question.scale || "number"}
onSelect={(option) => updateQuestion(questionIdx, { scale: option.value })}
onSelect={(option) => {
if (option.value === "star") {
updateQuestion(questionIdx, { scale: option.value, isColorCodingEnabled: false });
return;
}
updateQuestion(questionIdx, { scale: option.value });
}}
/>
</div>
</div>
@@ -168,6 +175,20 @@ export const RatingQuestionForm = ({
</div>
)}
</div>
{question.scale !== "star" && (
<AdvancedOptionToggle
isChecked={question.isColorCodingEnabled}
onToggle={() =>
updateQuestion(questionIdx, { isColorCodingEnabled: !question.isColorCodingEnabled })
}
htmlId="isColorCodingEnabled"
title="Add color coding"
description="Add red, orange and green color codes to the options."
childBorder
customContainerClass="p-0 mt-4"
/>
)}
</form>
);
};

View File

@@ -44,6 +44,7 @@ export const RatingSummary = ({ questionSummary, survey, attributeClasses }: Rat
scale={questionSummary.question.scale}
answer={result.rating}
range={questionSummary.question.range}
addColors={questionSummary.question.isColorCodingEnabled}
/>
</div>
<div>

View File

@@ -19,6 +19,7 @@ import { isLight, mixColor } from "@formbricks/lib/utils/colors";
import type { TSurvey, TSurveyStyling } from "@formbricks/types/surveys";
import { TSurveyQuestionTypeEnum } from "@formbricks/types/surveys";
import { RatingSmiley } from "@formbricks/ui/RatingSmiley";
import { getNPSOptionColor, getRatingNumberOptionColor } from "../../utils";
interface PreviewEmailTemplateProps {
survey: TSurvey;
@@ -108,9 +109,15 @@ export function PreviewEmailTemplate({ survey, surveyUrl, styling }: PreviewEmai
<Section className="border-input-border-color rounded-custom block overflow-hidden border">
{Array.from({ length: 11 }, (_, i) => (
<EmailButton
className="border-input-border-color m-0 inline-flex h-10 w-10 items-center justify-center border p-0 text-slate-800"
href={`${urlWithPrefilling}${firstQuestion.id}=${i.toString()}`}
key={i}>
key={i}
className={cn(
firstQuestion.isColorCodingEnabled ? "h-[46px]" : "h-10",
"border-input-border-color relative m-0 inline-flex w-10 items-center justify-center border p-0 text-slate-800"
)}>
{firstQuestion.isColorCodingEnabled ? (
<Section className={`absolute left-0 top-0 h-[6px] w-full ${getNPSOptionColor(i)}`} />
) : null}
{i}
</EmailButton>
))}
@@ -188,18 +195,33 @@ export function PreviewEmailTemplate({ survey, surveyUrl, styling }: PreviewEmai
{Array.from({ length: firstQuestion.range }, (_, i) => (
<EmailButton
className={cn(
"m-0 h-10 w-full p-0 text-center align-middle leading-10 text-slate-800",
"relative m-0 w-full overflow-hidden p-0 text-center align-middle leading-10 text-slate-800",
{
"border border-solid border-gray-200": firstQuestion.scale === "number",
}
},
firstQuestion.isColorCodingEnabled && firstQuestion.scale === "number"
? "h-[46px]"
: "h-10"
)}
href={`${urlWithPrefilling}${firstQuestion.id}=${(i + 1).toString()}`}
key={i}>
{firstQuestion.scale === "smiley" && (
<RatingSmiley active={false} idx={i} range={firstQuestion.range} />
<RatingSmiley
active={false}
idx={i}
range={firstQuestion.range}
addColors={firstQuestion.isColorCodingEnabled}
/>
)}
{firstQuestion.scale === "number" && (
<Text className="m-0 flex h-10 items-center">{i + 1}</Text>
<>
{firstQuestion.isColorCodingEnabled ? (
<Section
className={`absolute left-0 top-0 h-[6px] w-full ${getRatingNumberOptionColor(firstQuestion.range, i + 1)}`}
/>
) : null}
<Text className="m-0 flex h-10 items-center">{i + 1}</Text>
</>
)}
{firstQuestion.scale === "star" && <Text className="text-3xl"></Text>}
</EmailButton>

View File

@@ -0,0 +1,20 @@
export const getNPSOptionColor = (idx: number): string => {
if (idx > 8) return "bg-emerald-100";
if (idx > 6) return "bg-orange-100";
return "bg-rose-100";
};
export const getRatingNumberOptionColor = (range: number, idx: number) => {
if (range > 5) {
if (range - idx < 2) return "bg-emerald-100";
if (range - idx < 4) return "bg-orange-100";
return "bg-rose-100";
} else if (range < 5) {
if (range - idx < 1) return "bg-emerald-100";
if (range - idx < 2) return "bg-orange-100";
return "bg-rose-100";
}
if (range - idx < 2) return "bg-emerald-100";
if (range - idx < 3) return "bg-orange-100";
return "bg-rose-100";
};

View File

@@ -402,6 +402,7 @@ export const mockTranslatedRatingQuestion = {
subheader: { default: "Don't worry, be honest.", de: "" },
lowerLabel: { default: "Not good", de: "" },
upperLabel: { default: "Very good", de: "" },
isColorCodingEnabled: false,
};
export const mockLegacyRatingQuestion = {
@@ -420,6 +421,7 @@ export const mockTranslatedNpsQuestion = {
},
lowerLabel: { default: "Not at all likely", de: "" },
upperLabel: { default: "Extremely likely", de: "" },
isColorCodingEnabled: false,
};
export const mockLegacyNpsQuestion = {

View File

@@ -424,6 +424,7 @@ export const templates: TTemplate[] = [
range: 5,
lowerLabel: { default: "Very dissatisfied" },
upperLabel: { default: "Very satisfied" },
isColorCodingEnabled: false,
},
{
id: createId(),
@@ -557,6 +558,7 @@ export const templates: TTemplate[] = [
range: 5,
lowerLabel: { default: "Very dissatisfied" },
upperLabel: { default: "Very satisfied" },
isColorCodingEnabled: false,
},
{
id: createId(),
@@ -1092,6 +1094,7 @@ export const templates: TTemplate[] = [
required: true,
lowerLabel: { default: "Not good" },
upperLabel: { default: "Very satisfied" },
isColorCodingEnabled: false,
},
{
id: createId(),
@@ -1495,6 +1498,7 @@ export const templates: TTemplate[] = [
required: true,
lowerLabel: { default: "Not important" },
upperLabel: { default: "Very important" },
isColorCodingEnabled: false,
},
{
id: createId(),
@@ -1532,6 +1536,7 @@ export const templates: TTemplate[] = [
upperLabel: { default: "Very important" },
range: 5,
scale: "number",
isColorCodingEnabled: false,
},
{
id: createId(),
@@ -1648,6 +1653,7 @@ export const templates: TTemplate[] = [
required: true,
lowerLabel: { default: "Not easy" },
upperLabel: { default: "Very easy" },
isColorCodingEnabled: false,
},
{
id: "mko13ptjj6tpi5u2pl7a5drz",
@@ -1775,6 +1781,7 @@ export const templates: TTemplate[] = [
required: false,
lowerLabel: { default: "Not likely" },
upperLabel: { default: "Very likely" },
isColorCodingEnabled: false,
},
{
id: createId(),
@@ -1807,6 +1814,7 @@ export const templates: TTemplate[] = [
required: true,
lowerLabel: { default: "Not satisfied" },
upperLabel: { default: "Very satisfied" },
isColorCodingEnabled: false,
},
{
id: createId(),
@@ -1850,6 +1858,7 @@ export const templates: TTemplate[] = [
subheader: { default: "Don't worry, be honest." },
lowerLabel: { default: "Not good" },
upperLabel: { default: "Very good" },
isColorCodingEnabled: false,
},
{
id: createId(),
@@ -1879,6 +1888,7 @@ export const templates: TTemplate[] = [
required: true,
lowerLabel: { default: "Not good" },
upperLabel: { default: "Very good" },
isColorCodingEnabled: false,
},
{
id: createId(),
@@ -2024,6 +2034,7 @@ export const templates: TTemplate[] = [
upperLabel: { default: "Very easy" },
scale: "number",
range: 5,
isColorCodingEnabled: false,
},
{
id: createId(),
@@ -2108,6 +2119,7 @@ export const templates: TTemplate[] = [
required: true,
lowerLabel: { default: "Disagree strongly" },
upperLabel: { default: "Agree strongly" },
isColorCodingEnabled: false,
},
{
id: createId(),
@@ -2141,6 +2153,7 @@ export const templates: TTemplate[] = [
required: true,
lowerLabel: { default: "Very difficult" },
upperLabel: { default: "Very easy" },
isColorCodingEnabled: false,
},
{
id: createId(),
@@ -2183,6 +2196,7 @@ export const templates: TTemplate[] = [
required: true,
lowerLabel: { default: "Not at all relevant" },
upperLabel: { default: "Very relevant" },
isColorCodingEnabled: false,
},
{
id: createId(),
@@ -2225,6 +2239,7 @@ export const templates: TTemplate[] = [
required: true,
lowerLabel: { default: "Not at all well" },
upperLabel: { default: "Extremely well" },
isColorCodingEnabled: false,
},
{
id: createId(),
@@ -2284,6 +2299,7 @@ export const templates: TTemplate[] = [
required: true,
lowerLabel: { default: "Very difficult" },
upperLabel: { default: "Very easy" },
isColorCodingEnabled: false,
},
{
id: "s0999bhpaz8vgf7ps264piek",
@@ -2356,6 +2372,7 @@ export const templates: TTemplate[] = [
required: true,
lowerLabel: { default: "Not at all likely" },
upperLabel: { default: "Very likely" },
isColorCodingEnabled: false,
},
{
id: createId(),
@@ -2469,6 +2486,7 @@ export const templates: TTemplate[] = [
required: true,
lowerLabel: { default: "Not at all satisfied" },
upperLabel: { default: "Extremely satisfied" },
isColorCodingEnabled: false,
},
{
id: createId(),
@@ -2509,6 +2527,7 @@ export const templates: TTemplate[] = [
required: true,
lowerLabel: { default: "Not at all likely" },
upperLabel: { default: "Extremely likely" },
isColorCodingEnabled: false,
},
{
id: "y19mwcmstlc7pi7s4izxk1ll",
@@ -2557,6 +2576,7 @@ export const templates: TTemplate[] = [
required: true,
lowerLabel: { default: "Meh" },
upperLabel: { default: "Great" },
isColorCodingEnabled: false,
},
{
id: "k3s6gm5ivkc5crpycdbpzkpa",
@@ -2627,7 +2647,9 @@ export const templates: TTemplate[] = [
required: true,
lowerLabel: { default: "Very difficult" },
upperLabel: { default: "Very easy" },
isColorCodingEnabled: false,
},
{
id: "ndacjg9lqf5jcpq9w8ote666",
type: TSurveyQuestionTypeEnum.OpenText,
@@ -2662,6 +2684,7 @@ export const templates: TTemplate[] = [
required: true,
lowerLabel: { default: "Not valuable" },
upperLabel: { default: "Very valuable" },
isColorCodingEnabled: false,
},
{
id: "mmiuun3z4e7gk4ufuwh8lq8q",

View File

@@ -59,6 +59,10 @@ export const NPSQuestion = ({
}, 250);
};
const getNPSOptionColor = (idx: number) => {
return idx > 8 ? "bg-emerald-100" : idx > 6 ? "bg-orange-100" : "bg-rose-100";
};
return (
<form
key={question.id}
@@ -103,9 +107,12 @@ export const NPSQuestion = ({
value === number
? "border-border-highlight bg-accent-selected-bg z-10 border"
: "border-border",
"text-heading first:rounded-l-custom last:rounded-r-custom focus:border-brand relative h-10 flex-1 cursor-pointer border-b border-l border-t text-center text-sm leading-10 last:border-r focus:border-2 focus:outline-none",
"text-heading first:rounded-l-custom last:rounded-r-custom focus:border-brand relative h-10 flex-1 cursor-pointer overflow-hidden border-b border-l border-t text-center text-sm leading-10 last:border-r focus:border-2 focus:outline-none",
hoveredNumber === number ? "bg-accent-bg" : ""
)}>
{question.isColorCodingEnabled && (
<div className={`absolute left-0 top-0 h-[6px] w-full ${getNPSOptionColor(idx)}`} />
)}
<input
type="radio"
id={number.toString()}

View File

@@ -88,6 +88,22 @@ export const RatingQuestion = ({
setHoveredNumber(0);
}, [question.id, setHoveredNumber]);
const getRatingNumberOptionColor = (range: number, idx: number) => {
if (range > 5) {
if (range - idx < 2) return "bg-emerald-100";
if (range - idx < 4) return "bg-orange-100";
return "bg-rose-100";
} else if (range < 5) {
if (range - idx < 1) return "bg-emerald-100";
if (range - idx < 2) return "bg-orange-100";
return "bg-rose-100";
} else {
if (range - idx < 2) return "bg-emerald-100";
if (range - idx < 3) return "bg-orange-100";
return "bg-rose-100";
}
};
return (
<form
key={question.id}
@@ -137,9 +153,15 @@ export const RatingQuestion = ({
: "border-border",
a.length === number ? "rounded-r-custom border-r" : "",
number === 1 ? "rounded-l-custom" : "",
hoveredNumber === number ? "bg-accent-bg " : "",
"text-heading focus:border-brand relative flex min-h-[41px] w-full cursor-pointer items-center justify-center border-b border-l border-t focus:border-2 focus:outline-none"
hoveredNumber === number ? "bg-accent-bg" : "",
question.isColorCodingEnabled ? "min-h-[47px]" : "min-h-[41px]",
"text-heading focus:border-brand relative flex w-full cursor-pointer items-center justify-center overflow-hidden border-b border-l border-t focus:border-2 focus:outline-none"
)}>
{question.isColorCodingEnabled && (
<div
className={`absolute left-0 top-0 h-[6px] w-full ${getRatingNumberOptionColor(question.range, number)}`}
/>
)}
<HiddenRadioInput number={number} id={number.toString()} />
{number}
</label>
@@ -158,7 +180,7 @@ export const RatingQuestion = ({
number <= hoveredNumber || number <= (value as number)
? "text-amber-400"
: "text-[#8696AC]",
hoveredNumber === number ? "text-amber-400 " : "",
hoveredNumber === number ? "text-amber-400" : "",
"relative flex max-h-16 min-h-9 cursor-pointer justify-center focus:outline-none"
)}
onFocus={() => setHoveredNumber(number)}
@@ -193,11 +215,12 @@ export const RatingQuestion = ({
onFocus={() => setHoveredNumber(number)}
onBlur={() => setHoveredNumber(0)}>
<HiddenRadioInput number={number} id={number.toString()} />
<div className="h-full w-full max-w-[74px] object-contain">
<div className={cn("h-full w-full max-w-[74px] object-contain")}>
<RatingSmiley
active={value === number || hoveredNumber === number}
idx={i}
range={question.range}
addColors={question.isColorCodingEnabled}
/>
</div>
</label>
@@ -246,12 +269,46 @@ interface RatingSmileyProps {
active: boolean;
idx: number;
range: number;
addColors?: boolean;
}
const RatingSmiley = ({ active, idx, range }: RatingSmileyProps): JSX.Element => {
const activeColor = "fill-rating-fill";
const inactiveColor = "fill-none";
let icons = [
const getSmileyColor = (range: number, idx: number) => {
if (range > 5) {
if (range - idx < 3) return "fill-emerald-100";
if (range - idx < 5) return "fill-orange-100";
return "fill-rose-100";
} else if (range < 5) {
if (range - idx < 2) return "fill-emerald-100";
if (range - idx < 3) return "fill-orange-100";
return "fill-rose-100";
} else {
if (range - idx < 3) return "fill-emerald-100";
if (range - idx < 4) return "fill-orange-100";
return "fill-rose-100";
}
};
const getActiveSmileyColor = (range: number, idx: number) => {
if (range > 5) {
if (range - idx < 3) return "fill-emerald-300";
if (range - idx < 5) return "fill-orange-300";
return "fill-rose-300";
} else if (range < 5) {
if (range - idx < 2) return "fill-emerald-300";
if (range - idx < 3) return "fill-orange-300";
return "fill-rose-300";
} else {
if (range - idx < 3) return "fill-emerald-300";
if (range - idx < 4) return "fill-orange-300";
return "fill-rose-300";
}
};
const getSmiley = (iconIdx: number, idx: number, range: number, active: boolean, addColors: boolean) => {
const activeColor = addColors ? getActiveSmileyColor(range, idx) : "fill-rating-fill";
const inactiveColor = addColors ? getSmileyColor(range, idx) : "fill-none";
const icons = [
<TiredFace className={active ? activeColor : inactiveColor} />,
<WearyFace className={active ? activeColor : inactiveColor} />,
<PerseveringFace className={active ? activeColor : inactiveColor} />,
@@ -264,9 +321,16 @@ const RatingSmiley = ({ active, idx, range }: RatingSmileyProps): JSX.Element =>
<GrinningSquintingFace className={active ? activeColor : inactiveColor} />,
];
if (range == 7) icons = [icons[1], icons[3], icons[4], icons[5], icons[6], icons[8], icons[9]];
else if (range == 5) icons = [icons[3], icons[4], icons[5], icons[6], icons[7]];
else if (range == 4) icons = [icons[4], icons[5], icons[6], icons[7]];
else if (range == 3) icons = [icons[4], icons[5], icons[7]];
return icons[idx];
return icons[iconIdx];
};
export const RatingSmiley = ({ active, idx, range, addColors = false }: RatingSmileyProps): JSX.Element => {
let iconsIdx: number[] = [];
if (range === 10) iconsIdx = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
else if (range === 7) iconsIdx = [1, 3, 4, 5, 6, 8, 9];
else if (range === 5) iconsIdx = [3, 4, 5, 6, 7];
else if (range === 4) iconsIdx = [4, 5, 6, 7];
else if (range === 3) iconsIdx = [4, 5, 7];
return getSmiley(iconsIdx[idx], idx, range, active, addColors);
};

View File

@@ -61,6 +61,7 @@ p.fb-editor-paragraph {
--slate-900: rgb(15 23 42);
--gray-100: rgb(243 244 246);
--gray-200: rgb(229 231 235);
--yellow-100: rgb(254 249 195);
--yellow-300: rgb(253 224 71);
--yellow-500: rgb(234 179 8);
@@ -85,7 +86,7 @@ p.fb-editor-paragraph {
--fb-input-background-color-selected: var(--slate-200);
--fb-placeholder-color: var(--slate-400);
--fb-shadow-color: var(--slate-300);
--fb-rating-fill: var(--yellow-300);
--fb-rating-fill: var(--yellow-100);
--fb-rating-hover: var(--yellow-500);
--fb-back-btn-border: transparent;
--fb-submit-btn-border: transparent;

View File

@@ -326,6 +326,7 @@ export const ZSurveyNPSQuestion = ZSurveyQuestionBase.extend({
type: z.literal(TSurveyQuestionTypeEnum.NPS),
lowerLabel: ZI18nString.optional(),
upperLabel: ZI18nString.optional(),
isColorCodingEnabled: z.boolean().optional().default(false),
logic: z.array(ZSurveyNPSLogic).optional(),
});
@@ -348,6 +349,7 @@ export const ZSurveyRatingQuestion = ZSurveyQuestionBase.extend({
range: z.union([z.literal(5), z.literal(3), z.literal(4), z.literal(7), z.literal(10)]),
lowerLabel: ZI18nString.optional(),
upperLabel: ZI18nString.optional(),
isColorCodingEnabled: z.boolean().optional().default(false),
logic: z.array(ZSurveyRatingLogic).optional(),
});

View File

@@ -1,24 +1,19 @@
import { StarIcon } from "lucide-react";
import {
ConfusedFace,
FrowningFace,
GrinningFaceWithSmilingEyes,
GrinningSquintingFace,
NeutralFace,
PerseveringFace,
SlightlySmilingFace,
SmilingFaceWithSmilingEyes,
TiredFace,
WearyFace,
} from "../SingleResponseCard/components/Smileys";
import { RatingSmiley } from "../RatingSmiley";
interface RatingResponseProps {
scale?: "number" | "star" | "smiley";
range?: number;
answer: string | number | string[];
addColors?: boolean;
}
export const RatingResponse: React.FC<RatingResponseProps> = ({ scale, range, answer }) => {
export const RatingResponse: React.FC<RatingResponseProps> = ({
scale,
range,
answer,
addColors = false,
}) => {
if (typeof answer !== "number") return null;
if (typeof scale === "undefined" || typeof range === "undefined") return answer;
@@ -35,100 +30,8 @@ export const RatingResponse: React.FC<RatingResponseProps> = ({ scale, range, an
return <div className="flex">{stars}</div>;
}
if (scale === "smiley") {
if (range === 10 && answer === 1) {
return (
<div className="h-10 w-10">
<TiredFace />
</div>
);
}
if ((range === 10 && answer === 2) || (range === 7 && answer === 1)) {
return (
<div className="h-10 w-10">
<WearyFace />
</div>
);
}
if (range === 10 && answer === 3) {
return (
<div className="h-10 w-10">
<PerseveringFace />
</div>
);
}
if ((range === 10 && answer === 4) || (range === 7 && answer === 2) || (range === 5 && answer === 1)) {
return (
<div className="h-10 w-10">
<FrowningFace />
</div>
);
}
if (
(range === 10 && answer === 5) ||
(range === 7 && answer === 3) ||
(range === 5 && answer === 2) ||
(range === 4 && answer === 1) ||
(range === 3 && answer === 1)
) {
return (
<div className="h-10 w-10">
<ConfusedFace />
</div>
);
}
if (
(range === 10 && answer === 6) ||
(range === 7 && answer === 4) ||
(range === 5 && answer === 3) ||
(range === 4 && answer === 2) ||
(range === 3 && answer === 2)
) {
return (
<div className="h-10 w-10">
<NeutralFace />
</div>
);
}
if (
(range === 10 && answer === 7) ||
(range === 7 && answer === 5) ||
(range === 5 && answer === 4) ||
(range === 4 && answer === 3)
) {
return (
<div className="h-10 w-10">
<SlightlySmilingFace />
</div>
);
}
if (
(range === 10 && answer === 8) ||
(range === 5 && answer === 5) ||
(range === 4 && answer === 4) ||
(range === 3 && answer === 3)
) {
return (
<div className="h-10 w-10">
<SmilingFaceWithSmilingEyes />
</div>
);
}
if ((range === 10 && answer === 9) || (range === 7 && answer === 6)) {
return (
<div className="h-10 w-10">
<GrinningFaceWithSmilingEyes />
</div>
);
}
if ((range === 10 && answer === 10) || (range === 7 && answer === 7)) {
return (
<div className="h-10 w-10">
<GrinningSquintingFace />
</div>
);
}
}
if (scale === "smiley")
return <RatingSmiley active={false} idx={answer - 1} range={range} addColors={addColors} />;
return answer;
};

View File

@@ -15,12 +15,30 @@ interface RatingSmileyProps {
active: boolean;
idx: number;
range: number;
addColors?: boolean;
}
export const RatingSmiley = ({ active, idx, range }: RatingSmileyProps): JSX.Element => {
const getSmileyColor = (range: number, idx: number) => {
if (range > 5) {
if (range - idx < 3) return "fill-emerald-100";
if (range - idx < 5) return "fill-orange-100";
return "fill-rose-100";
} else if (range < 5) {
if (range - idx < 2) return "fill-emerald-100";
if (range - idx < 3) return "fill-orange-100";
return "fill-rose-100";
} else {
if (range - idx < 3) return "fill-emerald-100";
if (range - idx < 4) return "fill-orange-100";
return "fill-rose-100";
}
};
const getSmiley = (iconIdx: number, idx: number, range: number, active: boolean, addColors: boolean) => {
const activeColor = "fill-rating-fill";
const inactiveColor = "fill-none";
let icons = [
const inactiveColor = addColors ? getSmileyColor(range, idx) : "fill-none";
const icons = [
<TiredFace className={active ? activeColor : inactiveColor} />,
<WearyFace className={active ? activeColor : inactiveColor} />,
<PerseveringFace className={active ? activeColor : inactiveColor} />,
@@ -33,9 +51,16 @@ export const RatingSmiley = ({ active, idx, range }: RatingSmileyProps): JSX.Ele
<GrinningSquintingFace className={active ? activeColor : inactiveColor} />,
];
if (range == 7) icons = [icons[1], icons[3], icons[4], icons[5], icons[6], icons[8], icons[9]];
else if (range == 5) icons = [icons[3], icons[4], icons[5], icons[6], icons[7]];
else if (range == 4) icons = [icons[4], icons[5], icons[6], icons[7]];
else if (range == 3) icons = [icons[4], icons[5], icons[7]];
return icons[idx];
return icons[iconIdx];
};
export const RatingSmiley = ({ active, idx, range, addColors = false }: RatingSmileyProps): JSX.Element => {
let iconsIdx: number[] = [];
if (range === 10) iconsIdx = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
else if (range === 7) iconsIdx = [1, 3, 4, 5, 6, 8, 9];
else if (range === 5) iconsIdx = [3, 4, 5, 6, 7];
else if (range === 4) iconsIdx = [4, 5, 6, 7];
else if (range === 3) iconsIdx = [4, 5, 7];
return getSmiley(iconsIdx[idx], idx, range, active, addColors);
};

View File

@@ -9,6 +9,7 @@ import {
TSurveyPictureSelectionQuestion,
TSurveyQuestion,
TSurveyQuestionTypeEnum,
TSurveyRatingQuestion,
} from "@formbricks/types/surveys";
import { AddressResponse } from "../../AddressResponse";
import { FileUploadResponse } from "../../FileUploadResponse";
@@ -51,7 +52,7 @@ export const SingleResponseCardBody = ({
return (
<span
key={index}
className="ml-0.5 mr-0.5 rounded-md border border-slate-200 bg-slate-50 px-1 py-0.5 text-sm first:ml-0 ">
className="ml-0.5 mr-0.5 rounded-md border border-slate-200 bg-slate-50 px-1 py-0.5 text-sm first:ml-0">
@{part}
</span>
);
@@ -69,7 +70,14 @@ export const SingleResponseCardBody = ({
switch (questionType) {
case TSurveyQuestionTypeEnum.Rating:
if (typeof responseData === "number")
return <RatingResponse scale={question.scale} answer={responseData} range={question.range} />;
return (
<RatingResponse
scale={question.scale}
answer={responseData}
range={question.range}
addColors={(question as TSurveyRatingQuestion).isColorCodingEnabled}
/>
);
case TSurveyQuestionTypeEnum.Date:
if (typeof responseData === "string") {
const formattedDateString = formatDateWithOrdinal(new Date(responseData));