Skip to content

Commit

Permalink
Wire in "prompt" and "prompt parts"; use react-hook form in React com…
Browse files Browse the repository at this point in the history
…ponents.
  • Loading branch information
danielnaab committed Jan 12, 2024
1 parent 4b316a0 commit 2d753c2
Show file tree
Hide file tree
Showing 19 changed files with 426 additions and 139 deletions.
1 change: 1 addition & 0 deletions apps/spotlight/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"cheerio": "1.0.0-rc.12",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.49.3",
"react-router-dom": "^6.21.1"
},
"devDependencies": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { extractFormFieldData, suggestFormDetails } from '@atj/documents';
import { SuggestedForm, UD105_TEST_DATA } from '@atj/documents/src/suggestions';

import { onFileInputChangeGetFile } from '../../../lib/file-input';
import { FormFieldset } from '../form/view';
import { FormBuilder } from '.';
import { FormView } from '../form/view';
import { EditFieldset } from '../form/edit';

type State = { page: number; suggestedForm?: SuggestedForm };
type Action =
Expand Down Expand Up @@ -145,7 +145,7 @@ export const DocumentImporter = () => {
});
}}
>
<FormBuilder fields={state.suggestedForm as SuggestedForm} />
<EditFieldset fields={state.suggestedForm as SuggestedForm} />
<ButtonBar />
</form>
);
Expand All @@ -161,7 +161,7 @@ export const DocumentImporter = () => {
});
}}
>
<FormFieldset fields={state.suggestedForm as SuggestedForm} />
<FormView prompt={state.suggestedForm as SuggestedForm} />
<ButtonBar />
</form>
);
Expand Down
52 changes: 0 additions & 52 deletions apps/spotlight/src/components/react/form-builder/index.tsx
Original file line number Diff line number Diff line change
@@ -1,52 +0,0 @@
import React from 'react';

import { SuggestedForm } from '@atj/documents/src/suggestions';

export const FormBuilder = ({ fields }: { fields: SuggestedForm }) => {
return (
<div>
{fields.map((field, index) => {
return <FieldBuilder key={index} field={field} />;
})}
</div>
);
};

const FieldBuilder = ({ field }: { field: SuggestedForm[number] }) => {
const fieldId = `field-${field.id}`;
return (
<div className="grid-row grid-gap">
<div className="grid-col">
<label className="usa-label">
Input type
<select className="usa-select" name={`input-type-${fieldId}`}>
<option value={'input'}>Input</option>
<option value={'textarea'}>Textarea</option>
</select>
</label>
</div>
<div className="grid-col">
<label className="usa-label">
Field label
<input
className="usa-input"
type="text"
name={`description-${fieldId}`}
defaultValue={field.label}
></input>
</label>
</div>
<div className="grid-col">
<label className="usa-label">
Default value
<input
className="usa-input"
type="text"
name={`default-value-${fieldId}`}
defaultValue={field.value}
></input>
</label>
</div>
</div>
);
};
135 changes: 134 additions & 1 deletion apps/spotlight/src/components/react/form/edit.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import React from 'react';
import { useForm } from 'react-hook-form';
import { Link } from 'react-router-dom';

import {
Form,
Question,
addQuestions,
createForm,
getFlatFieldList,
} from '@atj/forms';
import { getFormFromStorage, saveFormToStorage } from '../../../lib/form-repo';
import { addQuestions } from '@atj/forms';

export const FormEdit = ({ formId }: { formId: string }) => {
const form = getFormFromStorage(window.localStorage, formId);
Expand Down Expand Up @@ -46,6 +54,131 @@ export const FormEdit = ({ formId }: { formId: string }) => {
<Link to="/">View all forms</Link>
</li>
</ul>
<EditForm
form={form}
onSave={form => saveFormToStorage(window.localStorage, formId, form)}
/>
</div>
);
};

type FieldProps = {
fieldType: 'input' | 'textarea';
label: string;
initial: string;
required: boolean;
};
type FieldMap = Record<string, FieldProps>;

const EditForm = ({
form,
onSave,
}: {
form: Form;
onSave: (form: Form) => void;
}) => {
const formData: FieldMap = Object.fromEntries(
Object.entries(form.questions).map(([key, value]) => {
return [
key,
{
fieldType: 'input',
label: value.text,
initial: value.initial,
required: value.required,
},
];
})
);
const { register, handleSubmit } = useForm<FieldMap>({
defaultValues: formData,
});
const fields = getFlatFieldList(form);
return (
<form
onSubmit={handleSubmit(data => {
const updatedForm = replaceFormQuestions(form, data);
onSave(updatedForm);
})}
>
<ButtonBar />
<fieldset>
{fields.map((field, index) => {
const fieldId = field.id;
return (
<div key={index} className="grid-row grid-gap">
<div className="grid-col">
<label className="usa-label">
Input type
<select
className="usa-select"
{...register(`${fieldId}.fieldType`)}
>
<option value={'input'}>Input</option>
<option value={'textarea'}>Textarea</option>
</select>
</label>
</div>
<div className="grid-col">
<label className="usa-label">
Field label
<input
className="usa-input"
{...register(`${fieldId}.label`)}
type="text"
></input>
</label>
</div>
<div className="grid-col">
<label className="usa-label">
Default value
<input
className="usa-input"
type="text"
{...register(`${fieldId}.initial`)}
></input>
</label>
</div>
<div className="grid-col">
<div className="usa-checkbox">
<input
className="usa-checkbox__input"
type="checkbox"
id={`${fieldId}.required`}
{...register(`${fieldId}.required`)}
/>
<label
className="usa-checkbox__label"
htmlFor={`${fieldId}.required`}
>
Required
</label>
</div>
</div>
</div>
);
})}
</fieldset>
<ButtonBar />
</form>
);
};

const ButtonBar = () => {
return (
<div>
<button className="usa-button">Save</button>
</div>
);
};

const replaceFormQuestions = (form: Form, data: FieldMap) => {
const questions = Object.entries(data).map(([id, field]) => ({
id,
text: field.label,
initial: field.initial,
required: field.required,
}));
const newForm = createForm(form.summary);
return addQuestions(newForm, questions);
};
4 changes: 2 additions & 2 deletions apps/spotlight/src/components/react/form/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useParams, HashRouter, Route, Routes } from 'react-router-dom';
import { FormDelete } from './delete';
import { FormEdit } from './edit';
import { FormList } from './list';
import { FormView } from './view';
import { FormViewById } from './view';

export const FormSection = () => {
return (
Expand All @@ -18,7 +18,7 @@ export const FormSection = () => {
if (formId === undefined) {
return <div>formId is undefined</div>;
}
return <FormView formId={formId} />;
return <FormViewById formId={formId} />;
}}
/>
<Route
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
import React from 'react';

import { Field } from '.';
interface Field {
tag: string;
type: string;
name: string;
id: string;
class: string;
value?: string;
label: string;
title?: string;
required?: boolean;
options?: { name: string; value: string }[]; // For select and radio fields
items?: { tag: string; content: string }[];
arialabelledby?: string;
ariadescribedby?: string;
linkurl?: string;
}

export const TextField = ({ field }: { field: Field }) => {
return (
Expand Down
11 changes: 11 additions & 0 deletions apps/spotlight/src/components/react/form/prompts/form-summary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react';

import { FormSummaryPrompt } from '@atj/forms';

export const FormSummary = ({ prompt }: { prompt: FormSummaryPrompt }) => {
return (
<legend className="usa-legend usa-legend--large">
{prompt.title} - {prompt.description}
</legend>
);
};
16 changes: 16 additions & 0 deletions apps/spotlight/src/components/react/form/prompts/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from 'react';

import { type PromptPart } from '@atj/forms';
import { FormSummary } from './form-summary';
import { TextInput } from './text-input';

export const PromptSegment = ({ promptPart }: { promptPart: PromptPart }) => {
if (promptPart.type === 'form-summary') {
return <FormSummary prompt={promptPart} />;
} else if (promptPart.type === 'text') {
return <TextInput prompt={promptPart} />;
} else {
const _exhaustiveCheck: never = promptPart;
return <></>;
}
};
30 changes: 30 additions & 0 deletions apps/spotlight/src/components/react/form/prompts/text-input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react';
import { useFormContext } from 'react-hook-form';

import { type TextInputPrompt } from '@atj/forms';

export const TextInput = ({ prompt }: { prompt: TextInputPrompt }) => {
const { register } = useFormContext();
return (
<div className="usa-form-group" key={prompt.id}>
<label className="usa-label" htmlFor={prompt.id}>
{prompt.label}
{prompt.required && (
<>
{' '}
<abbr title="required" className="usa-hint usa-hint--required">
*
</abbr>
</>
)}
</label>
<input
className="usa-input"
defaultValue={prompt.value}
{...register(prompt.id, {
required: prompt.required,
})}
/>
</div>
);
};
Loading

0 comments on commit 2d753c2

Please sign in to comment.