Skip to content

Commit

Permalink
Merge branch 'feature/310-copy-fieldset' into feature/310-multiple-en…
Browse files Browse the repository at this point in the history
…tries-component
  • Loading branch information
ethangardner committed Oct 11, 2024
2 parents 6054a64 + 15a76ef commit ef72fa6
Show file tree
Hide file tree
Showing 31 changed files with 836 additions and 112 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
_site
.turbo/
.vscode/
.idea/
coverage/
html/
node_modules/
Expand Down
5 changes: 5 additions & 0 deletions packages/common/src/locales/en/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)',
},
},
};
9 changes: 6 additions & 3 deletions packages/design/src/Form/components/RadioGroup/RadioGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,20 @@ export const RadioGroupPattern: PatternComponent<RadioGroupProps> = props => {
{props.legend}
</legend>
{props.options.map((option, index) => {
const id = props.idSuffix ? `${option.id}${props.idSuffix}` : option.id;
return (
<div key={index} className="usa-radio">
<input
className="usa-radio__input"
type="radio"
id={option.id}
{...register(props.groupId)}
id={`input-${id}`}
{...register(
`${props.groupId}${props.idSuffix ? props.idSuffix : ''}`
)}
value={option.id}
defaultChecked={option.defaultChecked}
/>
<label htmlFor={option.id} className="usa-radio__label">
<label htmlFor={`input-${id}`} className="usa-radio__label">
{option.label}
</label>
</div>
Expand Down
17 changes: 17 additions & 0 deletions packages/design/src/Form/components/Repeater/Repeater.stories.tsx
Original file line number Diff line number Diff line change
@@ -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<typeof Repeater>;

export const RepeaterSection = {
args: {
legend: 'Default Heading',
type: 'repeater',
_patternId: 'test-id',
},
} satisfies StoryObj<typeof Repeater>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* @vitest-environment jsdom
*/
import { describeStories } from '../../../test-helper.js';
import meta, * as stories from './Repeater.stories.js';

describeStories(meta, stories);
19 changes: 19 additions & 0 deletions packages/design/src/Form/components/Repeater/edit.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react';
import { type RepeaterProps } from '@atj/forms';
import { type PatternComponent } from '../../../Form/index.js';

const RepeaterEditView: PatternComponent<RepeaterProps> = props => {
return (
<fieldset className="usa-fieldset width-full padding-top-2">
{props.legend !== '' && props.legend !== undefined && (
<legend className="usa-legend text-bold text-uppercase line-height-body-4 width-full margin-top-0 padding-top-3 padding-bottom-1">
{props.legend}
</legend>
)}

{props.children}
</fieldset>
);
};

export default RepeaterEditView;
95 changes: 95 additions & 0 deletions packages/design/src/Form/components/Repeater/index.tsx
Original file line number Diff line number Diff line change
@@ -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<RepeaterProps> = 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 (
<fieldset className="usa-fieldset width-full padding-top-2">
{props.legend && (
<legend className="usa-legend text-bold text-uppercase line-height-body-4 width-full margin-top-0 padding-top-3 padding-bottom-1">
{props.legend}
</legend>
)}
{hasFields && (
<>
<ul className="add-list-reset margin-bottom-4">
{fields.map((field, index) => (
<li
key={field.id}
className="padding-bottom-4 border-bottom border-base-lighter"
>
{renderWithUniqueIds(props.children, index)}
</li>
))}
</ul>
<div className="usa-button-group margin-bottom-4">
<button
type="button"
className="usa-button usa-button--outline"
onClick={() => append({})}
>
Add new item
</button>
<button
type="button"
className="usa-button usa-button--outline"
onClick={() => remove(fields.length - 1)}
disabled={fields.length === 1}
>
Delete item
</button>
</div>
</>
)}
</fieldset>
);
};

export default Repeater;
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { type PatternComponent } from '../../../Form/index.js';

const SubmissionConfirmation: PatternComponent<
SubmissionConfirmationProps
> = props => {
> = (/* props */) => {
return (
<>
<legend className="usa-legend usa-legend--large">
Expand Down Expand Up @@ -39,30 +39,35 @@ const SubmissionConfirmation: PatternComponent<
Submission details
</button>
</h4>
<div
id="submission-confirmation-table"
className="usa-accordion__content usa-prose"
hidden={true}
>
<table className="usa-table usa-table--striped width-full">
<thead>
<tr>
<th scope="col">Form field</th>
<th scope="col">Provided value</th>
</tr>
</thead>
<tbody>
{props.table.map((row, index) => {
return (
<tr key={index}>
<th scope="row">{row.label}</th>
<td>{row.value}</td>
</tr>
);
})}
</tbody>
</table>
</div>
{/*
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.
*/}
{/*<div*/}
{/* id="submission-confirmation-table"*/}
{/* className="usa-accordion__content usa-prose"*/}
{/* hidden={true}*/}
{/*>*/}
{/* <table className="usa-table usa-table--striped width-full">*/}
{/* <thead>*/}
{/* <tr>*/}
{/* <th scope="col">Form field</th>*/}
{/* <th scope="col">Provided value</th>*/}
{/* </tr>*/}
{/* </thead>*/}
{/* <tbody>*/}
{/* {props.table.map((row, index) => {*/}
{/* return (*/}
{/* <tr key={index}>*/}
{/* <th scope="row">{row.label}</th>*/}
{/* <td>{row.value}</td>*/}
{/* </tr>*/}
{/* );*/}
{/* })}*/}
{/* </tbody>*/}
{/* </table>*/}
{/*</div>*/}
</div>
</>
);
Expand Down
13 changes: 8 additions & 5 deletions packages/design/src/Form/components/TextInput/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import { type PatternComponent } from '../../../Form/index.js';

const TextInput: PatternComponent<TextInputProps> = props => {
const { register } = useFormContext();
const id = props.idSuffix
? `${props.inputId}${props.idSuffix}`
: props.inputId;
return (
<div className="usa-form-group-wrapper" key={props.inputId}>
<div
Expand All @@ -18,13 +21,13 @@ const TextInput: PatternComponent<TextInputProps> = props => {
className={classNames('usa-label', {
'usa-label--error': props.error,
})}
id={`input-message-${props.inputId}`}
id={`input-message-${id}`}
>
{props.label}
{props.error && (
<span
className="usa-error-message"
id={`input-error-message-${props.inputId}`}
id={`input-error-message-${id}`}
role="alert"
>
{props.error.message}
Expand All @@ -34,13 +37,13 @@ const TextInput: PatternComponent<TextInputProps> = props => {
className={classNames('usa-input', {
'usa-input--error': props.error,
})}
id={`input-${props.inputId}`}
id={`input-${id}`}
defaultValue={props.value}
{...register(props.inputId || Math.random().toString(), {
{...register(id || Math.random().toString(), {
//required: props.required,
})}
type="text"
aria-describedby={`input-message-${props.inputId}`}
aria-describedby={`input-message-${id}`}
/>
</label>
</div>
Expand Down
2 changes: 2 additions & 0 deletions packages/design/src/Form/components/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Page from './Page/index.js';
import PageSet from './PageSet/index.js';
import Paragraph from './Paragraph/index.js';
import RadioGroup from './RadioGroup/index.js';
import Repeater from './Repeater/index.js';
import RichText from './RichText/index.js';
import Sequence from './Sequence/index.js';
import SubmissionConfirmation from './SubmissionConfirmation/index.js';
Expand All @@ -23,6 +24,7 @@ export const defaultPatternComponents: ComponentForPattern = {
'page-set': PageSet as PatternComponent,
paragraph: Paragraph as PatternComponent,
'radio-group': RadioGroup as PatternComponent,
repeater: Repeater as PatternComponent,
'rich-text': RichText as PatternComponent,
sequence: Sequence as PatternComponent,
'submission-confirmation': SubmissionConfirmation as PatternComponent,
Expand Down
Loading

0 comments on commit ef72fa6

Please sign in to comment.