From a862db2dfa2255556808e5e4de8523c5c8e2e53c Mon Sep 17 00:00:00 2001 From: Jo Humphrey <31373245+jamdelion@users.noreply.github.com> Date: Wed, 18 Dec 2024 16:31:27 +0000 Subject: [PATCH] refactor: split Checklist into sub-components and hooks (#4088) --- .../components/Checklist/Public/Public.tsx | 245 +----------------- .../AutoAnsweredChecklist.tsx | 2 +- .../Public/components/ChecklistItems.tsx | 56 ++++ .../components/ExclusiveChecklistItem.tsx | 32 +++ .../components/GroupedChecklistOptions.tsx | 71 +++++ .../Public/components/VisibleChecklist.tsx | 138 ++++++++++ .../Public/hooks/useExclusiveOption.tsx | 23 ++ .../Public/hooks/useExpandedGroups.tsx | 26 ++ .../Public/hooks/useSortedOptions.tsx | 28 ++ .../Checklist/Public/tests/Public.test.tsx | 3 +- .../Checklist/Public/tests/mockOptions.ts | 5 +- .../src/@planx/components/Checklist/model.ts | 4 +- 12 files changed, 385 insertions(+), 248 deletions(-) rename editor.planx.uk/src/@planx/components/Checklist/Public/{ => components}/AutoAnsweredChecklist.tsx (90%) create mode 100644 editor.planx.uk/src/@planx/components/Checklist/Public/components/ChecklistItems.tsx create mode 100644 editor.planx.uk/src/@planx/components/Checklist/Public/components/ExclusiveChecklistItem.tsx create mode 100644 editor.planx.uk/src/@planx/components/Checklist/Public/components/GroupedChecklistOptions.tsx create mode 100644 editor.planx.uk/src/@planx/components/Checklist/Public/components/VisibleChecklist.tsx create mode 100644 editor.planx.uk/src/@planx/components/Checklist/Public/hooks/useExclusiveOption.tsx create mode 100644 editor.planx.uk/src/@planx/components/Checklist/Public/hooks/useExpandedGroups.tsx create mode 100644 editor.planx.uk/src/@planx/components/Checklist/Public/hooks/useSortedOptions.tsx diff --git a/editor.planx.uk/src/@planx/components/Checklist/Public/Public.tsx b/editor.planx.uk/src/@planx/components/Checklist/Public/Public.tsx index 2e5a08ded6..fa60443d44 100644 --- a/editor.planx.uk/src/@planx/components/Checklist/Public/Public.tsx +++ b/editor.planx.uk/src/@planx/components/Checklist/Public/Public.tsx @@ -1,40 +1,9 @@ -import Box from "@mui/material/Box"; -import Grid from "@mui/material/Grid"; -import Typography from "@mui/material/Typography"; -import { visuallyHidden } from "@mui/utils"; -import { - checklistValidationSchema, - getFlatOptions, - getLayout, -} from "@planx/components/Checklist/model"; -import ImageButton from "@planx/components/shared/Buttons/ImageButton"; -import Card from "@planx/components/shared/Preview/Card"; -import { CardHeader } from "@planx/components/shared/Preview/CardHeader/CardHeader"; -import { getIn, useFormik } from "formik"; -import { partition } from "lodash"; import { useStore } from "pages/FlowEditor/lib/store"; -import React, { useState } from "react"; -import { ExpandableList, ExpandableListItem } from "ui/public/ExpandableList"; -import FormWrapper from "ui/public/FormWrapper"; -import FullWidthWrapper from "ui/public/FullWidthWrapper"; -import ChecklistItem from "ui/shared/ChecklistItem/ChecklistItem"; -import ErrorWrapper from "ui/shared/ErrorWrapper"; -import { object } from "yup"; +import React from "react"; -import { Option } from "../../shared"; import { Props } from "../types"; -import { AutoAnsweredChecklist } from "./AutoAnsweredChecklist"; -import { - getInitialExpandedGroups, - toggleInArray, - toggleNonExclusiveCheckbox, -} from "./helpers"; - -export enum ChecklistLayout { - Basic, - Grouped, - Images, -} +import { AutoAnsweredChecklist } from "./components/AutoAnsweredChecklist"; +import { VisibleChecklist } from "./components/VisibleChecklist"; const ChecklistComponent: React.FC = (props) => { const autoAnswerableOptions = useStore( @@ -56,212 +25,4 @@ const ChecklistComponent: React.FC = (props) => { return ; }; -const VisibleChecklist: React.FC = (props) => { - const { - description = "", - groupedOptions, - handleSubmit, - howMeasured, - info, - options, - policyRef, - text, - img, - previouslySubmittedData, - id, - } = props; - - const formik = useFormik<{ checked: Array }>({ - initialValues: { - checked: previouslySubmittedData?.answers || [], - }, - onSubmit: (values) => { - handleSubmit?.({ answers: values.checked }); - }, - validateOnBlur: false, - validateOnChange: false, - validationSchema: object({ - checked: checklistValidationSchema(props), - }), - }); - - const setCheckedFieldValue = (optionIds: string[]) => { - const sortedCheckedIds = sortCheckedIds(optionIds); - formik.setFieldValue("checked", sortedCheckedIds); - }; - - const initialExpandedGroups = getInitialExpandedGroups( - groupedOptions, - previouslySubmittedData - ); - - const [expandedGroups, setExpandedGroups] = useState>( - initialExpandedGroups - ); - - const layout = getLayout({ options, groupedOptions }); - const flatOptions = getFlatOptions({ options, groupedOptions }); - - const sortCheckedIds = (ids: string[]): string[] => { - const originalIds = flatOptions.map((cb) => cb.id); - return ids.sort((a, b) => originalIds.indexOf(a) - originalIds.indexOf(b)); - }; - - const [exclusiveOptions, nonExclusiveOptions]: Option[][] = partition( - options, - (option) => option.data.exclusive - ); - - const exclusiveOrOption = exclusiveOptions[0]; - - const exclusiveOptionIsChecked = - exclusiveOrOption && formik.values.checked.includes(exclusiveOrOption.id); - - const toggleExclusiveCheckbox = (checkboxId: string) => { - return exclusiveOptionIsChecked ? [] : [checkboxId]; - }; - - const changeCheckbox = (id: string) => () => { - const currentCheckedIds = formik.values.checked; - - const currentCheckboxIsExclusiveOption = - exclusiveOrOption && id === exclusiveOrOption.id; - - if (currentCheckboxIsExclusiveOption) { - const newCheckedIds = toggleExclusiveCheckbox(id); - setCheckedFieldValue(newCheckedIds); - return; - } - const newCheckedIds = toggleNonExclusiveCheckbox( - id, - currentCheckedIds, - exclusiveOrOption - ); - setCheckedFieldValue(newCheckedIds); - }; - - return ( - - - - - - {text} - {nonExclusiveOptions.map((option) => - layout === ChecklistLayout.Basic ? ( - - - - - - ) : ( - - - - ) - )} - {exclusiveOrOption && ( - - - - or - - - - - - )} - - {groupedOptions && ( - - - - {groupedOptions.map((group, index) => { - const isExpanded = expandedGroups.includes(index); - return ( - { - setExpandedGroups((previous) => - toggleInArray(index, previous) - ); - }} - headingId={`group-${index}-heading`} - groupId={`group-${index}-content`} - title={group.title} - > - - {group.children.map((option) => ( - - ))} - - - ); - })} - - - - )} - - - - - ); -}; export default ChecklistComponent; diff --git a/editor.planx.uk/src/@planx/components/Checklist/Public/AutoAnsweredChecklist.tsx b/editor.planx.uk/src/@planx/components/Checklist/Public/components/AutoAnsweredChecklist.tsx similarity index 90% rename from editor.planx.uk/src/@planx/components/Checklist/Public/AutoAnsweredChecklist.tsx rename to editor.planx.uk/src/@planx/components/Checklist/Public/components/AutoAnsweredChecklist.tsx index 1bb8c1ae1a..bca1b81cc4 100644 --- a/editor.planx.uk/src/@planx/components/Checklist/Public/AutoAnsweredChecklist.tsx +++ b/editor.planx.uk/src/@planx/components/Checklist/Public/components/AutoAnsweredChecklist.tsx @@ -1,6 +1,6 @@ import React, { useEffect } from "react"; -import { Props } from "../types"; +import { Props } from "../../types"; // An auto-answered Checklist won't be seen by the user, but still leaves a breadcrumb export const AutoAnsweredChecklist: React.FC< diff --git a/editor.planx.uk/src/@planx/components/Checklist/Public/components/ChecklistItems.tsx b/editor.planx.uk/src/@planx/components/Checklist/Public/components/ChecklistItems.tsx new file mode 100644 index 0000000000..16f676ea8e --- /dev/null +++ b/editor.planx.uk/src/@planx/components/Checklist/Public/components/ChecklistItems.tsx @@ -0,0 +1,56 @@ +import Grid from "@mui/material/Grid"; +import { Option } from "@planx/components/shared"; +import ImageButton from "@planx/components/shared/Buttons/ImageButton"; +import { FormikProps } from "formik"; +import React from "react"; +import FormWrapper from "ui/public/FormWrapper"; +import ChecklistItem from "ui/shared/ChecklistItem/ChecklistItem"; + +import { ChecklistLayout } from "./VisibleChecklist"; + +interface Props { + nonExclusiveOptions: Option[]; + layout: ChecklistLayout; + changeCheckbox: (id: string) => () => void; + formik: FormikProps<{ checked: Array }>; + exclusiveOptionIsChecked: boolean; +} + +export const ChecklistItems = ({ + nonExclusiveOptions, + layout, + changeCheckbox, + formik, + exclusiveOptionIsChecked, +}: Props) => ( + <> + {nonExclusiveOptions.map((option: Option) => + layout === ChecklistLayout.Basic ? ( + + + + + + ) : ( + + + + ) + )} + +); diff --git a/editor.planx.uk/src/@planx/components/Checklist/Public/components/ExclusiveChecklistItem.tsx b/editor.planx.uk/src/@planx/components/Checklist/Public/components/ExclusiveChecklistItem.tsx new file mode 100644 index 0000000000..05cffb287f --- /dev/null +++ b/editor.planx.uk/src/@planx/components/Checklist/Public/components/ExclusiveChecklistItem.tsx @@ -0,0 +1,32 @@ +import Grid from "@mui/material/Grid"; +import Typography from "@mui/material/Typography"; +import { FormikProps } from "formik"; +import React from "react"; +import FormWrapper from "ui/public/FormWrapper"; +import ChecklistItem from "ui/shared/ChecklistItem/ChecklistItem"; + +import { Option } from "../../../shared"; + +export const ExclusiveChecklistItem = ({ + exclusiveOrOption, + changeCheckbox, + formik, +}: { + exclusiveOrOption: Option; + changeCheckbox: (id: string) => () => void; + formik: FormikProps<{ checked: Array }>; +}) => ( + + + + or + + + + +); diff --git a/editor.planx.uk/src/@planx/components/Checklist/Public/components/GroupedChecklistOptions.tsx b/editor.planx.uk/src/@planx/components/Checklist/Public/components/GroupedChecklistOptions.tsx new file mode 100644 index 0000000000..36259966fb --- /dev/null +++ b/editor.planx.uk/src/@planx/components/Checklist/Public/components/GroupedChecklistOptions.tsx @@ -0,0 +1,71 @@ +import Box from "@mui/material/Box"; +import Grid from "@mui/material/Grid"; +import { Group } from "@planx/components/Checklist/model"; +import { FormikProps } from "formik"; +import { Store } from "pages/FlowEditor/lib/store"; +import React from "react"; +import { ExpandableList, ExpandableListItem } from "ui/public/ExpandableList"; +import FormWrapper from "ui/public/FormWrapper"; +import ChecklistItem from "ui/shared/ChecklistItem/ChecklistItem"; + +import { Option } from "../../../shared"; +import { useExpandedGroups } from "../hooks/useExpandedGroups"; + +interface GroupedChecklistOptionsProps { + groupedOptions: Group