diff --git a/.gitignore b/.gitignore index 0f41fb70..41dc02f9 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ _site .turbo/ .vscode/ +.idea/ coverage/ html/ node_modules/ diff --git a/packages/common/src/locales/en/app.ts b/packages/common/src/locales/en/app.ts index d8b17262..052a96c7 100644 --- a/packages/common/src/locales/en/app.ts +++ b/packages/common/src/locales/en/app.ts @@ -41,5 +41,10 @@ export const en = { fieldLabel: 'Radio group label', errorTextMustContainChar: 'String must contain at least 1 character(s)', }, + repeater: { + ...defaults, + displayName: 'Repeatable Group', + errorTextMustContainChar: 'String must contain at least 1 character(s)', + }, }, }; diff --git a/packages/design/src/Form/components/RadioGroup/RadioGroup.tsx b/packages/design/src/Form/components/RadioGroup/RadioGroup.tsx index 663302f7..686f8d3b 100644 --- a/packages/design/src/Form/components/RadioGroup/RadioGroup.tsx +++ b/packages/design/src/Form/components/RadioGroup/RadioGroup.tsx @@ -13,17 +13,20 @@ export const RadioGroupPattern: PatternComponent = props => { {props.legend} {props.options.map((option, index) => { + const id = props.idSuffix ? `${option.id}${props.idSuffix}` : option.id; return (
-
diff --git a/packages/design/src/Form/components/Repeater/Repeater.stories.tsx b/packages/design/src/Form/components/Repeater/Repeater.stories.tsx new file mode 100644 index 00000000..5e9931c0 --- /dev/null +++ b/packages/design/src/Form/components/Repeater/Repeater.stories.tsx @@ -0,0 +1,17 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import Repeater from './index.js'; + +export default { + title: 'patterns/Repeater', + component: Repeater, + tags: ['autodocs'], +} satisfies Meta; + +export const RepeaterSection = { + args: { + legend: 'Default Heading', + type: 'repeater', + _patternId: 'test-id', + }, +} satisfies StoryObj; diff --git a/packages/design/src/Form/components/Repeater/Repeater.test.tsx b/packages/design/src/Form/components/Repeater/Repeater.test.tsx new file mode 100644 index 00000000..745c5a9e --- /dev/null +++ b/packages/design/src/Form/components/Repeater/Repeater.test.tsx @@ -0,0 +1,7 @@ +/** + * @vitest-environment jsdom + */ +import { describeStories } from '../../../test-helper.js'; +import meta, * as stories from './Repeater.stories.js'; + +describeStories(meta, stories); diff --git a/packages/design/src/Form/components/Repeater/edit.tsx b/packages/design/src/Form/components/Repeater/edit.tsx new file mode 100644 index 00000000..b69e09ef --- /dev/null +++ b/packages/design/src/Form/components/Repeater/edit.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { type RepeaterProps } from '@atj/forms'; +import { type PatternComponent } from '../../../Form/index.js'; + +const RepeaterEditView: PatternComponent = props => { + return ( +
+ {props.legend !== '' && props.legend !== undefined && ( + + {props.legend} + + )} + + {props.children} +
+ ); +}; + +export default RepeaterEditView; diff --git a/packages/design/src/Form/components/Repeater/index.tsx b/packages/design/src/Form/components/Repeater/index.tsx new file mode 100644 index 00000000..4177cd8a --- /dev/null +++ b/packages/design/src/Form/components/Repeater/index.tsx @@ -0,0 +1,95 @@ +import React from 'react'; +import { useFieldArray, useForm } from 'react-hook-form'; +import { type RepeaterProps } from '@atj/forms'; +import { type PatternComponent } from '../../../Form/index.js'; + +const Repeater: PatternComponent = props => { + const STORAGE_KEY = `repeater-${props._patternId}`; + + const loadInitialFields = (): number => { + const storedFields = localStorage.getItem(STORAGE_KEY); + if (storedFields) { + return parseInt(JSON.parse(storedFields), 10) || 1; + } + return 1; + }; + + const { control } = useForm({ + defaultValues: { + fields: Array(loadInitialFields()).fill({}), + }, + }); + + const { fields, append, remove } = useFieldArray({ + control, + name: 'fields', + }); + + React.useEffect(() => { + // if(!localStorage.getItem(STORAGE_KEY) && fields.length !== 1) { + // localStorage.setItem(STORAGE_KEY, JSON.stringify(fields.length)); + // } + }, [fields.length]); + + const hasFields = React.Children.toArray(props.children).length > 0; + + const renderWithUniqueIds = (children: React.ReactNode, index: number) => { + return React.Children.map(children, child => { + if (React.isValidElement(child) && child?.props?.component?.props) { + return React.cloneElement(child, { + component: { + ...child.props.component, + props: { + ...child.props.component.props, + idSuffix: `.repeater.${index}`, + }, + }, + }); + } + return child; + }); + }; + + return ( +
+ {props.legend && ( + + {props.legend} + + )} + {hasFields && ( + <> +
    + {fields.map((field, index) => ( +
  • + {renderWithUniqueIds(props.children, index)} +
  • + ))} +
+
+ + +
+ + )} +
+ ); +}; + +export default Repeater; diff --git a/packages/design/src/Form/components/SubmissionConfirmation/index.tsx b/packages/design/src/Form/components/SubmissionConfirmation/index.tsx index ed863d74..d4e9d97b 100644 --- a/packages/design/src/Form/components/SubmissionConfirmation/index.tsx +++ b/packages/design/src/Form/components/SubmissionConfirmation/index.tsx @@ -5,7 +5,7 @@ import { type PatternComponent } from '../../../Form/index.js'; const SubmissionConfirmation: PatternComponent< SubmissionConfirmationProps -> = props => { +> = (/* props */) => { return ( <> @@ -39,30 +39,35 @@ const SubmissionConfirmation: PatternComponent< Submission details - + {/* + EG: turn this off for now. Will need some design perhaps to see what the presentation + should look like. This was a minimal blocker for the repeater field due to the flat data structure + that was there previously. + */} + {/*