mirror of
https://github.com/formbricks/formbricks.git
synced 2026-02-18 01:00:40 -06:00
feat(packages/surveys): ability to customize colors & other improvements (#916)
Co-authored-by: Matthias Nannt <mail@matthiasnannt.com> Co-authored-by: Neil Chauhan <neilchauhan2@gmail.com>
This commit is contained in:
@@ -1,3 +1,26 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
/* Example on overriding packages/js colors */
|
||||
.dark {
|
||||
--fb-brand-color: red;
|
||||
--fb-brand-text-color: white;
|
||||
--fb-border-color: green;
|
||||
--fb-border-color-highlight: var(--slate-500);
|
||||
--fb-focus-color: red;
|
||||
--fb-heading-color: yellow;
|
||||
--fb-subheading-color: green;
|
||||
--fb-info-text-color: orange;
|
||||
--fb-signature-text-color: blue;
|
||||
--fb-survey-background-color: black;
|
||||
--fb-accent-background-color: rgb(13, 13, 12);
|
||||
--fb-accent-background-color-selected: red;
|
||||
--fb-placeholder-color: white;
|
||||
--fb-shadow-color: yellow;
|
||||
--fb-rating-fill: var(--yellow-300);
|
||||
--fb-rating-hover: var(--yellow-500);
|
||||
--fb-back-btn-border: currentColor;
|
||||
--fb-submit-btn-border: transparent;
|
||||
--fb-rating-selected: black;
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
"tailwindcss": "^3.3.3",
|
||||
"terser": "^5.22.0",
|
||||
"vite": "^4.4.11",
|
||||
"vite-plugin-dts": "^3.6.0"
|
||||
"vite-plugin-dts": "^3.6.0",
|
||||
"vite-tsconfig-paths": "^4.2.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
export default function Progress({ progress, brandColor }: { progress: number; brandColor: string }) {
|
||||
return (
|
||||
<div className="h-2 w-full rounded-full bg-slate-200">
|
||||
<div
|
||||
className="transition-width z-20 h-2 rounded-full duration-500"
|
||||
style={{ backgroundColor: brandColor, width: `${Math.floor(progress * 100)}%` }}></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { cn } from "../../../lib/cn";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface BackButtonProps {
|
||||
onClick: () => void;
|
||||
@@ -12,7 +12,7 @@ export function BackButton({ onClick, backButtonLabel, tabIndex = 2 }: BackButto
|
||||
tabIndex={tabIndex}
|
||||
type={"button"}
|
||||
className={cn(
|
||||
"flex items-center rounded-md border border-transparent px-3 py-3 text-base font-medium leading-4 shadow-sm hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-slate-500 focus:ring-offset-2"
|
||||
"border-back-button-border text-heading focus:ring-focus flex items-center rounded-md border px-3 py-3 text-base font-medium leading-4 shadow-sm hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-offset-2"
|
||||
)}
|
||||
onClick={onClick}>
|
||||
{backButtonLabel || "Back"}
|
||||
@@ -1,11 +1,8 @@
|
||||
import { useCallback } from "preact/hooks";
|
||||
import { cn } from "../../../lib/cn";
|
||||
import { isLight } from "../lib/utils";
|
||||
|
||||
interface SubmitButtonProps {
|
||||
buttonLabel: string | undefined;
|
||||
isLastQuestion: boolean;
|
||||
brandColor: string;
|
||||
onClick: () => void;
|
||||
focus?: boolean;
|
||||
tabIndex?: number;
|
||||
@@ -15,7 +12,6 @@ interface SubmitButtonProps {
|
||||
function SubmitButton({
|
||||
buttonLabel,
|
||||
isLastQuestion,
|
||||
brandColor,
|
||||
onClick,
|
||||
tabIndex = 1,
|
||||
focus = false,
|
||||
@@ -38,11 +34,7 @@ function SubmitButton({
|
||||
type={type}
|
||||
tabIndex={tabIndex}
|
||||
autoFocus={focus}
|
||||
className={cn(
|
||||
"flex items-center rounded-md border border-transparent px-3 py-3 text-base font-medium leading-4 shadow-sm hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-slate-500 focus:ring-offset-2",
|
||||
isLight(brandColor) ? "text-black" : "text-white"
|
||||
)}
|
||||
style={{ backgroundColor: brandColor }}
|
||||
className="bg-brand border-submit-button-border text-on-brand focus:ring-focus flex items-center rounded-md border px-3 py-3 text-base font-medium leading-4 shadow-sm hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-offset-2"
|
||||
onClick={onClick}>
|
||||
{buttonLabel || (isLastQuestion ? "Finish" : "Next")}
|
||||
</button>
|
||||
@@ -5,10 +5,10 @@ export default function FormbricksBranding() {
|
||||
target="_blank"
|
||||
tabIndex={-1}
|
||||
className="mb-5 mt-2 flex justify-center">
|
||||
<p className="text-xs text-slate-400">
|
||||
<p className="text-signature text-xs">
|
||||
Powered by{" "}
|
||||
<b>
|
||||
<span className="text-slate-500 hover:text-slate-700">Formbricks</span>
|
||||
<span className="text-info-text hover:text-heading">Formbricks</span>
|
||||
</b>
|
||||
</p>
|
||||
</a>
|
||||
@@ -7,11 +7,14 @@ interface HeadlineProps {
|
||||
|
||||
export default function Headline({ headline, questionId, style, required = true }: HeadlineProps) {
|
||||
return (
|
||||
<label htmlFor={questionId} className="mb-1.5 block text-base font-semibold leading-6 text-slate-900">
|
||||
<div className={"flex justify-between gap-4"} style={style}>
|
||||
<label
|
||||
htmlFor={questionId}
|
||||
className="text-heading mb-1.5 block text-base font-semibold leading-6"
|
||||
style={style}>
|
||||
<div className={"mr-[3ch] flex items-center justify-between"} style={style}>
|
||||
{headline}
|
||||
{!required && (
|
||||
<span className="self-start text-sm font-normal leading-7 text-slate-400" tabIndex={-1}>
|
||||
<span className="text-info-text self-start text-sm font-normal leading-7" tabIndex={-1}>
|
||||
Optional
|
||||
</span>
|
||||
)}
|
||||
@@ -1,11 +1,11 @@
|
||||
import { cleanHtml } from "../lib/cleanHtml";
|
||||
import { cleanHtml } from "@/lib/cleanHtml";
|
||||
|
||||
export default function HtmlBody({ htmlString, questionId }: { htmlString?: string; questionId: string }) {
|
||||
if (!htmlString) return null;
|
||||
return (
|
||||
<label
|
||||
htmlFor={questionId}
|
||||
className="block text-sm font-normal leading-6 text-slate-600"
|
||||
className="fb-htmlbody" // styles are in global.css
|
||||
dangerouslySetInnerHTML={{ __html: cleanHtml(htmlString) }}></label>
|
||||
);
|
||||
}
|
||||
9
packages/surveys/src/components/general/Progress.tsx
Normal file
9
packages/surveys/src/components/general/Progress.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
export default function Progress({ progress }: { progress: number }) {
|
||||
return (
|
||||
<div className="bg-accent-bg h-2 w-full rounded-full">
|
||||
<div
|
||||
className="transition-width bg-brand z-20 h-2 rounded-full duration-500"
|
||||
style={{ width: `${Math.floor(progress * 100)}%` }}></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,17 +1,16 @@
|
||||
import { TSurveyWithTriggers } from "@formbricks/types/js";
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
import Progress from "./Progress";
|
||||
import { calculateElementIdx } from "../lib/utils";
|
||||
import { calculateElementIdx } from "@/lib/utils";
|
||||
|
||||
interface ProgressBarProps {
|
||||
survey: TSurveyWithTriggers;
|
||||
questionId: string;
|
||||
brandColor: string;
|
||||
}
|
||||
|
||||
const PROGRESS_INCREMENT = 0.1;
|
||||
|
||||
export default function ProgressBar({ survey, questionId, brandColor }: ProgressBarProps) {
|
||||
export default function ProgressBar({ survey, questionId }: ProgressBarProps) {
|
||||
const [progress, setProgress] = useState(0); // [0, 1]
|
||||
const [prevQuestionIdx, setPrevQuestionIdx] = useState(0); // [0, survey.questions.length
|
||||
const [prevQuestionId, setPrevQuestionId] = useState(""); // [0, survey.questions.length
|
||||
@@ -48,5 +47,5 @@ export default function ProgressBar({ survey, questionId, brandColor }: Progress
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [questionId, survey, setPrevQuestionIdx]);
|
||||
|
||||
return <Progress progress={progress} brandColor={brandColor} />;
|
||||
return <Progress progress={progress} />;
|
||||
}
|
||||
@@ -1,14 +1,13 @@
|
||||
import { TResponseData } from "@formbricks/types/responses";
|
||||
import { TSurveyQuestion } from "@formbricks/types/surveys";
|
||||
import { TSurveyQuestionType } from "@formbricks/types/surveys";
|
||||
import CTAQuestion from "./CTAQuestion";
|
||||
import ConsentQuestion from "./ConsentQuestion";
|
||||
import MultipleChoiceMultiQuestion from "./MultipleChoiceMultiQuestion";
|
||||
import MultipleChoiceSingleQuestion from "./MultipleChoiceSingleQuestion";
|
||||
import NPSQuestion from "./NPSQuestion";
|
||||
import OpenTextQuestion from "./OpenTextQuestion";
|
||||
import RatingQuestion from "./RatingQuestion";
|
||||
import PictureSelectionQuestion from "./PictureSelectionQuestion";
|
||||
import { TSurveyQuestion, TSurveyQuestionType } from "@formbricks/types/surveys";
|
||||
import CTAQuestion from "@/components/questions/CTAQuestion";
|
||||
import ConsentQuestion from "@/components/questions/ConsentQuestion";
|
||||
import MultipleChoiceMultiQuestion from "@/components/questions/MultipleChoiceMultiQuestion";
|
||||
import MultipleChoiceSingleQuestion from "@/components/questions/MultipleChoiceSingleQuestion";
|
||||
import NPSQuestion from "@/components/questions/NPSQuestion";
|
||||
import OpenTextQuestion from "@/components/questions/OpenTextQuestion";
|
||||
import PictureSelectionQuestion from "@/components/questions/PictureSelectionQuestion";
|
||||
import RatingQuestion from "@/components/questions/RatingQuestion";
|
||||
|
||||
interface QuestionConditionalProps {
|
||||
question: TSurveyQuestion;
|
||||
@@ -18,7 +17,6 @@ interface QuestionConditionalProps {
|
||||
onBack: () => void;
|
||||
isFirstQuestion: boolean;
|
||||
isLastQuestion: boolean;
|
||||
brandColor: string;
|
||||
autoFocus?: boolean;
|
||||
}
|
||||
|
||||
@@ -30,7 +28,6 @@ export default function QuestionConditional({
|
||||
onBack,
|
||||
isFirstQuestion,
|
||||
isLastQuestion,
|
||||
brandColor,
|
||||
autoFocus = true,
|
||||
}: QuestionConditionalProps) {
|
||||
return question.type === TSurveyQuestionType.OpenText ? (
|
||||
@@ -42,7 +39,6 @@ export default function QuestionConditional({
|
||||
onBack={onBack}
|
||||
isFirstQuestion={isFirstQuestion}
|
||||
isLastQuestion={isLastQuestion}
|
||||
brandColor={brandColor}
|
||||
autoFocus={autoFocus}
|
||||
/>
|
||||
) : question.type === TSurveyQuestionType.MultipleChoiceSingle ? (
|
||||
@@ -54,7 +50,6 @@ export default function QuestionConditional({
|
||||
onBack={onBack}
|
||||
isFirstQuestion={isFirstQuestion}
|
||||
isLastQuestion={isLastQuestion}
|
||||
brandColor={brandColor}
|
||||
/>
|
||||
) : question.type === TSurveyQuestionType.MultipleChoiceMulti ? (
|
||||
<MultipleChoiceMultiQuestion
|
||||
@@ -65,7 +60,6 @@ export default function QuestionConditional({
|
||||
onBack={onBack}
|
||||
isFirstQuestion={isFirstQuestion}
|
||||
isLastQuestion={isLastQuestion}
|
||||
brandColor={brandColor}
|
||||
/>
|
||||
) : question.type === TSurveyQuestionType.NPS ? (
|
||||
<NPSQuestion
|
||||
@@ -76,7 +70,6 @@ export default function QuestionConditional({
|
||||
onBack={onBack}
|
||||
isFirstQuestion={isFirstQuestion}
|
||||
isLastQuestion={isLastQuestion}
|
||||
brandColor={brandColor}
|
||||
/>
|
||||
) : question.type === TSurveyQuestionType.CTA ? (
|
||||
<CTAQuestion
|
||||
@@ -87,7 +80,6 @@ export default function QuestionConditional({
|
||||
onBack={onBack}
|
||||
isFirstQuestion={isFirstQuestion}
|
||||
isLastQuestion={isLastQuestion}
|
||||
brandColor={brandColor}
|
||||
/>
|
||||
) : question.type === TSurveyQuestionType.Rating ? (
|
||||
<RatingQuestion
|
||||
@@ -98,7 +90,6 @@ export default function QuestionConditional({
|
||||
onBack={onBack}
|
||||
isFirstQuestion={isFirstQuestion}
|
||||
isLastQuestion={isLastQuestion}
|
||||
brandColor={brandColor}
|
||||
/>
|
||||
) : question.type === TSurveyQuestionType.Consent ? (
|
||||
<ConsentQuestion
|
||||
@@ -109,7 +100,6 @@ export default function QuestionConditional({
|
||||
onBack={onBack}
|
||||
isFirstQuestion={isFirstQuestion}
|
||||
isLastQuestion={isLastQuestion}
|
||||
brandColor={brandColor}
|
||||
/>
|
||||
) : question.type === TSurveyQuestionType.PictureSelection ? (
|
||||
<PictureSelectionQuestion
|
||||
@@ -120,7 +110,6 @@ export default function QuestionConditional({
|
||||
onBack={onBack}
|
||||
isFirstQuestion={isFirstQuestion}
|
||||
isLastQuestion={isLastQuestion}
|
||||
brandColor={brandColor}
|
||||
/>
|
||||
) : null;
|
||||
}
|
||||
@@ -35,7 +35,7 @@ export default function RedirectCountDown({ redirectUrl, isRedirectDisabled }: R
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="mt-10 rounded-md bg-slate-100 p-2 text-sm">
|
||||
<div className="bg-accent-bg text-subheading mt-10 rounded-md p-2 text-sm">
|
||||
<span>You're redirected in </span>
|
||||
<span>{timeRemaining}</span>
|
||||
</div>
|
||||
@@ -1,6 +1,6 @@
|
||||
export default function Subheader({ subheader, questionId }: { subheader?: string; questionId: string }) {
|
||||
return (
|
||||
<label htmlFor={questionId} className="block text-sm font-normal leading-6 text-slate-600">
|
||||
<label htmlFor={questionId} className="text-subheading block text-sm font-normal leading-6">
|
||||
{subheader}
|
||||
</label>
|
||||
);
|
||||
@@ -1,10 +1,10 @@
|
||||
import FormbricksBranding from "@/components/general/FormbricksBranding";
|
||||
import { AutoCloseWrapper } from "@/components/wrappers/AutoCloseWrapper";
|
||||
import { evaluateCondition } from "@/lib/logicEvaluator";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { SurveyBaseProps } from "@/types/props";
|
||||
import type { TResponseData } from "@formbricks/types/responses";
|
||||
import { useEffect, useRef, useState } from "preact/hooks";
|
||||
import { evaluateCondition } from "../lib/logicEvaluator";
|
||||
import { cn } from "../lib/utils";
|
||||
import { SurveyBaseProps } from "../types/props";
|
||||
import { AutoCloseWrapper } from "./AutoCloseWrapper";
|
||||
import FormbricksBranding from "./FormbricksBranding";
|
||||
import ProgressBar from "./ProgressBar";
|
||||
import QuestionConditional from "./QuestionConditional";
|
||||
import ThankYouCard from "./ThankYouCard";
|
||||
@@ -12,7 +12,6 @@ import WelcomeCard from "./WelcomeCard";
|
||||
|
||||
export function Survey({
|
||||
survey,
|
||||
brandColor,
|
||||
isBrandingEnabled,
|
||||
activeQuestionId,
|
||||
onDisplay = () => {},
|
||||
@@ -129,7 +128,6 @@ export function Survey({
|
||||
fileUrl={survey.welcomeCard.fileUrl}
|
||||
buttonLabel={survey.welcomeCard.buttonLabel}
|
||||
timeToFinish={survey.welcomeCard.timeToFinish}
|
||||
brandColor={brandColor}
|
||||
onSubmit={onSubmit}
|
||||
survey={survey}
|
||||
/>
|
||||
@@ -139,7 +137,6 @@ export function Survey({
|
||||
<ThankYouCard
|
||||
headline={survey.thankYouCard.headline}
|
||||
subheader={survey.thankYouCard.subheader}
|
||||
brandColor={brandColor}
|
||||
redirectUrl={survey.redirectUrl}
|
||||
isRedirectDisabled={isRedirectDisabled}
|
||||
/>
|
||||
@@ -160,7 +157,6 @@ export function Survey({
|
||||
: currQues.id === survey?.questions[0]?.id
|
||||
}
|
||||
isLastQuestion={currQues.id === survey.questions[survey.questions.length - 1].id}
|
||||
brandColor={brandColor}
|
||||
/>
|
||||
)
|
||||
);
|
||||
@@ -169,8 +165,8 @@ export function Survey({
|
||||
|
||||
return (
|
||||
<>
|
||||
<AutoCloseWrapper survey={survey} brandColor={brandColor} onClose={onClose}>
|
||||
<div className="flex h-full w-full flex-col justify-between bg-white px-6 pb-3 pt-6">
|
||||
<AutoCloseWrapper survey={survey} onClose={onClose}>
|
||||
<div className="flex h-full w-full flex-col justify-between bg-[--fb-survey-background-color] px-6 pb-3 pt-6">
|
||||
<div ref={contentRef} className={cn(loadingElement ? "animate-pulse opacity-60" : "", "my-auto")}>
|
||||
{survey.questions.length === 0 && !survey.welcomeCard.enabled && !survey.thankYouCard.enabled ? (
|
||||
// Handle the case when there are no questions and both welcome and thank you cards are disabled
|
||||
@@ -181,7 +177,7 @@ export function Survey({
|
||||
</div>
|
||||
<div className="mt-8">
|
||||
{isBrandingEnabled && <FormbricksBranding />}
|
||||
<ProgressBar survey={survey} questionId={questionId} brandColor={brandColor} />
|
||||
<ProgressBar survey={survey} questionId={questionId} />
|
||||
</div>
|
||||
</div>
|
||||
</AutoCloseWrapper>
|
||||
@@ -1,9 +1,8 @@
|
||||
import { SurveyBaseProps } from "../types/props";
|
||||
import { SurveyBaseProps } from "@/types/props";
|
||||
import { Survey } from "./Survey";
|
||||
|
||||
export function SurveyInline({
|
||||
survey,
|
||||
brandColor,
|
||||
isBrandingEnabled,
|
||||
activeQuestionId,
|
||||
onDisplay = () => {},
|
||||
@@ -14,10 +13,9 @@ export function SurveyInline({
|
||||
isRedirectDisabled = false,
|
||||
}: SurveyBaseProps) {
|
||||
return (
|
||||
<div id="fbjs" className="h-full w-full">
|
||||
<div id="fbjs" className="formbricks-form h-full w-full">
|
||||
<Survey
|
||||
survey={survey}
|
||||
brandColor={brandColor}
|
||||
isBrandingEnabled={isBrandingEnabled}
|
||||
activeQuestionId={activeQuestionId}
|
||||
onDisplay={onDisplay}
|
||||
@@ -1,11 +1,10 @@
|
||||
import { useState } from "preact/hooks";
|
||||
import { SurveyModalProps } from "../types/props";
|
||||
import Modal from "./Modal";
|
||||
import { SurveyModalProps } from "@/types/props";
|
||||
import Modal from "@/components/wrappers/Modal";
|
||||
import { Survey } from "./Survey";
|
||||
|
||||
export function SurveyModal({
|
||||
survey,
|
||||
brandColor,
|
||||
isBrandingEnabled,
|
||||
activeQuestionId,
|
||||
placement,
|
||||
@@ -29,7 +28,7 @@ export function SurveyModal({
|
||||
};
|
||||
|
||||
return (
|
||||
<div id="fbjs">
|
||||
<div id="fbjs" className="formbricks-form">
|
||||
<Modal
|
||||
placement={placement}
|
||||
clickOutside={clickOutside}
|
||||
@@ -39,7 +38,6 @@ export function SurveyModal({
|
||||
onClose={close}>
|
||||
<Survey
|
||||
survey={survey}
|
||||
brandColor={brandColor}
|
||||
isBrandingEnabled={isBrandingEnabled}
|
||||
activeQuestionId={activeQuestionId}
|
||||
onDisplay={onDisplay}
|
||||
@@ -1,11 +1,10 @@
|
||||
import Headline from "./Headline";
|
||||
import RedirectCountDown from "./RedirectCountdown";
|
||||
import Subheader from "./Subheader";
|
||||
import Headline from "@/components/general/Headline";
|
||||
import RedirectCountDown from "@/components/general/RedirectCountdown";
|
||||
import Subheader from "@/components/general/Subheader";
|
||||
|
||||
interface ThankYouCardProps {
|
||||
headline?: string;
|
||||
subheader?: string;
|
||||
brandColor: string;
|
||||
redirectUrl: string | null;
|
||||
isRedirectDisabled: boolean;
|
||||
}
|
||||
@@ -13,13 +12,12 @@ interface ThankYouCardProps {
|
||||
export default function ThankYouCard({
|
||||
headline,
|
||||
subheader,
|
||||
brandColor,
|
||||
redirectUrl,
|
||||
isRedirectDisabled,
|
||||
}: ThankYouCardProps) {
|
||||
return (
|
||||
<div className="text-center">
|
||||
<div className="flex items-center justify-center" style={{ color: brandColor }}>
|
||||
<div className="text-brand flex items-center justify-center">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
@@ -35,7 +33,7 @@ export default function ThankYouCard({
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<span className="mb-[10px] inline-block h-1 w-16 rounded-[100%] bg-slate-300"></span>
|
||||
<span className="bg-shadow mb-[10px] inline-block h-1 w-16 rounded-[100%]"></span>
|
||||
|
||||
<div>
|
||||
<Headline headline={headline} questionId="thankYouCard" style={{ "justify-content": "center" }} />
|
||||
@@ -1,8 +1,8 @@
|
||||
import SubmitButton from "@/components/buttons/SubmitButton";
|
||||
import { calculateElementIdx } from "@/lib/utils";
|
||||
import { TSurveyWithTriggers } from "@formbricks/types/js";
|
||||
import Headline from "./Headline";
|
||||
import HtmlBody from "./HtmlBody";
|
||||
import SubmitButton from "./SubmitButton";
|
||||
import { calculateElementIdx } from "../lib/utils";
|
||||
import { TSurveyWithTriggers } from "@formbricks/types/js";
|
||||
|
||||
interface WelcomeCardProps {
|
||||
headline?: string;
|
||||
@@ -10,7 +10,6 @@ interface WelcomeCardProps {
|
||||
fileUrl?: string;
|
||||
buttonLabel?: string;
|
||||
timeToFinish?: boolean;
|
||||
brandColor: string;
|
||||
onSubmit: (data: { [x: string]: any }) => void;
|
||||
survey: TSurveyWithTriggers;
|
||||
}
|
||||
@@ -38,7 +37,6 @@ export default function WelcomeCard({
|
||||
fileUrl,
|
||||
buttonLabel,
|
||||
timeToFinish,
|
||||
brandColor,
|
||||
onSubmit,
|
||||
survey,
|
||||
}: WelcomeCardProps) {
|
||||
@@ -85,14 +83,13 @@ export default function WelcomeCard({
|
||||
<SubmitButton
|
||||
buttonLabel={buttonLabel}
|
||||
isLastQuestion={false}
|
||||
brandColor={brandColor}
|
||||
focus={true}
|
||||
onClick={() => {
|
||||
onSubmit({ ["welcomeCard"]: "clicked" });
|
||||
}}
|
||||
type="button"
|
||||
/>
|
||||
<div className="flex items-center text-xs text-slate-600">Press Enter ↵</div>
|
||||
<div className="text-subheading flex items-center text-xs">Press Enter ↵</div>
|
||||
</div>
|
||||
</div>
|
||||
{timeToFinish && (
|
||||
@@ -1,9 +1,9 @@
|
||||
import { BackButton } from "@/components/buttons/BackButton";
|
||||
import SubmitButton from "@/components/buttons/SubmitButton";
|
||||
import Headline from "@/components/general/Headline";
|
||||
import HtmlBody from "@/components/general/HtmlBody";
|
||||
import { TResponseData } from "@formbricks/types/responses";
|
||||
import type { TSurveyCTAQuestion } from "@formbricks/types/surveys";
|
||||
import { BackButton } from "./BackButton";
|
||||
import Headline from "./Headline";
|
||||
import HtmlBody from "./HtmlBody";
|
||||
import SubmitButton from "./SubmitButton";
|
||||
|
||||
interface CTAQuestionProps {
|
||||
question: TSurveyCTAQuestion;
|
||||
@@ -13,7 +13,6 @@ interface CTAQuestionProps {
|
||||
onBack: () => void;
|
||||
isFirstQuestion: boolean;
|
||||
isLastQuestion: boolean;
|
||||
brandColor: string;
|
||||
}
|
||||
|
||||
export default function CTAQuestion({
|
||||
@@ -22,7 +21,6 @@ export default function CTAQuestion({
|
||||
onBack,
|
||||
isFirstQuestion,
|
||||
isLastQuestion,
|
||||
brandColor,
|
||||
}: CTAQuestionProps) {
|
||||
return (
|
||||
<div>
|
||||
@@ -47,14 +45,13 @@ export default function CTAQuestion({
|
||||
onClick={() => {
|
||||
onSubmit({ [question.id]: "dismissed" });
|
||||
}}
|
||||
className="mr-4 flex items-center rounded-md px-3 py-3 text-base font-medium leading-4 hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-slate-500 focus:ring-offset-2 dark:text-slate-400">
|
||||
className="text-heading focus:ring-focus mr-4 flex items-center rounded-md px-3 py-3 text-base font-medium leading-4 hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-offset-2">
|
||||
{question.dismissButtonLabel || "Skip"}
|
||||
</button>
|
||||
)}
|
||||
<SubmitButton
|
||||
buttonLabel={question.buttonLabel}
|
||||
isLastQuestion={isLastQuestion}
|
||||
brandColor={brandColor}
|
||||
focus={true}
|
||||
onClick={() => {
|
||||
if (question.buttonExternal && question.buttonUrl) {
|
||||
@@ -1,9 +1,9 @@
|
||||
import { TResponseData } from "@formbricks/types/responses";
|
||||
import type { TSurveyConsentQuestion } from "@formbricks/types/surveys";
|
||||
import { BackButton } from "./BackButton";
|
||||
import Headline from "./Headline";
|
||||
import HtmlBody from "./HtmlBody";
|
||||
import SubmitButton from "./SubmitButton";
|
||||
import { BackButton } from "@/components/buttons/BackButton";
|
||||
import SubmitButton from "@/components/buttons/SubmitButton";
|
||||
import Headline from "@/components/general/Headline";
|
||||
import HtmlBody from "@/components/general/HtmlBody";
|
||||
|
||||
interface ConsentQuestionProps {
|
||||
question: TSurveyConsentQuestion;
|
||||
@@ -13,7 +13,6 @@ interface ConsentQuestionProps {
|
||||
onBack: () => void;
|
||||
isFirstQuestion: boolean;
|
||||
isLastQuestion: boolean;
|
||||
brandColor: string;
|
||||
}
|
||||
|
||||
export default function ConsentQuestion({
|
||||
@@ -24,7 +23,6 @@ export default function ConsentQuestion({
|
||||
onBack,
|
||||
isFirstQuestion,
|
||||
isLastQuestion,
|
||||
brandColor,
|
||||
}: ConsentQuestionProps) {
|
||||
return (
|
||||
<div>
|
||||
@@ -49,7 +47,7 @@ export default function ConsentQuestion({
|
||||
onChange({ [question.id]: "accepted" });
|
||||
}
|
||||
}}
|
||||
className="relative z-10 mt-4 flex w-full cursor-pointer items-center rounded-md border border-gray-200 p-4 text-sm text-slate-800 hover:bg-slate-50 focus:bg-slate-50 focus:outline-none focus:ring-2 focus:ring-slate-500 focus:ring-offset-2">
|
||||
className="border-border bg-survey-bg text-heading hover:bg-accent-bg focus:bg-accent-bg focus:ring-border-highlight relative z-10 mt-4 flex w-full cursor-pointer items-center rounded-md border p-4 text-sm focus:outline-none focus:ring-2 focus:ring-offset-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
id={question.id}
|
||||
@@ -63,9 +61,8 @@ export default function ConsentQuestion({
|
||||
}
|
||||
}}
|
||||
checked={value === "accepted"}
|
||||
className="h-4 w-4 border border-slate-300 focus:ring-0 focus:ring-offset-0"
|
||||
className="border-brand text-brand h-4 w-4 border focus:ring-0 focus:ring-offset-0"
|
||||
aria-labelledby={`${question.id}-label`}
|
||||
style={{ borderColor: brandColor, color: brandColor }}
|
||||
required={question.required}
|
||||
/>
|
||||
<span id={`${question.id}-label`} className="ml-3 font-medium">
|
||||
@@ -80,7 +77,6 @@ export default function ConsentQuestion({
|
||||
<div />
|
||||
<SubmitButton
|
||||
tabIndex={2}
|
||||
brandColor={brandColor}
|
||||
buttonLabel={question.buttonLabel}
|
||||
isLastQuestion={isLastQuestion}
|
||||
onClick={() => {}}
|
||||
@@ -1,11 +1,11 @@
|
||||
import { BackButton } from "@/components/buttons/BackButton";
|
||||
import SubmitButton from "@/components/buttons/SubmitButton";
|
||||
import Headline from "@/components/general/Headline";
|
||||
import Subheader from "@/components/general/Subheader";
|
||||
import { cn, shuffleQuestions } from "@/lib/utils";
|
||||
import { TResponseData } from "@formbricks/types/responses";
|
||||
import type { TSurveyMultipleChoiceMultiQuestion } from "@formbricks/types/surveys";
|
||||
import { useMemo, useRef, useState, useEffect, useCallback } from "preact/hooks";
|
||||
import { cn, shuffleQuestions } from "../lib/utils";
|
||||
import { BackButton } from "./BackButton";
|
||||
import Headline from "./Headline";
|
||||
import Subheader from "./Subheader";
|
||||
import SubmitButton from "./SubmitButton";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "preact/hooks";
|
||||
|
||||
interface MultipleChoiceMultiProps {
|
||||
question: TSurveyMultipleChoiceMultiQuestion;
|
||||
@@ -15,7 +15,6 @@ interface MultipleChoiceMultiProps {
|
||||
onBack: () => void;
|
||||
isFirstQuestion: boolean;
|
||||
isLastQuestion: boolean;
|
||||
brandColor: string;
|
||||
}
|
||||
|
||||
export default function MultipleChoiceMultiQuestion({
|
||||
@@ -26,7 +25,6 @@ export default function MultipleChoiceMultiQuestion({
|
||||
onBack,
|
||||
isFirstQuestion,
|
||||
isLastQuestion,
|
||||
brandColor,
|
||||
}: MultipleChoiceMultiProps) {
|
||||
const getChoicesWithoutOtherLabels = useCallback(
|
||||
() => question.choices.filter((choice) => choice.id !== "other").map((item) => item.label),
|
||||
@@ -104,7 +102,7 @@ export default function MultipleChoiceMultiQuestion({
|
||||
<div className="mt-4">
|
||||
<fieldset>
|
||||
<legend className="sr-only">Options</legend>
|
||||
<div className="relative max-h-[42vh] space-y-2 overflow-y-auto rounded-md bg-white py-0.5 pr-2">
|
||||
<div className="bg-survey-bg relative max-h-[42vh] space-y-2 overflow-y-auto rounded-md py-0.5 pr-2">
|
||||
{questionChoices.map((choice, idx) => (
|
||||
<label
|
||||
key={choice.id}
|
||||
@@ -119,8 +117,10 @@ export default function MultipleChoiceMultiQuestion({
|
||||
}
|
||||
}}
|
||||
className={cn(
|
||||
value === choice.label ? "z-10 border-slate-400 bg-slate-50" : "border-gray-200",
|
||||
"relative flex cursor-pointer flex-col rounded-md border p-4 text-slate-800 focus-within:border-slate-400 hover:bg-slate-50 focus:bg-slate-50 focus:outline-none "
|
||||
value === choice.label
|
||||
? "border-border-highlight bg-accent-selected-bg z-10"
|
||||
: "border-border",
|
||||
"text-heading focus-within:border-border-highlight hover:bg-accent-bg focus:bg-accent-bg relative flex cursor-pointer flex-col rounded-md border p-4 focus:outline-none"
|
||||
)}>
|
||||
<span className="flex items-center text-sm">
|
||||
<input
|
||||
@@ -129,7 +129,7 @@ export default function MultipleChoiceMultiQuestion({
|
||||
name={question.id}
|
||||
tabIndex={-1}
|
||||
value={choice.label}
|
||||
className="h-4 w-4 border border-slate-300 focus:ring-0 focus:ring-offset-0"
|
||||
className="border-brand text-brand h-4 w-4 border focus:ring-0 focus:ring-offset-0"
|
||||
aria-labelledby={`${choice.id}-label`}
|
||||
onChange={(e) => {
|
||||
if ((e.target as HTMLInputElement)?.checked) {
|
||||
@@ -139,7 +139,6 @@ export default function MultipleChoiceMultiQuestion({
|
||||
}
|
||||
}}
|
||||
checked={Array.isArray(value) && value.includes(choice.label)}
|
||||
style={{ borderColor: brandColor, color: brandColor }}
|
||||
required={
|
||||
question.required && Array.isArray(value) && value.length ? false : question.required
|
||||
}
|
||||
@@ -154,8 +153,10 @@ export default function MultipleChoiceMultiQuestion({
|
||||
<label
|
||||
tabIndex={questionChoices.length + 1}
|
||||
className={cn(
|
||||
value === otherOption.label ? "z-10 border-slate-400 bg-slate-50" : "border-gray-200",
|
||||
"relative flex cursor-pointer flex-col rounded-md border p-4 text-slate-800 focus-within:border-slate-400 focus-within:bg-slate-50 hover:bg-slate-50 focus:outline-none"
|
||||
value === otherOption.label
|
||||
? "border-border-highlight bg-accent-selected-bg z-10"
|
||||
: "border-border",
|
||||
"text-heading focus-within:border-border-highlight focus-within:bg-accent-bg hover:bg-accent-bg relative flex cursor-pointer flex-col rounded-md border p-4 focus:outline-none"
|
||||
)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key == "Enter") {
|
||||
@@ -169,7 +170,7 @@ export default function MultipleChoiceMultiQuestion({
|
||||
id={otherOption.id}
|
||||
name={question.id}
|
||||
value={otherOption.label}
|
||||
className="h-4 w-4 border border-slate-300 focus:ring-0 focus:ring-offset-0"
|
||||
className="border-brand text-brand h-4 w-4 border focus:ring-0 focus:ring-offset-0"
|
||||
aria-labelledby={`${otherOption.id}-label`}
|
||||
onChange={(e) => {
|
||||
setOtherSelected(!otherSelected);
|
||||
@@ -181,7 +182,6 @@ export default function MultipleChoiceMultiQuestion({
|
||||
}
|
||||
}}
|
||||
checked={otherSelected}
|
||||
style={{ borderColor: brandColor, color: brandColor }}
|
||||
/>
|
||||
<span id={`${otherOption.id}-label`} className="ml-3 font-medium">
|
||||
{otherOption.label}
|
||||
@@ -206,7 +206,7 @@ export default function MultipleChoiceMultiQuestion({
|
||||
}
|
||||
}}
|
||||
placeholder="Please specify"
|
||||
className="mt-3 flex h-10 w-full rounded-md border border-slate-300 bg-transparent bg-white px-3 py-2 text-sm text-slate-800 placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-500 dark:text-slate-300"
|
||||
className="placeholder:text-placeholder border-border bg-survey-bg text-heading focus:ring-focus mt-3 flex h-10 w-full rounded-md border px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
required={question.required}
|
||||
aria-labelledby={`${otherOption.id}-label`}
|
||||
/>
|
||||
@@ -229,7 +229,6 @@ export default function MultipleChoiceMultiQuestion({
|
||||
tabIndex={questionChoices.length + 2}
|
||||
buttonLabel={question.buttonLabel}
|
||||
isLastQuestion={isLastQuestion}
|
||||
brandColor={brandColor}
|
||||
onClick={() => {}}
|
||||
/>
|
||||
</div>
|
||||
@@ -1,11 +1,11 @@
|
||||
import { BackButton } from "@/components/buttons/BackButton";
|
||||
import SubmitButton from "@/components/buttons/SubmitButton";
|
||||
import Headline from "@/components/general/Headline";
|
||||
import Subheader from "@/components/general/Subheader";
|
||||
import { cn, shuffleQuestions } from "@/lib/utils";
|
||||
import { TResponseData } from "@formbricks/types/responses";
|
||||
import type { TSurveyMultipleChoiceSingleQuestion } from "@formbricks/types/surveys";
|
||||
import { useMemo, useRef, useState, useEffect } from "preact/hooks";
|
||||
import { cn, shuffleQuestions } from "../lib/utils";
|
||||
import { BackButton } from "./BackButton";
|
||||
import Headline from "./Headline";
|
||||
import Subheader from "./Subheader";
|
||||
import SubmitButton from "./SubmitButton";
|
||||
import { useEffect, useMemo, useRef, useState } from "preact/hooks";
|
||||
|
||||
interface MultipleChoiceSingleProps {
|
||||
question: TSurveyMultipleChoiceSingleQuestion;
|
||||
@@ -15,7 +15,6 @@ interface MultipleChoiceSingleProps {
|
||||
onBack: () => void;
|
||||
isFirstQuestion: boolean;
|
||||
isLastQuestion: boolean;
|
||||
brandColor: string;
|
||||
}
|
||||
|
||||
export default function MultipleChoiceSingleQuestion({
|
||||
@@ -26,7 +25,6 @@ export default function MultipleChoiceSingleQuestion({
|
||||
onBack,
|
||||
isFirstQuestion,
|
||||
isLastQuestion,
|
||||
brandColor,
|
||||
}: MultipleChoiceSingleProps) {
|
||||
const [otherSelected, setOtherSelected] = useState(
|
||||
!!value && !question.choices.find((c) => c.label === value)
|
||||
@@ -73,8 +71,9 @@ export default function MultipleChoiceSingleQuestion({
|
||||
<div className="mt-4">
|
||||
<fieldset>
|
||||
<legend className="sr-only">Options</legend>
|
||||
|
||||
<div
|
||||
className="relative max-h-[42vh] space-y-2 overflow-y-auto rounded-md bg-white py-0.5 pr-2"
|
||||
className="bg-survey-bg relative max-h-[42vh] space-y-2 overflow-y-auto rounded-md py-0.5 pr-2"
|
||||
role="radiogroup">
|
||||
{questionChoices.map((choice, idx) => (
|
||||
<label
|
||||
@@ -89,8 +88,10 @@ export default function MultipleChoiceSingleQuestion({
|
||||
}
|
||||
}}
|
||||
className={cn(
|
||||
value === choice.label ? "z-10 border-slate-400 bg-slate-50" : "border-gray-200",
|
||||
"relative flex cursor-pointer flex-col rounded-md border p-4 text-slate-800 focus-within:border-slate-400 focus-within:bg-slate-50 hover:bg-slate-50 focus:outline-none "
|
||||
value === choice.label
|
||||
? "border-border-highlight bg-accent-selected-bg z-10"
|
||||
: "border-border",
|
||||
"text-heading focus-within:border-border-highlight focus-within:bg-accent-bg hover:bg-accent-bg relative flex cursor-pointer flex-col rounded-md border p-4 focus:outline-none"
|
||||
)}>
|
||||
<span className="flex items-center text-sm">
|
||||
<input
|
||||
@@ -99,14 +100,13 @@ export default function MultipleChoiceSingleQuestion({
|
||||
id={choice.id}
|
||||
name={question.id}
|
||||
value={choice.label}
|
||||
className="h-4 w-4 border border-slate-300 focus:ring-0 focus:ring-offset-0"
|
||||
className="border-brand text-brand h-4 w-4 border focus:ring-0 focus:ring-offset-0"
|
||||
aria-labelledby={`${choice.id}-label`}
|
||||
onChange={() => {
|
||||
setOtherSelected(false);
|
||||
onChange({ [question.id]: choice.label });
|
||||
}}
|
||||
checked={value === choice.label}
|
||||
style={{ borderColor: brandColor, color: brandColor }}
|
||||
required={question.required && idx === 0}
|
||||
/>
|
||||
<span id={`${choice.id}-label`} className="ml-3 font-medium">
|
||||
@@ -119,8 +119,10 @@ export default function MultipleChoiceSingleQuestion({
|
||||
<label
|
||||
tabIndex={questionChoices.length + 1}
|
||||
className={cn(
|
||||
value === otherOption.label ? "z-10 border-slate-400 bg-slate-50" : "border-gray-200",
|
||||
"relative flex cursor-pointer flex-col rounded-md border p-4 text-slate-800 focus-within:border-slate-400 focus-within:bg-slate-50 hover:bg-slate-50 focus:outline-none"
|
||||
value === otherOption.label
|
||||
? "border-border-highlight bg-accent-selected-bg z-10"
|
||||
: "border-border",
|
||||
"text-heading focus-within:border-border-highlight focus-within:bg-accent-bg hover:bg-accent-bg relative flex cursor-pointer flex-col rounded-md border p-4 focus:outline-none"
|
||||
)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key == "Enter") {
|
||||
@@ -135,14 +137,13 @@ export default function MultipleChoiceSingleQuestion({
|
||||
tabIndex={-1}
|
||||
name={question.id}
|
||||
value={otherOption.label}
|
||||
className="h-4 w-4 border border-slate-300 focus:ring-0 focus:ring-offset-0"
|
||||
className="border-brand text-brand h-4 w-4 border focus:ring-0 focus:ring-offset-0"
|
||||
aria-labelledby={`${otherOption.id}-label`}
|
||||
onChange={() => {
|
||||
setOtherSelected(!otherSelected);
|
||||
onChange({ [question.id]: "" });
|
||||
}}
|
||||
checked={otherSelected}
|
||||
style={{ borderColor: brandColor, color: brandColor }}
|
||||
/>
|
||||
<span id={`${otherOption.id}-label`} className="ml-3 font-medium">
|
||||
{otherOption.label}
|
||||
@@ -166,7 +167,7 @@ export default function MultipleChoiceSingleQuestion({
|
||||
}
|
||||
}}
|
||||
placeholder="Please specify"
|
||||
className="mt-3 flex h-10 w-full rounded-md border border-slate-300 bg-transparent bg-white px-3 py-2 text-sm text-slate-800 placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-500 dark:text-slate-300"
|
||||
className="placeholder:text-placeholder border-border bg-survey-bg text-heading focus:ring-focus mt-3 flex h-10 w-full rounded-md border px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
required={question.required}
|
||||
aria-labelledby={`${otherOption.id}-label`}
|
||||
/>
|
||||
@@ -189,7 +190,6 @@ export default function MultipleChoiceSingleQuestion({
|
||||
tabIndex={questionChoices.length + 2}
|
||||
buttonLabel={question.buttonLabel}
|
||||
isLastQuestion={isLastQuestion}
|
||||
brandColor={brandColor}
|
||||
onClick={() => {}}
|
||||
/>
|
||||
</div>
|
||||
@@ -1,10 +1,10 @@
|
||||
import { BackButton } from "@/components/buttons/BackButton";
|
||||
import SubmitButton from "@/components/buttons/SubmitButton";
|
||||
import Headline from "@/components/general/Headline";
|
||||
import Subheader from "@/components/general/Subheader";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { TResponseData } from "@formbricks/types/responses";
|
||||
import type { TSurveyNPSQuestion } from "@formbricks/types/surveys";
|
||||
import { cn } from "../lib/utils";
|
||||
import { BackButton } from "./BackButton";
|
||||
import Headline from "./Headline";
|
||||
import Subheader from "./Subheader";
|
||||
import SubmitButton from "./SubmitButton";
|
||||
|
||||
interface NPSQuestionProps {
|
||||
question: TSurveyNPSQuestion;
|
||||
@@ -14,7 +14,6 @@ interface NPSQuestionProps {
|
||||
onBack: () => void;
|
||||
isFirstQuestion: boolean;
|
||||
isLastQuestion: boolean;
|
||||
brandColor: string;
|
||||
}
|
||||
|
||||
export default function NPSQuestion({
|
||||
@@ -25,7 +24,6 @@ export default function NPSQuestion({
|
||||
onBack,
|
||||
isFirstQuestion,
|
||||
isLastQuestion,
|
||||
brandColor,
|
||||
}: NPSQuestionProps) {
|
||||
return (
|
||||
<form
|
||||
@@ -55,8 +53,8 @@ export default function NPSQuestion({
|
||||
}
|
||||
}}
|
||||
className={cn(
|
||||
value === number ? "z-10 border-slate-400 bg-slate-50" : "",
|
||||
"relative h-10 flex-1 cursor-pointer border bg-white text-center text-sm leading-10 text-slate-800 first:rounded-l-md last:rounded-r-md hover:bg-gray-100 focus:bg-gray-100 focus:outline-none"
|
||||
value === number ? "border-border-highlight bg-accent-selected-bg z-10" : "border-border",
|
||||
"bg-survey-bg text-heading hover:bg-accent-bg relative h-10 flex-1 cursor-pointer border text-center text-sm leading-10 first:rounded-l-md last:rounded-r-md focus:outline-none"
|
||||
)}>
|
||||
<input
|
||||
type="radio"
|
||||
@@ -78,7 +76,7 @@ export default function NPSQuestion({
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex justify-between px-1.5 text-xs leading-6 text-slate-500">
|
||||
<div className="text-info-text flex justify-between px-1.5 text-xs leading-6">
|
||||
<p>{question.lowerLabel}</p>
|
||||
<p>{question.upperLabel}</p>
|
||||
</div>
|
||||
@@ -101,7 +99,6 @@ export default function NPSQuestion({
|
||||
tabIndex={12}
|
||||
buttonLabel={question.buttonLabel}
|
||||
isLastQuestion={isLastQuestion}
|
||||
brandColor={brandColor}
|
||||
onClick={() => {}}
|
||||
/>
|
||||
)}
|
||||
@@ -1,9 +1,9 @@
|
||||
import { BackButton } from "@/components/buttons/BackButton";
|
||||
import SubmitButton from "@/components/buttons/SubmitButton";
|
||||
import Headline from "@/components/general/Headline";
|
||||
import Subheader from "@/components/general/Subheader";
|
||||
import { TResponseData } from "@formbricks/types/responses";
|
||||
import type { TSurveyOpenTextQuestion } from "@formbricks/types/surveys";
|
||||
import { BackButton } from "./BackButton";
|
||||
import Headline from "./Headline";
|
||||
import Subheader from "./Subheader";
|
||||
import SubmitButton from "./SubmitButton";
|
||||
import { useCallback } from "react";
|
||||
|
||||
interface OpenTextQuestionProps {
|
||||
@@ -14,7 +14,6 @@ interface OpenTextQuestionProps {
|
||||
onBack: () => void;
|
||||
isFirstQuestion: boolean;
|
||||
isLastQuestion: boolean;
|
||||
brandColor: string;
|
||||
autoFocus?: boolean;
|
||||
}
|
||||
|
||||
@@ -26,7 +25,6 @@ export default function OpenTextQuestion({
|
||||
onBack,
|
||||
isFirstQuestion,
|
||||
isLastQuestion,
|
||||
brandColor,
|
||||
autoFocus = true,
|
||||
}: OpenTextQuestionProps) {
|
||||
const handleInputChange = (inputValue: string) => {
|
||||
@@ -76,6 +74,7 @@ export default function OpenTextQuestion({
|
||||
type={question.inputType}
|
||||
onInput={(e) => handleInputChange(e.currentTarget.value)}
|
||||
autoFocus={autoFocus}
|
||||
className="border-border bg-survey-bg focus:border-border-highlight block w-full rounded-md border p-2 shadow-sm focus:outline-none focus:ring-0 sm:text-sm"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" && isInputEmpty(value as string)) {
|
||||
e.preventDefault(); // Prevent form submission
|
||||
@@ -85,9 +84,6 @@ export default function OpenTextQuestion({
|
||||
}}
|
||||
pattern={question.inputType === "phone" ? "[+][0-9 ]+" : ".*"}
|
||||
title={question.inputType === "phone" ? "Enter a valid phone number" : undefined}
|
||||
className={`block w-full rounded-md border
|
||||
border-slate-100
|
||||
bg-slate-50 p-2 shadow-sm focus:border-slate-500 focus:outline-none focus:ring-0 sm:text-sm`}
|
||||
/>
|
||||
) : (
|
||||
<textarea
|
||||
@@ -102,11 +98,10 @@ export default function OpenTextQuestion({
|
||||
type={question.inputType}
|
||||
onInput={(e) => handleInputChange(e.currentTarget.value)}
|
||||
autoFocus={autoFocus}
|
||||
className="border-border bg-survey-bg text-subheading focus:border-border-highlight block w-full rounded-md border p-2 shadow-sm focus:ring-0 sm:text-sm"
|
||||
pattern={question.inputType === "phone" ? "[+][0-9 ]+" : ".*"}
|
||||
title={question.inputType === "phone" ? "Please enter a valid phone number" : undefined}
|
||||
className={`block w-full rounded-md border
|
||||
border-slate-100
|
||||
bg-slate-50 p-2 shadow-sm focus:border-slate-500 focus:outline-none focus:ring-0 sm:text-sm`}></textarea>
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -120,12 +115,7 @@ export default function OpenTextQuestion({
|
||||
/>
|
||||
)}
|
||||
<div></div>
|
||||
<SubmitButton
|
||||
buttonLabel={question.buttonLabel}
|
||||
isLastQuestion={isLastQuestion}
|
||||
brandColor={brandColor}
|
||||
onClick={() => {}}
|
||||
/>
|
||||
<SubmitButton buttonLabel={question.buttonLabel} isLastQuestion={isLastQuestion} onClick={() => {}} />
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
@@ -1,11 +1,11 @@
|
||||
import { BackButton } from "@/components/buttons/BackButton";
|
||||
import SubmitButton from "@/components/buttons/SubmitButton";
|
||||
import Headline from "@/components/general/Headline";
|
||||
import Subheader from "@/components/general/Subheader";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { TResponseData } from "@formbricks/types/responses";
|
||||
import type { TSurveyPictureSelectionQuestion } from "@formbricks/types/surveys";
|
||||
import { useEffect } from "preact/hooks";
|
||||
import { cn } from "../lib/utils";
|
||||
import { BackButton } from "./BackButton";
|
||||
import Headline from "./Headline";
|
||||
import Subheader from "./Subheader";
|
||||
import SubmitButton from "./SubmitButton";
|
||||
|
||||
interface PictureSelectionProps {
|
||||
question: TSurveyPictureSelectionQuestion;
|
||||
@@ -15,7 +15,6 @@ interface PictureSelectionProps {
|
||||
onBack: () => void;
|
||||
isFirstQuestion: boolean;
|
||||
isLastQuestion: boolean;
|
||||
brandColor: string;
|
||||
}
|
||||
|
||||
export default function PictureSelectionQuestion({
|
||||
@@ -26,7 +25,6 @@ export default function PictureSelectionQuestion({
|
||||
onBack,
|
||||
isFirstQuestion,
|
||||
isLastQuestion,
|
||||
brandColor,
|
||||
}: PictureSelectionProps) {
|
||||
const addItem = (item: string) => {
|
||||
let values: string[] = [];
|
||||
@@ -95,7 +93,7 @@ export default function PictureSelectionQuestion({
|
||||
<div className="mt-4">
|
||||
<fieldset>
|
||||
<legend className="sr-only">Options</legend>
|
||||
<div className="relative grid max-h-[42vh] grid-cols-2 gap-x-5 gap-y-4 overflow-y-auto rounded-md bg-white pr-2.5">
|
||||
<div className="rounded-m bg-survey-bg relative grid max-h-[42vh] grid-cols-2 gap-x-5 gap-y-4 overflow-y-auto pr-2.5">
|
||||
{questionChoices.map((choice, idx) => (
|
||||
<label
|
||||
key={choice.id}
|
||||
@@ -106,17 +104,12 @@ export default function PictureSelectionQuestion({
|
||||
handleChange(choice.id);
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
borderColor:
|
||||
Array.isArray(value) && value.includes(choice.id) ? brandColor : "border-slate-400",
|
||||
color: brandColor,
|
||||
}}
|
||||
onClick={() => handleChange(choice.id)}
|
||||
className={cn(
|
||||
Array.isArray(value) && value.includes(choice.id)
|
||||
? `z-10 border-4 shadow-xl focus:border-4`
|
||||
? `border-brand text-brand z-10 border-4 shadow-xl focus:border-4`
|
||||
: "",
|
||||
"relative box-border inline-block h-28 w-full overflow-hidden rounded-xl border border-slate-400 focus:border-slate-600 focus:bg-slate-50 focus:outline-none"
|
||||
"border-border focus:border-border-highlight focus:bg-accent-selected-bg relative box-border inline-block h-28 w-full overflow-hidden rounded-xl border focus:outline-none"
|
||||
)}>
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
@@ -132,8 +125,10 @@ export default function PictureSelectionQuestion({
|
||||
type="checkbox"
|
||||
tabindex={-1}
|
||||
checked={Array.isArray(value) && value.includes(choice.id)}
|
||||
style={{ borderColor: brandColor, color: brandColor }}
|
||||
className="pointer-events-none absolute right-2 top-2 z-20 h-5 w-5 rounded border border-slate-400"
|
||||
className={cn(
|
||||
"border-border pointer-events-none absolute right-2 top-2 z-20 h-5 w-5 rounded border",
|
||||
Array.isArray(value) && value.includes(choice.id) ? "border-brand text-brand" : ""
|
||||
)}
|
||||
required={
|
||||
question.required && Array.isArray(value) && value.length ? false : question.required
|
||||
}
|
||||
@@ -145,8 +140,10 @@ export default function PictureSelectionQuestion({
|
||||
type="radio"
|
||||
tabindex={-1}
|
||||
checked={Array.isArray(value) && value.includes(choice.id)}
|
||||
style={{ borderColor: brandColor, color: brandColor }}
|
||||
className="pointer-events-none absolute right-2 top-2 z-20 h-5 w-5 "
|
||||
className={cn(
|
||||
"border-border pointer-events-none absolute right-2 top-2 z-20 h-5 w-5 rounded-full border",
|
||||
Array.isArray(value) && value.includes(choice.id) ? "border-brand text-brand" : ""
|
||||
)}
|
||||
required={
|
||||
question.required && Array.isArray(value) && value.length ? false : question.required
|
||||
}
|
||||
@@ -170,7 +167,6 @@ export default function PictureSelectionQuestion({
|
||||
tabIndex={questionChoices.length + 2}
|
||||
buttonLabel={question.buttonLabel}
|
||||
isLastQuestion={isLastQuestion}
|
||||
brandColor={brandColor}
|
||||
onClick={() => {}}
|
||||
/>
|
||||
</div>
|
||||
@@ -1,9 +1,10 @@
|
||||
import { BackButton } from "@/components/buttons/BackButton";
|
||||
import SubmitButton from "@/components/buttons/SubmitButton";
|
||||
import Headline from "@/components/general/Headline";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { TResponseData } from "@formbricks/types/responses";
|
||||
import type { TSurveyRatingQuestion } from "@formbricks/types/surveys";
|
||||
import { useState } from "preact/hooks";
|
||||
import { cn } from "../lib/utils";
|
||||
import { BackButton } from "./BackButton";
|
||||
import Headline from "./Headline";
|
||||
import {
|
||||
ConfusedFace,
|
||||
FrowningFace,
|
||||
@@ -15,9 +16,8 @@ import {
|
||||
SmilingFaceWithSmilingEyes,
|
||||
TiredFace,
|
||||
WearyFace,
|
||||
} from "./Smileys";
|
||||
import Subheader from "./Subheader";
|
||||
import SubmitButton from "./SubmitButton";
|
||||
} from "../general/Smileys";
|
||||
import Subheader from "../general/Subheader";
|
||||
|
||||
interface RatingQuestionProps {
|
||||
question: TSurveyRatingQuestion;
|
||||
@@ -27,7 +27,6 @@ interface RatingQuestionProps {
|
||||
onBack: () => void;
|
||||
isFirstQuestion: boolean;
|
||||
isLastQuestion: boolean;
|
||||
brandColor: string;
|
||||
}
|
||||
|
||||
export default function RatingQuestion({
|
||||
@@ -38,7 +37,6 @@ export default function RatingQuestion({
|
||||
onBack,
|
||||
isFirstQuestion,
|
||||
isLastQuestion,
|
||||
brandColor,
|
||||
}: RatingQuestionProps) {
|
||||
const [hoveredNumber, setHoveredNumber] = useState(0);
|
||||
|
||||
@@ -87,7 +85,7 @@ export default function RatingQuestion({
|
||||
key={number}
|
||||
onMouseOver={() => setHoveredNumber(number)}
|
||||
onMouseLeave={() => setHoveredNumber(0)}
|
||||
className="max-w-10 relative max-h-10 flex-1 cursor-pointer bg-white text-center text-sm leading-10">
|
||||
className="max-w-10 bg-survey-bg relative max-h-10 flex-1 cursor-pointer text-center text-sm leading-10">
|
||||
{question.scale === "number" ? (
|
||||
<label
|
||||
tabIndex={i + 1}
|
||||
@@ -97,10 +95,10 @@ export default function RatingQuestion({
|
||||
}
|
||||
}}
|
||||
className={cn(
|
||||
value === number ? "z-10 border-slate-400 bg-slate-50" : "",
|
||||
value === number ? "bg-accent-selected-bg border-border-highlight z-10" : "",
|
||||
a.length === number ? "rounded-r-md" : "",
|
||||
number === 1 ? "rounded-l-md" : "",
|
||||
"block h-full w-full border text-slate-800 hover:bg-gray-100 focus:bg-gray-100 focus:outline-none"
|
||||
"text-heading hover:bg-accent-bg focus:bg-accent-bg block h-full w-full border focus:outline-none"
|
||||
)}>
|
||||
<HiddenRadioInput number={number} />
|
||||
{number}
|
||||
@@ -114,14 +112,14 @@ export default function RatingQuestion({
|
||||
}
|
||||
}}
|
||||
className={cn(
|
||||
number <= hoveredNumber ? "text-yellow-500" : "",
|
||||
"flex h-full w-full justify-center focus:text-yellow-500 focus:outline-none"
|
||||
number <= hoveredNumber ? "text-rating-focus" : "text-heading",
|
||||
"focus:text-rating-focus flex h-full w-full justify-center focus:outline-none"
|
||||
)}
|
||||
onFocus={() => setHoveredNumber(number)}
|
||||
onBlur={() => setHoveredNumber(0)}>
|
||||
<HiddenRadioInput number={number} />
|
||||
{typeof value === "number" && value >= number ? (
|
||||
<span className="text-yellow-300">
|
||||
<span className="text-rating-fill">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
@@ -152,13 +150,18 @@ export default function RatingQuestion({
|
||||
</label>
|
||||
) : (
|
||||
<label
|
||||
className={cn(
|
||||
"flex h-full w-full justify-center",
|
||||
value === number || hoveredNumber === number
|
||||
? "stroke-rating-selected text-rating-selected"
|
||||
: "stroke-heading text-heading"
|
||||
)}
|
||||
tabIndex={i + 1}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key == "Enter") {
|
||||
handleSelect(number);
|
||||
}
|
||||
}}
|
||||
className="flex h-full w-full justify-center text-slate-800 focus:outline-none"
|
||||
onFocus={() => setHoveredNumber(number)}
|
||||
onBlur={() => setHoveredNumber(0)}>
|
||||
<HiddenRadioInput number={number} />
|
||||
@@ -172,7 +175,7 @@ export default function RatingQuestion({
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex justify-between px-1.5 text-xs leading-6 text-slate-500">
|
||||
<div className="text-subheading flex justify-between px-1.5 text-xs leading-6">
|
||||
<p className="w-1/2 text-left">{question.lowerLabel}</p>
|
||||
<p className="w-1/2 text-right">{question.upperLabel}</p>
|
||||
</div>
|
||||
@@ -195,7 +198,6 @@ export default function RatingQuestion({
|
||||
tabIndex={question.range + 1}
|
||||
buttonLabel={question.buttonLabel}
|
||||
isLastQuestion={isLastQuestion}
|
||||
brandColor={brandColor}
|
||||
onClick={() => {}}
|
||||
/>
|
||||
)}
|
||||
@@ -211,7 +213,7 @@ interface RatingSmileyProps {
|
||||
}
|
||||
|
||||
function RatingSmiley({ active, idx, range }: RatingSmileyProps): JSX.Element {
|
||||
const activeColor = "fill-yellow-500";
|
||||
const activeColor = "fill-rating-fill";
|
||||
const inactiveColor = "fill-none";
|
||||
let icons = [
|
||||
<TiredFace className={active ? activeColor : inactiveColor} />,
|
||||
@@ -1,15 +1,14 @@
|
||||
import { TSurveyWithTriggers } from "@formbricks/types/js";
|
||||
import { useEffect, useRef, useState } from "preact/hooks";
|
||||
import Progress from "./Progress";
|
||||
import Progress from "../general/Progress";
|
||||
|
||||
interface AutoCloseProps {
|
||||
survey: TSurveyWithTriggers;
|
||||
brandColor: string;
|
||||
onClose: () => void;
|
||||
children: any;
|
||||
}
|
||||
|
||||
export function AutoCloseWrapper({ survey, brandColor, onClose, children }: AutoCloseProps) {
|
||||
export function AutoCloseWrapper({ survey, onClose, children }: AutoCloseProps) {
|
||||
const [countdownProgress, setCountdownProgress] = useState(100);
|
||||
const [countdownStop, setCountdownStop] = useState(false);
|
||||
const startRef = useRef(performance.now());
|
||||
@@ -49,9 +48,7 @@ export function AutoCloseWrapper({ survey, brandColor, onClose, children }: Auto
|
||||
|
||||
return (
|
||||
<>
|
||||
{!countdownStop && survey.autoClose && (
|
||||
<Progress progress={countdownProgress} brandColor={brandColor} />
|
||||
)}
|
||||
{!countdownStop && survey.autoClose && <Progress progress={countdownProgress} />}
|
||||
<div onClick={handleStopCountdown} onMouseOver={handleStopCountdown} className="h-full w-full">
|
||||
{children}
|
||||
</div>
|
||||
@@ -1,7 +1,7 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import { TPlacement } from "@formbricks/types/common";
|
||||
import { VNode } from "preact";
|
||||
import { useEffect, useMemo, useRef, useState } from "preact/hooks";
|
||||
import { cn } from "../lib/utils";
|
||||
|
||||
interface ModalProps {
|
||||
children: VNode;
|
||||
@@ -108,7 +108,7 @@ export default function Modal({
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
class="relative rounded-md text-slate-400 hover:text-slate-500 focus:outline-none focus:ring-2 focus:ring-slate-500 focus:ring-offset-2">
|
||||
class="text-close-button hover:text-close-button-focus focus:ring-close-button-focus relative rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2">
|
||||
<span class="sr-only">Close</span>
|
||||
<svg
|
||||
class="h-4 w-4"
|
||||
@@ -1,11 +1,13 @@
|
||||
import { SurveyInline } from "@/components/general/SurveyInline";
|
||||
import { SurveyModal } from "@/components/general/SurveyModal";
|
||||
import { addCustomThemeToDom, addStylesToDom } from "@/lib/styles";
|
||||
import { SurveyInlineProps, SurveyModalProps } from "@/types/props";
|
||||
import { h, render } from "preact";
|
||||
import { SurveyModal } from "./components/SurveyModal";
|
||||
import { addStylesToDom } from "./lib/styles";
|
||||
import { SurveyInlineProps, SurveyModalProps } from "./types/props";
|
||||
import { SurveyInline } from "./components/SurveyInline";
|
||||
|
||||
export const renderSurveyInline = (props: SurveyInlineProps) => {
|
||||
export const renderSurveyInline = (props: SurveyInlineProps & { brandColor: string }) => {
|
||||
addStylesToDom();
|
||||
addCustomThemeToDom({ brandColor: props.brandColor });
|
||||
|
||||
const { containerId, ...surveyProps } = props;
|
||||
const element = document.getElementById(containerId);
|
||||
if (!element) {
|
||||
@@ -14,8 +16,10 @@ export const renderSurveyInline = (props: SurveyInlineProps) => {
|
||||
render(h(SurveyInline, surveyProps), element);
|
||||
};
|
||||
|
||||
export const renderSurveyModal = (props: SurveyModalProps) => {
|
||||
export const renderSurveyModal = (props: SurveyModalProps & { brandColor: string }) => {
|
||||
addStylesToDom();
|
||||
addCustomThemeToDom({ brandColor: props.brandColor });
|
||||
|
||||
// add container element to DOM
|
||||
const element = document.createElement("div");
|
||||
element.id = "formbricks-modal-container";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import global from "../styles/global.css?inline";
|
||||
import preflight from "../styles/preflight.css?inline";
|
||||
import global from "@/styles/global.css?inline";
|
||||
import preflight from "@/styles/preflight.css?inline";
|
||||
import editorCss from "../../../ui/Editor/stylesEditorFrontend.css?inline";
|
||||
|
||||
export const addStylesToDom = () => {
|
||||
@@ -10,3 +10,16 @@ export const addStylesToDom = () => {
|
||||
document.head.appendChild(styleElement);
|
||||
}
|
||||
};
|
||||
|
||||
export const addCustomThemeToDom = ({ brandColor }: { brandColor: string }) => {
|
||||
if (document.getElementById("formbricks__css") === null) return;
|
||||
|
||||
const styleElement = document.createElement("style");
|
||||
styleElement.id = "formbricks__css__custom";
|
||||
styleElement.innerHTML = `
|
||||
:root {
|
||||
--fb-brand-color: ${brandColor};
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(styleElement);
|
||||
};
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
/* @import "./survey.css"; */
|
||||
|
||||
/* Firefox */
|
||||
#fbjs * {
|
||||
scrollbar-width: thin;
|
||||
@@ -24,3 +26,55 @@
|
||||
border: 3px solid #cbd5e1;
|
||||
border-radius: 99px;
|
||||
}
|
||||
|
||||
/* this is for styling the HtmlBody component */
|
||||
.fb-htmlbody {
|
||||
@apply block text-sm font-normal leading-6;
|
||||
/* need to use !important because in packages/ui/components/editor/stylesEditorFrontend.css the color is defined for some classes */
|
||||
color: var(--fb-subheading-color) !important;
|
||||
}
|
||||
/* without this, it wont override the color */
|
||||
p.fb-editor-paragraph {
|
||||
color: var(--fb-subheading-color) !important;
|
||||
}
|
||||
|
||||
/* theming */
|
||||
:root {
|
||||
--slate-50: rgb(248 250 252);
|
||||
--slate-100: rgb(241 245 249);
|
||||
--slate-200: rgb(226 232 240);
|
||||
--slate-300: rgb(203 213 225);
|
||||
--slate-400: rgb(148 163 184);
|
||||
--slate-500: rgb(100 116 139);
|
||||
--slate-600: rgb(71 85 105);
|
||||
--slate-700: rgb(51 65 85);
|
||||
--slate-800: rgb(30 41 59);
|
||||
--slate-900: rgb(15 23 42);
|
||||
--gray-100: rgb(243 244 246);
|
||||
--gray-200: rgb(229 231 235);
|
||||
--yellow-300: rgb(253 224 71);
|
||||
--yellow-500: rgb(234 179 8);
|
||||
|
||||
/* Default Light Theme, you can override everything by changing these values */
|
||||
--fb-brand-color: rgb(255, 255, 255);
|
||||
--fb-brand-text-color: black;
|
||||
--fb-border-color: var(--slate-300);
|
||||
--fb-border-color-highlight: var(--slate-500);
|
||||
--fb-focus-color: var(--slate-500);
|
||||
--fb-heading-color: var(--slate-900);
|
||||
--fb-subheading-color: var(--slate-700);
|
||||
--fb-info-text-color: var(--slate-500);
|
||||
--fb-signature-text-color: var(--slate-400);
|
||||
--fb-survey-background-color: white;
|
||||
--fb-accent-background-color: var(--slate-200);
|
||||
--fb-accent-background-color-selected: var(--slate-100);
|
||||
--fb-placeholder-color: var(--slate-400);
|
||||
--fb-shadow-color: var(--slate-300);
|
||||
--fb-rating-fill: var(--yellow-300);
|
||||
--fb-rating-hover: var(--yellow-500);
|
||||
--fb-back-btn-border: transparent;
|
||||
--fb-submit-btn-border: transparent;
|
||||
--fb-rating-selected: black;
|
||||
--fb-close-btn-color: var(--slate-500);
|
||||
--fb-close-btn-color-hover: var(--slate-700);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import { TResponseData, TResponseUpdate } from "@formbricks/types/responses";
|
||||
|
||||
export interface SurveyBaseProps {
|
||||
survey: TSurveyWithTriggers;
|
||||
brandColor: string;
|
||||
isBrandingEnabled: boolean;
|
||||
activeQuestionId?: string;
|
||||
onDisplay?: () => void;
|
||||
|
||||
@@ -7,6 +7,29 @@ module.exports = {
|
||||
},
|
||||
content: ["./src/**/*.{tsx,ts,jsx,js}"],
|
||||
theme: {
|
||||
colors: {
|
||||
brand: "var(--fb-brand-color)",
|
||||
"on-brand": "var(--fb-brand-text-color)",
|
||||
border: "var(--fb-border-color)",
|
||||
"border-highlight": "var(--fb-border-color-highlight)",
|
||||
focus: "var(--fb-focus-color)",
|
||||
heading: "var(--fb-heading-color)",
|
||||
subheading: "var(--fb-subheading-color)",
|
||||
"info-text": "var(--fb-info-text-color)",
|
||||
signature: "var(--fb-signature-text-color)",
|
||||
"survey-bg": "var(--fb-survey-background-color)",
|
||||
"accent-bg": "var(--fb-accent-background-color)",
|
||||
"accent-selected-bg": "var(--fb-accent-background-color-selected)",
|
||||
placeholder: "var(--fb-placeholder-color)",
|
||||
shadow: "var(--fb-shadow-color)",
|
||||
"rating-fill": "var(--fb-rating-fill)",
|
||||
"rating-focus": "var(--fb-rating-hover)",
|
||||
"rating-selected": "var(--fb-rating-selected)",
|
||||
"back-button-border": "var(--fb-back-btn-border)",
|
||||
"submit-button-border": "var(--fb-submit-btn-border)",
|
||||
"close-button": "var(--fb-close-btn-color)",
|
||||
"close-button-focus": "var(--fb-close-btn-hover-color)",
|
||||
},
|
||||
extend: {
|
||||
zIndex: {
|
||||
999999: "999999",
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "preact"
|
||||
"jsxImportSource": "preact",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { resolve } from "path";
|
||||
import { defineConfig } from "vite";
|
||||
import preact from "@preact/preset-vite";
|
||||
import dts from "vite-plugin-dts";
|
||||
import tsconfigPaths from "vite-tsconfig-paths";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
@@ -18,5 +19,5 @@ export default defineConfig({
|
||||
fileName: "index",
|
||||
},
|
||||
},
|
||||
plugins: [preact(), dts({ rollupTypes: true })],
|
||||
plugins: [preact(), dts({ rollupTypes: true }), tsconfigPaths()],
|
||||
});
|
||||
|
||||
37
pnpm-lock.yaml
generated
37
pnpm-lock.yaml
generated
@@ -744,6 +744,9 @@ importers:
|
||||
vite-plugin-dts:
|
||||
specifier: ^3.6.0
|
||||
version: 3.6.2(typescript@5.2.2)(vite@4.5.0)
|
||||
vite-tsconfig-paths:
|
||||
specifier: ^4.2.1
|
||||
version: 4.2.1(typescript@5.2.2)(vite@4.5.0)
|
||||
|
||||
packages/tailwind-config:
|
||||
devDependencies:
|
||||
@@ -14296,6 +14299,10 @@ packages:
|
||||
merge2: 1.4.1
|
||||
slash: 3.0.0
|
||||
|
||||
/globrex@0.1.2:
|
||||
resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==}
|
||||
dev: true
|
||||
|
||||
/glogg@1.0.2:
|
||||
resolution: {integrity: sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA==}
|
||||
engines: {node: '>= 0.10'}
|
||||
@@ -22123,6 +22130,19 @@ packages:
|
||||
resolution: {integrity: sha512-pefrkcd4lmIVR0LA49Imjf9DYLK8vtWhqBPA3Ya1ir8xCW0O2yjL9dsCVvI7pCodLC5q7smNpEtDR2yVulQxOg==}
|
||||
dev: true
|
||||
|
||||
/tsconfck@2.1.2(typescript@5.2.2):
|
||||
resolution: {integrity: sha512-ghqN1b0puy3MhhviwO2kGF8SeMDNhEbnKxjK7h6+fvY9JAxqvXi8y5NAHSQv687OVboS2uZIByzGd45/YxrRHg==}
|
||||
engines: {node: ^14.13.1 || ^16 || >=18}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
typescript: ^4.3.5 || ^5.0.0
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
dependencies:
|
||||
typescript: 5.2.2
|
||||
dev: true
|
||||
|
||||
/tsconfig-paths@3.14.2:
|
||||
resolution: {integrity: sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==}
|
||||
dependencies:
|
||||
@@ -23063,6 +23083,23 @@ packages:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/vite-tsconfig-paths@4.2.1(typescript@5.2.2)(vite@4.5.0):
|
||||
resolution: {integrity: sha512-GNUI6ZgPqT3oervkvzU+qtys83+75N/OuDaQl7HmOqFTb0pjZsuARrRipsyJhJ3enqV8beI1xhGbToR4o78nSQ==}
|
||||
peerDependencies:
|
||||
vite: '*'
|
||||
peerDependenciesMeta:
|
||||
vite:
|
||||
optional: true
|
||||
dependencies:
|
||||
debug: 4.3.4
|
||||
globrex: 0.1.2
|
||||
tsconfck: 2.1.2(typescript@5.2.2)
|
||||
vite: 4.5.0(terser@5.22.0)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
- typescript
|
||||
dev: true
|
||||
|
||||
/vite@4.5.0(terser@5.22.0):
|
||||
resolution: {integrity: sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
|
||||
Reference in New Issue
Block a user