mirror of
https://github.com/formbricks/formbricks.git
synced 2026-02-13 11:18:46 -06:00
fix: keyboard nav for MQP with multiple questions (#6926)
Co-authored-by: pandeymangg <anshuman.pandey9999@gmail.com>
This commit is contained in:
@@ -43,8 +43,6 @@ export function AddressElement({
|
||||
return Array.isArray(value) ? value : ["", "", "", "", "", ""];
|
||||
}, [value]);
|
||||
|
||||
const isCurrent = element.id === currentElementId;
|
||||
|
||||
const fields = useMemo(
|
||||
() => [
|
||||
{
|
||||
@@ -166,7 +164,7 @@ export function AddressElement({
|
||||
handleChange(field.id, e.currentTarget.value);
|
||||
}}
|
||||
ref={index === 0 ? addressRef : null}
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
tabIndex={0}
|
||||
aria-label={field.label}
|
||||
dir={!safeValue[index] ? dir : "auto"}
|
||||
/>
|
||||
|
||||
@@ -32,7 +32,6 @@ export function ConsentElement({
|
||||
}: Readonly<ConsentElementProps>) {
|
||||
const [startTime, setStartTime] = useState(performance.now());
|
||||
const isMediaAvailable = element.imageUrl || element.videoUrl;
|
||||
const isCurrent = element.id === currentElementId;
|
||||
|
||||
useTtc(element.id, ttc, setTtc, startTime, setStartTime, element.id === currentElementId);
|
||||
|
||||
@@ -66,7 +65,7 @@ export function ConsentElement({
|
||||
/>
|
||||
<label
|
||||
ref={consentRef}
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
tabIndex={0} // NOSONAR - needed for keyboard navigation through options
|
||||
id={`${element.id}-label`}
|
||||
onKeyDown={(e) => {
|
||||
// Accessibility: if spacebar was pressed pass this down to the input
|
||||
|
||||
@@ -37,7 +37,6 @@ export function ContactInfoElement({
|
||||
const isMediaAvailable = element.imageUrl || element.videoUrl;
|
||||
const formRef = useRef<HTMLFormElement>(null);
|
||||
useTtc(element.id, ttc, setTtc, startTime, setStartTime, element.id === currentElementId);
|
||||
const isCurrent = element.id === currentElementId;
|
||||
const safeValue = useMemo(() => {
|
||||
return Array.isArray(value) ? value : ["", "", "", "", ""];
|
||||
}, [value]);
|
||||
@@ -149,7 +148,7 @@ export function ContactInfoElement({
|
||||
onChange={(e) => {
|
||||
handleChange(field.id, e.currentTarget.value);
|
||||
}}
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
tabIndex={0}
|
||||
aria-label={field.label}
|
||||
dir={!safeValue[index] ? dir : "auto"}
|
||||
/>
|
||||
|
||||
@@ -67,7 +67,7 @@ export function CTAElement({
|
||||
<button
|
||||
dir="auto"
|
||||
type="button"
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
tabIndex={0}
|
||||
onClick={handleExternalButtonClick}
|
||||
className="fb-text-heading focus:fb-ring-focus 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(element.ctaButtonLabel, languageCode)}
|
||||
|
||||
@@ -86,7 +86,6 @@ export function DateElement({
|
||||
const [errorMessage, setErrorMessage] = useState("");
|
||||
const isMediaAvailable = element.imageUrl || element.videoUrl;
|
||||
useTtc(element.id, ttc, setTtc, startTime, setStartTime, element.id === currentElementId);
|
||||
const isCurrent = element.id === currentElementId;
|
||||
const [datePickerOpen, setDatePickerOpen] = useState(false);
|
||||
const [selectedDate, setSelectedDate] = useState<Date | undefined>(value ? new Date(value) : undefined);
|
||||
const [hideInvalid, setHideInvalid] = useState(!selectedDate);
|
||||
@@ -161,7 +160,7 @@ export function DateElement({
|
||||
onClick={() => {
|
||||
setDatePickerOpen(true);
|
||||
}}
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
tabIndex={0}
|
||||
type="button"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === " ") setDatePickerOpen(true);
|
||||
|
||||
@@ -31,7 +31,6 @@ export function MatrixElement({
|
||||
const [startTime, setStartTime] = useState(performance.now());
|
||||
const isMediaAvailable = element.imageUrl || element.videoUrl;
|
||||
useTtc(element.id, ttc, setTtc, startTime, setStartTime, element.id === currentElementId);
|
||||
const isCurrent = element.id === currentElementId;
|
||||
const rowShuffleIdx = useMemo(() => {
|
||||
if (element.shuffleOption !== "none") {
|
||||
return getShuffledRowIndices(element.rows.length, element.shuffleOption);
|
||||
@@ -127,7 +126,7 @@ export function MatrixElement({
|
||||
{element.columns.map((column, columnIndex) => (
|
||||
<td
|
||||
key={`column-${columnIndex.toString()}`}
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
tabIndex={0}
|
||||
className={`fb-outline-brand fb-px-4 fb-py-2 fb-text-slate-800 ${columnIndex === element.columns.length - 1 ? "fb-rounded-r-custom" : ""}`}
|
||||
onClick={() => {
|
||||
handleSelect(
|
||||
|
||||
@@ -57,7 +57,6 @@ export function MultipleChoiceMultiElement({
|
||||
const [startTime, setStartTime] = useState(performance.now());
|
||||
const isMediaAvailable = element.imageUrl || element.videoUrl;
|
||||
useTtc(element.id, ttc, setTtc, startTime, setStartTime, element.id === currentElementId);
|
||||
const isCurrent = element.id === currentElementId;
|
||||
const shuffledChoicesIds = useMemo(() => {
|
||||
if (element.shuffleOption) {
|
||||
return getShuffledChoicesIds(element.choices, element.shuffleOption);
|
||||
@@ -212,9 +211,9 @@ export function MultipleChoiceMultiElement({
|
||||
return (
|
||||
<label
|
||||
key={choice.id}
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
tabIndex={0} // NOSONAR - needed for keyboard navigation through options
|
||||
className={labelClassName}
|
||||
onKeyDown={handleKeyDown(choice.id)}
|
||||
onKeyDown={handleKeyDown(choice.id)} // NOSONAR - needed for keyboard navigation through options
|
||||
autoFocus={idx === 0 && autoFocusEnabled}>
|
||||
<span className="fb-flex fb-items-center fb-text-sm">
|
||||
<input
|
||||
@@ -261,14 +260,15 @@ export function MultipleChoiceMultiElement({
|
||||
|
||||
return (
|
||||
<label
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
tabIndex={0} // NOSONAR - needed for keyboard navigation through options
|
||||
className={labelClassName}
|
||||
onKeyDown={handleKeyDown(otherOption.id)}>
|
||||
onKeyDown={handleKeyDown(otherOption.id)} // NOSONAR - needed for keyboard navigation through options
|
||||
>
|
||||
<span className="fb-flex fb-items-center fb-text-sm">
|
||||
<input
|
||||
type="checkbox"
|
||||
dir={dir}
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
tabIndex={-1}
|
||||
id={otherOption.id}
|
||||
name={element.id}
|
||||
value={otherLabel}
|
||||
@@ -289,7 +289,7 @@ export function MultipleChoiceMultiElement({
|
||||
id={`${otherOption.id}-specify`}
|
||||
maxLength={250}
|
||||
name={element.id}
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
tabIndex={0}
|
||||
value={otherValue}
|
||||
pattern=".*\S+.*"
|
||||
onChange={(e) => setOtherValue(e.currentTarget.value)}
|
||||
@@ -314,9 +314,10 @@ export function MultipleChoiceMultiElement({
|
||||
|
||||
return (
|
||||
<label
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
tabIndex={0} // NOSONAR - needed for keyboard navigation through options
|
||||
className={labelClassName}
|
||||
onKeyDown={handleKeyDown(noneOption.id)}>
|
||||
onKeyDown={handleKeyDown(noneOption.id)} // NOSONAR - needed for keyboard navigation through options
|
||||
>
|
||||
<span className="fb-flex fb-items-center fb-text-sm">
|
||||
<input
|
||||
type="checkbox"
|
||||
|
||||
@@ -36,7 +36,6 @@ export function MultipleChoiceSingleElement({
|
||||
const otherSpecify = useRef<HTMLInputElement | null>(null);
|
||||
const choicesContainerRef = useRef<HTMLDivElement | null>(null);
|
||||
const isMediaAvailable = element.imageUrl || element.videoUrl;
|
||||
const isCurrent = element.id === currentElementId;
|
||||
const shuffledChoicesIds = useMemo(() => {
|
||||
if (element.shuffleOption) {
|
||||
return getShuffledChoicesIds(element.choices, element.shuffleOption);
|
||||
@@ -158,9 +157,9 @@ export function MultipleChoiceSingleElement({
|
||||
return (
|
||||
<label
|
||||
key={choice.id}
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
tabIndex={0} // NOSONAR - needed for keyboard navigation through options
|
||||
className={labelClassName}
|
||||
onKeyDown={handleKeyDown(choice.id)}
|
||||
onKeyDown={handleKeyDown(choice.id)} // NOSONAR - needed for keyboard navigation through options
|
||||
autoFocus={idx === 0 && autoFocusEnabled}>
|
||||
<span className="fb-flex fb-items-center fb-text-sm">
|
||||
<input
|
||||
@@ -197,7 +196,11 @@ export function MultipleChoiceSingleElement({
|
||||
: "Please specify";
|
||||
|
||||
return (
|
||||
<label tabIndex={isCurrent ? 0 : -1} className={labelClassName} onKeyDown={handleOtherKeyDown}>
|
||||
<label
|
||||
tabIndex={0} // NOSONAR - needed for keyboard navigation through options
|
||||
className={labelClassName}
|
||||
onKeyDown={handleOtherKeyDown} // NOSONAR - needed for keyboard navigation through options
|
||||
>
|
||||
<span className="fb-flex fb-items-center fb-text-sm">
|
||||
<input
|
||||
tabIndex={-1}
|
||||
@@ -246,7 +249,7 @@ export function MultipleChoiceSingleElement({
|
||||
|
||||
return (
|
||||
<label
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
tabIndex={0} // NOSONAR - needed for keyboard navigation through options
|
||||
className={labelClassName}
|
||||
onKeyDown={handleKeyDown(noneOption.id)}>
|
||||
<span className="fb-flex fb-items-center fb-text-sm">
|
||||
|
||||
@@ -33,7 +33,6 @@ export function NPSElement({
|
||||
const [startTime, setStartTime] = useState(performance.now());
|
||||
const [hoveredNumber, setHoveredNumber] = useState(-1);
|
||||
const isMediaAvailable = element.imageUrl || element.videoUrl;
|
||||
const isCurrent = element.id === currentElementId;
|
||||
useTtc(element.id, ttc, setTtc, startTime, setStartTime, element.id === currentElementId);
|
||||
|
||||
const handleClick = (number: number) => {
|
||||
@@ -74,7 +73,7 @@ export function NPSElement({
|
||||
return (
|
||||
<label
|
||||
key={number}
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
tabIndex={0} // NOSONAR - needed for keyboard navigation through options
|
||||
onMouseOver={() => {
|
||||
setHoveredNumber(number);
|
||||
}}
|
||||
|
||||
@@ -169,7 +169,7 @@ export function OpenTextElement({
|
||||
<input
|
||||
ref={inputRef as RefObject<HTMLInputElement>}
|
||||
autoFocus={isCurrent ? autoFocusEnabled : undefined}
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
tabIndex={0}
|
||||
name={element.id}
|
||||
id={element.id}
|
||||
placeholder={getLocalizedValue(element.placeholder, languageCode)}
|
||||
@@ -195,7 +195,7 @@ export function OpenTextElement({
|
||||
rows={3}
|
||||
autoFocus={isCurrent ? autoFocusEnabled : undefined}
|
||||
name={element.id}
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
tabIndex={0}
|
||||
aria-label="textarea"
|
||||
id={element.id}
|
||||
placeholder={getLocalizedValue(element.placeholder, languageCode, true)}
|
||||
|
||||
@@ -148,7 +148,7 @@ export function PictureSelectionElement({
|
||||
<div className="fb-relative" key={choice.id}>
|
||||
<button
|
||||
type="button"
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
tabIndex={0}
|
||||
onKeyDown={handleKeyDown}
|
||||
onClick={() => handleChange(choice.id)}
|
||||
className={getButtonClassName(choice.id)}>
|
||||
|
||||
@@ -159,7 +159,7 @@ export function RankingElement({
|
||||
)}>
|
||||
<button
|
||||
autoFocus={idx === 0 && autoFocusEnabled}
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
tabIndex={0}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === " ") {
|
||||
e.preventDefault();
|
||||
|
||||
@@ -46,7 +46,6 @@ export function RatingElement({
|
||||
const [hoveredNumber, setHoveredNumber] = useState(0);
|
||||
const [startTime, setStartTime] = useState(performance.now());
|
||||
const isMediaAvailable = element.imageUrl || element.videoUrl;
|
||||
const isCurrent = element.id === currentElementId;
|
||||
useTtc(element.id, ttc, setTtc, startTime, setStartTime, element.id === currentElementId);
|
||||
|
||||
const handleSelect = (number: number) => {
|
||||
@@ -163,7 +162,7 @@ export function RatingElement({
|
||||
const renderNumberScale = (number: number, totalLength: number) => {
|
||||
return (
|
||||
<label
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
tabIndex={0} // NOSONAR - needed for keyboard navigation through options
|
||||
onKeyDown={handleKeyDown(number)}
|
||||
className={getNumberLabelClassName(number, totalLength)}>
|
||||
{element.isColorCodingEnabled && (
|
||||
@@ -180,7 +179,7 @@ export function RatingElement({
|
||||
const renderStarScale = (number: number) => {
|
||||
return (
|
||||
<label
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
tabIndex={0} // NOSONAR - needed for keyboard navigation through options
|
||||
onKeyDown={handleKeyDown(number)}
|
||||
className={getStarLabelClassName(number)}
|
||||
onFocus={handleFocus(number)}
|
||||
@@ -201,7 +200,7 @@ export function RatingElement({
|
||||
const renderSmileyScale = (number: number, idx: number) => {
|
||||
return (
|
||||
<label
|
||||
tabIndex={isCurrent ? 0 : -1}
|
||||
tabIndex={0} // NOSONAR - needed for keyboard navigation through options
|
||||
className={getSmileyLabelClassName(number)}
|
||||
onKeyDown={handleKeyDown(number)}
|
||||
onFocus={handleFocus(number)}
|
||||
|
||||
Reference in New Issue
Block a user