mirror of
https://github.com/formbricks/formbricks.git
synced 2026-03-09 09:09:56 -05:00
fix: autofocus and keyboard navigation issues (#3328)
This commit is contained in:
committed by
GitHub
parent
9cc83bc01a
commit
a6a815c014
@@ -19,13 +19,13 @@ export const LegalFooter = ({
|
||||
<div className="absolute bottom-0 z-[1500] h-10 w-full">
|
||||
<div className="mx-auto flex h-full max-w-lg items-center justify-center p-2 text-center text-xs text-slate-400 text-opacity-50">
|
||||
{IMPRINT_URL && (
|
||||
<Link href={IMPRINT_URL} target="_blank" className="hover:underline">
|
||||
<Link href={IMPRINT_URL} target="_blank" className="hover:underline" tabIndex={-1}>
|
||||
Imprint
|
||||
</Link>
|
||||
)}
|
||||
{IMPRINT_URL && PRIVACY_URL && <span className="px-2">|</span>}
|
||||
{PRIVACY_URL && (
|
||||
<Link href={PRIVACY_URL} target="_blank" className="hover:underline">
|
||||
<Link href={PRIVACY_URL} target="_blank" className="hover:underline" tabIndex={-1}>
|
||||
Privacy Policy
|
||||
</Link>
|
||||
)}
|
||||
@@ -34,7 +34,8 @@ export const LegalFooter = ({
|
||||
<Link
|
||||
href={`https://app.formbricks.com/s/clxbivtla014iye2vfrn436xd?surveyUrl=${surveyUrl}`}
|
||||
target="_blank"
|
||||
className="hover:underline">
|
||||
className="hover:underline"
|
||||
tabIndex={-1}>
|
||||
Report Survey
|
||||
</Link>
|
||||
)}
|
||||
|
||||
@@ -128,7 +128,7 @@ export const EndingCard = ({
|
||||
variablesData
|
||||
)}
|
||||
isLastQuestion={false}
|
||||
focus={autoFocusEnabled}
|
||||
focus={isCurrent ? autoFocusEnabled : false}
|
||||
onClick={handleSubmit}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import { HTMLAttributes } from "preact/compat";
|
||||
import { forwardRef } from "preact/compat";
|
||||
|
||||
export interface InputProps extends HTMLAttributes<HTMLInputElement> {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const Input = ({ className, ...props }: InputProps) => {
|
||||
export const Input = forwardRef<HTMLInputElement, InputProps>(({ className, ...props }, ref) => {
|
||||
return (
|
||||
<input
|
||||
{...props}
|
||||
ref={ref} // Forward the ref to the input element
|
||||
className={cn(
|
||||
"focus:fb-border-brand fb-bg-input-bg fb-flex fb-w-full fb-border fb-border-border fb-rounded-custom fb-px-3 fb-py-2 fb-text-sm fb-text-subheading placeholder:fb-text-placeholder focus:fb-outline-none focus:fb-ring-2 focus:fb-ring-offset-2 disabled:fb-cursor-not-allowed disabled:fb-opacity-50 dark:fb-border-slate-500 dark:fb-text-slate-300",
|
||||
className ?? ""
|
||||
@@ -16,4 +18,4 @@ export const Input = ({ className, ...props }: InputProps) => {
|
||||
dir="auto"
|
||||
/>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
@@ -282,6 +282,7 @@ export const QuestionConditional = ({
|
||||
ttc={ttc}
|
||||
setTtc={setTtc}
|
||||
currentQuestionId={currentQuestionId}
|
||||
autoFocusEnabled={autoFocusEnabled}
|
||||
/>
|
||||
) : question.type === TSurveyQuestionTypeEnum.Ranking ? (
|
||||
<RankingQuestion
|
||||
@@ -311,6 +312,7 @@ export const QuestionConditional = ({
|
||||
ttc={ttc}
|
||||
setTtc={setTtc}
|
||||
currentQuestionId={currentQuestionId}
|
||||
autoFocusEnabled={autoFocusEnabled}
|
||||
/>
|
||||
) : null;
|
||||
};
|
||||
|
||||
@@ -161,7 +161,8 @@ export const WelcomeCard = ({
|
||||
<SubmitButton
|
||||
buttonLabel={getLocalizedValue(buttonLabel, languageCode)}
|
||||
isLastQuestion={false}
|
||||
focus={autoFocusEnabled}
|
||||
focus={isCurrent ? autoFocusEnabled : false}
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
onClick={handleSubmit}
|
||||
type="button"
|
||||
onKeyDown={(e) => e.key === "Enter" && e.preventDefault()}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Subheader } from "@/components/general/Subheader";
|
||||
import { ScrollableContainer } from "@/components/wrappers/ScrollableContainer";
|
||||
import { getUpdatedTtc, useTtc } from "@/lib/ttc";
|
||||
import { useMemo, useRef, useState } from "preact/hooks";
|
||||
import { useCallback } from "react";
|
||||
import { getLocalizedValue } from "@formbricks/lib/i18n/utils";
|
||||
import { TResponseData, TResponseTtc } from "@formbricks/types/responses";
|
||||
import type { TSurveyAddressQuestion } from "@formbricks/types/surveys/types";
|
||||
@@ -23,6 +24,7 @@ interface AddressQuestionProps {
|
||||
ttc: TResponseTtc;
|
||||
setTtc: (ttc: TResponseTtc) => void;
|
||||
currentQuestionId: string;
|
||||
autoFocusEnabled: boolean;
|
||||
}
|
||||
|
||||
export const AddressQuestion = ({
|
||||
@@ -37,15 +39,16 @@ export const AddressQuestion = ({
|
||||
ttc,
|
||||
setTtc,
|
||||
currentQuestionId,
|
||||
autoFocusEnabled,
|
||||
}: AddressQuestionProps) => {
|
||||
const [startTime, setStartTime] = useState(performance.now());
|
||||
const isMediaAvailable = question.imageUrl || question.videoUrl;
|
||||
const formRef = useRef<HTMLFormElement>(null);
|
||||
useTtc(question.id, ttc, setTtc, startTime, setStartTime, question.id === currentQuestionId);
|
||||
|
||||
const safeValue = useMemo(() => {
|
||||
return Array.isArray(value) ? value : ["", "", "", "", "", ""];
|
||||
}, [value]);
|
||||
const isCurrent = question.id === currentQuestionId;
|
||||
|
||||
const fields = [
|
||||
{
|
||||
@@ -103,6 +106,16 @@ export const AddressQuestion = ({
|
||||
}
|
||||
};
|
||||
|
||||
const addressRef = useCallback(
|
||||
(currentElement: HTMLInputElement | null) => {
|
||||
// will focus on current element when the question ID matches the current question
|
||||
if (question.id && currentElement && autoFocusEnabled && question.id === currentQuestionId) {
|
||||
currentElement.focus();
|
||||
}
|
||||
},
|
||||
[question.id, autoFocusEnabled, currentQuestionId]
|
||||
);
|
||||
|
||||
return (
|
||||
<form key={question.id} onSubmit={handleSubmit} className="fb-w-full" ref={formRef}>
|
||||
<ScrollableContainer>
|
||||
@@ -146,6 +159,8 @@ export const AddressQuestion = ({
|
||||
className="fb-py-3"
|
||||
type={field.id === "email" ? "email" : "text"}
|
||||
onChange={(e) => handleChange(field.id, e?.currentTarget?.value ?? "")}
|
||||
ref={index === 0 ? addressRef : null}
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
/>
|
||||
)
|
||||
);
|
||||
@@ -153,10 +168,17 @@ export const AddressQuestion = ({
|
||||
</div>
|
||||
</div>
|
||||
</ScrollableContainer>
|
||||
<div className="fb-flex fb-w-full fb-justify-between fb-px-6 fb-py-4">
|
||||
<div className="fb-flex fb-flex-row-reverse fb-w-full fb-justify-between fb-px-6 fb-py-4">
|
||||
<SubmitButton
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
buttonLabel={getLocalizedValue(question.buttonLabel, languageCode)}
|
||||
isLastQuestion={isLastQuestion}
|
||||
onClick={() => {}}
|
||||
/>
|
||||
<div></div>
|
||||
{!isFirstQuestion && (
|
||||
<BackButton
|
||||
tabIndex={8}
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
backButtonLabel={getLocalizedValue(question.backButtonLabel, languageCode)}
|
||||
onClick={() => {
|
||||
const updatedttc = getUpdatedTtc(ttc, question.id, performance.now() - startTime);
|
||||
@@ -165,13 +187,6 @@ export const AddressQuestion = ({
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<div></div>
|
||||
<SubmitButton
|
||||
tabIndex={7}
|
||||
buttonLabel={getLocalizedValue(question.buttonLabel, languageCode)}
|
||||
isLastQuestion={isLastQuestion}
|
||||
onClick={() => {}}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
|
||||
@@ -41,8 +41,8 @@ export const CTAQuestion = ({
|
||||
}: CTAQuestionProps) => {
|
||||
const [startTime, setStartTime] = useState(performance.now());
|
||||
const isMediaAvailable = question.imageUrl || question.videoUrl;
|
||||
|
||||
useTtc(question.id, ttc, setTtc, startTime, setStartTime, question.id === currentQuestionId);
|
||||
const isCurrent = question.id === currentQuestionId;
|
||||
useTtc(question.id, ttc, setTtc, startTime, setStartTime, isCurrent);
|
||||
|
||||
return (
|
||||
<div key={question.id}>
|
||||
@@ -57,38 +57,13 @@ export const CTAQuestion = ({
|
||||
<HtmlBody htmlString={getLocalizedValue(question.html, languageCode)} questionId={question.id} />
|
||||
</div>
|
||||
</ScrollableContainer>
|
||||
<div className="fb-flex fb-w-full fb-justify-between fb-px-6 fb-py-4">
|
||||
{!isFirstQuestion && (
|
||||
<BackButton
|
||||
backButtonLabel={getLocalizedValue(question.backButtonLabel, languageCode)}
|
||||
onClick={() => {
|
||||
const updatedTtcObj = getUpdatedTtc(ttc, question.id, performance.now() - startTime);
|
||||
setTtc(updatedTtcObj);
|
||||
onSubmit({ [question.id]: "" }, updatedTtcObj);
|
||||
onBack();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<div className="fb-flex fb-w-full fb-justify-end">
|
||||
{!question.required && (
|
||||
<button
|
||||
dir="auto"
|
||||
tabIndex={0}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
const updatedTtcObj = getUpdatedTtc(ttc, question.id, performance.now() - startTime);
|
||||
setTtc(updatedTtcObj);
|
||||
onSubmit({ [question.id]: "" }, updatedTtcObj);
|
||||
onChange({ [question.id]: "" });
|
||||
}}
|
||||
className="fb-text-heading focus:fb-ring-focus fb-mr-4 fb-flex fb-items-center fb-rounded-md fb-px-3 fb-py-3 fb-text-base fb-font-medium fb-leading-4 hover:fb-opacity-90 focus:fb-outline-none focus:fb-ring-2 focus:fb-ring-offset-2">
|
||||
{getLocalizedValue(question.dismissButtonLabel, languageCode) || "Skip"}
|
||||
</button>
|
||||
)}
|
||||
<div className="fb-flex fb-flex-row-reverse fb-w-full fb-justify-between fb-px-6 fb-py-4">
|
||||
<div className="fb-flex fb-flex-row-reverse fb-w-full fb-justify-start">
|
||||
<SubmitButton
|
||||
buttonLabel={getLocalizedValue(question.buttonLabel, languageCode)}
|
||||
isLastQuestion={isLastQuestion}
|
||||
focus={autoFocusEnabled}
|
||||
focus={isCurrent ? autoFocusEnabled : false}
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
onClick={() => {
|
||||
if (question.buttonExternal && question.buttonUrl) {
|
||||
window?.open(question.buttonUrl, "_blank")?.focus();
|
||||
@@ -100,7 +75,34 @@ export const CTAQuestion = ({
|
||||
}}
|
||||
type="button"
|
||||
/>
|
||||
{!question.required && (
|
||||
<button
|
||||
dir="auto"
|
||||
type="button"
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
onClick={() => {
|
||||
const updatedTtcObj = getUpdatedTtc(ttc, question.id, performance.now() - startTime);
|
||||
setTtc(updatedTtcObj);
|
||||
onSubmit({ [question.id]: "" }, updatedTtcObj);
|
||||
onChange({ [question.id]: "" });
|
||||
}}
|
||||
className="fb-text-heading focus:fb-ring-focus fb-mr-4 fb-flex fb-items-center fb-rounded-md fb-px-3 fb-py-3 fb-text-base fb-font-medium fb-leading-4 hover:fb-opacity-90 focus:fb-outline-none focus:fb-ring-2 focus:fb-ring-offset-2">
|
||||
{getLocalizedValue(question.dismissButtonLabel, languageCode) || "Skip"}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{!isFirstQuestion && (
|
||||
<BackButton
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
backButtonLabel={getLocalizedValue(question.backButtonLabel, languageCode)}
|
||||
onClick={() => {
|
||||
const updatedTtcObj = getUpdatedTtc(ttc, question.id, performance.now() - startTime);
|
||||
setTtc(updatedTtcObj);
|
||||
onSubmit({ [question.id]: "" }, updatedTtcObj);
|
||||
onBack();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -44,7 +44,7 @@ export const CalQuestion = ({
|
||||
const isMediaAvailable = question.imageUrl || question.videoUrl;
|
||||
const [errorMessage, setErrorMessage] = useState("");
|
||||
useTtc(question.id, ttc, setTtc, startTime, setStartTime, question.id === currentQuestionId);
|
||||
|
||||
const isCurrent = question.id === currentQuestionId;
|
||||
const onSuccessfulBooking = useCallback(() => {
|
||||
onChange({ [question.id]: "booked" });
|
||||
const updatedttc = getUpdatedTtc(ttc, question.id, performance.now() - startTime);
|
||||
@@ -87,20 +87,22 @@ export const CalQuestion = ({
|
||||
</>
|
||||
</div>
|
||||
</ScrollableContainer>
|
||||
<div className="fb-flex fb-w-full fb-justify-between fb-px-6 fb-py-4">
|
||||
<div className="fb-flex fb-flex-row-reverse fb-w-full fb-justify-between fb-px-6 fb-py-4">
|
||||
{!question.required && (
|
||||
<SubmitButton
|
||||
buttonLabel={getLocalizedValue(question.buttonLabel, languageCode)}
|
||||
isLastQuestion={isLastQuestion}
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
/>
|
||||
)}
|
||||
<div></div>
|
||||
{!isFirstQuestion && (
|
||||
<BackButton
|
||||
backButtonLabel={getLocalizedValue(question.backButtonLabel, languageCode)}
|
||||
onClick={() => {
|
||||
onBack();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<div></div>
|
||||
{!question.required && (
|
||||
<SubmitButton
|
||||
buttonLabel={getLocalizedValue(question.buttonLabel, languageCode)}
|
||||
isLastQuestion={isLastQuestion}
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { HtmlBody } from "@/components/general/HtmlBody";
|
||||
import { QuestionMedia } from "@/components/general/QuestionMedia";
|
||||
import { ScrollableContainer } from "@/components/wrappers/ScrollableContainer";
|
||||
import { getUpdatedTtc, useTtc } from "@/lib/ttc";
|
||||
import { useState } from "preact/hooks";
|
||||
import { useCallback, useState } from "preact/hooks";
|
||||
import { getLocalizedValue } from "@formbricks/lib/i18n/utils";
|
||||
import { TResponseData, TResponseTtc } from "@formbricks/types/responses";
|
||||
import type { TSurveyConsentQuestion } from "@formbricks/types/surveys/types";
|
||||
@@ -37,12 +37,24 @@ export const ConsentQuestion = ({
|
||||
ttc,
|
||||
setTtc,
|
||||
currentQuestionId,
|
||||
autoFocusEnabled,
|
||||
}: ConsentQuestionProps) => {
|
||||
const [startTime, setStartTime] = useState(performance.now());
|
||||
const isMediaAvailable = question.imageUrl || question.videoUrl;
|
||||
const isCurrent = question.id === currentQuestionId;
|
||||
|
||||
useTtc(question.id, ttc, setTtc, startTime, setStartTime, question.id === currentQuestionId);
|
||||
|
||||
const consentRef = useCallback(
|
||||
(currentElement: HTMLLabelElement | null) => {
|
||||
// will focus on current element when the question ID matches the current question
|
||||
if (question.id && currentElement && autoFocusEnabled && question.id === currentQuestionId) {
|
||||
currentElement.focus();
|
||||
}
|
||||
},
|
||||
[question.id, autoFocusEnabled, currentQuestionId]
|
||||
);
|
||||
|
||||
return (
|
||||
<form
|
||||
key={question.id}
|
||||
@@ -66,8 +78,9 @@ export const ConsentQuestion = ({
|
||||
/>
|
||||
<div className="fb-bg-survey-bg fb-sticky -fb-bottom-2 fb-z-10 fb-w-full fb-px-1 fb-py-1">
|
||||
<label
|
||||
ref={consentRef}
|
||||
dir="auto"
|
||||
tabIndex={1}
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
id={`${question.id}-label`}
|
||||
onKeyDown={(e) => {
|
||||
// Accessibility: if spacebar was pressed pass this down to the input
|
||||
@@ -79,6 +92,7 @@ export const ConsentQuestion = ({
|
||||
}}
|
||||
className="fb-border-border fb-bg-input-bg fb-text-heading hover:fb-bg-input-bg-selected focus:fb-bg-input-bg-selected focus:fb-ring-brand fb-rounded-custom fb-relative fb-z-10 fb-my-2 fb-flex fb-w-full fb-cursor-pointer fb-items-center fb-border fb-p-4 fb-text-sm focus:fb-outline-none focus:fb-ring-2 focus:fb-ring-offset-2">
|
||||
<input
|
||||
tabIndex={-1}
|
||||
type="checkbox"
|
||||
id={question.id}
|
||||
name={question.id}
|
||||
@@ -103,10 +117,16 @@ export const ConsentQuestion = ({
|
||||
</div>
|
||||
</ScrollableContainer>
|
||||
|
||||
<div className="fb-flex fb-w-full fb-justify-between fb-px-6 fb-py-4">
|
||||
<div className="fb-flex fb-flex-row-reverse fb-w-full fb-justify-between fb-px-6 fb-py-4">
|
||||
<SubmitButton
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
buttonLabel={getLocalizedValue(question.buttonLabel, languageCode)}
|
||||
isLastQuestion={isLastQuestion}
|
||||
/>
|
||||
<div></div>
|
||||
{!isFirstQuestion && (
|
||||
<BackButton
|
||||
tabIndex={3}
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
backButtonLabel={getLocalizedValue(question.backButtonLabel, languageCode)}
|
||||
onClick={() => {
|
||||
const updatedTtcObj = getUpdatedTtc(ttc, question.id, performance.now() - startTime);
|
||||
@@ -116,12 +136,6 @@ export const ConsentQuestion = ({
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<div />
|
||||
<SubmitButton
|
||||
tabIndex={2}
|
||||
buttonLabel={getLocalizedValue(question.buttonLabel, languageCode)}
|
||||
isLastQuestion={isLastQuestion}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
|
||||
@@ -6,7 +6,7 @@ import { QuestionMedia } from "@/components/general/QuestionMedia";
|
||||
import { Subheader } from "@/components/general/Subheader";
|
||||
import { ScrollableContainer } from "@/components/wrappers/ScrollableContainer";
|
||||
import { getUpdatedTtc, useTtc } from "@/lib/ttc";
|
||||
import { useMemo, useRef, useState } from "preact/hooks";
|
||||
import { useCallback, useMemo, useRef, useState } from "preact/hooks";
|
||||
import { getLocalizedValue } from "@formbricks/lib/i18n/utils";
|
||||
import { TResponseData, TResponseTtc } from "@formbricks/types/responses";
|
||||
import type { TSurveyContactInfoQuestion } from "@formbricks/types/surveys/types";
|
||||
@@ -24,6 +24,7 @@ interface ContactInfoQuestionProps {
|
||||
ttc: TResponseTtc;
|
||||
setTtc: (ttc: TResponseTtc) => void;
|
||||
currentQuestionId: string;
|
||||
autoFocusEnabled: boolean;
|
||||
}
|
||||
|
||||
export const ContactInfoQuestion = ({
|
||||
@@ -38,12 +39,13 @@ export const ContactInfoQuestion = ({
|
||||
ttc,
|
||||
setTtc,
|
||||
currentQuestionId,
|
||||
autoFocusEnabled,
|
||||
}: ContactInfoQuestionProps) => {
|
||||
const [startTime, setStartTime] = useState(performance.now());
|
||||
const isMediaAvailable = question.imageUrl || question.videoUrl;
|
||||
const formRef = useRef<HTMLFormElement>(null);
|
||||
useTtc(question.id, ttc, setTtc, startTime, setStartTime, question.id === currentQuestionId);
|
||||
|
||||
const isCurrent = question.id === currentQuestionId;
|
||||
const safeValue = useMemo(() => {
|
||||
return Array.isArray(value) ? value : ["", "", "", "", ""];
|
||||
}, [value]);
|
||||
@@ -99,6 +101,16 @@ export const ContactInfoQuestion = ({
|
||||
}
|
||||
};
|
||||
|
||||
const contactInfoRef = useCallback(
|
||||
(currentElement: HTMLInputElement | null) => {
|
||||
// will focus on current element when the question ID matches the current question
|
||||
if (question.id && currentElement && autoFocusEnabled && question.id === currentQuestionId) {
|
||||
currentElement.focus();
|
||||
}
|
||||
},
|
||||
[question.id, autoFocusEnabled, currentQuestionId]
|
||||
);
|
||||
|
||||
return (
|
||||
<form key={question.id} onSubmit={handleSubmit} className="fb-w-full" ref={formRef}>
|
||||
<ScrollableContainer>
|
||||
@@ -142,6 +154,7 @@ export const ContactInfoQuestion = ({
|
||||
return (
|
||||
field.show && (
|
||||
<Input
|
||||
ref={index === 0 ? contactInfoRef : null}
|
||||
key={field.id}
|
||||
placeholder={isFieldRequired() ? `${field.placeholder}*` : field.placeholder}
|
||||
required={isFieldRequired()}
|
||||
@@ -149,6 +162,7 @@ export const ContactInfoQuestion = ({
|
||||
className="fb-py-3"
|
||||
type={inputType}
|
||||
onChange={(e) => handleChange(field.id, e?.currentTarget?.value ?? "")}
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
/>
|
||||
)
|
||||
);
|
||||
@@ -157,10 +171,16 @@ export const ContactInfoQuestion = ({
|
||||
</div>
|
||||
</ScrollableContainer>
|
||||
|
||||
<div className="fb-flex fb-w-full fb-justify-between fb-px-6 fb-py-4">
|
||||
<div className="fb-flex fb-flex-row-reverse fb-w-full fb-justify-between fb-px-6 fb-py-4">
|
||||
<SubmitButton
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
buttonLabel={getLocalizedValue(question.buttonLabel, languageCode)}
|
||||
isLastQuestion={isLastQuestion}
|
||||
onClick={() => {}}
|
||||
/>
|
||||
{!isFirstQuestion && (
|
||||
<BackButton
|
||||
tabIndex={8}
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
backButtonLabel={getLocalizedValue(question.backButtonLabel, languageCode)}
|
||||
onClick={() => {
|
||||
const updatedttc = getUpdatedTtc(ttc, question.id, performance.now() - startTime);
|
||||
@@ -169,13 +189,6 @@ export const ContactInfoQuestion = ({
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<div></div>
|
||||
<SubmitButton
|
||||
tabIndex={7}
|
||||
buttonLabel={getLocalizedValue(question.buttonLabel, languageCode)}
|
||||
isLastQuestion={isLastQuestion}
|
||||
onClick={() => {}}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
|
||||
@@ -92,7 +92,7 @@ export const DateQuestion = ({
|
||||
const [errorMessage, setErrorMessage] = useState("");
|
||||
const isMediaAvailable = question.imageUrl || question.videoUrl;
|
||||
useTtc(question.id, ttc, setTtc, startTime, setStartTime, question.id === currentQuestionId);
|
||||
|
||||
const isCurrent = question.id === currentQuestionId;
|
||||
const [datePickerOpen, setDatePickerOpen] = useState(false);
|
||||
const [selectedDate, setSelectedDate] = useState<Date | undefined>(value ? new Date(value) : undefined);
|
||||
const [hideInvalid, setHideInvalid] = useState(!selectedDate);
|
||||
@@ -162,7 +162,7 @@ export const DateQuestion = ({
|
||||
{!datePickerOpen && (
|
||||
<div
|
||||
onClick={() => setDatePickerOpen(true)}
|
||||
tabIndex={0}
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === " ") setDatePickerOpen(true);
|
||||
}}
|
||||
@@ -253,25 +253,23 @@ export const DateQuestion = ({
|
||||
</div>
|
||||
</div>
|
||||
</ScrollableContainer>
|
||||
<div className="fb-flex fb-w-full fb-justify-between fb-px-6 fb-py-4">
|
||||
<div>
|
||||
{!isFirstQuestion && (
|
||||
<BackButton
|
||||
backButtonLabel={getLocalizedValue(question.backButtonLabel, languageCode)}
|
||||
onClick={() => {
|
||||
const updatedTtcObj = getUpdatedTtc(ttc, question.id, performance.now() - startTime);
|
||||
setTtc(updatedTtcObj);
|
||||
onBack();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="fb-flex fb-flex-row-reverse fb-w-full fb-justify-between fb-px-6 fb-py-4">
|
||||
<SubmitButton
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
isLastQuestion={isLastQuestion}
|
||||
buttonLabel={getLocalizedValue(question.buttonLabel, languageCode)}
|
||||
tabIndex={0}
|
||||
/>
|
||||
{!isFirstQuestion && (
|
||||
<BackButton
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
backButtonLabel={getLocalizedValue(question.backButtonLabel, languageCode)}
|
||||
onClick={() => {
|
||||
const updatedTtcObj = getUpdatedTtc(ttc, question.id, performance.now() - startTime);
|
||||
setTtc(updatedTtcObj);
|
||||
onBack();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
|
||||
@@ -48,6 +48,7 @@ export const FileUploadQuestion = ({
|
||||
const [startTime, setStartTime] = useState(performance.now());
|
||||
const isMediaAvailable = question.imageUrl || question.videoUrl;
|
||||
useTtc(question.id, ttc, setTtc, startTime, setStartTime, question.id === currentQuestionId);
|
||||
const isCurrent = question.id === currentQuestionId;
|
||||
|
||||
return (
|
||||
<form
|
||||
@@ -103,20 +104,21 @@ export const FileUploadQuestion = ({
|
||||
/>
|
||||
</div>
|
||||
</ScrollableContainer>
|
||||
<div className="fb-flex fb-w-full fb-justify-between fb-px-6 fb-py-4">
|
||||
<div className="fb-flex fb-flex-row-reverse fb-w-full fb-justify-between fb-px-6 fb-py-4">
|
||||
<SubmitButton
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
buttonLabel={getLocalizedValue(question.buttonLabel, languageCode)}
|
||||
isLastQuestion={isLastQuestion}
|
||||
/>
|
||||
{!isFirstQuestion && (
|
||||
<BackButton
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
backButtonLabel={getLocalizedValue(question.backButtonLabel, languageCode)}
|
||||
onClick={() => {
|
||||
onBack();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<div></div>
|
||||
<SubmitButton
|
||||
buttonLabel={getLocalizedValue(question.buttonLabel, languageCode)}
|
||||
isLastQuestion={isLastQuestion}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
|
||||
@@ -42,7 +42,7 @@ export const MatrixQuestion = ({
|
||||
const [startTime, setStartTime] = useState(performance.now());
|
||||
const isMediaAvailable = question.imageUrl || question.videoUrl;
|
||||
useTtc(question.id, ttc, setTtc, startTime, setStartTime, question.id === currentQuestionId);
|
||||
|
||||
const isCurrent = question.id === currentQuestionId;
|
||||
const rowShuffleIdx = useMemo(() => {
|
||||
if (question.shuffleOption) {
|
||||
return getShuffledRowIndices(question.rows.length, question.shuffleOption);
|
||||
@@ -149,7 +149,7 @@ export const MatrixQuestion = ({
|
||||
{question.columns.map((column, columnIndex) => (
|
||||
<td
|
||||
key={columnIndex}
|
||||
tabIndex={0}
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
className={`fb-outline-brand fb-px-4 fb-py-2 fb-text-gray-800 ${columnIndex === question.columns.length - 1 ? "fb-rounded-r-custom" : ""}`}
|
||||
onClick={() =>
|
||||
handleSelect(
|
||||
@@ -195,21 +195,20 @@ export const MatrixQuestion = ({
|
||||
</div>
|
||||
</div>
|
||||
</ScrollableContainer>
|
||||
<div className="fb-flex fb-w-full fb-justify-between fb-px-6 fb-py-4">
|
||||
{!isFirstQuestion && (
|
||||
<BackButton
|
||||
backButtonLabel={getLocalizedValue(question.backButtonLabel, languageCode)}
|
||||
onClick={handleBackButtonClick}
|
||||
tabIndex={0}
|
||||
/>
|
||||
)}
|
||||
<div></div>
|
||||
<div className="fb-flex fb-flex-row-reverse fb-w-full fb-justify-between fb-px-6 fb-py-4">
|
||||
<SubmitButton
|
||||
buttonLabel={getLocalizedValue(question.buttonLabel, languageCode)}
|
||||
isLastQuestion={isLastQuestion}
|
||||
onClick={() => {}}
|
||||
tabIndex={0}
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
/>
|
||||
{!isFirstQuestion && (
|
||||
<BackButton
|
||||
backButtonLabel={getLocalizedValue(question.backButtonLabel, languageCode)}
|
||||
onClick={handleBackButtonClick}
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
|
||||
@@ -43,7 +43,7 @@ export const MultipleChoiceMultiQuestion = ({
|
||||
const [startTime, setStartTime] = useState(performance.now());
|
||||
const isMediaAvailable = question.imageUrl || question.videoUrl;
|
||||
useTtc(question.id, ttc, setTtc, startTime, setStartTime, question.id === currentQuestionId);
|
||||
|
||||
const isCurrent = question.id === currentQuestionId;
|
||||
const shuffledChoicesIds = useMemo(() => {
|
||||
if (question.shuffleOption) {
|
||||
return getShuffledChoicesIds(question.choices, question.shuffleOption);
|
||||
@@ -165,7 +165,7 @@ export const MultipleChoiceMultiQuestion = ({
|
||||
return (
|
||||
<label
|
||||
key={choice.id}
|
||||
tabIndex={idx + 1}
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
className={cn(
|
||||
value.includes(getLocalizedValue(choice.label, languageCode))
|
||||
? "fb-border-brand fb-bg-input-bg-selected fb-z-10"
|
||||
@@ -216,7 +216,7 @@ export const MultipleChoiceMultiQuestion = ({
|
||||
})}
|
||||
{otherOption && (
|
||||
<label
|
||||
tabIndex={questionChoices.length + 1}
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
className={cn(
|
||||
value.includes(getLocalizedValue(otherOption.label, languageCode))
|
||||
? "fb-border-brand fb-bg-input-bg-selected fb-z-10"
|
||||
@@ -260,7 +260,7 @@ export const MultipleChoiceMultiQuestion = ({
|
||||
dir="auto"
|
||||
id={`${otherOption.id}-label`}
|
||||
name={question.id}
|
||||
tabIndex={questionChoices.length + 1}
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
value={otherValue}
|
||||
onChange={(e) => {
|
||||
setOtherValue(e.currentTarget.value);
|
||||
@@ -282,10 +282,15 @@ export const MultipleChoiceMultiQuestion = ({
|
||||
</div>
|
||||
</ScrollableContainer>
|
||||
|
||||
<div className="fb-flex fb-w-full fb-justify-between fb-px-6 fb-py-4">
|
||||
<div className="fb-flex fb-flex-row-reverse fb-w-full fb-justify-between fb-px-6 fb-py-4">
|
||||
<SubmitButton
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
buttonLabel={getLocalizedValue(question.buttonLabel, languageCode)}
|
||||
isLastQuestion={isLastQuestion}
|
||||
/>
|
||||
{!isFirstQuestion && (
|
||||
<BackButton
|
||||
tabIndex={questionChoices.length + 3}
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
backButtonLabel={getLocalizedValue(question.backButtonLabel, languageCode)}
|
||||
onClick={() => {
|
||||
const updatedTtcObj = getUpdatedTtc(ttc, question.id, performance.now() - startTime);
|
||||
@@ -294,12 +299,6 @@ export const MultipleChoiceMultiQuestion = ({
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<div></div>
|
||||
<SubmitButton
|
||||
tabIndex={questionChoices.length + 2}
|
||||
buttonLabel={getLocalizedValue(question.buttonLabel, languageCode)}
|
||||
isLastQuestion={isLastQuestion}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
|
||||
@@ -45,7 +45,7 @@ export const MultipleChoiceSingleQuestion = ({
|
||||
const otherSpecify = useRef<HTMLInputElement | null>(null);
|
||||
const choicesContainerRef = useRef<HTMLDivElement | null>(null);
|
||||
const isMediaAvailable = question.imageUrl || question.videoUrl;
|
||||
|
||||
const isCurrent = question.id === currentQuestionId;
|
||||
const shuffledChoicesIds = useMemo(() => {
|
||||
if (question.shuffleOption) {
|
||||
return getShuffledChoicesIds(question.choices, question.shuffleOption);
|
||||
@@ -132,8 +132,8 @@ export const MultipleChoiceSingleQuestion = ({
|
||||
return (
|
||||
<label
|
||||
dir="auto"
|
||||
tabIndex={idx + 1}
|
||||
key={choice.id}
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
className={cn(
|
||||
value === getLocalizedValue(choice.label, languageCode)
|
||||
? "fb-border-brand fb-bg-input-bg-selected fb-z-10"
|
||||
@@ -176,7 +176,7 @@ export const MultipleChoiceSingleQuestion = ({
|
||||
{otherOption && (
|
||||
<label
|
||||
dir="auto"
|
||||
tabIndex={questionChoices.length + 1}
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
className={cn(
|
||||
value === getLocalizedValue(otherOption.label, languageCode)
|
||||
? "fb-border-brand fb-bg-input-bg-selected fb-z-10"
|
||||
@@ -193,10 +193,10 @@ export const MultipleChoiceSingleQuestion = ({
|
||||
}}>
|
||||
<span className="fb-flex fb-items-center fb-text-sm">
|
||||
<input
|
||||
tabIndex={-1}
|
||||
dir="auto"
|
||||
type="radio"
|
||||
id={otherOption.id}
|
||||
tabIndex={-1}
|
||||
name={question.id}
|
||||
value={getLocalizedValue(otherOption.label, languageCode)}
|
||||
className="fb-border-brand fb-text-brand fb-h-4 fb-w-4 fb-border focus:fb-ring-0 focus:fb-ring-offset-0"
|
||||
@@ -217,7 +217,6 @@ export const MultipleChoiceSingleQuestion = ({
|
||||
{otherSelected && (
|
||||
<input
|
||||
ref={otherSpecify}
|
||||
tabIndex={questionChoices.length + 1}
|
||||
id={`${otherOption.id}-label`}
|
||||
dir="auto"
|
||||
name={question.id}
|
||||
@@ -240,11 +239,16 @@ export const MultipleChoiceSingleQuestion = ({
|
||||
</div>
|
||||
</div>
|
||||
</ScrollableContainer>
|
||||
<div className="fb-flex fb-w-full fb-justify-between fb-px-6 fb-py-4">
|
||||
<div className="fb-flex fb-flex-row-reverse fb-w-full fb-justify-between fb-px-6 fb-py-4">
|
||||
<SubmitButton
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
buttonLabel={getLocalizedValue(question.buttonLabel, languageCode)}
|
||||
isLastQuestion={isLastQuestion}
|
||||
/>
|
||||
{!isFirstQuestion && (
|
||||
<BackButton
|
||||
backButtonLabel={getLocalizedValue(question.backButtonLabel, languageCode)}
|
||||
tabIndex={questionChoices.length + 3}
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
onClick={() => {
|
||||
const updatedTtcObj = getUpdatedTtc(ttc, question.id, performance.now() - startTime);
|
||||
setTtc(updatedTtcObj);
|
||||
@@ -252,12 +256,6 @@ export const MultipleChoiceSingleQuestion = ({
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<div></div>
|
||||
<SubmitButton
|
||||
tabIndex={questionChoices.length + 2}
|
||||
buttonLabel={getLocalizedValue(question.buttonLabel, languageCode)}
|
||||
isLastQuestion={isLastQuestion}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
|
||||
@@ -42,7 +42,7 @@ export const NPSQuestion = ({
|
||||
const [startTime, setStartTime] = useState(performance.now());
|
||||
const [hoveredNumber, setHoveredNumber] = useState(-1);
|
||||
const isMediaAvailable = question.imageUrl || question.videoUrl;
|
||||
|
||||
const isCurrent = question.id === currentQuestionId;
|
||||
useTtc(question.id, ttc, setTtc, startTime, setStartTime, question.id === currentQuestionId);
|
||||
|
||||
const handleClick = (number: number) => {
|
||||
@@ -92,7 +92,7 @@ export const NPSQuestion = ({
|
||||
return (
|
||||
<label
|
||||
key={number}
|
||||
tabIndex={idx + 1}
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
onMouseOver={() => setHoveredNumber(number)}
|
||||
onMouseLeave={() => setHoveredNumber(-1)}
|
||||
onKeyDown={(e) => {
|
||||
@@ -127,6 +127,7 @@ export const NPSQuestion = ({
|
||||
className="fb-absolute fb-left-0 fb-h-full fb-w-full fb-cursor-pointer fb-opacity-0"
|
||||
onClick={() => handleClick(number)}
|
||||
required={question.required}
|
||||
tabIndex={-1}
|
||||
/>
|
||||
{number}
|
||||
</label>
|
||||
@@ -141,10 +142,17 @@ export const NPSQuestion = ({
|
||||
</div>
|
||||
</div>
|
||||
</ScrollableContainer>
|
||||
<div className="fb-flex fb-w-full fb-justify-between fb-px-6 fb-py-4">
|
||||
<div className="fb-flex fb-flex-row-reverse fb-w-full fb-justify-between fb-px-6 fb-py-4">
|
||||
{!question.required && (
|
||||
<SubmitButton
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
buttonLabel={getLocalizedValue(question.buttonLabel, languageCode)}
|
||||
isLastQuestion={isLastQuestion}
|
||||
/>
|
||||
)}
|
||||
{!isFirstQuestion && (
|
||||
<BackButton
|
||||
tabIndex={isLastQuestion ? 12 : 13}
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
backButtonLabel={getLocalizedValue(question.backButtonLabel, languageCode)}
|
||||
onClick={() => {
|
||||
const updatedTtcObj = getUpdatedTtc(ttc, question.id, performance.now() - startTime);
|
||||
@@ -153,14 +161,6 @@ export const NPSQuestion = ({
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<div></div>
|
||||
{!question.required && (
|
||||
<SubmitButton
|
||||
tabIndex={12}
|
||||
buttonLabel={getLocalizedValue(question.buttonLabel, languageCode)}
|
||||
isLastQuestion={isLastQuestion}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
|
||||
@@ -44,7 +44,7 @@ export const OpenTextQuestion = ({
|
||||
}: OpenTextQuestionProps) => {
|
||||
const [startTime, setStartTime] = useState(performance.now());
|
||||
const isMediaAvailable = question.imageUrl || question.videoUrl;
|
||||
|
||||
const isCurrent = question.id === currentQuestionId;
|
||||
useTtc(question.id, ttc, setTtc, startTime, setStartTime, question.id === currentQuestionId);
|
||||
|
||||
const handleInputChange = (inputValue: string) => {
|
||||
@@ -96,7 +96,7 @@ export const OpenTextQuestion = ({
|
||||
{question.longAnswer === false ? (
|
||||
<input
|
||||
ref={openTextRef}
|
||||
tabIndex={1}
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
name={question.id}
|
||||
id={question.id}
|
||||
placeholder={getLocalizedValue(question.placeholder, languageCode)}
|
||||
@@ -106,7 +106,6 @@ export const OpenTextQuestion = ({
|
||||
value={value ? (value as string) : ""}
|
||||
type={question.inputType}
|
||||
onInput={(e) => handleInputChange(e.currentTarget.value)}
|
||||
autoFocus={autoFocusEnabled}
|
||||
className="fb-border-border placeholder:fb-text-placeholder fb-text-subheading focus:fb-border-brand fb-bg-input-bg fb-rounded-custom fb-block fb-w-full fb-border fb-p-2 fb-shadow-sm focus:fb-outline-none focus:fb-ring-0 sm:fb-text-sm"
|
||||
pattern={question.inputType === "phone" ? "[0-9+ ]+" : ".*"}
|
||||
title={question.inputType === "phone" ? "Enter a valid phone number" : undefined}
|
||||
@@ -116,7 +115,7 @@ export const OpenTextQuestion = ({
|
||||
ref={openTextRef}
|
||||
rows={3}
|
||||
name={question.id}
|
||||
tabIndex={1}
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
aria-label="textarea"
|
||||
id={question.id}
|
||||
placeholder={getLocalizedValue(question.placeholder, languageCode)}
|
||||
@@ -128,7 +127,6 @@ export const OpenTextQuestion = ({
|
||||
handleInputChange(e.currentTarget.value);
|
||||
handleInputResize(e);
|
||||
}}
|
||||
autoFocus={autoFocusEnabled}
|
||||
className="fb-border-border placeholder:fb-text-placeholder fb-bg-input-bg fb-text-subheading focus:fb-border-brand fb-rounded-custom fb-block fb-w-full fb-border fb-p-2 fb-shadow-sm focus:fb-ring-0 sm:fb-text-sm"
|
||||
pattern={question.inputType === "phone" ? "[+][0-9 ]+" : ".*"}
|
||||
title={question.inputType === "phone" ? "Please enter a valid phone number" : undefined}
|
||||
@@ -137,9 +135,16 @@ export const OpenTextQuestion = ({
|
||||
</div>
|
||||
</div>
|
||||
</ScrollableContainer>
|
||||
<div className="fb-flex fb-w-full fb-justify-between fb-px-6 fb-py-4">
|
||||
<div className="fb-flex fb-flex-row-reverse fb-w-full fb-justify-between fb-px-6 fb-py-4">
|
||||
<SubmitButton
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
buttonLabel={getLocalizedValue(question.buttonLabel, languageCode)}
|
||||
isLastQuestion={isLastQuestion}
|
||||
onClick={() => {}}
|
||||
/>
|
||||
{!isFirstQuestion && (
|
||||
<BackButton
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
backButtonLabel={getLocalizedValue(question.backButtonLabel, languageCode)}
|
||||
onClick={() => {
|
||||
const updatedttc = getUpdatedTtc(ttc, question.id, performance.now() - startTime);
|
||||
@@ -148,12 +153,6 @@ export const OpenTextQuestion = ({
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<div></div>
|
||||
<SubmitButton
|
||||
buttonLabel={getLocalizedValue(question.buttonLabel, languageCode)}
|
||||
isLastQuestion={isLastQuestion}
|
||||
onClick={() => {}}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
|
||||
@@ -41,8 +41,8 @@ export const PictureSelectionQuestion = ({
|
||||
}: PictureSelectionProps) => {
|
||||
const [startTime, setStartTime] = useState(performance.now());
|
||||
const isMediaAvailable = question.imageUrl || question.videoUrl;
|
||||
|
||||
useTtc(question.id, ttc, setTtc, startTime, setStartTime, question.id === currentQuestionId);
|
||||
const isCurrent = question.id === currentQuestionId;
|
||||
useTtc(question.id, ttc, setTtc, startTime, setStartTime, isCurrent);
|
||||
|
||||
const addItem = (item: string) => {
|
||||
let values: string[] = [];
|
||||
@@ -111,10 +111,10 @@ export const PictureSelectionQuestion = ({
|
||||
<fieldset>
|
||||
<legend className="fb-sr-only">Options</legend>
|
||||
<div className="fb-bg-survey-bg fb-relative fb-grid fb-grid-cols-2 fb-gap-4">
|
||||
{questionChoices.map((choice, idx) => (
|
||||
{questionChoices.map((choice) => (
|
||||
<label
|
||||
key={choice.id}
|
||||
tabIndex={idx + 1}
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
htmlFor={choice.id}
|
||||
onKeyDown={(e) => {
|
||||
// Accessibility: if spacebar was pressed pass this down to the input
|
||||
@@ -196,10 +196,15 @@ export const PictureSelectionQuestion = ({
|
||||
</div>
|
||||
</div>
|
||||
</ScrollableContainer>
|
||||
<div className="fb-flex fb-w-full fb-justify-between fb-px-6 fb-py-4">
|
||||
<div className="fb-flex fb-flex-row-reverse fb-w-full fb-justify-between fb-px-6 fb-py-4">
|
||||
<SubmitButton
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
buttonLabel={getLocalizedValue(question.buttonLabel, languageCode)}
|
||||
isLastQuestion={isLastQuestion}
|
||||
/>
|
||||
{!isFirstQuestion && (
|
||||
<BackButton
|
||||
tabIndex={questionChoices.length + 3}
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
backButtonLabel={getLocalizedValue(question.backButtonLabel, languageCode)}
|
||||
onClick={() => {
|
||||
const updatedTtcObj = getUpdatedTtc(ttc, question.id, performance.now() - startTime);
|
||||
@@ -208,12 +213,6 @@ export const PictureSelectionQuestion = ({
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<div></div>
|
||||
<SubmitButton
|
||||
tabIndex={questionChoices.length + 2}
|
||||
buttonLabel={getLocalizedValue(question.buttonLabel, languageCode)}
|
||||
isLastQuestion={isLastQuestion}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
|
||||
@@ -42,7 +42,7 @@ export const RankingQuestion = ({
|
||||
currentQuestionId,
|
||||
}: RankingQuestionProps) => {
|
||||
const [startTime, setStartTime] = useState(performance.now());
|
||||
|
||||
const isCurrent = question.id === currentQuestionId;
|
||||
const shuffledChoicesIds = useMemo(() => {
|
||||
if (question.shuffleOption) {
|
||||
return getShuffledChoicesIds(question.choices, question.shuffleOption);
|
||||
@@ -148,7 +148,12 @@ export const RankingQuestion = ({
|
||||
return (
|
||||
<div
|
||||
key={item.id}
|
||||
tabIndex={idx + 1}
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === " ") {
|
||||
handleItemClick(item);
|
||||
}
|
||||
}}
|
||||
className={cn(
|
||||
"fb-flex fb-h-12 fb-items-center fb-mb-2 fb-border fb-border-border fb-transition-all fb-text-heading focus-within:fb-border-brand hover:fb-bg-input-bg-selected focus:fb-bg-input-bg-selected fb-rounded-custom fb-relative fb-cursor-pointer focus:fb-outline-none fb-transform fb-duration-500 fb-ease-in-out",
|
||||
isSorted ? "fb-bg-input-bg-selected" : "fb-bg-input-bg"
|
||||
@@ -173,6 +178,7 @@ export const RankingQuestion = ({
|
||||
{isSorted && (
|
||||
<div className="fb-flex fb-flex-col fb-h-full fb-grow-0 fb-border-l fb-border-border">
|
||||
<button
|
||||
tabIndex={-1}
|
||||
type="button"
|
||||
onClick={() => handleMove(item.id, "up")}
|
||||
className={cn(
|
||||
@@ -197,6 +203,7 @@ export const RankingQuestion = ({
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
tabIndex={-1}
|
||||
type="button"
|
||||
onClick={() => handleMove(item.id, "down")}
|
||||
className={cn(
|
||||
@@ -232,11 +239,16 @@ export const RankingQuestion = ({
|
||||
</div>
|
||||
</ScrollableContainer>
|
||||
|
||||
<div className="fb-flex fb-w-full fb-justify-between fb-px-6 fb-py-4">
|
||||
<div className="fb-flex fb-flex-row-reverse fb-w-full fb-justify-between fb-px-6 fb-py-4">
|
||||
<SubmitButton
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
buttonLabel={getLocalizedValue(question.buttonLabel, languageCode)}
|
||||
isLastQuestion={isLastQuestion}
|
||||
/>
|
||||
{!isFirstQuestion && (
|
||||
<BackButton
|
||||
backButtonLabel={getLocalizedValue(question.backButtonLabel, languageCode)}
|
||||
tabIndex={question.choices.length + 3}
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
onClick={() => {
|
||||
const updatedTtcObj = getUpdatedTtc(ttc, question.id, performance.now() - startTime);
|
||||
setTtc(updatedTtcObj);
|
||||
@@ -244,12 +256,6 @@ export const RankingQuestion = ({
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<div></div>
|
||||
<SubmitButton
|
||||
tabIndex={question.choices.length + 2}
|
||||
buttonLabel={getLocalizedValue(question.buttonLabel, languageCode)}
|
||||
isLastQuestion={isLastQuestion}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
|
||||
@@ -54,7 +54,7 @@ export const RatingQuestion = ({
|
||||
const [hoveredNumber, setHoveredNumber] = useState(0);
|
||||
const [startTime, setStartTime] = useState(performance.now());
|
||||
const isMediaAvailable = question.imageUrl || question.videoUrl;
|
||||
|
||||
const isCurrent = question.id === currentQuestionId;
|
||||
useTtc(question.id, ttc, setTtc, startTime, setStartTime, question.id === currentQuestionId);
|
||||
|
||||
const handleSelect = (number: number) => {
|
||||
@@ -138,7 +138,7 @@ export const RatingQuestion = ({
|
||||
className="fb-bg-survey-bg fb-flex-1 fb-text-center fb-text-sm">
|
||||
{question.scale === "number" ? (
|
||||
<label
|
||||
tabIndex={i + 1}
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
onKeyDown={(e) => {
|
||||
// Accessibility: if spacebar was pressed pass this down to the input
|
||||
if (e.key === " ") {
|
||||
@@ -167,7 +167,7 @@ export const RatingQuestion = ({
|
||||
</label>
|
||||
) : question.scale === "star" ? (
|
||||
<label
|
||||
tabIndex={i + 1}
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
onKeyDown={(e) => {
|
||||
// Accessibility: if spacebar was pressed pass this down to the input
|
||||
if (e.key === " ") {
|
||||
@@ -197,13 +197,13 @@ export const RatingQuestion = ({
|
||||
</label>
|
||||
) : (
|
||||
<label
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
className={cn(
|
||||
"fb-relative fb-flex fb-max-h-16 fb-min-h-9 fb-w-full fb-cursor-pointer fb-justify-center",
|
||||
value === number || hoveredNumber === number
|
||||
? "fb-stroke-rating-selected fb-text-rating-selected"
|
||||
: "fb-stroke-heading fb-text-heading focus:fb-border-accent-bg focus:fb-border-2 focus:fb-outline-none"
|
||||
)}
|
||||
tabIndex={i + 1}
|
||||
onKeyDown={(e) => {
|
||||
// Accessibility: if spacebar was pressed pass this down to the input
|
||||
if (e.key === " ") {
|
||||
@@ -240,10 +240,18 @@ export const RatingQuestion = ({
|
||||
</div>
|
||||
</div>
|
||||
</ScrollableContainer>
|
||||
<div className="fb-flex fb-w-full fb-justify-between fb-px-6 fb-py-4">
|
||||
<div className="fb-flex fb-flex-row-reverse fb-w-full fb-justify-between fb-px-6 fb-py-4">
|
||||
{!question.required && (
|
||||
<SubmitButton
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
buttonLabel={getLocalizedValue(question.buttonLabel, languageCode)}
|
||||
isLastQuestion={isLastQuestion}
|
||||
/>
|
||||
)}
|
||||
<div></div>
|
||||
{!isFirstQuestion && (
|
||||
<BackButton
|
||||
tabIndex={!question.required || value ? question.range + 2 : question.range + 1}
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
backButtonLabel={getLocalizedValue(question.backButtonLabel, languageCode)}
|
||||
onClick={() => {
|
||||
const updatedTtcObj = getUpdatedTtc(ttc, question.id, performance.now() - startTime);
|
||||
@@ -252,14 +260,6 @@ export const RatingQuestion = ({
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<div></div>
|
||||
{!question.required && (
|
||||
<SubmitButton
|
||||
tabIndex={question.range + 1}
|
||||
buttonLabel={getLocalizedValue(question.buttonLabel, languageCode)}
|
||||
isLastQuestion={isLastQuestion}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user