mirror of
https://github.com/formbricks/formbricks.git
synced 2026-01-06 13:49:54 -06:00
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:
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
20
packages/email/utils/index.ts
Normal file
20
packages/email/utils/index.ts
Normal 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";
|
||||
};
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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()}
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(),
|
||||
});
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user