mirror of
https://github.com/formbricks/formbricks.git
synced 2026-02-26 10:30:48 -06:00
Improve Preview in Survey Editor with Mobile & Desktop View (#573)
* made modal component responsive * added tab switch * added mobile preview mode for surveys * did some refactors * did some refactors * added type defs * ran pnpm format * removed an unused comment * fixed variable name typo * fixed UI bugs and added mobile mockup to link surveys * restored changes from fix long description PR * fixed scroll to top issue and toggle hide bug * fixed minor animation bug * fixed placement issue * re-embed restart button, make phone preview more responsive --------- Co-authored-by: Matthias Nannt <mail@matthiasnannt.com>
This commit is contained in:
committed by
GitHub
parent
52a09aa3ae
commit
98cdf941e6
@@ -4,6 +4,7 @@ import FormbricksSignature from "@/components/preview/FormbricksSignature";
|
||||
import Modal from "@/components/preview/Modal";
|
||||
import Progress from "@/components/preview/Progress";
|
||||
import QuestionConditional from "@/components/preview/QuestionConditional";
|
||||
import TabOption from "@/components/preview/TabOption";
|
||||
import ThankYouCard from "@/components/preview/ThankYouCard";
|
||||
import type { Logic, Question } from "@formbricks/types/questions";
|
||||
import { Survey } from "@formbricks/types/surveys";
|
||||
@@ -11,6 +12,7 @@ import type { TEnvironment } from "@formbricks/types/v1/environment";
|
||||
import type { TProduct } from "@formbricks/types/v1/product";
|
||||
import { Button } from "@formbricks/ui";
|
||||
import { ArrowPathRoundedSquareIcon } from "@heroicons/react/24/outline";
|
||||
import { ComputerDesktopIcon, DevicePhoneMobileIcon } from "@heroicons/react/24/solid";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
interface PreviewSurveyProps {
|
||||
setActiveQuestionId: (id: string | null) => void;
|
||||
@@ -26,6 +28,77 @@ interface PreviewSurveyProps {
|
||||
environment: TEnvironment;
|
||||
}
|
||||
|
||||
function QuestionRenderer({
|
||||
activeQuestionId,
|
||||
lastActiveQuestionId,
|
||||
questions,
|
||||
brandColor,
|
||||
thankYouCard,
|
||||
gotoNextQuestion,
|
||||
showBackButton,
|
||||
goToPreviousQuestion,
|
||||
storedResponseValue,
|
||||
}) {
|
||||
return (
|
||||
<div>
|
||||
{(activeQuestionId || lastActiveQuestionId) === "thank-you-card" ? (
|
||||
<ThankYouCard
|
||||
brandColor={brandColor}
|
||||
headline={thankYouCard?.headline || "Thank you!"}
|
||||
subheader={thankYouCard?.subheader || "We appreciate your feedback."}
|
||||
/>
|
||||
) : (
|
||||
questions.map((question, idx) =>
|
||||
(activeQuestionId || lastActiveQuestionId) === question.id ? (
|
||||
<QuestionConditional
|
||||
key={question.id}
|
||||
question={question}
|
||||
brandColor={brandColor}
|
||||
lastQuestion={idx === questions.length - 1}
|
||||
onSubmit={gotoNextQuestion}
|
||||
storedResponseValue={storedResponseValue}
|
||||
goToNextQuestion={gotoNextQuestion}
|
||||
goToPreviousQuestion={showBackButton ? goToPreviousQuestion : undefined}
|
||||
autoFocus={false}
|
||||
/>
|
||||
) : null
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function PreviewModalContent({
|
||||
activeQuestionId,
|
||||
lastActiveQuestionId,
|
||||
questions,
|
||||
brandColor,
|
||||
thankYouCard,
|
||||
gotoNextQuestion,
|
||||
showBackButton,
|
||||
goToPreviousQuestion,
|
||||
storedResponseValue,
|
||||
showFormbricksSignature,
|
||||
}) {
|
||||
return (
|
||||
<div className="px-4 py-6 sm:p-6">
|
||||
<QuestionRenderer
|
||||
activeQuestionId={activeQuestionId}
|
||||
lastActiveQuestionId={lastActiveQuestionId}
|
||||
questions={questions}
|
||||
brandColor={brandColor}
|
||||
thankYouCard={thankYouCard}
|
||||
gotoNextQuestion={gotoNextQuestion}
|
||||
showBackButton={showBackButton}
|
||||
goToPreviousQuestion={goToPreviousQuestion}
|
||||
storedResponseValue={storedResponseValue}
|
||||
/>
|
||||
|
||||
{showFormbricksSignature && <FormbricksSignature />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function PreviewSurvey({
|
||||
setActiveQuestionId,
|
||||
activeQuestionId,
|
||||
@@ -46,8 +119,9 @@ export default function PreviewSurvey({
|
||||
const [finished, setFinished] = useState(false);
|
||||
const [storedResponseValue, setStoredResponseValue] = useState<any>();
|
||||
const [storedResponse, setStoredResponse] = useState<Record<string, any>>({});
|
||||
|
||||
const [previewMode, setPreviewMode] = useState("desktop");
|
||||
const showBackButton = progress !== 0 && !finished;
|
||||
const ContentRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (product) {
|
||||
@@ -108,6 +182,13 @@ export default function PreviewSurvey({
|
||||
}, [autoClose]);
|
||||
|
||||
useEffect(() => {
|
||||
if (ContentRef.current) {
|
||||
// scroll to top whenever question changes
|
||||
ContentRef.current.scrollTop = 0;
|
||||
}
|
||||
if (activeQuestionId !== "end") {
|
||||
setFinished(false);
|
||||
}
|
||||
if (activeQuestionId) {
|
||||
setLastActiveQuestionId(activeQuestionId);
|
||||
setProgress(calculateProgress(questions, activeQuestionId));
|
||||
@@ -268,105 +349,159 @@ export default function PreviewSurvey({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-5/6 flex-1 flex-col rounded-lg border border-slate-300 bg-slate-200 ">
|
||||
<div className="flex h-8 items-center rounded-t-lg bg-slate-100">
|
||||
<div className="ml-6 flex space-x-2">
|
||||
<div className="h-3 w-3 rounded-full bg-red-500"></div>
|
||||
<div className="h-3 w-3 rounded-full bg-amber-500"></div>
|
||||
<div className="h-3 w-3 rounded-full bg-emerald-500"></div>
|
||||
</div>
|
||||
<p>
|
||||
<span className="ml-4 font-mono text-sm text-slate-400">
|
||||
{previewType === "modal" ? "Your web app" : "Preview"}
|
||||
</span>
|
||||
</p>
|
||||
<div className="ml-auto flex items-center">
|
||||
<Button
|
||||
variant="minimal"
|
||||
className="mx-2 my-4 px-2 py-0.2 text-sm text-slate-500 bg-white"
|
||||
onClick={resetQuestionProgress}>
|
||||
Restart
|
||||
<ArrowPathRoundedSquareIcon className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{previewType === "modal" ? (
|
||||
<Modal
|
||||
isOpen={isModalOpen}
|
||||
placement={product.placement}
|
||||
highlightBorderColor={product.highlightBorderColor}>
|
||||
{!countdownStop && autoClose !== null && autoClose > 0 && (
|
||||
<Progress progress={countdownProgress} brandColor={brandColor} />
|
||||
)}
|
||||
<div
|
||||
onClick={() => handleStopCountdown()}
|
||||
onMouseOver={() => handleStopCountdown()}
|
||||
className="px-4 py-6 sm:p-6">
|
||||
{(activeQuestionId || lastActiveQuestionId) === "thank-you-card" ? (
|
||||
<ThankYouCard
|
||||
brandColor={brandColor}
|
||||
headline={thankYouCard?.headline || "Thank you!"}
|
||||
subheader={thankYouCard?.subheader || "We appreciate your feedback."}
|
||||
/>
|
||||
) : (
|
||||
questions.map((question, idx) =>
|
||||
(activeQuestionId || lastActiveQuestionId) === question.id ? (
|
||||
<QuestionConditional
|
||||
key={question.id}
|
||||
question={question}
|
||||
<div className="flex h-full w-full flex-col items-center justify-items-center">
|
||||
<div className="relative flex h-[95%] max-h-[95%] w-5/6 items-center justify-center rounded-lg border border-slate-300 bg-slate-200">
|
||||
{previewMode === "mobile" && (
|
||||
<>
|
||||
<div className="absolute right-0 top-0 m-2">
|
||||
<ResetProgressButton resetQuestionProgress={resetQuestionProgress} />
|
||||
</div>
|
||||
<div className="relative h-[90%] max-h-[40rem] w-80 overflow-hidden rounded-[3rem] border-8 border-slate-500 bg-slate-400">
|
||||
{/* below element is use to create notch for the mobile device mockup */}
|
||||
<div className="absolute left-1/2 right-1/2 top-0 z-20 h-4 w-1/2 -translate-x-1/2 transform rounded-b-md bg-slate-500"></div>
|
||||
{previewType === "modal" ? (
|
||||
<Modal
|
||||
isOpen={isModalOpen}
|
||||
placement={product.placement}
|
||||
highlightBorderColor={product.highlightBorderColor}
|
||||
previewMode="mobile">
|
||||
{!countdownStop && autoClose !== null && autoClose > 0 && (
|
||||
<Progress progress={countdownProgress} brandColor={brandColor} />
|
||||
)}
|
||||
<PreviewModalContent
|
||||
activeQuestionId={activeQuestionId}
|
||||
lastActiveQuestionId={lastActiveQuestionId}
|
||||
questions={questions}
|
||||
brandColor={brandColor}
|
||||
lastQuestion={idx === questions.length - 1}
|
||||
onSubmit={gotoNextQuestion}
|
||||
thankYouCard={thankYouCard}
|
||||
gotoNextQuestion={gotoNextQuestion}
|
||||
showBackButton={showBackButton}
|
||||
goToPreviousQuestion={goToPreviousQuestion}
|
||||
storedResponseValue={storedResponseValue}
|
||||
goToNextQuestion={gotoNextQuestion}
|
||||
goToPreviousQuestion={showBackButton ? goToPreviousQuestion : undefined}
|
||||
autoFocus={false}
|
||||
showFormbricksSignature={showFormbricksSignature}
|
||||
/>
|
||||
) : null
|
||||
)
|
||||
)}
|
||||
{showFormbricksSignature && <FormbricksSignature />}
|
||||
</div>
|
||||
<Progress progress={progress} brandColor={brandColor} />
|
||||
</Modal>
|
||||
) : (
|
||||
<div className="flex flex-grow flex-col overflow-y-auto">
|
||||
<div className="flex w-full flex-grow flex-col items-center justify-center bg-white py-6">
|
||||
<div className="w-full max-w-md">
|
||||
{(activeQuestionId || lastActiveQuestionId) === "thank-you-card" ? (
|
||||
<ThankYouCard
|
||||
brandColor={brandColor}
|
||||
headline={thankYouCard?.headline || "Thank you!"}
|
||||
subheader={thankYouCard?.subheader || "We appreciate your feedback."}
|
||||
/>
|
||||
<Progress progress={progress} brandColor={brandColor} />
|
||||
</Modal>
|
||||
) : (
|
||||
questions.map((question, idx) =>
|
||||
(activeQuestionId || lastActiveQuestionId) === question.id ? (
|
||||
<QuestionConditional
|
||||
key={question.id}
|
||||
question={question}
|
||||
brandColor={brandColor}
|
||||
lastQuestion={idx === questions.length - 1}
|
||||
onSubmit={gotoNextQuestion}
|
||||
storedResponseValue={storedResponseValue}
|
||||
goToNextQuestion={gotoNextQuestion}
|
||||
goToPreviousQuestion={showBackButton ? goToPreviousQuestion : undefined}
|
||||
autoFocus={false}
|
||||
/>
|
||||
) : null
|
||||
)
|
||||
<div
|
||||
className="absolute top-0 z-10 flex h-full w-full flex-grow flex-col overflow-y-auto"
|
||||
ref={ContentRef}>
|
||||
<div className="flex w-full flex-grow flex-col items-center justify-center bg-white py-6">
|
||||
<div className="w-full max-w-md px-4">
|
||||
<QuestionRenderer
|
||||
activeQuestionId={activeQuestionId}
|
||||
lastActiveQuestionId={lastActiveQuestionId}
|
||||
questions={questions}
|
||||
brandColor={brandColor}
|
||||
thankYouCard={thankYouCard}
|
||||
gotoNextQuestion={gotoNextQuestion}
|
||||
showBackButton={showBackButton}
|
||||
goToPreviousQuestion={goToPreviousQuestion}
|
||||
storedResponseValue={storedResponseValue}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="z-10 w-full rounded-b-lg bg-white">
|
||||
<div className="mx-auto max-w-md space-y-6 p-6 pt-4">
|
||||
<Progress progress={progress} brandColor={brandColor} />
|
||||
{showFormbricksSignature && <FormbricksSignature />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="z-10 w-full rounded-b-lg bg-white">
|
||||
<div className="mx-auto max-w-md space-y-6 p-6 pt-4">
|
||||
<Progress progress={progress} brandColor={brandColor} />
|
||||
{showFormbricksSignature && <FormbricksSignature />}
|
||||
</>
|
||||
)}
|
||||
{previewMode === "desktop" && (
|
||||
<div className="flex h-full w-5/6 flex-1 flex-col">
|
||||
<div className="flex h-8 w-full items-center rounded-t-lg bg-slate-100">
|
||||
<div className="ml-6 flex space-x-2">
|
||||
<div className="h-3 w-3 rounded-full bg-red-500"></div>
|
||||
<div className="h-3 w-3 rounded-full bg-amber-500"></div>
|
||||
<div className="h-3 w-3 rounded-full bg-emerald-500"></div>
|
||||
</div>
|
||||
<p className="ml-4 flex w-full justify-between font-mono text-sm text-slate-400">
|
||||
{previewType === "modal" ? "Your web app" : "Preview"}
|
||||
<ResetProgressButton resetQuestionProgress={resetQuestionProgress} />
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{previewType === "modal" ? (
|
||||
<Modal
|
||||
isOpen={isModalOpen}
|
||||
placement={product.placement}
|
||||
highlightBorderColor={product.highlightBorderColor}
|
||||
previewMode="desktop">
|
||||
{!countdownStop && autoClose !== null && autoClose > 0 && (
|
||||
<Progress progress={countdownProgress} brandColor={brandColor} />
|
||||
)}
|
||||
<PreviewModalContent
|
||||
activeQuestionId={activeQuestionId}
|
||||
lastActiveQuestionId={lastActiveQuestionId}
|
||||
questions={questions}
|
||||
brandColor={brandColor}
|
||||
thankYouCard={thankYouCard}
|
||||
gotoNextQuestion={gotoNextQuestion}
|
||||
showBackButton={showBackButton}
|
||||
goToPreviousQuestion={goToPreviousQuestion}
|
||||
storedResponseValue={storedResponseValue}
|
||||
showFormbricksSignature={showFormbricksSignature}
|
||||
/>
|
||||
<Progress progress={progress} brandColor={brandColor} />
|
||||
</Modal>
|
||||
) : (
|
||||
<div className="flex flex-grow flex-col overflow-y-auto" ref={ContentRef}>
|
||||
<div className="flex w-full flex-grow flex-col items-center justify-center bg-white py-6">
|
||||
<div className="w-full max-w-md">
|
||||
<QuestionRenderer
|
||||
activeQuestionId={activeQuestionId}
|
||||
lastActiveQuestionId={lastActiveQuestionId}
|
||||
questions={questions}
|
||||
brandColor={brandColor}
|
||||
thankYouCard={thankYouCard}
|
||||
gotoNextQuestion={gotoNextQuestion}
|
||||
showBackButton={showBackButton}
|
||||
goToPreviousQuestion={goToPreviousQuestion}
|
||||
storedResponseValue={storedResponseValue}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="z-10 w-full rounded-b-lg bg-white">
|
||||
<div className="mx-auto max-w-md space-y-6 p-6 pt-4">
|
||||
<Progress progress={progress} brandColor={brandColor} />
|
||||
{showFormbricksSignature && <FormbricksSignature />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
{/* for toggling between mobile and desktop mode */}
|
||||
<div className="mt-2 flex rounded-full border-2 border-slate-300 p-1">
|
||||
<TabOption
|
||||
active={previewMode === "mobile"}
|
||||
icon={<DevicePhoneMobileIcon className="mx-4 my-2 h-4 w-4 text-slate-700" />}
|
||||
onClick={() => setPreviewMode("mobile")}
|
||||
/>
|
||||
<TabOption
|
||||
active={previewMode === "desktop"}
|
||||
icon={<ComputerDesktopIcon className="mx-4 my-2 h-4 w-4 text-slate-700" />}
|
||||
onClick={() => setPreviewMode("desktop")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ResetProgressButton({ resetQuestionProgress }) {
|
||||
return (
|
||||
<Button
|
||||
variant="minimal"
|
||||
className="py-0.2 bg-white px-2 text-sm text-slate-500"
|
||||
onClick={resetQuestionProgress}>
|
||||
Restart
|
||||
<ArrowPathRoundedSquareIcon className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
import { getPlacementStyle } from "@/lib/preview";
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
import { PlacementType } from "@formbricks/types/js";
|
||||
import { ReactNode, useEffect, useMemo, useState } from "react";
|
||||
import { ReactNode, useEffect, useMemo, useState, useRef } from "react";
|
||||
|
||||
export default function Modal({
|
||||
children,
|
||||
isOpen,
|
||||
placement,
|
||||
previewMode,
|
||||
highlightBorderColor,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
isOpen: boolean;
|
||||
placement: PlacementType;
|
||||
previewMode: string;
|
||||
highlightBorderColor: string | null | undefined;
|
||||
}) {
|
||||
const [show, setShow] = useState(false);
|
||||
const modalRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const highlightBorderColorStyle = useMemo(() => {
|
||||
if (!highlightBorderColor) return {};
|
||||
@@ -29,15 +32,35 @@ export default function Modal({
|
||||
setShow(isOpen);
|
||||
}, [isOpen]);
|
||||
|
||||
// scroll to top whenever question in modal changes
|
||||
useEffect(() => {
|
||||
if (modalRef.current) {
|
||||
modalRef.current.scrollTop = 0;
|
||||
}
|
||||
}, [children]);
|
||||
|
||||
const slidingAnimationClass =
|
||||
previewMode === "desktop"
|
||||
? show
|
||||
? "translate-x-0 opacity-100"
|
||||
: "translate-x-32 opacity-0"
|
||||
: previewMode === "mobile"
|
||||
? show
|
||||
? "bottom-0"
|
||||
: "-bottom-full"
|
||||
: "";
|
||||
|
||||
return (
|
||||
<div aria-live="assertive" className="relative h-full w-full">
|
||||
<div aria-live="assertive" className="relative h-full w-full overflow-hidden">
|
||||
<div
|
||||
ref={modalRef}
|
||||
style={highlightBorderColorStyle}
|
||||
className={cn(
|
||||
show ? "translate-x-0 opacity-100" : "translate-x-32 opacity-0",
|
||||
"pointer-events-auto absolute h-fit max-h-[90%] w-full max-w-sm overflow-hidden overflow-y-auto rounded-lg bg-white shadow-lg ring-1 ring-black ring-opacity-5 transition-all duration-500 ease-in-out",
|
||||
getPlacementStyle(placement)
|
||||
)}
|
||||
style={highlightBorderColorStyle}>
|
||||
"pointer-events-auto absolute max-h-[90%] w-full h-fit max-w-sm overflow-y-auto rounded-lg bg-white shadow-lg ring-1 ring-black ring-opacity-5 transition-all duration-500 ease-in-out",
|
||||
previewMode === "desktop" ? getPlacementStyle(placement) : "max-w-full ",
|
||||
slidingAnimationClass
|
||||
)}>
|
||||
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -117,7 +117,7 @@ export default function MultipleChoiceMultiQuestion({
|
||||
<div className="mt-4">
|
||||
<fieldset>
|
||||
<legend className="sr-only">Options</legend>
|
||||
<div className="xs:max-h-[41vh] relative max-h-[60vh] space-y-2 overflow-y-auto rounded-md py-0.5 pr-2">
|
||||
<div className="relative space-y-2 rounded-md py-0.5">
|
||||
{questionChoices.map((choice) => (
|
||||
<div key={choice.id}>
|
||||
<label
|
||||
|
||||
@@ -102,7 +102,7 @@ export default function MultipleChoiceSingleQuestion({
|
||||
<div className="mt-4">
|
||||
<fieldset>
|
||||
<legend className="sr-only">Options</legend>
|
||||
<div className="xs:max-h-[41vh] relative max-h-[60vh] space-y-2 overflow-y-auto rounded-md py-0.5 pr-2">
|
||||
<div className="relative space-y-2 rounded-md py-0.5">
|
||||
{questionChoices.map((choice, idx) => (
|
||||
<label
|
||||
key={choice.id}
|
||||
|
||||
17
apps/web/components/preview/TabOption.tsx
Normal file
17
apps/web/components/preview/TabOption.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { ReactNode } from "react";
|
||||
|
||||
export default function OptionButton({
|
||||
active,
|
||||
icon,
|
||||
onClick,
|
||||
}: {
|
||||
active: boolean;
|
||||
icon: ReactNode;
|
||||
onClick: () => void;
|
||||
}) {
|
||||
return (
|
||||
<div className={`${active ? "rounded-full bg-slate-200" : ""} cursor-pointer`} onClick={onClick}>
|
||||
{icon}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -11,7 +11,7 @@ export const getPlacementStyle = (placement: PlacementType) => {
|
||||
case "bottomLeft":
|
||||
return "bottom-3 sm:left-3";
|
||||
case "center":
|
||||
return "top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2";
|
||||
return "top-1/2 left-1/2 transform !-translate-x-1/2 -translate-y-1/2";
|
||||
default:
|
||||
return "bottom-3 sm:right-3";
|
||||
}
|
||||
|
||||
@@ -3,6 +3,12 @@ import { h, VNode } from "preact";
|
||||
import { useEffect, useMemo, useRef, useState } from "preact/hooks";
|
||||
import { cn } from "../lib/utils";
|
||||
|
||||
// CSS classes object
|
||||
const mobileClasses = {
|
||||
show: "fb--translate-y-full",
|
||||
hide: "fb-translate-y-0",
|
||||
};
|
||||
|
||||
export default function Modal({
|
||||
children,
|
||||
isOpen,
|
||||
@@ -21,6 +27,7 @@ export default function Modal({
|
||||
close: () => void;
|
||||
}) {
|
||||
const [show, setShow] = useState(false);
|
||||
const [isMobile, setIsMobile] = useState(false);
|
||||
const isCenter = placement === "center";
|
||||
const modalRef = useRef(null);
|
||||
|
||||
@@ -42,20 +49,36 @@ export default function Modal({
|
||||
};
|
||||
}, [show, clickOutside, close, isCenter]);
|
||||
|
||||
const handleMobileClasses = (isMobile, show) => {
|
||||
return isMobile ? (show ? mobileClasses.show : mobileClasses.hide) : "";
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handleResize = () => {
|
||||
setIsMobile(window.innerWidth < 640);
|
||||
};
|
||||
window.addEventListener("resize", handleResize);
|
||||
handleResize();
|
||||
return () => {
|
||||
window.removeEventListener("resize", handleResize);
|
||||
};
|
||||
}, []);
|
||||
|
||||
// This classes will be applied only when screen size is greater than sm, hence sm is common prefix for all
|
||||
const getPlacementStyle = (placement: PlacementType) => {
|
||||
switch (placement) {
|
||||
case "bottomRight":
|
||||
return "fb-bottom-3 sm:fb-right-3";
|
||||
return "sm:fb-bottom-3 sm:fb-right-3";
|
||||
case "topRight":
|
||||
return "sm:fb-top-3 sm:fb-right-3 fb-bottom-3";
|
||||
return "sm:fb-top-3 sm:fb-right-3 sm:fb-bottom-3";
|
||||
case "topLeft":
|
||||
return "sm:fb-top-3 sm:fb-left-3 fb-bottom-3";
|
||||
return "sm:fb-top-3 sm:fb-left-3 sm:fb-bottom-3";
|
||||
case "bottomLeft":
|
||||
return "fb-bottom-3 sm:fb-left-3";
|
||||
return "sm:fb-bottom-3 sm:fb-left-3";
|
||||
case "center":
|
||||
return "fb-top-1/2 fb-left-1/2 fb-transform -fb-translate-x-1/2 -fb-translate-y-1/2";
|
||||
return "sm:fb-top-1/2 sm:fb-left-1/2 sm:fb-transform sm:-fb-translate-x-1/2 sm:-fb-translate-y-1/2";
|
||||
default:
|
||||
return "fb-bottom-3 sm:fb-right-3";
|
||||
return "sm:fb-bottom-3 sm:fb-right-3";
|
||||
}
|
||||
};
|
||||
|
||||
@@ -75,7 +98,7 @@ export default function Modal({
|
||||
aria-live="assertive"
|
||||
className={cn(
|
||||
isCenter ? "fb-pointer-events-auto" : "fb-pointer-events-none",
|
||||
"fb-fixed fb-inset-0 fb-flex fb-items-end fb-z-999999 fb-p-3 sm:fb-p-0"
|
||||
"fb-fixed fb-inset-0 fb-flex fb-items-end fb-z-999999"
|
||||
)}>
|
||||
<div
|
||||
className={cn(
|
||||
@@ -91,7 +114,9 @@ export default function Modal({
|
||||
className={cn(
|
||||
getPlacementStyle(placement),
|
||||
show ? "fb-opacity-100" : "fb-opacity-0",
|
||||
"fb-h-fit fb-pointer-events-auto fb-absolute fb-w-full fb-max-w-sm fb-overflow-hidden fb-rounded-lg fb-bg-white fb-shadow-lg fb-ring-1 fb-ring-black fb-ring-opacity-5 fb-transition-all fb-duration-500 fb-ease-in-out sm:fb-m-4"
|
||||
"fb-h-fit fb-pointer-events-auto fb-absolute fb-w-full sm:fb-max-w-sm fb-overflow-hidden fb-rounded-lg fb-bg-white fb-shadow-lg fb-ring-1 fb-ring-black fb-ring-opacity-5 fb-transition-all fb-duration-500 fb-ease-in-out sm:fb-m-4",
|
||||
isMobile && "fb-top-full",
|
||||
handleMobileClasses(isMobile, show)
|
||||
)}>
|
||||
<div class="fb-absolute fb-top-0 fb-right-0 fb-pt-4 fb-pr-4 fb-block">
|
||||
<button
|
||||
|
||||
@@ -118,7 +118,7 @@ export default function MultipleChoiceMultiQuestion({
|
||||
<div className="fb-mt-4">
|
||||
<fieldset>
|
||||
<legend className="fb-sr-only">Options</legend>
|
||||
<div className="fb-relative fb-space-y-2 fb-rounded-md fb-bg-white fb-max-h-[42vh] fb-overflow-y-auto fb-pr-2 fb-py-0.5">
|
||||
<div className="fb-relative fb-space-y-2 fb-rounded-md fb-bg-white">
|
||||
{questionChoices.map((choice) => (
|
||||
<label
|
||||
key={choice.id}
|
||||
|
||||
@@ -101,7 +101,7 @@ export default function MultipleChoiceSingleQuestion({
|
||||
<div className="fb-mt-4">
|
||||
<fieldset>
|
||||
<legend className="fb-sr-only">Options</legend>
|
||||
<div className="fb-relative fb-space-y-2 fb-rounded-md fb-bg-white fb-max-h-[42vh] fb-overflow-y-auto fb-pr-2 fb-py-0.5">
|
||||
<div className="fb-relative fb-space-y-2 fb-rounded-md fb-bg-white">
|
||||
{questionChoices.map((choice, idx) => (
|
||||
<label
|
||||
key={choice.id}
|
||||
|
||||
Reference in New Issue
Block a user