Skip to content

Commit

Permalink
fix: Adds focus heading behavior for forms with pages (groups) (#4201)
Browse files Browse the repository at this point in the history
* Adds scroll top behavior for forms with pages

* Updates to use combined DOM and refs for focussing

* Updates comments and names

* Updates naming

* Updates names part 2
  • Loading branch information
thiessenp-cds committed Aug 26, 2024
1 parent afcee50 commit cb31f6e
Show file tree
Hide file tree
Showing 7 changed files with 69 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Button } from "@clientComponents/forms";
import { Language } from "@lib/types/form-builder-types";

// Must be placed withing context of the GCFormsContext.Provider
export const BackButton = ({ language }: { language: Language }) => {
export const BackButton = ({ language, onClick }: { language: Language; onClick?: () => void }) => {
const { t } = useTranslation("review");
const { setGroup, previousGroup } = useGCFormsContext();
return (
Expand All @@ -13,6 +13,7 @@ export const BackButton = ({ language }: { language: Language }) => {
secondary={true}
onClick={() => {
setGroup(previousGroup);
onClick && onClick();
}}
>
{t("goBack", { lng: language })}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { ErrorSaving } from "@formBuilder/components/shared/ErrorSaving";
import { toast } from "@formBuilder/components/shared";
import { defaultForm } from "@lib/store/defaults";
import { showReviewPage } from "@lib/utils/form-builder/showReviewPage";
import { focusElement } from "@lib/client/clientHelpers";

export const Preview = ({
disableSubmit = true,
Expand Down Expand Up @@ -209,7 +210,10 @@ export const Preview = ({
return (
<>
{allowGrouping && isShowReviewPage && (
<BackButton language={language} />
<BackButton
language={language}
onClick={() => focusElement("h2")}
/>
)}
<Button
type="submit"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@ import { Button } from "@clientComponents/globals";
import { LockedSections } from "@formBuilder/components/shared/right-panel/treeview/types";
import { Language } from "@lib/types/form-builder-types";

export const BackButtonGroup = ({ language }: { language: Language }) => {
export const BackButtonGroup = ({
language,
onClick,
}: {
language: Language;
onClick?: () => void;
}) => {
const { currentGroup, getGroupHistory, handlePreviousAction } = useGCFormsContext();
const { t } = useTranslation("form-builder");

Expand All @@ -28,6 +34,7 @@ export const BackButtonGroup = ({ language }: { language: Language }) => {
onClick={async (e) => {
e.preventDefault();
handlePreviousAction();
onClick && onClick();
}}
type="button"
className="mr-4"
Expand Down
15 changes: 12 additions & 3 deletions components/clientComponents/forms/Form/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ const InnerForm: React.FC<InnerFormProps> = (props) => {
const isGroupsCheck = groupsCheck(props.allowGrouping);
const isShowReviewPage = showReviewPage(form);
const showIntro = isGroupsCheck ? currentGroup === LockedSections.START : true;
const groupsHeadingRef = useRef<HTMLHeadingElement>(null);

const { t } = useTranslation();

Expand Down Expand Up @@ -255,7 +256,9 @@ const InnerForm: React.FC<InnerFormProps> = (props) => {
isShowReviewPage &&
currentGroup !== LockedSections.REVIEW &&
currentGroup !== LockedSections.START && (
<h2 className="pb-8">{getGroupTitle(currentGroup, language as Language)}</h2>
<h2 className="pb-8" tabIndex={-1} ref={groupsHeadingRef}>
{getGroupTitle(currentGroup, language as Language)}
</h2>
)}

{children}
Expand All @@ -274,7 +277,10 @@ const InnerForm: React.FC<InnerFormProps> = (props) => {

<div className="flex">
{isGroupsCheck && isShowReviewPage && (
<BackButtonGroup language={language as Language} />
<BackButtonGroup
language={language as Language}
onClick={() => groupsHeadingRef.current?.focus()}
/>
)}
{props.renderSubmit ? (
props.renderSubmit({
Expand All @@ -285,7 +291,10 @@ const InnerForm: React.FC<InnerFormProps> = (props) => {
{isGroupsCheck &&
isShowReviewPage &&
currentGroup === LockedSections.REVIEW && (
<BackButton language={language as Language} />
<BackButton
language={language as Language}
onClick={() => groupsHeadingRef.current?.focus()}
/>
)}
<div className="inline-block">
<SubmitButton
Expand Down
2 changes: 2 additions & 0 deletions components/clientComponents/forms/NextButton/NextButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { Language } from "@lib/types/form-builder-types";

import { getLocalizedProperty } from "@lib/utils";
import { showReviewPage } from "@lib/utils/form-builder/showReviewPage";
import { focusElement } from "@lib/client/clientHelpers";

export const NextButton = ({
validateForm,
Expand Down Expand Up @@ -90,6 +91,7 @@ export const NextButton = ({
e.preventDefault();
if (await handleValidation()) {
handleNextAction();
focusElement("h2");
}
}}
type="button"
Expand Down
29 changes: 24 additions & 5 deletions components/clientComponents/forms/Review/Review.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,9 @@ export const Review = ({ language }: { language: Language }): React.ReactElement
const { groups, getValues, formRecord, getGroupHistory, getGroupTitle, matchedIds } =
useGCFormsContext();

const headingRef = useRef(null);
useFocusIt({ elRef: headingRef });
const groupsHeadingRef = useRef<HTMLHeadingElement>(null);
// Focus heading on load
useFocusIt({ elRef: groupsHeadingRef });

const reviewItems: ReviewItem[] = useMemo(() => {
const formValues: void | FormValues = getValues();
Expand Down Expand Up @@ -146,7 +147,9 @@ export const Review = ({ language }: { language: Language }): React.ReactElement

return (
<>
<h2 ref={headingRef}>{t("reviewForm", { lng: language })}</h2>
<h2 ref={groupsHeadingRef} tabIndex={-1}>
{t("reviewForm", { lng: language })}
</h2>
<div className="my-16">
{Array.isArray(reviewItems) &&
reviewItems.map((reviewItem) => {
Expand All @@ -159,14 +162,26 @@ export const Review = ({ language }: { language: Language }): React.ReactElement
className="mb-10 rounded-lg border-2 border-slate-400 px-6 py-4"
>
<h3 className="text-slate-700">
<EditButton reviewItem={reviewItem} theme="link">
<EditButton
reviewItem={reviewItem}
theme="link"
onClick={() => {
groupsHeadingRef.current?.focus();
}}
>
{title}
</EditButton>
</h3>
<div className="mb-10 ml-1">
<QuestionsAnswersList reviewItem={reviewItem} />
</div>
<EditButton reviewItem={reviewItem} theme="secondary">
<EditButton
reviewItem={reviewItem}
theme="secondary"
onClick={() => {
groupsHeadingRef.current?.focus();
}}
>
{t("edit", { lng: language })}
</EditButton>
</div>
Expand Down Expand Up @@ -233,10 +248,12 @@ const EditButton = ({
reviewItem,
theme,
children,
onClick,
}: {
reviewItem: ReviewItem;
theme: Theme;
children: React.ReactElement | string;
onClick?: () => void;
}): React.ReactElement => {
const { setGroup, clearHistoryAfterId } = useGCFormsContext();
return (
Expand All @@ -246,6 +263,8 @@ const EditButton = ({
onClick={() => {
setGroup(reviewItem.id);
clearHistoryAfterId(reviewItem.id);
// Focus groups heading on navigation
onClick && onClick();
}}
>
{children}
Expand Down
16 changes: 16 additions & 0 deletions lib/client/clientHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,22 @@ export const scrollToBottom = (containerEl: HTMLElement) => {
}
};

/**
* When a function does not have access to an element (ref), use the DOM to get an element and
* after the next tick (after the current event loop), focus the element. This moves the page to
* the element location and announces the text in the element to AT. Use a react ref over this to
* get and focus an element when possible.
* @param elementSelector selector to get the DOM element (first one found). Defaults to H1. Must
* have a tabIndex.
* @returns undefined
*/
export const focusElement = (elementSelector = "H1") => {
const NEXT_TICK = 0;
setTimeout(() => {
(document.querySelector(elementSelector) as HTMLElement)?.focus();
}, NEXT_TICK);
};

/**
* Like a UUID but smaller and not as unique. So best to append this to the element name.
* e.g. id = `myElementName-${randomId()}`
Expand Down

0 comments on commit cb31f6e

Please sign in to comment.