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/apps/spotlight/src/components/SplashPage.tsx b/apps/spotlight/src/components/SplashPage.tsx new file mode 100644 index 00000000..0dac27d2 --- /dev/null +++ b/apps/spotlight/src/components/SplashPage.tsx @@ -0,0 +1,3 @@ +export default () => { + return

Hero

+} \ No newline at end of file diff --git a/packages/common/src/locales/en/app.ts b/packages/common/src/locales/en/app.ts index fa715580..13815462 100644 --- a/packages/common/src/locales/en/app.ts +++ b/packages/common/src/locales/en/app.ts @@ -89,5 +89,10 @@ export const en = { hint: 'For example, 555-11-0000', 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..109232f5 --- /dev/null +++ b/packages/design/src/Form/components/Repeater/Repeater.stories.tsx @@ -0,0 +1,66 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import React from 'react'; +import Repeater from './index.js'; +import { expect, userEvent } from '@storybook/test'; +import { FormProvider, useForm } from 'react-hook-form'; + +export default { + title: 'patterns/Repeater', + component: Repeater, + decorators: [ + (Story, args) => { + const FormDecorator = () => { + const formMethods = useForm(); + return ( + + + + ); + }; + return ; + }, + ], + tags: ['autodocs'], +} satisfies Meta; + +const defaultArgs = { + legend: 'Default Heading', + _patternId: 'test-id', +}; + +export const Default = { + args: { + ...defaultArgs, + type: 'repeater', + }, +} satisfies StoryObj; + +export const WithContents = { + play: async ({ mount, args }) => { + const canvas = await mount(); + + const addButton = canvas.getByRole('button', { name: /Add new item/ }); + const deleteButton = canvas.getByRole('button', { name: /Delete item/ }); + await userEvent.click(addButton); + + let inputs = canvas.queryAllByRole('textbox'); + await expect(inputs).toHaveLength(1); + + await userEvent.click(deleteButton); + inputs = canvas.queryAllByRole('textbox'); + await expect(inputs).toHaveLength(0); + }, + args: { + ...defaultArgs, + type: 'repeater', + children: [ + // eslint-disable-next-line +
+ + +
, + ], + }, +} 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..afcbd66f --- /dev/null +++ b/packages/design/src/Form/components/Repeater/index.tsx @@ -0,0 +1,110 @@ +import React from 'react'; +import { useFieldArray, useForm } from 'react-hook-form'; +import { type RepeaterProps } from '@atj/forms'; +import { type PatternComponent } from '../../index.js'; + +const Repeater: PatternComponent = props => { + const { control } = useForm(); + + const { fields, append, remove } = useFieldArray({ + control, + name: 'fields', + }); + + const hasFields = React.Children.toArray(props.children).length > 0; + + /** + * TODO: we want to have an array of objects so it is grouped correctly when submitted + * child components when submitted need to escalate validation logic to the repeater and rows without + * any input should not be considered fields that we care about for validation. + * + * Each row of the repeater should have its own unique index + */ + + const renderWithUniqueIds = (children: React.ReactNode) => { + return React.Children.map(children, child => { + if (React.isValidElement(child) && child?.props?.component?.props) { + console.group('renderwithuniqueids'); + console.log(child.props); + console.groupEnd(); + return React.cloneElement(child as React.ReactElement, { + component: { + ...child.props.component, + props: { + ...child.props.component.props, + }, + }, + }); + } + return child; + }); + }; + + return ( +
+ {props.legend && ( + + {props.legend} + + )} + {hasFields && ( + <> + {fields.length ? ( +
    + {fields.map((field, index) => ( +
  • + {renderWithUniqueIds(props.children, index)} +
  • + ))} +
+ ) : ( +
+

+ This section is empty. Start by{' '} + + . +

+
+ )} +
+ + +
+ + )} +
+ ); +}; + +export default Repeater; diff --git a/packages/design/src/Form/components/SubmissionConfirmation/index.tsx b/packages/design/src/Form/components/SubmissionConfirmation/index.tsx index 7e422663..19a33255 100644 --- a/packages/design/src/Form/components/SubmissionConfirmation/index.tsx +++ b/packages/design/src/Form/components/SubmissionConfirmation/index.tsx @@ -40,30 +40,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. + */} + {/*