From afd950d9bae5f5aecf80e3c8023876080ffc13a7 Mon Sep 17 00:00:00 2001 From: Daniel Naab Date: Wed, 27 Mar 2024 11:39:28 -0500 Subject: [PATCH 1/6] Naming and organization improvements (#85) * flatten location of form elements * Move form element definitions * Rename UI component types * Rename Pattern -> PatternProps * Rename FormElement -> Pattern * Rename FormDefinition -> BluePrint * Rename FormSession -> Session. Also, create preview components once when initializing the FormEditProvider, rather than on every render. * Remove required attribute * Revert "Rename FormSession -> Session." This reverts commit 746802ae5f18d763b7633913e2347211af0e973e. * Rename Pattern.default to Pattern.initial * Remove ambiguity around createSession / createFormSession * Rename forms/elements to forms/patterns * Rename forms/pattern.ts to forms/components.ts * To avoid confusion, remove "Pattern" from the name of component props. * Use PatternProps at the point of component prop definition, rather than where comsumed. * Update README with terminology * Rename forms/element.ts to forms/pattern.ts * Replace additional references to "element" with "pattern" --- README.md | 17 ++ apps/doj-demo/astro.config.mjs | 2 +- .../src/components/AppFormManager.tsx | 8 +- .../doj-demo/src/components/AppFormRouter.tsx | 4 +- .../src/components/AppFormManager.tsx | 8 +- .../src/components/AppFormRouter.tsx | 4 +- packages/design/src/Form/index.tsx | 11 +- .../FormManager/DocumentImporter/index.tsx | 10 +- .../FormManager/FormEdit/DraggableList.tsx | 57 ++--- .../{FormElementEdit.tsx => PatternEdit.tsx} | 42 +-- .../src/FormManager/FormEdit/Preview.tsx | 26 +- .../design/src/FormManager/FormEdit/index.tsx | 16 +- .../design/src/FormManager/FormEdit/store.tsx | 46 ++-- .../design/src/FormManager/FormEdit/types.ts | 25 +- .../FormList/PDFFileSelect/file-input.test.ts | 2 +- .../FormList/PDFFileSelect/file-input.ts | 2 +- .../src/FormManager/FormPreview/index.tsx | 4 +- .../src/config/edit/FormSummaryEdit.tsx | 13 +- ...utElementEdit.tsx => InputPatternEdit.tsx} | 43 ++-- ...ementEdit.tsx => ParagraphPatternEdit.tsx} | 15 +- ...lementEdit.tsx => SequencePatternEdit.tsx} | 67 +++-- .../edit/SubmissionConfirmationEdit.tsx | 25 +- packages/design/src/config/edit/index.ts | 12 +- .../design/src/config/view/Fieldset/index.tsx | 6 +- .../view/FormSummary/FormSummary.stories.tsx | 9 +- .../src/config/view/FormSummary/index.tsx | 8 +- .../src/config/view/Paragraph/index.tsx | 8 +- .../design/src/config/view/Sequence/index.tsx | 9 +- .../SubmissionConfirmation.stories.tsx | 9 +- .../view/SubmissionConfirmation/index.tsx | 10 +- .../view/TextInput/TestInput.stories.tsx | 10 +- .../src/config/view/TextInput/index.tsx | 8 +- packages/design/src/config/view/index.tsx | 2 +- packages/design/src/test-form.ts | 55 ++-- .../src/context/browser/form-repo.ts | 16 +- .../form-service/src/context/test/storage.ts | 4 +- .../form-service/src/operations/add-form.ts | 4 +- .../form-service/src/operations/get-form.ts | 4 +- .../form-service/src/operations/save-form.ts | 4 +- .../src/operations/submit-form.ts | 4 +- packages/form-service/src/types.ts | 8 +- packages/forms/src/builder/index.ts | 30 +-- packages/forms/src/components.ts | 157 ++++++++++++ packages/forms/src/config.ts | 1 + .../forms/src/config/elements/fieldset.ts | 54 ---- .../forms/src/config/elements/form-summary.ts | 38 --- packages/forms/src/config/elements/input.ts | 55 ---- .../forms/src/config/elements/paragraph.ts | 40 --- .../forms/src/config/elements/sequence.ts | 47 ---- packages/forms/src/config/index.ts | 28 -- packages/forms/src/documents/document.ts | 78 +++--- packages/forms/src/documents/pdf/generate.ts | 6 +- packages/forms/src/documents/pdf/mock-api.ts | 75 +++--- packages/forms/src/documents/suggestions.ts | 2 +- packages/forms/src/element.ts | 105 -------- packages/forms/src/index.ts | 215 ++++++---------- packages/forms/src/pattern.ts | 241 ++++++++---------- packages/forms/src/patterns/fieldset.ts | 53 ++++ packages/forms/src/patterns/form-summary.ts | 37 +++ .../{config/config.ts => patterns/index.ts} | 13 +- packages/forms/src/patterns/input.ts | 54 ++++ packages/forms/src/patterns/paragraph.ts | 38 +++ packages/forms/src/patterns/sequence.ts | 50 ++++ packages/forms/src/response.ts | 32 +-- packages/forms/src/session.ts | 80 +++--- .../{transform/index.ts => util/transform.ts} | 0 packages/forms/src/util/zod.ts | 4 +- packages/forms/tests/two-field-form.test.ts | 51 ++-- 68 files changed, 1069 insertions(+), 1152 deletions(-) rename packages/design/src/FormManager/FormEdit/{FormElementEdit.tsx => PatternEdit.tsx} (63%) rename packages/design/src/config/edit/{InputElementEdit.tsx => InputPatternEdit.tsx} (55%) rename packages/design/src/config/edit/{ParagraphElementEdit.tsx => ParagraphPatternEdit.tsx} (70%) rename packages/design/src/config/edit/{SequenceElementEdit.tsx => SequencePatternEdit.tsx} (60%) create mode 100644 packages/forms/src/components.ts create mode 100644 packages/forms/src/config.ts delete mode 100644 packages/forms/src/config/elements/fieldset.ts delete mode 100644 packages/forms/src/config/elements/form-summary.ts delete mode 100644 packages/forms/src/config/elements/input.ts delete mode 100644 packages/forms/src/config/elements/paragraph.ts delete mode 100644 packages/forms/src/config/elements/sequence.ts delete mode 100644 packages/forms/src/config/index.ts delete mode 100644 packages/forms/src/element.ts create mode 100644 packages/forms/src/patterns/fieldset.ts create mode 100644 packages/forms/src/patterns/form-summary.ts rename packages/forms/src/{config/config.ts => patterns/index.ts} (58%) create mode 100644 packages/forms/src/patterns/input.ts create mode 100644 packages/forms/src/patterns/paragraph.ts create mode 100644 packages/forms/src/patterns/sequence.ts rename packages/forms/src/{transform/index.ts => util/transform.ts} (100%) diff --git a/README.md b/README.md index da1ef07d..5a103777 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,23 @@ Additional documentation: - [Architectural Decision Records (ADRs)](./documents/adr/) - [Non-project contributions](./documents/value-created-log.md) +## Overview + +The platform is made up of the following high-level terms. + +### Key personas + +- Content authors: legal experts who craft guided interview experiences via a "no code" interface +- Self-represented litigants (SREs): end-users who interact with the court via guided interviews created by content authors + +### Things + +- **Blueprint**: produced by a content author, the blueprint defines the structure of an interactive session between a court and an SRL +- **Conversation**: one instance of a blueprint; the interactive session between a court and an SRL. Other terms for this concept include dialogue or session. +- **Pattern**: the building blocks of a blueprint, patterns implement UX best-practices, defining the content and behavior of the user interface. +- **Prompt**: produced by a pattern, the prompt defines what is presented to the end user at single point in a conversation. +- **Component**: user interface component that acts as the building block of prompts. + ## Development This project uses [pnpm workspaces](https://pnpm.io/workspaces). To work with this project, [install pnpm](https://pnpm.io/installation) and then the project dependencies: diff --git a/apps/doj-demo/astro.config.mjs b/apps/doj-demo/astro.config.mjs index 8562221c..5dc1c0bf 100644 --- a/apps/doj-demo/astro.config.mjs +++ b/apps/doj-demo/astro.config.mjs @@ -5,7 +5,7 @@ import react from '@astrojs/react'; // https://astro.build/config export default defineConfig({ output: 'server', - trailingSlash: 'never', + trailingSlash: 'always', base: addTrailingSlash(process.env.BASEURL || ''), adapter: node({ mode: 'standalone', diff --git a/apps/doj-demo/src/components/AppFormManager.tsx b/apps/doj-demo/src/components/AppFormManager.tsx index 394120a1..32ddbb86 100644 --- a/apps/doj-demo/src/components/AppFormManager.tsx +++ b/apps/doj-demo/src/components/AppFormManager.tsx @@ -2,8 +2,8 @@ import React from 'react'; import { FormManager, - defaultFormElementComponents, - defaultFormElementEditComponents, + defaultPatternComponents, + defaultPatternEditComponents, } from '@atj/design'; import { getAppContext } from '../context'; @@ -14,8 +14,8 @@ export default function () { > = Record< - string, - FormElementComponent ->; +export type ComponentForPattern< + T extends PatternProps = PatternProps, +> = Record>; -export type FormElementComponent> = +export type PatternComponent> = React.ComponentType<{ pattern: T; children?: React.ReactNode; diff --git a/packages/design/src/FormManager/DocumentImporter/index.tsx b/packages/design/src/FormManager/DocumentImporter/index.tsx index 733f64e7..a969dc83 100644 --- a/packages/design/src/FormManager/DocumentImporter/index.tsx +++ b/packages/design/src/FormManager/DocumentImporter/index.tsx @@ -3,7 +3,7 @@ import { useNavigate } from 'react-router-dom'; import { type DocumentFieldMap, - type FormDefinition, + type Blueprint, SAMPLE_DOCUMENTS, addDocument, addDocumentFieldsToForm, @@ -24,7 +24,7 @@ const DocumentImporter = ({ baseUrl: string; formId: string; context: FormUIContext; - form: FormDefinition; + form: Blueprint; formService: FormService; }) => { const { state, actions } = useDocumentImporter(formService, form, baseUrl); @@ -184,13 +184,13 @@ const DocumentImporter = ({ type State = { page: number; - previewForm: FormDefinition; + previewForm: Blueprint; documentFields?: DocumentFieldMap; }; const useDocumentImporter = ( formService: FormService, - form: FormDefinition, + form: Blueprint, baseUrl: string ) => { const navigate = useNavigate(); @@ -203,7 +203,7 @@ const useDocumentImporter = ( data: { path: string; fields: DocumentFieldMap; - previewForm: FormDefinition; + previewForm: Blueprint; }; } | { diff --git a/packages/design/src/FormManager/FormEdit/DraggableList.tsx b/packages/design/src/FormManager/FormEdit/DraggableList.tsx index 0cadabd5..a33cbb71 100644 --- a/packages/design/src/FormManager/FormEdit/DraggableList.tsx +++ b/packages/design/src/FormManager/FormEdit/DraggableList.tsx @@ -17,13 +17,13 @@ import { import { CSS } from '@dnd-kit/utilities'; import { - getFormElement, - type FormDefinition, - type FormElement, - FormElementId, + getPattern, + type Blueprint, + type Pattern, + PatternId, } from '@atj/forms'; -import { SequenceElement } from '@atj/forms/src/config/elements/sequence'; +import { SequencePattern } from '@atj/forms/src/patterns/sequence'; const SortableItem = ({ id, @@ -59,19 +59,19 @@ const SortableItem = ({ }; type DraggableListProps = React.PropsWithChildren<{ - element: FormElement; - form: FormDefinition; - setSelectedElement: (element: FormElement) => void; + pattern: Pattern; + form: Blueprint; + setSelectedPattern: (pattern: Pattern) => void; }>; export const DraggableList: React.FC = ({ - element, + pattern, form, - setSelectedElement, + setSelectedPattern, children, }) => { - const [elements, setElements] = useState( - element.data.elements.map((elementId: FormElementId) => { - return getFormElement(form, elementId); + const [patterns, setPatterns] = useState( + pattern.data.patterns.map((patternId: PatternId) => { + return getPattern(form, patternId); }) ); const sensors = useSensors( @@ -90,32 +90,31 @@ export const DraggableList: React.FC = ({ return; } if (active.id !== over.id) { - const oldIndex = elements.findIndex(element => { - return element.id === active.id; + const oldIndex = patterns.findIndex(pattern => { + return pattern.id === active.id; }); - const newIndex = elements.findIndex(element => { - return element.id === over.id; + const newIndex = patterns.findIndex(pattern => { + return pattern.id === over.id; }); - const newOrder = arrayMove(elements, oldIndex, newIndex); - setElements(newOrder); - setSelectedElement({ - id: element.id, - type: element.type, + const newOrder = arrayMove(patterns, oldIndex, newIndex); + setPatterns(newOrder); + setSelectedPattern({ + id: pattern.id, + type: pattern.type, data: { - elements: newOrder.map(element => element.id), + patterns: newOrder.map(pattern => pattern.id), }, - default: { - elements: [], + initial: { + patterns: [], }, - required: element.required, - } satisfies SequenceElement); + } satisfies SequencePattern); } }} > - +
    {arrayChildren.map((child, index) => ( - + {child} ))} diff --git a/packages/design/src/FormManager/FormEdit/FormElementEdit.tsx b/packages/design/src/FormManager/FormEdit/PatternEdit.tsx similarity index 63% rename from packages/design/src/FormManager/FormEdit/FormElementEdit.tsx rename to packages/design/src/FormManager/FormEdit/PatternEdit.tsx index a5015519..55e61fff 100644 --- a/packages/design/src/FormManager/FormEdit/FormElementEdit.tsx +++ b/packages/design/src/FormManager/FormEdit/PatternEdit.tsx @@ -1,45 +1,45 @@ import React, { useEffect, useRef } from 'react'; import { FormProvider, useForm } from 'react-hook-form'; -import { type FormElementMap } from '@atj/forms'; +import { type PatternMap } from '@atj/forms'; import { useFormEditStore } from './store'; -export const FormElementEdit = () => { +export const PatternEdit = () => { const context = useFormEditStore(state => state.context); const form = useFormEditStore(state => state.form); - const selectedElement = useFormEditStore(state => state.selectedElement); - const { setSelectedElement, updateSelectedFormElement } = useFormEditStore( + const selectedPattern = useFormEditStore(state => state.selectedPattern); + const { setSelectedPattern, updateSelectedPattern } = useFormEditStore( state => ({ - setSelectedElement: state.setSelectedElement, - updateSelectedFormElement: state.updateSelectedFormElement, + setSelectedPattern: state.setSelectedPattern, + updateSelectedPattern: state.updateSelectedPattern, }) ); - const methods = useForm({ - defaultValues: selectedElement + const methods = useForm({ + defaultValues: selectedPattern ? { - [selectedElement.id]: selectedElement, + [selectedPattern.id]: selectedPattern, } : {}, }); const settingsContainerRef = useRef(null); useEffect(() => { - if (selectedElement === undefined) { + if (selectedPattern === undefined) { return; } methods.reset(); - methods.setValue(selectedElement.id, selectedElement); - }, [selectedElement]); + methods.setValue(selectedPattern.id, selectedPattern); + }, [selectedPattern]); // Updates the scroll position of the edit form when it's visible useEffect(() => { let frameId: number; const updatePosition = () => { if (window.innerWidth > 879) { - if (selectedElement) { + if (selectedPattern) { const element = document.querySelector( - `[data-id="${selectedElement.id}"]` + `[data-id="${selectedPattern.id}"]` ); if (element && settingsContainerRef.current) { const rect = element.getBoundingClientRect(); @@ -53,13 +53,13 @@ export const FormElementEdit = () => { return () => { cancelAnimationFrame(frameId); }; - }, [selectedElement]); + }, [selectedPattern]); - if (!selectedElement) { + if (!selectedPattern) { return; } - const SelectedEditComponent = context.editComponents[selectedElement.type]; + const SelectedEditComponent = context.editComponents[selectedPattern.type]; return (
    {
    { - updateSelectedFormElement(formData); + updateSelectedPattern(formData); })} > -

    Editing "{selectedElement.data.label}"...

    +

    Editing "{selectedPattern.data.label}"...

    setSelectedElement(undefined)} + onClick={() => setSelectedPattern(undefined)} className="usa-button close-button" type="submit" value="Cancel" diff --git a/packages/design/src/FormManager/FormEdit/Preview.tsx b/packages/design/src/FormManager/FormEdit/Preview.tsx index 3ee3c8da..2777a1a9 100644 --- a/packages/design/src/FormManager/FormEdit/Preview.tsx +++ b/packages/design/src/FormManager/FormEdit/Preview.tsx @@ -1,10 +1,10 @@ import React from 'react'; -import { type Pattern, createFormSession } from '@atj/forms'; +import { type PatternProps, createFormSession } from '@atj/forms'; import Form, { type ComponentForPattern, - type FormElementComponent, + type PatternComponent, type FormUIContext, } from '../../Form'; import { useFormEditStore } from './store'; @@ -64,20 +64,20 @@ const createPreviewComponents = ( /* const createSequencePatternPreviewComponent = ( - Component: FormElementComponent, + Component: PatternComponent, previewComponents: ComponentForPattern ) => { - const PatternPreviewSequenceComponent: FormElementComponent = ({ + const PatternPreviewSequenceComponent: PatternComponent = ({ pattern, }) => { - const { form, setSelectedElement } = usePreviewContext(); - const element = getFormElement(form, pattern._elementId); + const { form, setSelectedPattern } = usePreviewContext(); + const element = getPattern(form, pattern._patternId); const Component = previewComponents[pattern.type]; return ( @@ -88,24 +88,24 @@ const createSequencePatternPreviewComponent = ( */ const createPatternPreviewComponent = ( - Component: FormElementComponent, + Component: PatternComponent, uswdsRoot: string ) => { - const PatternPreviewComponent: FormElementComponent = ({ + const PatternPreviewComponent: PatternComponent = ({ pattern, }: { - pattern: Pattern; + pattern: PatternProps; }) => { - const selectedElement = useFormEditStore(state => state.selectedElement); + const selectedPattern = useFormEditStore(state => state.selectedPattern); const handleEditClick = useFormEditStore(state => state.handleEditClick); - const isSelected = selectedElement?.id === pattern._elementId; + const isSelected = selectedPattern?.id === pattern._patternId; const divClassNames = isSelected ? 'form-group-row field-selected' : 'form-group-row'; return ( -

    +
    diff --git a/packages/design/src/FormManager/FormEdit/store.tsx b/packages/design/src/FormManager/FormEdit/store.tsx index 3c1f5724..554a9c74 100644 --- a/packages/design/src/FormManager/FormEdit/store.tsx +++ b/packages/design/src/FormManager/FormEdit/store.tsx @@ -3,12 +3,12 @@ import { StoreApi, create } from 'zustand'; import { createContext } from 'zustand-utils'; import { - type FormDefinition, - type FormElementMap, - type Pattern, - getFormElement, + type Blueprint, + type PatternMap, + type PatternProps, + getPattern, FormBuilder, - FormElement, + Pattern, } from '@atj/forms'; import { type FormEditUIContext } from './types'; @@ -18,7 +18,7 @@ export const useFormEditStore = useStore; export const FormEditProvider = (props: { context: FormEditUIContext; - form: FormDefinition; + form: Blueprint; children: React.ReactNode; }) => { return ( @@ -30,12 +30,12 @@ export const FormEditProvider = (props: { type FormEditState = { context: FormEditUIContext; - form: FormDefinition; - selectedElement?: FormElement; + form: Blueprint; + selectedPattern?: Pattern; - handleEditClick: (pattern: Pattern) => void; - setSelectedElement: (element?: FormElement) => void; - updateSelectedFormElement: (formData: FormElementMap) => void; + handleEditClick: (pattern: PatternProps) => void; + setSelectedPattern: (element?: Pattern) => void; + updateSelectedPattern: (formData: PatternMap) => void; }; const createFormEditStore = ({ @@ -43,35 +43,35 @@ const createFormEditStore = ({ form, }: { context: FormEditUIContext; - form: FormDefinition; + form: Blueprint; }) => create((set, get) => ({ context, form, - handleEditClick: (pattern: Pattern) => { + handleEditClick: (pattern: PatternProps) => { const state = get(); - if (state.selectedElement?.id === pattern._elementId) { - set({ selectedElement: undefined }); + if (state.selectedPattern?.id === pattern._patternId) { + set({ selectedPattern: undefined }); } else { - const elementToSet = getFormElement(state.form, pattern._elementId); - set({ selectedElement: elementToSet }); + const elementToSet = getPattern(state.form, pattern._patternId); + set({ selectedPattern: elementToSet }); } }, - setSelectedElement: selectedElement => set({ selectedElement }), - updateSelectedFormElement: (formData: FormElementMap) => { + setSelectedPattern: selectedPattern => set({ selectedPattern }), + updateSelectedPattern: (formData: PatternMap) => { const state = get(); - if (state.selectedElement === undefined) { + if (state.selectedPattern === undefined) { console.warn('No selected element'); return; } const builder = new FormBuilder(state.form); - const success = builder.updateFormElement( + const success = builder.updatePattern( state.context.config, - state.selectedElement, + state.selectedPattern, formData ); if (success) { - set({ form: builder.form, selectedElement: undefined }); + set({ form: builder.form, selectedPattern: undefined }); } }, })); diff --git a/packages/design/src/FormManager/FormEdit/types.ts b/packages/design/src/FormManager/FormEdit/types.ts index 04d13069..afa748d6 100644 --- a/packages/design/src/FormManager/FormEdit/types.ts +++ b/packages/design/src/FormManager/FormEdit/types.ts @@ -1,24 +1,21 @@ -import { - type FormConfig, - type FormDefinition, - type FormElement, -} from '@atj/forms'; +import { type FormConfig, type Blueprint, type Pattern } from '@atj/forms'; import { type ComponentForPattern } from '../../Form'; export type FormEditUIContext = { config: FormConfig; components: ComponentForPattern; - editComponents: EditComponentForFormElement; + editComponents: EditComponentForPattern; uswdsRoot: `${string}/`; }; -export type FormElementEditComponent = - React.ComponentType<{ - context: FormEditUIContext; - form: FormDefinition; - element: T; - }>; +export type PatternEditComponent = React.ComponentType<{ + context: FormEditUIContext; + form: Blueprint; + pattern: T; +}>; -export type EditComponentForFormElement = - Record>; +export type EditComponentForPattern = Record< + string, + PatternEditComponent +>; diff --git a/packages/design/src/FormManager/FormList/PDFFileSelect/file-input.test.ts b/packages/design/src/FormManager/FormList/PDFFileSelect/file-input.test.ts index 5f8276f1..882a3116 100644 --- a/packages/design/src/FormManager/FormList/PDFFileSelect/file-input.test.ts +++ b/packages/design/src/FormManager/FormList/PDFFileSelect/file-input.test.ts @@ -21,7 +21,7 @@ describe('onFileInputChangeGetFile', () => { target: { files: [new File([], 'file-name.xml')] as unknown as FileList, }, - } as ChangeEvent); + } as ChangeEvent); }); }); }); diff --git a/packages/design/src/FormManager/FormList/PDFFileSelect/file-input.ts b/packages/design/src/FormManager/FormList/PDFFileSelect/file-input.ts index 4aadf56a..7b7a641a 100644 --- a/packages/design/src/FormManager/FormList/PDFFileSelect/file-input.ts +++ b/packages/design/src/FormManager/FormList/PDFFileSelect/file-input.ts @@ -13,7 +13,7 @@ const readFileAsync = (file: File) => { export const onFileInputChangeGetFile = (setFile: ({ name, data }: { name: string; data: Uint8Array }) => void) => - (event: ChangeEvent) => { + (event: ChangeEvent) => { if (event.target.files && event.target.files.length > 0) { const inputFile = event.target.files[0]; readFileAsync(inputFile).then(data => { diff --git a/packages/design/src/FormManager/FormPreview/index.tsx b/packages/design/src/FormManager/FormPreview/index.tsx index a9101172..7dd933f8 100644 --- a/packages/design/src/FormManager/FormPreview/index.tsx +++ b/packages/design/src/FormManager/FormPreview/index.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { type FormDefinition, createFormSession } from '@atj/forms'; +import { type Blueprint, createFormSession } from '@atj/forms'; import { FormService } from '@atj/form-service'; import Form, { type FormUIContext } from '../../Form'; @@ -10,7 +10,7 @@ export default function FormPreview({ form, }: { context: FormUIContext; - form: FormDefinition; + form: Blueprint; }) { const session = createFormSession(form); return ; diff --git a/packages/design/src/config/edit/FormSummaryEdit.tsx b/packages/design/src/config/edit/FormSummaryEdit.tsx index 1092e298..d74d7457 100644 --- a/packages/design/src/config/edit/FormSummaryEdit.tsx +++ b/packages/design/src/config/edit/FormSummaryEdit.tsx @@ -1,13 +1,10 @@ import React from 'react'; import { useFormContext } from 'react-hook-form'; -import { type FormSummary } from '@atj/forms/src/config/elements/form-summary'; +import { type FormSummary } from '@atj/forms/src/patterns/form-summary'; +import { PatternEditComponent } from '../../FormManager/FormEdit/types'; -import { FormElementEditComponent } from '..'; - -const FormSummaryEdit: FormElementEditComponent = ({ - element, -}) => { +const FormSummaryEdit: PatternEditComponent = ({ pattern }) => { const { register } = useFormContext(); return (
    @@ -16,7 +13,7 @@ const FormSummaryEdit: FormElementEditComponent = ({ Title @@ -26,7 +23,7 @@ const FormSummaryEdit: FormElementEditComponent = ({ Description
    diff --git a/packages/design/src/config/edit/InputElementEdit.tsx b/packages/design/src/config/edit/InputPatternEdit.tsx similarity index 55% rename from packages/design/src/config/edit/InputElementEdit.tsx rename to packages/design/src/config/edit/InputPatternEdit.tsx index 3e4f0fe1..ce208244 100644 --- a/packages/design/src/config/edit/InputElementEdit.tsx +++ b/packages/design/src/config/edit/InputPatternEdit.tsx @@ -1,58 +1,55 @@ import React from 'react'; import { useFormContext } from 'react-hook-form'; -import { type InputElement } from '@atj/forms/src/config/elements/input'; +import { type InputPattern } from '@atj/forms/src/patterns/input'; +import { PatternEditComponent } from '../../FormManager/FormEdit/types'; -import { FormElementEditComponent } from '..'; - -const InputElementEdit: FormElementEditComponent = ({ - element, -}) => { +const InputPatternEdit: PatternEditComponent = ({ pattern }) => { const { register } = useFormContext(); return (
    -
    -
    -
    - @@ -23,7 +24,7 @@ const ParagraphElementEdit: FormElementComponent = ({
    @@ -67,15 +67,15 @@ const SortableItem = ({ id, form, element, context }: ItemProps) => { ); }; -const SequenceElementEdit: FormElementEditComponent = ({ +const SequencePatternEdit: PatternEditComponent = ({ context, form, - element, + pattern, }) => { const { register, setValue } = useFormContext(); - const [elements, setElements] = useState( - element.data.elements.map(elementId => { - return form.elements[elementId]; + const [patterns, setPatterns] = useState( + pattern.data.patterns.map((patternId: string) => { + return form.patterns[patternId]; }) ); const sensors = useSensors( @@ -94,38 +94,37 @@ const SequenceElementEdit: FormElementEditComponent = ({ return; } if (active.id !== over.id) { - const oldIndex = elements.findIndex(element => { - return element.id === active.id; + const oldIndex = patterns.findIndex(pattern => { + return pattern.id === active.id; }); - const newIndex = elements.findIndex(element => { - return element.id === over.id; + const newIndex = patterns.findIndex(pattern => { + return pattern.id === over.id; }); - const newOrder = arrayMove(elements, oldIndex, newIndex); - setElements(newOrder); - setValue(element.id, { - id: element.id, - type: element.type, + const newOrder = arrayMove(patterns, oldIndex, newIndex); + setPatterns(newOrder); + setValue(pattern.id, { + ...pattern, data: { - elements: newOrder.map(element => element.id), + patterns: newOrder.map(pattern => pattern.id), }, - } satisfies SequenceElement); + } satisfies SequencePattern); } }} >
      - - - - {elements.map(elements => ( + + + + {patterns.map(patterns => ( ))} @@ -136,4 +135,4 @@ const SequenceElementEdit: FormElementEditComponent = ({ ); }; -export default SequenceElementEdit; +export default SequencePatternEdit; diff --git a/packages/design/src/config/edit/SubmissionConfirmationEdit.tsx b/packages/design/src/config/edit/SubmissionConfirmationEdit.tsx index db5ec764..0c4b28d9 100644 --- a/packages/design/src/config/edit/SubmissionConfirmationEdit.tsx +++ b/packages/design/src/config/edit/SubmissionConfirmationEdit.tsx @@ -1,12 +1,11 @@ import React from 'react'; import { useFormContext } from 'react-hook-form'; -import { type InputElement } from '@atj/forms/src/config/elements/input'; +import { type InputPattern } from '@atj/forms/src/patterns/input'; +import { type PatternEditComponent } from '../../FormManager/FormEdit/types'; -import { FormElementEditComponent } from '..'; - -const InputElementEdit: FormElementEditComponent = ({ - element, +const SubmissionConfirmationEdit: PatternEditComponent = ({ + pattern, }) => { const { register } = useFormContext(); return ( @@ -16,7 +15,7 @@ const InputElementEdit: FormElementEditComponent = ({ Field label @@ -27,7 +26,7 @@ const InputElementEdit: FormElementEditComponent = ({
    @@ -37,14 +36,14 @@ const InputElementEdit: FormElementEditComponent = ({
    @@ -54,12 +53,12 @@ const InputElementEdit: FormElementEditComponent = ({ @@ -69,4 +68,4 @@ const InputElementEdit: FormElementEditComponent = ({ ); }; -export default InputElementEdit; +export default SubmissionConfirmationEdit; diff --git a/packages/design/src/config/edit/index.ts b/packages/design/src/config/edit/index.ts index cf4e2dd9..8813fc0f 100644 --- a/packages/design/src/config/edit/index.ts +++ b/packages/design/src/config/edit/index.ts @@ -1,10 +1,10 @@ -import InputElementEdit from './InputElementEdit'; -import SequenceElementEdit from './SequenceElementEdit'; +import InputPatternEdit from './InputPatternEdit'; +import SequencePatternEdit from './SequencePatternEdit'; import SubmissionConfirmationEdit from './SubmissionConfirmationEdit'; -import { type EditComponentForFormElement } from '../../FormManager/FormEdit/types'; +import { type EditComponentForPattern } from '../../FormManager/FormEdit/types'; -export const defaultFormElementEditComponents: EditComponentForFormElement = { - input: InputElementEdit, - sequence: SequenceElementEdit, +export const defaultPatternEditComponents: EditComponentForPattern = { + input: InputPatternEdit, + sequence: SequencePatternEdit, 'submission-confirmation': SubmissionConfirmationEdit, }; diff --git a/packages/design/src/config/view/Fieldset/index.tsx b/packages/design/src/config/view/Fieldset/index.tsx index d2b23dd5..2e09f7c1 100644 --- a/packages/design/src/config/view/Fieldset/index.tsx +++ b/packages/design/src/config/view/Fieldset/index.tsx @@ -1,10 +1,10 @@ import React from 'react'; -import { type FieldsetPattern, type Pattern } from '@atj/forms'; +import { type FieldsetProps } from '@atj/forms'; -import { type FormElementComponent } from '../../../Form'; +import { type PatternComponent } from '../../../Form'; -const FormSummary: FormElementComponent> = ({ +const FormSummary: PatternComponent = ({ pattern, children, }) => { diff --git a/packages/design/src/config/view/FormSummary/FormSummary.stories.tsx b/packages/design/src/config/view/FormSummary/FormSummary.stories.tsx index b0c63e52..b77317ca 100644 --- a/packages/design/src/config/view/FormSummary/FormSummary.stories.tsx +++ b/packages/design/src/config/view/FormSummary/FormSummary.stories.tsx @@ -1,6 +1,7 @@ import type { Meta, StoryObj } from '@storybook/react'; import FormSummary from '.'; +import { type FormSummaryProps } from '@atj/forms'; export default { title: 'patterns/FormSummary', @@ -11,22 +12,22 @@ export default { export const FormSummaryWithLongDescription = { args: { pattern: { - _elementId: 'test-id', + _patternId: 'test-id', type: 'form-summary', title: 'Form title', description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', - }, + } as FormSummaryProps, }, } satisfies StoryObj; export const FormSummaryWithShortDescription = { args: { pattern: { - _elementId: 'test-id', + _patternId: 'test-id', type: 'form-summary', title: 'Title 2', description: 'Short description', - }, + } as FormSummaryProps, }, } satisfies StoryObj; diff --git a/packages/design/src/config/view/FormSummary/index.tsx b/packages/design/src/config/view/FormSummary/index.tsx index 95a7002e..0d77b53a 100644 --- a/packages/design/src/config/view/FormSummary/index.tsx +++ b/packages/design/src/config/view/FormSummary/index.tsx @@ -1,11 +1,9 @@ import React from 'react'; -import { Pattern, type FormSummaryPattern } from '@atj/forms'; -import { type FormElementComponent } from '../../../Form'; +import { type FormSummaryProps } from '@atj/forms'; +import { type PatternComponent } from '../../../Form'; -const FormSummary: FormElementComponent> = ({ - pattern, -}) => { +const FormSummary: PatternComponent = ({ pattern }) => { return ( <>
    diff --git a/packages/design/src/config/view/Paragraph/index.tsx b/packages/design/src/config/view/Paragraph/index.tsx index bf9428a0..ef01808f 100644 --- a/packages/design/src/config/view/Paragraph/index.tsx +++ b/packages/design/src/config/view/Paragraph/index.tsx @@ -1,12 +1,10 @@ import React from 'react'; -import { type ParagraphPattern, type Pattern } from '@atj/forms'; +import { type ParagraphProps } from '@atj/forms'; -import { type FormElementComponent } from '../../../Form'; +import { type PatternComponent } from '../../../Form'; -const FormSummary: FormElementComponent> = ({ - pattern, -}) => { +const FormSummary: PatternComponent = ({ pattern }) => { if (pattern.style === 'heading') { return ( <> diff --git a/packages/design/src/config/view/Sequence/index.tsx b/packages/design/src/config/view/Sequence/index.tsx index 92e92d75..955b4765 100644 --- a/packages/design/src/config/view/Sequence/index.tsx +++ b/packages/design/src/config/view/Sequence/index.tsx @@ -1,13 +1,8 @@ import React from 'react'; -import { type Pattern } from '@atj/forms'; -import { SequenceElement } from '@atj/forms/src/config/elements/sequence'; +import { type PatternComponent } from '../../../Form'; -import { FormElementComponent } from '../../../Form'; - -const Sequence: FormElementComponent> = ({ - children, -}) => { +const Sequence: PatternComponent = ({ children }) => { return <>{children}; }; diff --git a/packages/design/src/config/view/SubmissionConfirmation/SubmissionConfirmation.stories.tsx b/packages/design/src/config/view/SubmissionConfirmation/SubmissionConfirmation.stories.tsx index b745e3d6..fea444e5 100644 --- a/packages/design/src/config/view/SubmissionConfirmation/SubmissionConfirmation.stories.tsx +++ b/packages/design/src/config/view/SubmissionConfirmation/SubmissionConfirmation.stories.tsx @@ -1,6 +1,7 @@ import type { Meta, StoryObj } from '@storybook/react'; -import SubmissionConfirmation, { type SubmissionConfirmationProps } from '.'; +import SubmissionConfirmation from '.'; +import { type SubmissionConfirmationProps } from '@atj/forms'; export default { title: 'patterns/SubmissionConfirmation', @@ -10,7 +11,7 @@ export default { export const SubmissionConfirmationExample = { args: { - prompt: { + pattern: { type: 'submission-confirmation', table: [ { label: 'Field 1', value: 'Value 1' }, @@ -18,6 +19,6 @@ export const SubmissionConfirmationExample = { { label: 'Field 3', value: 'Value 3' }, { label: 'Field 4', value: 'Value 4' }, ], - }, - } satisfies SubmissionConfirmationProps, + } as SubmissionConfirmationProps, + }, } satisfies StoryObj; diff --git a/packages/design/src/config/view/SubmissionConfirmation/index.tsx b/packages/design/src/config/view/SubmissionConfirmation/index.tsx index b9cd3294..d763ecb1 100644 --- a/packages/design/src/config/view/SubmissionConfirmation/index.tsx +++ b/packages/design/src/config/view/SubmissionConfirmation/index.tsx @@ -1,11 +1,11 @@ import React from 'react'; -import { Pattern, type SubmissionConfirmationPattern } from '@atj/forms'; -import { FormElementComponent } from '../../../Form'; +import { type SubmissionConfirmationProps } from '@atj/forms'; +import { type PatternComponent } from '../../../Form'; -const SubmissionConfirmation: FormElementComponent< - Pattern -> = ({ pattern }) => { +const SubmissionConfirmation: PatternComponent = ({ + pattern, +}) => { return ( <> diff --git a/packages/design/src/config/view/TextInput/TestInput.stories.tsx b/packages/design/src/config/view/TextInput/TestInput.stories.tsx index dd1b139b..71138680 100644 --- a/packages/design/src/config/view/TextInput/TestInput.stories.tsx +++ b/packages/design/src/config/view/TextInput/TestInput.stories.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { FormProvider, useForm } from 'react-hook-form'; import type { Meta, StoryObj } from '@storybook/react'; -import { type Pattern, type TextInputPattern } from '@atj/forms'; +import { type TextInputProps } from '@atj/forms'; import TextInput from '.'; export default { @@ -27,25 +27,25 @@ export default { export const Required = { args: { pattern: { - _elementId: '', + _patternId: '', type: 'input', inputId: 'test-prompt', value: '', label: 'Please enter your first name.', required: true, - } as Pattern, + } as TextInputProps, }, } satisfies StoryObj; export const NotRequired = { args: { pattern: { - _elementId: '', + _patternId: '', type: 'input', inputId: 'test-prompt', value: '', label: 'Please enter your first name.', required: false, - } as Pattern, + } as TextInputProps, }, } satisfies StoryObj; diff --git a/packages/design/src/config/view/TextInput/index.tsx b/packages/design/src/config/view/TextInput/index.tsx index 1b618d0a..3ea57703 100644 --- a/packages/design/src/config/view/TextInput/index.tsx +++ b/packages/design/src/config/view/TextInput/index.tsx @@ -2,12 +2,10 @@ import classNames from 'classnames'; import React from 'react'; import { useFormContext } from 'react-hook-form'; -import { Pattern, type TextInputPattern } from '@atj/forms'; -import { type FormElementComponent } from '../../../Form'; +import { type TextInputProps } from '@atj/forms'; +import { type PatternComponent } from '../../../Form'; -const TextInput: FormElementComponent> = ({ - pattern, -}) => { +const TextInput: PatternComponent = ({ pattern }) => { const { register } = useFormContext(); return (
    diff --git a/packages/design/src/config/view/index.tsx b/packages/design/src/config/view/index.tsx index 1e704d5e..0066eb4e 100644 --- a/packages/design/src/config/view/index.tsx +++ b/packages/design/src/config/view/index.tsx @@ -6,7 +6,7 @@ import SubmissionConfirmation from './SubmissionConfirmation'; import TextInput from './TextInput'; import { type ComponentForPattern } from '../../Form'; -export const defaultFormElementComponents: ComponentForPattern = { +export const defaultPatternComponents: ComponentForPattern = { fieldset: Fieldset, 'form-summary': FormSummary, input: TextInput, diff --git a/packages/design/src/test-form.ts b/packages/design/src/test-form.ts index 4caaf04d..4a77f193 100644 --- a/packages/design/src/test-form.ts +++ b/packages/design/src/test-form.ts @@ -1,11 +1,13 @@ import { createForm, createFormSession, defaultFormConfig } from '@atj/forms'; import { - defaultFormElementComponents, - defaultFormElementEditComponents, + defaultPatternComponents, + defaultPatternEditComponents, } from './config'; import { FormUIContext } from 'Form'; import { type FormEditUIContext } from './FormManager/FormEdit/types'; +import { SequencePattern } from '@atj/forms/src/patterns/sequence'; +import { InputPattern } from '@atj/forms/src/patterns/input'; export const createTestForm = () => { return createForm( @@ -15,40 +17,49 @@ export const createTestForm = () => { }, { root: 'root', - elements: [ + patterns: [ { type: 'sequence', id: 'root', data: { - elements: ['element-1', 'element-2'], + patterns: ['element-1', 'element-2'], }, - default: { - elements: [], + initial: { + patterns: [], }, - required: true, - }, + } as SequencePattern, { type: 'input', id: 'element-1', data: { - text: 'FormElement 1', + label: 'Pattern 1', + initial: '', required: true, + maxLength: 128, + }, + initial: { + label: 'Pattern 1', initial: '', + required: true, + maxLength: 128, }, - default: '', - required: true, - }, + } as InputPattern, { type: 'input', id: 'element-2', data: { - text: 'FormElement 2', - required: false, + label: 'Pattern 2', initial: 'test', + required: true, + maxLength: 128, + }, + initial: { + label: 'Pattern 2', + initial: 'test', + required: true, + maxLength: 128, }, - default: '', - required: true, - }, + } as InputPattern, ], } ); @@ -58,14 +69,14 @@ export const createTestFormConfig = () => { return defaultFormConfig; }; -export const createTestFormElementComponentMap = () => { - return defaultFormElementComponents; +export const createTestPatternComponentMap = () => { + return defaultPatternComponents; }; export const createTestFormContext = (): FormUIContext => { return { config: defaultFormConfig, - components: defaultFormElementComponents, + components: defaultPatternComponents, uswdsRoot: '/uswds/', }; }; @@ -73,8 +84,8 @@ export const createTestFormContext = (): FormUIContext => { export const createTestFormEditContext = (): FormEditUIContext => { return { config: defaultFormConfig, - components: defaultFormElementComponents, - editComponents: defaultFormElementEditComponents, + components: defaultPatternComponents, + editComponents: defaultPatternEditComponents, uswdsRoot: `/static/uswds/`, }; }; diff --git a/packages/form-service/src/context/browser/form-repo.ts b/packages/form-service/src/context/browser/form-repo.ts index f2e13978..ba2ccd10 100644 --- a/packages/form-service/src/context/browser/form-repo.ts +++ b/packages/form-service/src/context/browser/form-repo.ts @@ -1,10 +1,10 @@ import { Result } from '@atj/common'; -import { type FormDefinition } from '@atj/forms'; +import { type Blueprint } from '@atj/forms'; export const getFormFromStorage = ( storage: Storage, id?: string -): FormDefinition | null => { +): Blueprint | null => { if (!storage || !id) { return null; } @@ -33,7 +33,7 @@ export const getFormSummaryListFromStorage = (storage: Storage) => { return null; } return forms.map(key => { - const form = getFormFromStorage(storage, key) as FormDefinition; + const form = getFormFromStorage(storage, key) as Blueprint; if (form === null) { throw new Error('key mismatch'); } @@ -47,7 +47,7 @@ export const getFormSummaryListFromStorage = (storage: Storage) => { export const addFormToStorage = ( storage: Storage, - form: FormDefinition + form: Blueprint ): Result => { const uuid = crypto.randomUUID(); @@ -65,7 +65,7 @@ export const addFormToStorage = ( export const saveFormToStorage = ( storage: Storage, formId: string, - form: FormDefinition + form: Blueprint ) => { try { storage.setItem(formId, stringifyForm(form)); @@ -84,7 +84,7 @@ export const deleteFormFromStorage = (storage: Storage, formId: string) => { storage.removeItem(formId); }; -const stringifyForm = (form: FormDefinition) => { +const stringifyForm = (form: Blueprint) => { return JSON.stringify({ ...form, outputs: form.outputs.map(output => ({ @@ -95,8 +95,8 @@ const stringifyForm = (form: FormDefinition) => { }); }; -const parseStringForm = (formString: string): FormDefinition => { - const form = JSON.parse(formString) as FormDefinition; +const parseStringForm = (formString: string): Blueprint => { + const form = JSON.parse(formString) as Blueprint; return { ...form, outputs: form.outputs.map(output => ({ diff --git a/packages/form-service/src/context/test/storage.ts b/packages/form-service/src/context/test/storage.ts index bcc9831e..3de3dfbc 100644 --- a/packages/form-service/src/context/test/storage.ts +++ b/packages/form-service/src/context/test/storage.ts @@ -1,7 +1,7 @@ -import { type FormDefinition } from '@atj/forms'; +import { type Blueprint } from '@atj/forms'; import { saveFormToStorage } from '../browser/form-repo'; -export type TestData = Record; +export type TestData = Record; export const createTestStorage = (testData: TestData): Storage => { const records: Record = {}; diff --git a/packages/form-service/src/operations/add-form.ts b/packages/form-service/src/operations/add-form.ts index 997613c9..079300dd 100644 --- a/packages/form-service/src/operations/add-form.ts +++ b/packages/form-service/src/operations/add-form.ts @@ -1,11 +1,11 @@ import { Result } from '@atj/common'; -import { FormDefinition } from '@atj/forms'; +import { Blueprint } from '@atj/forms'; import { addFormToStorage } from '../context/browser/form-repo'; export const addForm = ( ctx: { storage: Storage }, - form: FormDefinition + form: Blueprint ): Result => { return addFormToStorage(ctx.storage, form); }; diff --git a/packages/form-service/src/operations/get-form.ts b/packages/form-service/src/operations/get-form.ts index 4c5c94b4..077a351b 100644 --- a/packages/form-service/src/operations/get-form.ts +++ b/packages/form-service/src/operations/get-form.ts @@ -1,12 +1,12 @@ import { Result } from '@atj/common'; -import { type FormDefinition } from '@atj/forms'; +import { type Blueprint } from '@atj/forms'; import { getFormFromStorage } from '../context/browser/form-repo'; export const getForm = ( ctx: { storage: Storage }, formId: string -): Result => { +): Result => { const result = getFormFromStorage(ctx.storage, formId); if (result === null) { return { diff --git a/packages/form-service/src/operations/save-form.ts b/packages/form-service/src/operations/save-form.ts index 7b537644..d689b1df 100644 --- a/packages/form-service/src/operations/save-form.ts +++ b/packages/form-service/src/operations/save-form.ts @@ -1,12 +1,12 @@ import { VoidResult } from '@atj/common'; -import { FormDefinition } from '@atj/forms'; +import { Blueprint } from '@atj/forms'; import { saveFormToStorage } from '../context/browser/form-repo'; export const saveForm = ( ctx: { storage: Storage }, formId: string, - form: FormDefinition + form: Blueprint ): VoidResult => { const result = saveFormToStorage(ctx.storage, formId, form); if (result.success === false) { diff --git a/packages/form-service/src/operations/submit-form.ts b/packages/form-service/src/operations/submit-form.ts index 5c9fe47d..e0d46e16 100644 --- a/packages/form-service/src/operations/submit-form.ts +++ b/packages/form-service/src/operations/submit-form.ts @@ -1,7 +1,7 @@ import { type Result } from '@atj/common'; import { type FormConfig, - type FormDefinition, + type Blueprint, type FormSession, applyPromptResponse, createFormOutputFieldData, @@ -55,7 +55,7 @@ export const submitForm = async ( }; const generateDocumentPackage = async ( - form: FormDefinition, + form: Blueprint, formData: Record ) => { const errors = new Array(); diff --git a/packages/form-service/src/types.ts b/packages/form-service/src/types.ts index fc65989f..8cc51342 100644 --- a/packages/form-service/src/types.ts +++ b/packages/form-service/src/types.ts @@ -1,14 +1,14 @@ import { Result, VoidResult } from '@atj/common'; -import { FormDefinition, FormSession } from '@atj/forms'; +import { Blueprint, FormSession } from '@atj/forms'; import { FormListItem } from './operations/get-form-list'; export type FormService = { - addForm: (form: FormDefinition) => Result; + addForm: (form: Blueprint) => Result; deleteForm: (formId: string) => VoidResult; - getForm: (formId: string) => Result; + getForm: (formId: string) => Result; getFormList: () => Result; - saveForm: (formId: string, form: FormDefinition) => VoidResult; + saveForm: (formId: string, form: Blueprint) => VoidResult; submitForm: ( //sessionId: string, session: FormSession, // TODO: load session from storage by ID diff --git a/packages/forms/src/builder/index.ts b/packages/forms/src/builder/index.ts index 7fa4116b..5ae55e78 100644 --- a/packages/forms/src/builder/index.ts +++ b/packages/forms/src/builder/index.ts @@ -1,23 +1,23 @@ import { - type FormDefinition, + type Blueprint, + type FormConfig, type FormSummary, + type Pattern, + type PatternMap, addDocument, - nullFormDefinition, + nullBlueprint, updateFormSummary, - updateFormElement, - FormElementMap, - FormElement, - FormConfig, + updatePatternFromFormData, } from '..'; export class FormBuilder { - private _form: FormDefinition; + private _form: Blueprint; - constructor(initialForm: FormDefinition = nullFormDefinition) { - this._form = initialForm || nullFormDefinition; + constructor(initialForm: Blueprint = nullBlueprint) { + this._form = initialForm || nullBlueprint; } - get form(): FormDefinition { + get form(): Blueprint { return this._form; } @@ -30,15 +30,11 @@ export class FormBuilder { this._form = updatedForm; } - updateFormElement( - config: FormConfig, - formElement: FormElement, - formData: FormElementMap - ) { - const updatedElement = updateFormElement( + updatePattern(config: FormConfig, pattern: Pattern, formData: PatternMap) { + const updatedElement = updatePatternFromFormData( config, this.form, - formElement, + pattern, formData ); if (!updatedElement) { diff --git a/packages/forms/src/components.ts b/packages/forms/src/components.ts new file mode 100644 index 00000000..51f79572 --- /dev/null +++ b/packages/forms/src/components.ts @@ -0,0 +1,157 @@ +import { getRootPattern } from '..'; +import { + type FormConfig, + type Pattern, + type PatternId, + getPatternConfig, +} from './pattern'; +import { type FormSession, nullSession, sessionIsComplete } from './session'; + +export type TextInputProps = PatternProps<{ + type: 'input'; + inputId: string; + value: string; + label: string; + required: boolean; + error?: string; +}>; + +export type FormSummaryProps = PatternProps<{ + type: 'form-summary'; + title: string; + description: string; +}>; + +export type SubmissionConfirmationProps = PatternProps<{ + type: 'submission-confirmation'; + table: { label: string; value: string }[]; +}>; + +export type ParagraphProps = PatternProps<{ + type: 'paragraph'; + text: string; + style: 'indent' | 'normal' | 'heading' | 'subheading'; +}>; + +export type FieldsetProps = PatternProps<{ + type: 'fieldset'; + legend: string; +}>; + +export type PatternProps = { + _patternId: PatternId; + _children: PromptPart[]; + type: string; +} & T; + +export type SubmitAction = { + type: 'submit'; + text: 'Submit'; +}; +export type PromptAction = SubmitAction; + +export type PromptPart = { + pattern: PatternProps; + children: PromptPart[]; +}; + +export type Prompt = { + actions: PromptAction[]; + parts: PromptPart[]; +}; + +export const createPrompt = ( + config: FormConfig, + session: FormSession, + options: { validate: boolean } +): Prompt => { + if (options.validate && sessionIsComplete(config, session)) { + return { + actions: [], + parts: [ + { + pattern: { + _patternId: 'submission-confirmation', + type: 'submission-confirmation', + table: Object.entries(session.data.values) + .filter(([patternId, value]) => { + const elemConfig = getPatternConfig( + config, + session.form.patterns[patternId].type + ); + return elemConfig.acceptsInput; + }) + .map(([patternId, value]) => { + return { + label: session.form.patterns[patternId].data.label, + value: value, + }; + }), + } as SubmissionConfirmationProps, + children: [], + }, + ], + }; + } + const parts: PromptPart[] = [ + { + pattern: { + _patternId: 'form-summary', + type: 'form-summary', + title: session.form.summary.title, + description: session.form.summary.description, + } as FormSummaryProps, + children: [], + }, + ]; + const root = getRootPattern(session.form); + parts.push(createPromptForPattern(config, session, root, options)); + return { + actions: [ + { + type: 'submit', + text: 'Submit', + }, + ], + parts, + }; +}; + +export type CreatePrompt = ( + config: FormConfig, + session: FormSession, + pattern: T, + options: { validate: boolean } +) => PromptPart; + +export const createPromptForPattern: CreatePrompt = ( + config, + session, + pattern, + options +) => { + const patternConfig = getPatternConfig(config, pattern.type); + return patternConfig.createPrompt(config, session, pattern, options); +}; + +export const isPromptAction = (prompt: Prompt, action: string) => { + return prompt.actions.find(a => a.type === action); +}; + +export const createNullPrompt = ({ + config, + pattern, +}: { + config: FormConfig; + pattern: Pattern; +}): Prompt => { + const formPatternConfig = getPatternConfig(config, pattern.type); + return { + parts: [ + formPatternConfig.createPrompt(config, nullSession, pattern, { + validate: false, + }), + ], + actions: [], + }; +}; diff --git a/packages/forms/src/config.ts b/packages/forms/src/config.ts new file mode 100644 index 00000000..f77b1adc --- /dev/null +++ b/packages/forms/src/config.ts @@ -0,0 +1 @@ +export { defaultFormConfig } from './patterns'; diff --git a/packages/forms/src/config/elements/fieldset.ts b/packages/forms/src/config/elements/fieldset.ts deleted file mode 100644 index 6fa993ab..00000000 --- a/packages/forms/src/config/elements/fieldset.ts +++ /dev/null @@ -1,54 +0,0 @@ -import * as z from 'zod'; - -import { type FormElementConfig } from '..'; -import { type FormElement, type FormElementId } from '../../element'; -import { - type FieldsetPattern, - type Pattern, - createPromptForElement, -} from '../../pattern'; -import { safeZodParse } from '../../util/zod'; -import { getFormElement } from '../..'; - -export type FieldsetElement = FormElement<{ - legend?: string; - elements: FormElementId[]; -}>; - -const FieldsetSchema = z.array(z.string()); - -const configSchema = z.object({ - legend: z.string().optional(), - elements: z.array(z.string()), -}); - -export const fieldsetConfig: FormElementConfig = { - acceptsInput: false, - initial: { - elements: [], - }, - parseData: (_, obj) => { - return safeZodParse(FieldsetSchema, obj); - }, - parseConfigData: obj => safeZodParse(configSchema, obj), - getChildren(element, elements) { - return element.data.elements.map( - (elementId: string) => elements[elementId] - ); - }, - createPrompt(config, session, element, options) { - const children = element.data.elements.map((elementId: string) => { - const element = getFormElement(session.form, elementId); - return createPromptForElement(config, session, element, options); - }); - return { - pattern: { - _children: children, - _elementId: element.id, - type: 'fieldset', - legend: element.data.legend, - } satisfies Pattern, - children, - }; - }, -}; diff --git a/packages/forms/src/config/elements/form-summary.ts b/packages/forms/src/config/elements/form-summary.ts deleted file mode 100644 index c1181189..00000000 --- a/packages/forms/src/config/elements/form-summary.ts +++ /dev/null @@ -1,38 +0,0 @@ -import * as z from 'zod'; - -import { type FormElementConfig } from '..'; -import { type FormElement } from '../../element'; -import { FormSummaryPattern, type Pattern } from '../../pattern'; -import { safeZodParse } from '../../util/zod'; - -const configSchema = z.object({ - title: z.string().max(128), - summary: z.string().max(2024), -}); -export type FormSummary = FormElement>; - -export const formSummaryConfig: FormElementConfig = { - acceptsInput: false, - initial: { - text: '', - initial: '', - required: true, - maxLength: 128, - }, - parseData: obj => safeZodParse(configSchema, obj), // make this optional? - parseConfigData: obj => safeZodParse(configSchema, obj), - getChildren() { - return []; - }, - createPrompt(_, session, element, options) { - return { - pattern: { - _elementId: element.id, - type: 'form-summary', - title: element.data.title, - description: element.data.description, - } as Pattern, - children: [], - }; - }, -}; diff --git a/packages/forms/src/config/elements/input.ts b/packages/forms/src/config/elements/input.ts deleted file mode 100644 index afe2c8a8..00000000 --- a/packages/forms/src/config/elements/input.ts +++ /dev/null @@ -1,55 +0,0 @@ -import * as z from 'zod'; - -import { type FormElementConfig } from '..'; -import { type FormElement, validateElement } from '../../element'; -import { type Pattern, type TextInputPattern } from '../../pattern'; -import { getFormSessionValue } from '../../session'; -import { safeZodParse } from '../../util/zod'; - -const configSchema = z.object({ - label: z.string(), - initial: z.string(), - required: z.boolean(), - maxLength: z.coerce.number(), -}); -export type InputElement = FormElement>; - -const createSchema = (data: InputElement['data']) => - z.string().max(data.maxLength); - -export const inputConfig: FormElementConfig = { - acceptsInput: true, - initial: { - label: '', - initial: '', - required: true, - maxLength: 128, - }, - parseData: (elementData, obj) => safeZodParse(createSchema(elementData), obj), - parseConfigData: obj => safeZodParse(configSchema, obj), - getChildren() { - return []; - }, - createPrompt(_, session, element, options) { - const extraAttributes: Record = {}; - const sessionValue = getFormSessionValue(session, element.id); - if (options.validate) { - const isValidResult = validateElement(inputConfig, element, sessionValue); - if (!isValidResult.success) { - extraAttributes['error'] = isValidResult.error; - } - } - return { - pattern: { - _elementId: element.id, - type: 'input', - inputId: element.id, - value: sessionValue, - label: element.data.label, - required: element.data.required, - ...extraAttributes, - } as Pattern, - children: [], - }; - }, -}; diff --git a/packages/forms/src/config/elements/paragraph.ts b/packages/forms/src/config/elements/paragraph.ts deleted file mode 100644 index 2e34c92c..00000000 --- a/packages/forms/src/config/elements/paragraph.ts +++ /dev/null @@ -1,40 +0,0 @@ -import * as z from 'zod'; - -import { type FormElementConfig } from '..'; -import { type FormElement, validateElement } from '../../element'; -import { type Pattern, type ParagraphPattern } from '../../pattern'; -import { getFormSessionValue } from '../../session'; -import { safeZodParse } from '../../util/zod'; - -const configSchema = z.object({ - text: z.string(), - maxLength: z.coerce.number(), -}); -export type ParagraphElement = FormElement>; - -const createSchema = (data: ParagraphElement['data']) => - z.string().max(data.maxLength); - -export const paragraphConfig: FormElementConfig = { - acceptsInput: false, - initial: { - text: 'normal', - maxLength: 2048, - }, - parseData: (elementData, obj) => safeZodParse(createSchema(elementData), obj), - parseConfigData: obj => safeZodParse(configSchema, obj), - getChildren() { - return []; - }, - createPrompt(_, session, element, options) { - return { - pattern: { - _elementId: element.id, - type: 'paragraph' as const, - text: element.data.text, - style: element.data.style, - } as Pattern, - children: [], - }; - }, -}; diff --git a/packages/forms/src/config/elements/sequence.ts b/packages/forms/src/config/elements/sequence.ts deleted file mode 100644 index 6d4624d1..00000000 --- a/packages/forms/src/config/elements/sequence.ts +++ /dev/null @@ -1,47 +0,0 @@ -import * as z from 'zod'; - -import { type FormElementConfig } from '..'; -import { type FormElement, type FormElementId } from '../../element'; -import { createPromptForElement } from '../../pattern'; -import { safeZodParse } from '../../util/zod'; -import { getFormElement } from '../..'; - -export type SequenceElement = FormElement<{ - elements: FormElementId[]; -}>; - -const sequenceSchema = z.array(z.string()); - -const configSchema = z.object({ - elements: z.array(z.string()), -}); - -export const sequenceConfig: FormElementConfig = { - acceptsInput: false, - initial: { - elements: [], - }, - parseData: (_, obj) => { - return safeZodParse(sequenceSchema, obj); - }, - parseConfigData: obj => safeZodParse(configSchema, obj), - getChildren(element, elements) { - return element.data.elements.map( - (elementId: string) => elements[elementId] - ); - }, - createPrompt(config, session, element, options) { - const children = element.data.elements.map((elementId: string) => { - const childElement = getFormElement(session.form, elementId); - return createPromptForElement(config, session, childElement, options); - }); - return { - pattern: { - _children: children, - _elementId: element.id, - type: 'sequence', - }, - children, - }; - }, -}; diff --git a/packages/forms/src/config/index.ts b/packages/forms/src/config/index.ts deleted file mode 100644 index fbb57c45..00000000 --- a/packages/forms/src/config/index.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { - type FormElement, - type FormElementId, - type ParseFormElementData, - type ParseFormElementConfigData, -} from '../element'; -import { type CreatePrompt } from '../pattern'; - -export { defaultFormConfig } from './config'; - -export type FormElementConfig = { - acceptsInput: boolean; - initial: ThisFormElement['data']; - parseData: ParseFormElementData; - parseConfigData: ParseFormElementConfigData; - getChildren: ( - element: ThisFormElement, - elements: Record - ) => FormElement[]; - createPrompt: CreatePrompt; -}; -export type FormConfig = { - elements: Record>; -}; - -export type ConfigElements = ReturnType< - Config['elements'][keyof Config['elements']]['parseData'] ->; diff --git a/packages/forms/src/documents/document.ts b/packages/forms/src/documents/document.ts index f5b3bb8a..dbea7c42 100644 --- a/packages/forms/src/documents/document.ts +++ b/packages/forms/src/documents/document.ts @@ -1,34 +1,34 @@ import { - FormDefinition, - FormElement, + Blueprint, + Pattern, addFormOutput, - addFormElements, - addFormElementMap, + addPatterns, + addPatternMap, updateFormSummary, } from '..'; -import { InputElement } from '../config/elements/input'; +import { InputPattern } from '../patterns/input'; import { PDFDocument, getDocumentFieldData } from './pdf'; -import { getSuggestedFormElementsFromCache } from './suggestions'; +import { getSuggestedPatternsFromCache } from './suggestions'; import { DocumentFieldMap } from './types'; export type DocumentTemplate = PDFDocument; export const addDocument = async ( - form: FormDefinition, + form: Blueprint, fileDetails: { name: string; data: Uint8Array; } ) => { const fields = await getDocumentFieldData(fileDetails.data); - const cachedPdf = await getSuggestedFormElementsFromCache(fileDetails.data); + const cachedPdf = await getSuggestedPatternsFromCache(fileDetails.data); if (cachedPdf) { form = updateFormSummary(form, { title: cachedPdf.title, description: '', }); - form = addFormElementMap(form, cachedPdf.elements, cachedPdf.root); + form = addPatternMap(form, cachedPdf.patterns, cachedPdf.root); const updatedForm = addFormOutput(form, { data: fileDetails.data, path: fileDetails.name, @@ -66,86 +66,81 @@ export const addDocument = async ( }; export const addDocumentFieldsToForm = ( - form: FormDefinition, + form: Blueprint, fields: DocumentFieldMap ) => { - const elements: FormElement[] = []; - Object.entries(fields).map(([elementId, field]) => { + const patterns: Pattern[] = []; + Object.entries(fields).map(([patternId, field]) => { if (field.type === 'CheckBox') { - elements.push({ + patterns.push({ type: 'input', - id: elementId, + id: patternId, data: { label: field.label, }, - default: { + initial: { label: '', initial: '', required: false, maxLength: 128, }, - required: field.required, - } satisfies InputElement); + } satisfies InputPattern); } else if (field.type === 'OptionList') { - elements.push({ + patterns.push({ type: 'input', - id: elementId, + id: patternId, data: { label: field.label, }, - default: { + initial: { label: '', initial: '', required: false, maxLength: 128, }, - required: field.required, - } satisfies InputElement); + } satisfies InputPattern); } else if (field.type === 'Dropdown') { - elements.push({ + patterns.push({ type: 'input', - id: elementId, + id: patternId, data: { label: field.label, }, - default: { + initial: { label: '', initial: '', required: false, maxLength: 128, }, - required: field.required, - } satisfies InputElement); + } satisfies InputPattern); } else if (field.type === 'TextField') { - elements.push({ + patterns.push({ type: 'input', - id: elementId, + id: patternId, data: { label: field.label, }, - default: { + initial: { label: '', initial: '', required: false, maxLength: 128, }, - required: field.required, - } satisfies InputElement); + } satisfies InputPattern); } else if (field.type === 'RadioGroup') { - elements.push({ + patterns.push({ type: 'input', - id: elementId, + id: patternId, data: { label: field.label, }, - default: { + initial: { label: '', initial: '', required: false, maxLength: 128, }, - required: field.required, - } satisfies InputElement); + } satisfies InputPattern); } else if (field.type === 'Paragraph') { // skip purely presentational fields } else if (field.type === 'not-supported') { @@ -154,14 +149,13 @@ export const addDocumentFieldsToForm = ( const _exhaustiveCheck: never = field; } }); - elements.push({ + patterns.push({ id: 'root', type: 'sequence', data: { - elements: elements.map(element => element.id), + patterns: patterns.map(pattern => pattern.id), }, - default: [], - required: true, + initial: [], }); - return addFormElements(form, elements, 'root'); + return addPatterns(form, patterns, 'root'); }; diff --git a/packages/forms/src/documents/pdf/generate.ts b/packages/forms/src/documents/pdf/generate.ts index 20ef721c..90e13513 100644 --- a/packages/forms/src/documents/pdf/generate.ts +++ b/packages/forms/src/documents/pdf/generate.ts @@ -9,14 +9,14 @@ export const createFormOutputFieldData = ( formData: Record ): Record => { const results = {} as Record; - Object.entries(output.fields).forEach(([elementId, docField]) => { + Object.entries(output.fields).forEach(([patternId, docField]) => { if (docField.type === 'not-supported') { return; } - const outputFieldId = output.formFields[elementId]; + const outputFieldId = output.formFields[patternId]; results[outputFieldId] = { type: docField.type, - value: formData[elementId], + value: formData[patternId], }; }); return results; diff --git a/packages/forms/src/documents/pdf/mock-api.ts b/packages/forms/src/documents/pdf/mock-api.ts index dddbd6c6..118d9f5f 100644 --- a/packages/forms/src/documents/pdf/mock-api.ts +++ b/packages/forms/src/documents/pdf/mock-api.ts @@ -1,17 +1,13 @@ import * as z from 'zod'; -import { - type FormElement, - type FormElementId, - type FormElementMap, -} from '../..'; +import { type Pattern, type PatternId, type PatternMap } from '../..'; -import { ParagraphElement } from '../../config/elements/paragraph'; -import { InputElement } from '../../config/elements/input'; -import { FieldsetElement } from '../../config/elements/fieldset'; +import { type FieldsetPattern } from '../../patterns/fieldset'; +import { type InputPattern } from '../../patterns/input'; +import { type ParagraphPattern } from '../../patterns/paragraph'; import { stringToBase64 } from '../util'; -import { DocumentFieldMap } from '../types'; +import { type DocumentFieldMap } from '../types'; import json from './al_name_change'; @@ -94,28 +90,28 @@ const ExtractedObject = z.object({ type ExtractedObject = z.infer; export type ParsedPdf = { - elements: FormElementMap; + patterns: PatternMap; outputs: DocumentFieldMap; // to populate FormOutput - root: FormElementId; + root: PatternId; title: string; }; export const parseAlabamaNameChangeForm = (): ParsedPdf => { const extracted: ExtractedObject = ExtractedObject.parse(json); const parsedPdf: ParsedPdf = { - elements: {}, + patterns: {}, outputs: {}, root: 'root', title: extracted.title, }; - const rootSequence: FormElementId[] = []; + const rootSequence: PatternId[] = []; for (const element of extracted.elements) { - const fieldsetElements: FormElementId[] = []; + const fieldsetPatterns: PatternId[] = []; if (element.inputs.length === 0) { - parsedPdf.elements[element.id] = { + parsedPdf.patterns[element.id] = { type: 'paragraph', id: element.id, - default: { + initial: { text: '', maxLength: 2048, }, @@ -123,18 +119,17 @@ export const parseAlabamaNameChangeForm = (): ParsedPdf => { text: element.element_params.text, style: element.element_params.text_style, }, - required: false, - } satisfies ParagraphElement; + } satisfies ParagraphPattern; rootSequence.push(element.id); continue; } for (const input of element.inputs) { if (input.input_type === 'Tx') { const id = stringToBase64(PdfFieldMap[input.input_params.output_id]); - parsedPdf.elements[id] = { + parsedPdf.patterns[id] = { type: 'input', id, - default: { + initial: { required: false, label: '', initial: '', @@ -143,9 +138,8 @@ export const parseAlabamaNameChangeForm = (): ParsedPdf => { data: { label: input.input_params.instructions, }, - required: false, - } satisfies InputElement; - fieldsetElements.push(id); + } satisfies InputPattern; + fieldsetPatterns.push(id); parsedPdf.outputs[id] = { type: 'TextField', name: PdfFieldMap[input.input_params.output_id], @@ -156,53 +150,50 @@ export const parseAlabamaNameChangeForm = (): ParsedPdf => { }; } } - if (fieldsetElements.length > 0) { - parsedPdf.elements[element.id] = { + if (fieldsetPatterns.length > 0) { + parsedPdf.patterns[element.id] = { id: element.id, type: 'fieldset', data: { legend: element.element_params.text, - elements: fieldsetElements, + patterns: fieldsetPatterns, }, - default: { - elements: [], + initial: { + patterns: [], }, - required: true, - } as FieldsetElement; + } as FieldsetPattern; rootSequence.push(element.id); } } - parsedPdf.elements['root'] = { + parsedPdf.patterns['root'] = { id: 'root', type: 'sequence', data: { - elements: rootSequence, + patterns: rootSequence, }, - default: { - elements: [], + initial: { + patterns: [], }, - required: true, }; return parsedPdf; }; -const getElementInputs = (element: ExtractedElement): FormElement[] => { +const getElementInputs = (element: ExtractedElement): Pattern[] => { return element.inputs .map((input: ExtractedInput) => { if (input.input_type === 'Tx') { return { type: 'input', id: input.input_params.output_id, - default: {} as unknown as any, + initial: {} as unknown as any, data: { label: input.input_params.instructions, }, - required: true, - } satisfies InputElement; + } satisfies InputPattern; } - return null as unknown as FormElement; + return null as unknown as Pattern; }) - .filter((item): item is NonNullable => item !== null); + .filter((item): item is NonNullable => item !== null); }; const PdfFieldMap: Record = { @@ -277,7 +268,7 @@ function parseInputs( function parseElements( pdfElements: ExtractedJsonType['elements'] -): FormElement[] { +): Pattern[] { const output = pdfElements.reduce((acc, element) => { const elementOutput = { type: 'Paragraph', diff --git a/packages/forms/src/documents/suggestions.ts b/packages/forms/src/documents/suggestions.ts index 4445199c..3103f297 100644 --- a/packages/forms/src/documents/suggestions.ts +++ b/packages/forms/src/documents/suggestions.ts @@ -9,7 +9,7 @@ export type SuggestedForm = { type?: 'text'; }[]; -export const getSuggestedFormElementsFromCache = async ( +export const getSuggestedPatternsFromCache = async ( rawData: Uint8Array ): Promise => { const cache = getFakeCache(); diff --git a/packages/forms/src/element.ts b/packages/forms/src/element.ts deleted file mode 100644 index 8a5d16bd..00000000 --- a/packages/forms/src/element.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { type Result } from '@atj/common'; -import { - type FormElementConfig, - type FormConfig, - type FormDefinition, -} from '..'; - -export type FormElement = { - type: string; - id: FormElementId; - data: C; - default: T; - required: boolean; -}; - -export type FormElementId = string; -export type FormElementValue = - T['default']; -export type FormElementValueMap = Record; -export type FormElementMap = Record; -export type GetFormElement = ( - form: FormDefinition, - id: FormElementId -) => FormElement; - -export type ParseFormElementData = ( - elementData: T['data'], - obj: string -) => Result; - -export type ParseFormElementConfigData = ( - elementData: T['data'] -) => Result; - -export const getFormElement: GetFormElement = (form, elementId) => { - return form.elements[elementId]; -}; - -export const getFormElementMap = (elements: FormElement[]) => { - return Object.fromEntries( - elements.map(element => { - return [element.id, element]; - }) - ); -}; - -export const getFormElements = ( - form: FormDefinition, - elementIds: FormElementId[] -) => { - return elementIds.map(elementId => getFormElement(form, elementId)); -}; - -export const getFormElementConfig = ( - config: FormConfig, - elementType: FormElement['type'] -) => { - return config.elements[elementType]; -}; - -export const validateElement = ( - elementConfig: FormElementConfig, - element: FormElement, - value: any -): Result => { - if (!elementConfig.acceptsInput) { - return { - success: true, - data: value, - }; - } - const parseResult = elementConfig.parseData(element, value); - if (!parseResult.success) { - return { - success: false, - error: parseResult.error, - }; - } - if (element.data.required && !parseResult.data) { - return { - success: false, - error: 'Required value not provided.', - }; - } - return { - success: true, - data: parseResult.data, - }; -}; - -export const getFirstFormElement = ( - config: FormConfig, - form: FormDefinition, - element?: FormElement -): FormElement => { - if (!element) { - element = form.elements[form.root]; - } - const elemConfig = getFormElementConfig(config, element.type); - const children = elemConfig.getChildren(element, form.elements); - if (children?.length === 0) { - return element; - } - return getFirstFormElement(config, form, children[0]); -}; diff --git a/packages/forms/src/index.ts b/packages/forms/src/index.ts index 6df46d1f..4a803db3 100644 --- a/packages/forms/src/index.ts +++ b/packages/forms/src/index.ts @@ -1,48 +1,45 @@ -import { FormConfig } from './config'; -import { SequenceElement } from './config/elements/sequence'; -import { DocumentFieldMap } from './documents'; +import { type SequencePattern } from './patterns/sequence'; +import { type DocumentFieldMap } from './documents'; import { - type FormElement, - type FormElementId, - type FormElementMap, - type FormElementValue, - type FormElementValueMap, - getFormElementMap, - getFormElementConfig, -} from './element'; + type FormConfig, + type Pattern, + type PatternId, + type PatternMap, + getPatternMap, + getPatternConfig, +} from './pattern'; export * from './builder'; +export * from './components'; export * from './config'; export * from './documents'; -export * from './element'; export * from './pattern'; export * from './response'; export * from './session'; -export type FormDefinition = { +export type Blueprint = { summary: FormSummary; - root: FormElementId; - elements: FormElementMap; + root: PatternId; + patterns: PatternMap; outputs: FormOutput[]; }; -export const nullFormDefinition: FormDefinition = { +export const nullBlueprint: Blueprint = { summary: { title: '', description: '', }, root: 'root', - elements: { + patterns: { root: { type: 'sequence', id: 'root', data: { - elements: [], + patterns: [], }, - default: { - elements: [], + initial: { + patterns: [], }, - required: true, }, }, outputs: [], @@ -53,17 +50,6 @@ export type FormSummary = { description: string; }; -export type FormSessionId = string; -type ErrorMap = Record; -export type FormSession = { - id: FormSessionId; - data: { - errors: ErrorMap; - values: FormElementValueMap; - }; - form: FormDefinition; -}; - export type FormOutput = { data: Uint8Array; path: string; @@ -74,75 +60,56 @@ export type FormOutput = { export const createForm = ( summary: FormSummary, initial: { - elements: FormElement[]; - root: FormElementId; + patterns: Pattern[]; + root: PatternId; } = { - elements: [ + patterns: [ { id: 'root', type: 'sequence', data: { - elements: [], + patterns: [], }, - default: { - elements: [], + initial: { + patterns: [], }, - required: true, - } satisfies SequenceElement, + } satisfies SequencePattern, ], root: 'root', } -): FormDefinition => { +): Blueprint => { return { summary, root: initial.root, - elements: getFormElementMap(initial.elements), + patterns: getPatternMap(initial.patterns), outputs: [], }; }; -export const getRootFormElement = (form: FormDefinition) => { - return form.elements[form.root]; -}; - -export const createFormSession = (form: FormDefinition): FormSession => { - return { - id: crypto.randomUUID(), - data: { - errors: {}, - values: Object.fromEntries( - Object.values(form.elements).map((element, index) => { - return [element.id, form.elements[element.id].data.initial]; - }) - ), - }, - form, - }; +export const getRootPattern = (form: Blueprint) => { + return form.patterns[form.root]; }; -export const updateForm = ( - context: FormSession, - id: FormElementId, - value: any -) => { - if (!(id in context.form.elements)) { - console.error(`FormElement "${id}" does not exist on form.`); +/* +export const updateForm = (context: Session, id: PatternId, value: any) => { + if (!(id in context.form.patterns)) { + console.error(`Pattern "${id}" does not exist on form.`); return context; } const nextForm = addValue(context, id, value); - const element = context.form.elements[id]; - if (element.type === 'input') { - if (element.data.required && !value) { + const pattern = context.form.patterns[id]; + if (pattern.type === 'input') { + if (pattern.data.required && !value) { return addError(nextForm, id, 'Required value not provided.'); } } return nextForm; }; -const addValue = ( +const addValue = ( form: FormSession, - id: FormElementId, - value: FormElementValue + id: PatternId, + value: PatternValue ): FormSession => ({ ...form, data: { @@ -156,7 +123,7 @@ const addValue = ( const addError = ( session: FormSession, - id: FormElementId, + id: PatternId, error: string ): FormSession => ({ ...session, @@ -168,112 +135,88 @@ const addError = ( }, }, }); +*/ -export const addFormElementMap = ( - form: FormDefinition, - elements: FormElementMap, - root?: FormElementId +export const addPatternMap = ( + form: Blueprint, + patterns: PatternMap, + root?: PatternId ) => { return { ...form, - elements: { ...form.elements, ...elements }, + patterns: { ...form.patterns, ...patterns }, root: root !== undefined ? root : form.root, }; }; -export const addFormElements = ( - form: FormDefinition, - elements: FormElement[], - root?: FormElementId +export const addPatterns = ( + form: Blueprint, + patterns: Pattern[], + root?: PatternId ) => { - const formElementMap = getFormElementMap(elements); - return addFormElementMap(form, formElementMap, root); + const formPatternMap = getPatternMap(patterns); + return addPatternMap(form, formPatternMap, root); }; -export const replaceFormElements = ( - form: FormDefinition, - elements: FormElement[] -): FormDefinition => { +export const replacePatterns = ( + form: Blueprint, + patterns: Pattern[] +): Blueprint => { return { ...form, - elements: elements.reduce( - (acc, element) => { - acc[element.id] = element; + patterns: patterns.reduce( + (acc, pattern) => { + acc[pattern.id] = pattern; return acc; }, - {} as Record + {} as Record ), }; }; -export const updateElements = ( +export const updatePatterns = ( config: FormConfig, - form: FormDefinition, - newElements: FormElementMap -): FormDefinition => { - const root = newElements[form.root]; - const targetElements: FormElementMap = { + form: Blueprint, + newPatterns: PatternMap +): Blueprint => { + const root = newPatterns[form.root]; + const targetPatterns: PatternMap = { root, }; - const resource = config.elements[root.type as keyof FormConfig]; - const children = resource.getChildren(root, newElements); - targetElements[root.id] = root; - children.forEach(child => (targetElements[child.id] = child)); + const resource = config.patterns[root.type as keyof FormConfig]; + const children = resource.getChildren(root, newPatterns); + targetPatterns[root.id] = root; + children.forEach(child => (targetPatterns[child.id] = child)); return { ...form, - elements: targetElements, + patterns: targetPatterns, }; }; -export const updateElement = ( - form: FormDefinition, - formElement: FormElement -): FormDefinition => { +export const updatePattern = (form: Blueprint, pattern: Pattern): Blueprint => { return { ...form, - elements: { - ...form.elements, - [formElement.id]: formElement, + patterns: { + ...form.patterns, + [pattern.id]: pattern, }, }; }; -export const addFormOutput = (form: FormDefinition, document: FormOutput) => { +export const addFormOutput = (form: Blueprint, document: FormOutput) => { return { ...form, outputs: [...form.outputs, document], }; }; -export const getFormElement = (form: FormDefinition, id: FormElementId) => { - return form.elements[id]; +export const getPattern = (form: Blueprint, id: PatternId) => { + return form.patterns[id]; }; -export const updateFormSummary = ( - form: FormDefinition, - summary: FormSummary -) => { +export const updateFormSummary = (form: Blueprint, summary: FormSummary) => { return { ...form, summary, }; }; - -export const updateFormElement = ( - config: FormConfig, - form: FormDefinition, - formElement: FormElement, - formData: FormElementMap -) => { - const elementConfig = getFormElementConfig(config, formElement.type); - const data = formData[formElement.id].data; - const result = elementConfig.parseConfigData(data); - if (!result.success) { - return; - } - const updatedForm = updateElement(form, { - ...formElement, - data: result.data, - }); - return updatedForm; -}; diff --git a/packages/forms/src/pattern.ts b/packages/forms/src/pattern.ts index 7657ee57..6a6b7d8b 100644 --- a/packages/forms/src/pattern.ts +++ b/packages/forms/src/pattern.ts @@ -1,164 +1,133 @@ -import { - type FormConfig, - type FormElement, - type FormElementId, - getRootFormElement, -} from '..'; -import { getFormElementConfig } from './element'; -import { type FormSession, nullSession, sessionIsComplete } from './session'; +import { type Result } from '@atj/common'; +import { updatePattern, type Blueprint } from '..'; -export type TextInputPattern = { - type: 'input'; - inputId: string; - value: string; - label: string; - required: boolean; - error?: string; -}; +import { type CreatePrompt } from './components'; -export type TextPrompt = { - type: 'text'; - id: string; - value: string; - error?: string; +export type Pattern = { + type: string; + id: PatternId; + data: C; + initial: T; }; -export type FormSummaryPattern = { - type: 'form-summary'; - title: string; - description: string; -}; +export type PatternId = string; +export type PatternValue = T['initial']; +export type PatternValueMap = Record; +export type PatternMap = Record; +export type GetPattern = (form: Blueprint, id: PatternId) => Pattern; -export type SubmissionConfirmationPattern = { - type: 'submission-confirmation'; - table: { label: string; value: string }[]; -}; +export type ParsePatternData = ( + patternData: T['data'], + obj: string +) => Result; -export type ParagraphPattern = { - type: 'paragraph'; - text: string; - style: 'indent' | 'normal' | 'heading' | 'subheading'; +export type ParsePatternConfigData = ( + patternData: T['data'] +) => Result; + +export const getPattern: GetPattern = (form, patternId) => { + return form.patterns[patternId]; }; -export type FieldsetPattern = { - type: 'fieldset'; - legend: string; +export type PatternConfig = { + acceptsInput: boolean; + initial: ThisPattern['data']; + parseData: ParsePatternData; + parseConfigData: ParsePatternConfigData; + getChildren: ( + pattern: ThisPattern, + patterns: Record + ) => Pattern[]; + createPrompt: CreatePrompt; +}; +export type FormConfig = { + patterns: Record>; }; -export type Pattern = { - _elementId: FormElementId; - _children: PromptPart[]; - type: string; -} & T; +export type ConfigPatterns = ReturnType< + Config['patterns'][keyof Config['patterns']]['parseData'] +>; -export type SubmitAction = { - type: 'submit'; - text: 'Submit'; +export const getPatternMap = (patterns: Pattern[]) => { + return Object.fromEntries( + patterns.map(pattern => { + return [pattern.id, pattern]; + }) + ); }; -export type PromptAction = SubmitAction; -export type PromptPart = { - pattern: Pattern; - children: PromptPart[]; +export const getPatterns = (form: Blueprint, patternIds: PatternId[]) => { + return patternIds.map(patternIds => getPattern(form, patternIds)); }; -export type Prompt = { - actions: PromptAction[]; - parts: PromptPart[]; +export const getPatternConfig = ( + config: FormConfig, + elementType: Pattern['type'] +) => { + return config.patterns[elementType]; }; -export const createPrompt = ( - config: FormConfig, - session: FormSession, - options: { validate: boolean } -): Prompt => { - if (options.validate && sessionIsComplete(config, session)) { +export const validatePattern = ( + elementConfig: PatternConfig, + element: Pattern, + value: any +): Result => { + if (!elementConfig.acceptsInput) { return { - actions: [], - parts: [ - { - pattern: { - _elementId: 'submission-confirmation', - type: 'submission-confirmation', - table: Object.entries(session.data.values) - .filter(([elementId, value]) => { - const elemConfig = getFormElementConfig( - config, - session.form.elements[elementId].type - ); - return elemConfig.acceptsInput; - }) - .map(([elementId, value]) => { - return { - label: session.form.elements[elementId].data.label, - value: value, - }; - }), - } as Pattern, - children: [], - }, - ], + success: true, + data: value, + }; + } + const parseResult = elementConfig.parseData(element, value); + if (!parseResult.success) { + return { + success: false, + error: parseResult.error, + }; + } + if (element.data.required && !parseResult.data) { + return { + success: false, + error: 'Required value not provided.', }; } - const parts: PromptPart[] = [ - { - pattern: { - _elementId: 'form-summary', - type: 'form-summary', - title: session.form.summary.title, - description: session.form.summary.description, - } as Pattern, - children: [], - }, - ]; - const root = getRootFormElement(session.form); - parts.push(createPromptForElement(config, session, root, options)); return { - actions: [ - { - type: 'submit', - text: 'Submit', - }, - ], - parts, + success: true, + data: parseResult.data, }; }; -export type CreatePrompt = ( +export const getFirstPattern = ( config: FormConfig, - session: FormSession, - element: T, - options: { validate: boolean } -) => PromptPart; - -export const createPromptForElement: CreatePrompt = ( - config, - session, - element, - options -) => { - const formElementConfig = getFormElementConfig(config, element.type); - return formElementConfig.createPrompt(config, session, element, options); -}; - -export const isPromptAction = (prompt: Prompt, action: string) => { - return prompt.actions.find(a => a.type === action); + form: Blueprint, + pattern?: Pattern +): Pattern => { + if (!pattern) { + pattern = form.patterns[form.root]; + } + const elemConfig = getPatternConfig(config, pattern.type); + const children = elemConfig.getChildren(pattern, form.patterns); + if (children?.length === 0) { + return pattern; + } + return getFirstPattern(config, form, children[0]); }; -export const createNullPrompt = ({ - config, - element, -}: { - config: FormConfig; - element: FormElement; -}): Prompt => { - const formElementConfig = getFormElementConfig(config, element.type); - return { - parts: [ - formElementConfig.createPrompt(config, nullSession, element, { - validate: false, - }), - ], - actions: [], - }; +export const updatePatternFromFormData = ( + config: FormConfig, + form: Blueprint, + pattern: Pattern, + formData: PatternMap +) => { + const elementConfig = getPatternConfig(config, pattern.type); + const data = formData[pattern.id].data; + const result = elementConfig.parseConfigData(data); + if (!result.success) { + return; + } + const updatedForm = updatePattern(form, { + ...pattern, + data: result.data, + }); + return updatedForm; }; diff --git a/packages/forms/src/patterns/fieldset.ts b/packages/forms/src/patterns/fieldset.ts new file mode 100644 index 00000000..4a713caf --- /dev/null +++ b/packages/forms/src/patterns/fieldset.ts @@ -0,0 +1,53 @@ +import * as z from 'zod'; + +import { + type Pattern, + type PatternConfig, + type PatternId, + getPattern, +} from '../pattern'; +import { type FieldsetProps, createPromptForPattern } from '../components'; +import { safeZodParse } from '../util/zod'; + +export type FieldsetPattern = Pattern<{ + legend?: string; + patterns: PatternId[]; +}>; + +const FieldsetSchema = z.array(z.string()); + +const configSchema = z.object({ + legend: z.string().optional(), + patterns: z.array(z.string()), +}); + +export const fieldsetConfig: PatternConfig = { + acceptsInput: false, + initial: { + patterns: [], + }, + parseData: (_, obj) => { + return safeZodParse(FieldsetSchema, obj); + }, + parseConfigData: obj => safeZodParse(configSchema, obj), + getChildren(pattern, patterns) { + return pattern.data.patterns.map( + (patternId: string) => patterns[patternId] + ); + }, + createPrompt(config, session, pattern, options) { + const children = pattern.data.patterns.map((patternId: string) => { + const pattern = getPattern(session.form, patternId); + return createPromptForPattern(config, session, pattern, options); + }); + return { + pattern: { + _children: children, + _patternId: pattern.id, + type: 'fieldset', + legend: pattern.data.legend, + } satisfies FieldsetProps, + children, + }; + }, +}; diff --git a/packages/forms/src/patterns/form-summary.ts b/packages/forms/src/patterns/form-summary.ts new file mode 100644 index 00000000..d58cd9e9 --- /dev/null +++ b/packages/forms/src/patterns/form-summary.ts @@ -0,0 +1,37 @@ +import * as z from 'zod'; + +import { type Pattern, type PatternConfig } from '../pattern'; +import { type FormSummaryProps } from '../components'; +import { safeZodParse } from '../util/zod'; + +const configSchema = z.object({ + title: z.string().max(128), + summary: z.string().max(2024), +}); +export type FormSummary = Pattern>; + +export const formSummaryConfig: PatternConfig = { + acceptsInput: false, + initial: { + text: '', + initial: '', + required: true, + maxLength: 128, + }, + parseData: obj => safeZodParse(configSchema, obj), // make this optional? + parseConfigData: obj => safeZodParse(configSchema, obj), + getChildren() { + return []; + }, + createPrompt(_, session, pattern, options) { + return { + pattern: { + _patternId: pattern.id, + type: 'form-summary', + title: pattern.data.title, + description: pattern.data.description, + } as FormSummaryProps, + children: [], + }; + }, +}; diff --git a/packages/forms/src/config/config.ts b/packages/forms/src/patterns/index.ts similarity index 58% rename from packages/forms/src/config/config.ts rename to packages/forms/src/patterns/index.ts index f3d19ed8..706e036b 100644 --- a/packages/forms/src/config/config.ts +++ b/packages/forms/src/patterns/index.ts @@ -1,14 +1,15 @@ -import { type FormConfig } from '.'; -import { fieldsetConfig } from './elements/fieldset'; -import { inputConfig } from './elements/input'; -import { paragraphConfig } from './elements/paragraph'; -import { sequenceConfig } from './elements/sequence'; +import { type FormConfig } from '../pattern'; + +import { fieldsetConfig } from './fieldset'; +import { inputConfig } from './input'; +import { paragraphConfig } from './paragraph'; +import { sequenceConfig } from './sequence'; // This configuration reflects what a user of this library would provide for // their usage scenarios. For now, keep here in the form service until we // understand the usage scenarios better. export const defaultFormConfig: FormConfig = { - elements: { + patterns: { fieldset: fieldsetConfig, input: inputConfig, paragraph: paragraphConfig, diff --git a/packages/forms/src/patterns/input.ts b/packages/forms/src/patterns/input.ts new file mode 100644 index 00000000..c1f6f68c --- /dev/null +++ b/packages/forms/src/patterns/input.ts @@ -0,0 +1,54 @@ +import * as z from 'zod'; + +import { type Pattern, type PatternConfig, validatePattern } from '../pattern'; +import { type TextInputProps } from '../components'; +import { getFormSessionValue } from '../session'; +import { safeZodParse } from '../util/zod'; + +const configSchema = z.object({ + label: z.string(), + initial: z.string(), + required: z.boolean(), + maxLength: z.coerce.number(), +}); +export type InputPattern = Pattern>; + +const createSchema = (data: InputPattern['data']) => + z.string().max(data.maxLength); + +export const inputConfig: PatternConfig = { + acceptsInput: true, + initial: { + label: '', + initial: '', + required: true, + maxLength: 128, + }, + parseData: (patternData, obj) => safeZodParse(createSchema(patternData), obj), + parseConfigData: obj => safeZodParse(configSchema, obj), + getChildren() { + return []; + }, + createPrompt(_, session, pattern, options) { + const extraAttributes: Record = {}; + const sessionValue = getFormSessionValue(session, pattern.id); + if (options.validate) { + const isValidResult = validatePattern(inputConfig, pattern, sessionValue); + if (!isValidResult.success) { + extraAttributes['error'] = isValidResult.error; + } + } + return { + pattern: { + _patternId: pattern.id, + type: 'input', + inputId: pattern.id, + value: sessionValue, + label: pattern.data.label, + required: pattern.data.required, + ...extraAttributes, + } as TextInputProps, + children: [], + }; + }, +}; diff --git a/packages/forms/src/patterns/paragraph.ts b/packages/forms/src/patterns/paragraph.ts new file mode 100644 index 00000000..e899d67e --- /dev/null +++ b/packages/forms/src/patterns/paragraph.ts @@ -0,0 +1,38 @@ +import * as z from 'zod'; + +import { type Pattern, type PatternConfig } from '../pattern'; +import { type ParagraphProps } from '../components'; +import { safeZodParse } from '../util/zod'; + +const configSchema = z.object({ + text: z.string(), + maxLength: z.coerce.number(), +}); +export type ParagraphPattern = Pattern>; + +const createSchema = (data: ParagraphPattern['data']) => + z.string().max(data.maxLength); + +export const paragraphConfig: PatternConfig = { + acceptsInput: false, + initial: { + text: 'normal', + maxLength: 2048, + }, + parseData: (patternData, obj) => safeZodParse(createSchema(patternData), obj), + parseConfigData: obj => safeZodParse(configSchema, obj), + getChildren() { + return []; + }, + createPrompt(_, session, pattern, options) { + return { + pattern: { + _patternId: pattern.id, + type: 'paragraph' as const, + text: pattern.data.text, + style: pattern.data.style, + } as ParagraphProps, + children: [], + }; + }, +}; diff --git a/packages/forms/src/patterns/sequence.ts b/packages/forms/src/patterns/sequence.ts new file mode 100644 index 00000000..4c8dabea --- /dev/null +++ b/packages/forms/src/patterns/sequence.ts @@ -0,0 +1,50 @@ +import * as z from 'zod'; + +import { + type Pattern, + type PatternConfig, + type PatternId, + getPattern, +} from '../pattern'; +import { createPromptForPattern } from '../components'; +import { safeZodParse } from '../util/zod'; + +export type SequencePattern = Pattern<{ + patterns: PatternId[]; +}>; + +const sequenceSchema = z.array(z.string()); + +const configSchema = z.object({ + patterns: z.array(z.string()), +}); + +export const sequenceConfig: PatternConfig = { + acceptsInput: false, + initial: { + patterns: [], + }, + parseData: (_, obj) => { + return safeZodParse(sequenceSchema, obj); + }, + parseConfigData: obj => safeZodParse(configSchema, obj), + getChildren(pattern, patterns) { + return pattern.data.patterns.map( + (patternId: string) => patterns[patternId] + ); + }, + createPrompt(config, session, pattern, options) { + const children = pattern.data.patterns.map((patternId: string) => { + const childPattern = getPattern(session.form, patternId); + return createPromptForPattern(config, session, childPattern, options); + }); + return { + pattern: { + _children: children, + _patternId: pattern.id, + type: 'sequence', + }, + children, + }; + }, +}; diff --git a/packages/forms/src/response.ts b/packages/forms/src/response.ts index 88274b4e..3e3db137 100644 --- a/packages/forms/src/response.ts +++ b/packages/forms/src/response.ts @@ -2,12 +2,12 @@ import { type Result } from '@atj/common'; import { type FormConfig, - type FormElementId, - getFormElement, - getFormElementConfig, - validateElement, + type PatternId, + getPattern, + getPatternConfig, + validatePattern, } from '.'; -import { type PromptAction, createPrompt, isPromptAction } from './pattern'; +import { type PromptAction, createPrompt, isPromptAction } from './components'; import { type FormSession, updateSession } from './session'; export type PromptResponse = { @@ -37,15 +37,15 @@ export const applyPromptResponse = ( }; }; -const parseElementValue = ( +const parsePatternValue = ( config: FormConfig, session: FormSession, - elementId: FormElementId, + patternId: PatternId, promptValue: string ) => { - const element = session.form.elements[elementId]; - const formElementConfig = getFormElementConfig(config, element.type); - return formElementConfig.parseData(element, promptValue); + const pattern = session.form.patterns[patternId]; + const patternConfig = getPatternConfig(config, pattern.type); + return patternConfig.parseData(pattern, promptValue); }; const parsePromptResponse = ( @@ -55,14 +55,14 @@ const parsePromptResponse = ( ) => { const values: Record = {}; const errors: Record = {}; - for (const [elementId, promptValue] of Object.entries(response.data)) { - const element = getFormElement(session.form, elementId); - const elementConfig = getFormElementConfig(config, element.type); - const isValidResult = validateElement(elementConfig, element, promptValue); + for (const [patternId, promptValue] of Object.entries(response.data)) { + const pattern = getPattern(session.form, patternId); + const patternConfig = getPatternConfig(config, pattern.type); + const isValidResult = validatePattern(patternConfig, pattern, promptValue); if (isValidResult.success) { - values[elementId] = isValidResult.data; + values[patternId] = isValidResult.data; } else { - errors[elementId] = isValidResult.error; + errors[patternId] = isValidResult.error; } } return { errors, values }; diff --git a/packages/forms/src/session.ts b/packages/forms/src/session.ts index 87659159..4b854366 100644 --- a/packages/forms/src/session.ts +++ b/packages/forms/src/session.ts @@ -1,25 +1,25 @@ import { type FormConfig, - type FormDefinition, - type FormElement, - getFormElementConfig, - validateElement, + type Blueprint, + type Pattern, + getPatternConfig, + validatePattern, } from '.'; -import { SequenceElement } from './config/elements/sequence'; +import { SequencePattern } from './patterns/sequence'; import { - type FormElementId, - type FormElementValue, - type FormElementValueMap, -} from './element'; + type PatternId, + type PatternValue, + type PatternValueMap, +} from './pattern'; -type ErrorMap = Record; +type ErrorMap = Record; export type FormSession = { data: { errors: ErrorMap; - values: FormElementValueMap; + values: PatternValueMap; }; - form: FormDefinition; + form: Blueprint; }; export const nullSession: FormSession = { @@ -30,16 +30,16 @@ export const nullSession: FormSession = { }, }, form: { - elements: { + patterns: { root: { id: 'root', type: 'sequence', required: false, - default: { - elements: [], + initial: { + patterns: [], }, data: {}, - } as SequenceElement, + } as SequencePattern, }, root: 'root', summary: { @@ -50,13 +50,13 @@ export const nullSession: FormSession = { }, }; -export const createFormSession = (form: FormDefinition): FormSession => { +export const createFormSession = (form: Blueprint): FormSession => { return { data: { errors: {}, values: Object.fromEntries( - Object.values(form.elements).map(element => { - return [element.id, form.elements[element.id].default]; + Object.values(form.patterns).map((pattern, index) => { + return [pattern.id, form.patterns[pattern.id].data.initial]; }) ), }, @@ -66,24 +66,24 @@ export const createFormSession = (form: FormDefinition): FormSession => { export const getFormSessionValue = ( session: FormSession, - elementId: FormElementId + patternId: PatternId ) => { - return session.data.values[elementId]; + return session.data.values[patternId]; }; export const updateSessionValue = ( session: FormSession, - id: FormElementId, - value: FormElementValue + id: PatternId, + value: PatternValue ): FormSession => { - if (!(id in session.form.elements)) { - console.error(`FormElement "${id}" does not exist on form.`); + if (!(id in session.form.patterns)) { + console.error(`Pattern "${id}" does not exist on form.`); return session; } const nextSession = addValue(session, id, value); - const element = session.form.elements[id]; - if (element.type === 'input') { - if (element.required && !value) { + const pattern = session.form.patterns[id]; + if (pattern.type === 'input') { + if (pattern && !value) { return addError(nextSession, id, 'Required value not provided.'); } } @@ -92,16 +92,16 @@ export const updateSessionValue = ( export const updateSession = ( session: FormSession, - values: FormElementValueMap, + values: PatternValueMap, errors: ErrorMap ): FormSession => { const keysValid = Object.keys(values).every( - elementId => elementId in session.form.elements + patternId => patternId in session.form.patterns ) && - Object.keys(errors).every(elementId => elementId in session.form.elements); + Object.keys(errors).every(patternId => patternId in session.form.patterns); if (!keysValid) { - throw new Error('invalid element reference updating session'); + throw new Error('invalid pattern reference updating session'); } return { ...session, @@ -119,18 +119,18 @@ export const updateSession = ( }; export const sessionIsComplete = (config: FormConfig, session: FormSession) => { - return Object.values(session.form.elements).every(element => { - const elementConfig = getFormElementConfig(config, element.type); - const value = getFormSessionValue(session, element.id); - const isValidResult = validateElement(elementConfig, element, value); + return Object.values(session.form.patterns).every(pattern => { + const patternConfig = getPatternConfig(config, pattern.type); + const value = getFormSessionValue(session, pattern.id); + const isValidResult = validatePattern(patternConfig, pattern, value); return isValidResult.success; }); }; -const addValue = ( +const addValue = ( form: FormSession, - id: FormElementId, - value: FormElementValue + id: PatternId, + value: PatternValue ): FormSession => ({ ...form, data: { @@ -144,7 +144,7 @@ const addValue = ( const addError = ( session: FormSession, - id: FormElementId, + id: PatternId, error: string ): FormSession => ({ ...session, diff --git a/packages/forms/src/transform/index.ts b/packages/forms/src/util/transform.ts similarity index 100% rename from packages/forms/src/transform/index.ts rename to packages/forms/src/util/transform.ts diff --git a/packages/forms/src/util/zod.ts b/packages/forms/src/util/zod.ts index 8f6ba5fe..262bfe5b 100644 --- a/packages/forms/src/util/zod.ts +++ b/packages/forms/src/util/zod.ts @@ -2,9 +2,9 @@ import * as z from 'zod'; import { type Result } from '@atj/common'; -import { type FormElement } from '..'; +import { type Pattern } from '..'; -export const safeZodParse = ( +export const safeZodParse = ( schema: z.Schema, obj: string ): Result => { diff --git a/packages/forms/tests/two-field-form.test.ts b/packages/forms/tests/two-field-form.test.ts index 4b0cf98e..42bc70b1 100644 --- a/packages/forms/tests/two-field-form.test.ts +++ b/packages/forms/tests/two-field-form.test.ts @@ -2,47 +2,44 @@ import { describe, expect, test } from 'vitest'; import * as forms from '../src'; -const elements: forms.FormElement[] = [ +const patterns: forms.Pattern[] = [ { type: 'sequence', id: 'root', data: { - elements: ['element-1', 'element-2'], + patterns: ['pattern-1', 'pattern-2'], }, - default: { - elements: [], + initial: { + patterns: [], }, - required: true, }, { type: 'input', - id: 'element-1', + id: 'pattern-1', data: { text: 'What is your first name?', initial: '', required: true, }, - default: '', - required: true, + initial: '', }, { type: 'input', - id: 'element-2', + id: 'pattern-2', data: { text: 'What is your favorite word?', initial: '', required: false, }, - default: '', - required: true, + initial: '', }, ]; const form = forms.createForm( { title: 'Form sample', - description: 'Form sample created via a list of elements.', + description: 'Form sample created via a list of patterns.', }, - { root: 'root', elements } + { root: 'root', patterns } ); describe('two element form session', () => { @@ -60,16 +57,16 @@ describe('two element form session', () => { test('empty field value on required field is stored with error', () => { const session = forms.createFormSession(form); - const nextSession = forms.updateForm(session, elements[0].id, null); + const nextSession = forms.updateForm(session, patterns[0].id, null); expect(nextSession).toEqual({ ...session, data: { errors: { - 'element-1': 'Required value not provided.', + 'pattern-1': 'Required value not provided.', }, values: { - 'element-1': null, - 'element-2': '', + 'pattern-1': null, + 'pattern-2': '', }, }, }); @@ -79,7 +76,7 @@ describe('two element form session', () => { const formSession = forms.createFormSession(form); const nextSession = forms.updateForm( formSession, - elements[0].id, + patterns[0].id, 'supercalifragilisticexpialidocious' ); expect(nextSession).toEqual({ @@ -87,8 +84,8 @@ describe('two element form session', () => { data: { errors: {}, values: { - 'element-1': 'supercalifragilisticexpialidocious', - 'element-2': '', + 'pattern-1': 'supercalifragilisticexpialidocious', + 'pattern-2': '', }, }, }); @@ -98,17 +95,17 @@ describe('two element form session', () => { const session = forms.createFormSession(form); const session2 = forms.updateForm( session, - elements[1].id, + patterns[1].id, 'supercalifragilisticexpialidocious' ); - const session3 = forms.updateForm(session2, elements[1].id, ''); + const session3 = forms.updateForm(session2, patterns[1].id, ''); expect(session3).toEqual({ ...session, data: { errors: {}, values: { - 'element-1': '', - 'element-2': '', + 'pattern-1': '', + 'pattern-2': '', }, }, }); @@ -118,7 +115,7 @@ describe('two element form session', () => { const session = forms.createFormSession(form); const nextSession = forms.updateForm( session, - elements[1].id, + patterns[1].id, 'supercalifragilisticexpialidocious' ); expect(nextSession).toEqual({ @@ -126,8 +123,8 @@ describe('two element form session', () => { data: { errors: {}, values: { - 'element-1': '', - 'element-2': 'supercalifragilisticexpialidocious', + 'pattern-1': '', + 'pattern-2': 'supercalifragilisticexpialidocious', }, }, }); From cdb6a0994f1e0edc161c92bf5edc0628fbf74045 Mon Sep 17 00:00:00 2001 From: Daniel Naab Date: Fri, 29 Mar 2024 08:57:40 -0500 Subject: [PATCH 2/6] "Add pattern" function added to builder UI (#89) * So we can support adding new patterns via the UI, add FormBuilder.addPattern() * "Add pattern" function added to form manager ui * Add missing edit UI components * Add interaction test for adding a new pattern via the UI --- .../src/FormManager/FormEdit/AddPattern.tsx | 27 ++++++ .../FormManager/FormEdit/FormEdit.stories.tsx | 19 +++++ .../design/src/FormManager/FormEdit/index.tsx | 6 ++ .../design/src/FormManager/FormEdit/store.tsx | 17 ++++ packages/design/src/config/edit/index.ts | 4 + packages/forms/src/builder/builder.test.ts | 82 +++++++++++++++++++ packages/forms/src/builder/index.ts | 12 ++- packages/forms/src/documents/document.ts | 1 - packages/forms/src/index.ts | 27 +++++- packages/forms/src/pattern.ts | 19 ++++- packages/forms/src/patterns/fieldset.ts | 5 +- packages/forms/src/patterns/form-summary.ts | 1 + packages/forms/src/patterns/input.ts | 1 + packages/forms/src/patterns/paragraph.ts | 1 + packages/forms/src/patterns/sequence.ts | 1 + 15 files changed, 210 insertions(+), 13 deletions(-) create mode 100644 packages/design/src/FormManager/FormEdit/AddPattern.tsx create mode 100644 packages/forms/src/builder/builder.test.ts diff --git a/packages/design/src/FormManager/FormEdit/AddPattern.tsx b/packages/design/src/FormManager/FormEdit/AddPattern.tsx new file mode 100644 index 00000000..f45ea6d1 --- /dev/null +++ b/packages/design/src/FormManager/FormEdit/AddPattern.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { useFormEditStore } from './store'; + +export const AddPattern = () => { + const store = useFormEditStore(state => ({ + availablePatterns: state.availablePatterns, + addPattern: state.addPattern, + })); + + return ( +
    + +
    + ); +}; diff --git a/packages/design/src/FormManager/FormEdit/FormEdit.stories.tsx b/packages/design/src/FormManager/FormEdit/FormEdit.stories.tsx index 788df60f..9c7654b8 100644 --- a/packages/design/src/FormManager/FormEdit/FormEdit.stories.tsx +++ b/packages/design/src/FormManager/FormEdit/FormEdit.stories.tsx @@ -35,6 +35,25 @@ export const FormEditTest: StoryObj = { }, }; +export const FormEditAddPattern: StoryObj = { + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + + // Select the first pattern for editing + const button = (await canvas.findAllByRole('button'))[0]; + await userEvent.click(button); + + // Get the initial count of inputs + const initialCount = (await canvas.getAllByRole('textbox')).length; + + const select = canvas.getByLabelText('Add a pattern:'); + await userEvent.selectOptions(select, 'Text input'); + + const finalCount = (await canvas.getAllByRole('textbox')).length; + expect(finalCount).toEqual(initialCount + 1); + }, +}; + const editFieldLabel = async ( element: HTMLElement, buttonIndex: number, diff --git a/packages/design/src/FormManager/FormEdit/index.tsx b/packages/design/src/FormManager/FormEdit/index.tsx index fc00ee69..0e71c3b5 100644 --- a/packages/design/src/FormManager/FormEdit/index.tsx +++ b/packages/design/src/FormManager/FormEdit/index.tsx @@ -3,6 +3,7 @@ import React, { useEffect } from 'react'; import { Blueprint } from '@atj/forms'; import { type FormService } from '@atj/form-service'; +import { AddPattern } from './AddPattern'; import { PatternEdit } from './PatternEdit'; import { PreviewForm } from './Preview'; import { FormEditProvider, useFormEditStore } from './store'; @@ -42,6 +43,11 @@ const EditForm = ({ saveForm }: { saveForm: (form: Blueprint) => void }) => { return (
    +
    +
    + +
    +
    diff --git a/packages/design/src/FormManager/FormEdit/store.tsx b/packages/design/src/FormManager/FormEdit/store.tsx index 554a9c74..574327b1 100644 --- a/packages/design/src/FormManager/FormEdit/store.tsx +++ b/packages/design/src/FormManager/FormEdit/store.tsx @@ -32,7 +32,12 @@ type FormEditState = { context: FormEditUIContext; form: Blueprint; selectedPattern?: Pattern; + availablePatterns: { + patternType: string; + displayName: string; + }[]; + addPattern: (patternType: string) => void; handleEditClick: (pattern: PatternProps) => void; setSelectedPattern: (element?: Pattern) => void; updateSelectedPattern: (formData: PatternMap) => void; @@ -48,6 +53,18 @@ const createFormEditStore = ({ create((set, get) => ({ context, form, + availablePatterns: Object.entries(context.config.patterns).map( + ([patternType, patternConfig]) => ({ + patternType, + displayName: patternConfig.displayName, + }) + ), + addPattern: (patternType: string) => { + const state = get(); + const builder = new FormBuilder(state.form); + const newPattern = builder.addPattern(state.context.config, patternType); + set({ form: builder.form, selectedPattern: newPattern }); + }, handleEditClick: (pattern: PatternProps) => { const state = get(); if (state.selectedPattern?.id === pattern._patternId) { diff --git a/packages/design/src/config/edit/index.ts b/packages/design/src/config/edit/index.ts index 8813fc0f..b25e9c07 100644 --- a/packages/design/src/config/edit/index.ts +++ b/packages/design/src/config/edit/index.ts @@ -1,10 +1,14 @@ +import FormSummaryEdit from './FormSummaryEdit'; import InputPatternEdit from './InputPatternEdit'; +import ParagraphPatternEdit from './ParagraphPatternEdit'; import SequencePatternEdit from './SequencePatternEdit'; import SubmissionConfirmationEdit from './SubmissionConfirmationEdit'; import { type EditComponentForPattern } from '../../FormManager/FormEdit/types'; export const defaultPatternEditComponents: EditComponentForPattern = { + paragraph: ParagraphPatternEdit, input: InputPatternEdit, + 'form-summary': FormSummaryEdit, sequence: SequencePatternEdit, 'submission-confirmation': SubmissionConfirmationEdit, }; diff --git a/packages/forms/src/builder/builder.test.ts b/packages/forms/src/builder/builder.test.ts new file mode 100644 index 00000000..a99b32db --- /dev/null +++ b/packages/forms/src/builder/builder.test.ts @@ -0,0 +1,82 @@ +import { describe, expect, it } from 'vitest'; + +import { FormBuilder } from '.'; +import { createForm } from '..'; +import { defaultFormConfig } from '../patterns'; +import { type InputPattern } from '../patterns/input'; +import { type SequencePattern } from '../patterns/sequence'; + +describe('form builder', () => { + it('addPattern adds initial pattern of given type', () => { + const builder = new FormBuilder(); + expect(Object.keys(builder.form.patterns).length).toEqual(1); + builder.addPattern(defaultFormConfig, 'input'); + expect(Object.keys(builder.form.patterns).length).toEqual(2); + }); + + it('addPattern preserves existing structure', () => { + const initial = createTestBlueprint(); + const newBuilder = new FormBuilder(initial); + const newPattern = newBuilder.addPattern(defaultFormConfig, 'input'); + expect(newBuilder.form.patterns[newPattern.id]).toEqual(newPattern); + expect( + newBuilder.form.patterns[newBuilder.form.root].data.patterns + ).toEqual([...initial.patterns[initial.root].data.patterns, newPattern.id]); + }); +}); + +export const createTestBlueprint = () => { + return createForm( + { + title: 'Test form', + description: 'Test description', + }, + { + root: 'root', + patterns: [ + { + type: 'sequence', + id: 'root', + data: { + patterns: ['element-1', 'element-2'], + }, + initial: { + patterns: [], + }, + } as SequencePattern, + { + type: 'input', + id: 'element-1', + data: { + label: 'Pattern 1', + initial: '', + required: true, + maxLength: 128, + }, + initial: { + label: 'Pattern 1', + initial: '', + required: true, + maxLength: 128, + }, + } as InputPattern, + { + type: 'input', + id: 'element-2', + data: { + label: 'Pattern 2', + initial: 'test', + required: true, + maxLength: 128, + }, + initial: { + label: 'Pattern 2', + initial: 'test', + required: true, + maxLength: 128, + }, + } as InputPattern, + ], + } + ); +}; diff --git a/packages/forms/src/builder/index.ts b/packages/forms/src/builder/index.ts index 5ae55e78..2c82ae7a 100644 --- a/packages/forms/src/builder/index.ts +++ b/packages/forms/src/builder/index.ts @@ -8,13 +8,15 @@ import { nullBlueprint, updateFormSummary, updatePatternFromFormData, + createPattern, + addPatternToRoot, } from '..'; export class FormBuilder { private _form: Blueprint; - constructor(initialForm: Blueprint = nullBlueprint) { - this._form = initialForm || nullBlueprint; + constructor(initial: Blueprint = nullBlueprint) { + this._form = initial; } get form(): Blueprint { @@ -30,6 +32,12 @@ export class FormBuilder { this._form = updatedForm; } + addPattern(config: FormConfig, patternType: string) { + const pattern = createPattern(config, patternType); + this._form = addPatternToRoot(this.form, pattern); + return pattern; + } + updatePattern(config: FormConfig, pattern: Pattern, formData: PatternMap) { const updatedElement = updatePatternFromFormData( config, diff --git a/packages/forms/src/documents/document.ts b/packages/forms/src/documents/document.ts index dbea7c42..53b9a6a4 100644 --- a/packages/forms/src/documents/document.ts +++ b/packages/forms/src/documents/document.ts @@ -39,7 +39,6 @@ export const addDocument = async ( }) ), }); - console.log(updatedForm); return { newFields: fields, updatedForm, diff --git a/packages/forms/src/index.ts b/packages/forms/src/index.ts index 4a803db3..f42c39a2 100644 --- a/packages/forms/src/index.ts +++ b/packages/forms/src/index.ts @@ -6,7 +6,6 @@ import { type PatternId, type PatternMap, getPatternMap, - getPatternConfig, } from './pattern'; export * from './builder'; @@ -158,6 +157,26 @@ export const addPatterns = ( return addPatternMap(form, formPatternMap, root); }; +export const addPatternToRoot = ( + bp: Blueprint, + pattern: Pattern +): Blueprint => { + const rootSequence = bp.patterns[bp.root] as SequencePattern; + return { + ...bp, + patterns: { + ...bp.patterns, + [rootSequence.id]: { + ...rootSequence, + data: { + patterns: [...rootSequence.data.patterns, pattern.id], + }, + }, + [pattern.id]: pattern, + }, + }; +}; + export const replacePatterns = ( form: Blueprint, patterns: Pattern[] @@ -181,10 +200,10 @@ export const updatePatterns = ( ): Blueprint => { const root = newPatterns[form.root]; const targetPatterns: PatternMap = { - root, + [root.id]: root, }; - const resource = config.patterns[root.type as keyof FormConfig]; - const children = resource.getChildren(root, newPatterns); + const patternConfig = config.patterns[root.type]; + const children = patternConfig.getChildren(root, newPatterns); targetPatterns[root.id] = root; children.forEach(child => (targetPatterns[child.id] = child)); return { diff --git a/packages/forms/src/pattern.ts b/packages/forms/src/pattern.ts index 6a6b7d8b..fb29aca5 100644 --- a/packages/forms/src/pattern.ts +++ b/packages/forms/src/pattern.ts @@ -30,6 +30,7 @@ export const getPattern: GetPattern = (form, patternId) => { }; export type PatternConfig = { + displayName: string; acceptsInput: boolean; initial: ThisPattern['data']; parseData: ParsePatternData; @@ -56,10 +57,6 @@ export const getPatternMap = (patterns: Pattern[]) => { ); }; -export const getPatterns = (form: Blueprint, patternIds: PatternId[]) => { - return patternIds.map(patternIds => getPattern(form, patternIds)); -}; - export const getPatternConfig = ( config: FormConfig, elementType: Pattern['type'] @@ -131,3 +128,17 @@ export const updatePatternFromFormData = ( }); return updatedForm; }; + +const generatePatternId = () => crypto.randomUUID(); + +export const createPattern = ( + config: FormConfig, + patternType: string +): Pattern => { + return { + id: generatePatternId(), + type: patternType, + data: config.patterns[patternType].initial, + initial: {}, + }; +}; diff --git a/packages/forms/src/patterns/fieldset.ts b/packages/forms/src/patterns/fieldset.ts index 4a713caf..96812523 100644 --- a/packages/forms/src/patterns/fieldset.ts +++ b/packages/forms/src/patterns/fieldset.ts @@ -22,6 +22,7 @@ const configSchema = z.object({ }); export const fieldsetConfig: PatternConfig = { + displayName: 'Fieldset', acceptsInput: false, initial: { patterns: [], @@ -37,8 +38,8 @@ export const fieldsetConfig: PatternConfig = { }, createPrompt(config, session, pattern, options) { const children = pattern.data.patterns.map((patternId: string) => { - const pattern = getPattern(session.form, patternId); - return createPromptForPattern(config, session, pattern, options); + const childPattern = getPattern(session.form, patternId); + return createPromptForPattern(config, session, childPattern, options); }); return { pattern: { diff --git a/packages/forms/src/patterns/form-summary.ts b/packages/forms/src/patterns/form-summary.ts index d58cd9e9..cb20aa0a 100644 --- a/packages/forms/src/patterns/form-summary.ts +++ b/packages/forms/src/patterns/form-summary.ts @@ -11,6 +11,7 @@ const configSchema = z.object({ export type FormSummary = Pattern>; export const formSummaryConfig: PatternConfig = { + displayName: 'Form summary', acceptsInput: false, initial: { text: '', diff --git a/packages/forms/src/patterns/input.ts b/packages/forms/src/patterns/input.ts index c1f6f68c..54190d9a 100644 --- a/packages/forms/src/patterns/input.ts +++ b/packages/forms/src/patterns/input.ts @@ -17,6 +17,7 @@ const createSchema = (data: InputPattern['data']) => z.string().max(data.maxLength); export const inputConfig: PatternConfig = { + displayName: 'Text input', acceptsInput: true, initial: { label: '', diff --git a/packages/forms/src/patterns/paragraph.ts b/packages/forms/src/patterns/paragraph.ts index e899d67e..ac1e98c0 100644 --- a/packages/forms/src/patterns/paragraph.ts +++ b/packages/forms/src/patterns/paragraph.ts @@ -14,6 +14,7 @@ const createSchema = (data: ParagraphPattern['data']) => z.string().max(data.maxLength); export const paragraphConfig: PatternConfig = { + displayName: 'Paragraph', acceptsInput: false, initial: { text: 'normal', diff --git a/packages/forms/src/patterns/sequence.ts b/packages/forms/src/patterns/sequence.ts index 4c8dabea..2701145a 100644 --- a/packages/forms/src/patterns/sequence.ts +++ b/packages/forms/src/patterns/sequence.ts @@ -20,6 +20,7 @@ const configSchema = z.object({ }); export const sequenceConfig: PatternConfig = { + displayName: 'Sequence', acceptsInput: false, initial: { patterns: [], From 5f9ef0c9c91416cbe646500204278eda573ff26e Mon Sep 17 00:00:00 2001 From: Daniel Naab Date: Wed, 3 Apr 2024 13:32:35 -0500 Subject: [PATCH 3/6] Initial address pattern (#94) * Add appropriate type annotations to each literal pattern. * Remove references to mismatched "initial" values; also, remove initial session values (this isn't necessary now, and is problematic. might need to revisit later) * Separate pattern config data type from pattern output data. * Added initial address component, using text inputs sequentially. Will also implement a unified address component, once this is fully working. TODO: handle address component validation in an orderly way and add to UI. * If it doesn't exist, don't try to display an edit component for a pattern. * Address pattern hashed out more; to ease testing, add a button to create a new empty blueprint. --- packages/design/src/Form/index.tsx | 11 +- .../FormManager/DocumentImporter/index.tsx | 6 +- .../src/FormManager/FormEdit/AddPattern.tsx | 8 +- .../FormManager/FormEdit/DraggableList.tsx | 3 - .../src/FormManager/FormEdit/PatternEdit.tsx | 44 ++--- .../src/FormManager/FormEdit/Preview.tsx | 24 +-- .../design/src/FormManager/FormEdit/store.tsx | 12 +- .../PDFFileSelect.stories.tsx | 10 +- .../PDFFileSelect.test.tsx | 2 +- .../file-input.test.ts | 0 .../file-input.ts | 0 .../{PDFFileSelect => CreateNew}/hooks.ts | 11 ++ .../{PDFFileSelect => CreateNew}/index.tsx | 8 +- .../design/src/FormManager/FormList/index.tsx | 29 ++- .../src/config/edit/InputPatternEdit.tsx | 6 +- .../config/view/Address/Address.stories.tsx | 0 .../src/config/view/Address/Address.test.tsx | 0 .../design/src/config/view/Address/index.tsx | 147 +++++++++++++++ .../design/src/config/view/Fieldset/index.tsx | 9 +- .../view/FormSummary/FormSummary.stories.tsx | 10 +- .../src/config/view/FormSummary/index.tsx | 6 +- .../src/config/view/Paragraph/index.tsx | 34 +--- .../SubmissionConfirmation.stories.tsx | 16 +- .../view/SubmissionConfirmation/index.tsx | 8 +- .../view/TextInput/TestInput.stories.tsx | 28 ++- .../src/config/view/TextInput/index.tsx | 32 ++-- .../design/src/config/view/ZipCode/index.tsx | 24 +++ packages/design/src/config/view/index.tsx | 2 + packages/design/src/test-form.ts | 21 +-- packages/forms/src/builder/builder.test.ts | 21 +-- packages/forms/src/components.ts | 11 +- packages/forms/src/documents/document.ts | 19 +- packages/forms/src/documents/pdf/mock-api.ts | 28 +-- packages/forms/src/index.ts | 8 +- packages/forms/src/pattern.ts | 49 +++-- .../src/patterns/address/address.test.ts | 0 packages/forms/src/patterns/address/index.ts | 175 ++++++++++++++++++ .../src/patterns/address/jurisdictions.ts | 138 ++++++++++++++ packages/forms/src/patterns/fieldset.ts | 7 - packages/forms/src/patterns/form-summary.ts | 10 +- packages/forms/src/patterns/index.ts | 2 + packages/forms/src/patterns/input.ts | 9 +- packages/forms/src/patterns/paragraph.ts | 8 +- packages/forms/src/patterns/sequence.ts | 7 - packages/forms/src/response.ts | 11 -- packages/forms/src/session.ts | 12 +- packages/forms/src/util/zod.ts | 4 +- packages/forms/tests/two-field-form.test.ts | 21 +-- 48 files changed, 722 insertions(+), 329 deletions(-) rename packages/design/src/FormManager/FormList/{PDFFileSelect => CreateNew}/PDFFileSelect.stories.tsx (61%) rename packages/design/src/FormManager/FormList/{PDFFileSelect => CreateNew}/PDFFileSelect.test.tsx (69%) rename packages/design/src/FormManager/FormList/{PDFFileSelect => CreateNew}/file-input.test.ts (100%) rename packages/design/src/FormManager/FormList/{PDFFileSelect => CreateNew}/file-input.ts (100%) rename packages/design/src/FormManager/FormList/{PDFFileSelect => CreateNew}/hooks.ts (79%) rename packages/design/src/FormManager/FormList/{PDFFileSelect => CreateNew}/index.tsx (91%) create mode 100644 packages/design/src/config/view/Address/Address.stories.tsx create mode 100644 packages/design/src/config/view/Address/Address.test.tsx create mode 100644 packages/design/src/config/view/Address/index.tsx create mode 100644 packages/design/src/config/view/ZipCode/index.tsx create mode 100644 packages/forms/src/patterns/address/address.test.ts create mode 100644 packages/forms/src/patterns/address/index.ts create mode 100644 packages/forms/src/patterns/address/jurisdictions.ts diff --git a/packages/design/src/Form/index.tsx b/packages/design/src/Form/index.tsx index 5295b4a1..4c2f978a 100644 --- a/packages/design/src/Form/index.tsx +++ b/packages/design/src/Form/index.tsx @@ -25,10 +25,11 @@ export type ComponentForPattern< > = Record>; export type PatternComponent> = - React.ComponentType<{ - pattern: T; - children?: React.ReactNode; - }>; + React.ComponentType< + T & { + children?: React.ReactNode; + } + >; const usePrompt = ( initialPrompt: Prompt, @@ -259,7 +260,7 @@ const PromptComponent = ({ }) => { const Component = context.components[promptPart.pattern.type]; return ( - + {promptPart.children?.map((child, index) => { return ( diff --git a/packages/design/src/FormManager/DocumentImporter/index.tsx b/packages/design/src/FormManager/DocumentImporter/index.tsx index a969dc83..e1ccc5fd 100644 --- a/packages/design/src/FormManager/DocumentImporter/index.tsx +++ b/packages/design/src/FormManager/DocumentImporter/index.tsx @@ -12,7 +12,7 @@ import { import { type FormService } from '@atj/form-service'; import Form, { FormUIContext } from '../../Form'; -import { onFileInputChangeGetFile } from '../FormList/PDFFileSelect/file-input'; +import { onFileInputChangeGetFile } from '../FormList/CreateNew/file-input'; const DocumentImporter = ({ baseUrl, @@ -69,7 +69,7 @@ const DocumentImporter = ({ } }; - const PDFFileSelect = () => { + const CreateNew = () => { return (
    - {state.page === 1 && } + {state.page === 1 && } {state.page === 2 && } {state.page === 3 && }
    diff --git a/packages/design/src/FormManager/FormEdit/AddPattern.tsx b/packages/design/src/FormManager/FormEdit/AddPattern.tsx index f45ea6d1..b2d04e21 100644 --- a/packages/design/src/FormManager/FormEdit/AddPattern.tsx +++ b/packages/design/src/FormManager/FormEdit/AddPattern.tsx @@ -10,11 +10,15 @@ export const AddPattern = () => { return (
    ); diff --git a/packages/design/src/FormManager/FormEdit/Preview.tsx b/packages/design/src/FormManager/FormEdit/Preview.tsx index 2777a1a9..1cec63c4 100644 --- a/packages/design/src/FormManager/FormEdit/Preview.tsx +++ b/packages/design/src/FormManager/FormEdit/Preview.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { type PatternProps, createFormSession } from '@atj/forms'; +import { createFormSession } from '@atj/forms'; import Form, { type ComponentForPattern, @@ -71,15 +71,15 @@ const createSequencePatternPreviewComponent = ( pattern, }) => { const { form, setSelectedPattern } = usePreviewContext(); - const element = getPattern(form, pattern._patternId); - const Component = previewComponents[pattern.type]; + const element = getPattern(form, props._patternId); + const Component = previewComponents[props.type]; return ( - + ); }; @@ -91,29 +91,25 @@ const createPatternPreviewComponent = ( Component: PatternComponent, uswdsRoot: string ) => { - const PatternPreviewComponent: PatternComponent = ({ - pattern, - }: { - pattern: PatternProps; - }) => { + const PatternPreviewComponent: PatternComponent = props => { const selectedPattern = useFormEditStore(state => state.selectedPattern); const handleEditClick = useFormEditStore(state => state.handleEditClick); - const isSelected = selectedPattern?.id === pattern._patternId; + const isSelected = selectedPattern?.id === props._patternId; const divClassNames = isSelected ? 'form-group-row field-selected' : 'form-group-row'; return ( -
    - +
    +
    -
    diff --git a/packages/design/src/config/view/Address/Address.stories.tsx b/packages/design/src/config/view/Address/Address.stories.tsx new file mode 100644 index 00000000..e69de29b diff --git a/packages/design/src/config/view/Address/Address.test.tsx b/packages/design/src/config/view/Address/Address.test.tsx new file mode 100644 index 00000000..e69de29b diff --git a/packages/design/src/config/view/Address/index.tsx b/packages/design/src/config/view/Address/index.tsx new file mode 100644 index 00000000..24750f96 --- /dev/null +++ b/packages/design/src/config/view/Address/index.tsx @@ -0,0 +1,147 @@ +import React from 'react'; +import { useFormContext } from 'react-hook-form'; + +import { type PatternComponent } from '../../../Form'; +import { AddressComponentProps } from '@atj/forms/src/patterns/address'; +import classNames from 'classnames'; + +const Address: PatternComponent = props => { + const { register } = useFormContext(); + return ( +
    + Mailing address + + + + + + + + + + + + +
    + ); +}; +export default Address; diff --git a/packages/design/src/config/view/Fieldset/index.tsx b/packages/design/src/config/view/Fieldset/index.tsx index 2e09f7c1..74cf1b22 100644 --- a/packages/design/src/config/view/Fieldset/index.tsx +++ b/packages/design/src/config/view/Fieldset/index.tsx @@ -4,14 +4,11 @@ import { type FieldsetProps } from '@atj/forms'; import { type PatternComponent } from '../../../Form'; -const FormSummary: PatternComponent = ({ - pattern, - children, -}) => { +const FormSummary: PatternComponent = props => { return (
    - {pattern.legend} - {children} + {props.legend} + {props.children}
    ); }; diff --git a/packages/design/src/config/view/FormSummary/FormSummary.stories.tsx b/packages/design/src/config/view/FormSummary/FormSummary.stories.tsx index b77317ca..b136856b 100644 --- a/packages/design/src/config/view/FormSummary/FormSummary.stories.tsx +++ b/packages/design/src/config/view/FormSummary/FormSummary.stories.tsx @@ -23,11 +23,9 @@ export const FormSummaryWithLongDescription = { export const FormSummaryWithShortDescription = { args: { - pattern: { - _patternId: 'test-id', - type: 'form-summary', - title: 'Title 2', - description: 'Short description', - } as FormSummaryProps, + _patternId: 'test-id', + type: 'form-summary', + title: 'Title 2', + description: 'Short description', }, } satisfies StoryObj; diff --git a/packages/design/src/config/view/FormSummary/index.tsx b/packages/design/src/config/view/FormSummary/index.tsx index 0d77b53a..7a0e7340 100644 --- a/packages/design/src/config/view/FormSummary/index.tsx +++ b/packages/design/src/config/view/FormSummary/index.tsx @@ -3,13 +3,13 @@ import React from 'react'; import { type FormSummaryProps } from '@atj/forms'; import { type PatternComponent } from '../../../Form'; -const FormSummary: PatternComponent = ({ pattern }) => { +const FormSummary: PatternComponent = props => { return ( <>
    {/* {pattern.title} */} -

    {pattern.title}

    - {pattern.description !== '' &&

    {pattern.description}

    } +

    {props.title}

    + {props.description !== '' &&

    {props.description}

    }
    ); diff --git a/packages/design/src/config/view/Paragraph/index.tsx b/packages/design/src/config/view/Paragraph/index.tsx index ef01808f..08822564 100644 --- a/packages/design/src/config/view/Paragraph/index.tsx +++ b/packages/design/src/config/view/Paragraph/index.tsx @@ -4,33 +4,11 @@ import { type ParagraphProps } from '@atj/forms'; import { type PatternComponent } from '../../../Form'; -const FormSummary: PatternComponent = ({ pattern }) => { - if (pattern.style === 'heading') { - return ( - <> -

    {pattern.text}

    - - ); - } else if (pattern.style === 'subheading') { - return ( - <> -

    {pattern.text}

    - - ); - } else if (pattern.style === 'indent') { - return ( - <> -
      -
    • {pattern.text}
    • -
    - - ); - } else { - return ( - <> -

    {pattern.text}

    - - ); - } +const FormSummary: PatternComponent = props => { + return ( + <> +

    {props.text}

    + + ); }; export default FormSummary; diff --git a/packages/design/src/config/view/SubmissionConfirmation/SubmissionConfirmation.stories.tsx b/packages/design/src/config/view/SubmissionConfirmation/SubmissionConfirmation.stories.tsx index fea444e5..c6a195de 100644 --- a/packages/design/src/config/view/SubmissionConfirmation/SubmissionConfirmation.stories.tsx +++ b/packages/design/src/config/view/SubmissionConfirmation/SubmissionConfirmation.stories.tsx @@ -11,14 +11,12 @@ export default { export const SubmissionConfirmationExample = { args: { - pattern: { - type: 'submission-confirmation', - table: [ - { label: 'Field 1', value: 'Value 1' }, - { label: 'Field 2', value: 'Value 2' }, - { label: 'Field 3', value: 'Value 3' }, - { label: 'Field 4', value: 'Value 4' }, - ], - } as SubmissionConfirmationProps, + type: 'submission-confirmation', + table: [ + { label: 'Field 1', value: 'Value 1' }, + { label: 'Field 2', value: 'Value 2' }, + { label: 'Field 3', value: 'Value 3' }, + { label: 'Field 4', value: 'Value 4' }, + ], }, } satisfies StoryObj; diff --git a/packages/design/src/config/view/SubmissionConfirmation/index.tsx b/packages/design/src/config/view/SubmissionConfirmation/index.tsx index d763ecb1..ce2b8a8b 100644 --- a/packages/design/src/config/view/SubmissionConfirmation/index.tsx +++ b/packages/design/src/config/view/SubmissionConfirmation/index.tsx @@ -3,9 +3,9 @@ import React from 'react'; import { type SubmissionConfirmationProps } from '@atj/forms'; import { type PatternComponent } from '../../../Form'; -const SubmissionConfirmation: PatternComponent = ({ - pattern, -}) => { +const SubmissionConfirmation: PatternComponent< + SubmissionConfirmationProps +> = props => { return ( <> @@ -52,7 +52,7 @@ const SubmissionConfirmation: PatternComponent = ({ - {pattern.table.map((row, index) => { + {props.table.map((row, index) => { return ( {row.label} diff --git a/packages/design/src/config/view/TextInput/TestInput.stories.tsx b/packages/design/src/config/view/TextInput/TestInput.stories.tsx index 71138680..42d95a5b 100644 --- a/packages/design/src/config/view/TextInput/TestInput.stories.tsx +++ b/packages/design/src/config/view/TextInput/TestInput.stories.tsx @@ -26,26 +26,22 @@ export default { export const Required = { args: { - pattern: { - _patternId: '', - type: 'input', - inputId: 'test-prompt', - value: '', - label: 'Please enter your first name.', - required: true, - } as TextInputProps, + _patternId: '', + type: 'input', + inputId: 'test-prompt', + value: '', + label: 'Please enter your first name.', + required: true, }, } satisfies StoryObj; export const NotRequired = { args: { - pattern: { - _patternId: '', - type: 'input', - inputId: 'test-prompt', - value: '', - label: 'Please enter your first name.', - required: false, - } as TextInputProps, + _patternId: '', + type: 'input', + inputId: 'test-prompt', + value: '', + label: 'Please enter your first name.', + required: false, }, } satisfies StoryObj; diff --git a/packages/design/src/config/view/TextInput/index.tsx b/packages/design/src/config/view/TextInput/index.tsx index 3ea57703..a595a782 100644 --- a/packages/design/src/config/view/TextInput/index.tsx +++ b/packages/design/src/config/view/TextInput/index.tsx @@ -5,44 +5,44 @@ import { useFormContext } from 'react-hook-form'; import { type TextInputProps } from '@atj/forms'; import { type PatternComponent } from '../../../Form'; -const TextInput: PatternComponent = ({ pattern }) => { +const TextInput: PatternComponent = props => { const { register } = useFormContext(); return ( -
    +
    - {pattern.error && ( + {props.error && ( - {pattern.error} + {props.error} )}
    diff --git a/packages/design/src/config/view/ZipCode/index.tsx b/packages/design/src/config/view/ZipCode/index.tsx new file mode 100644 index 00000000..9c1f8238 --- /dev/null +++ b/packages/design/src/config/view/ZipCode/index.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { useFormContext } from 'react-hook-form'; + +import { type ZipcodeProps } from '@atj/forms'; + +export const ZipCode = (props: ZipcodeProps) => { + const { register } = useFormContext(); + return ( + <> + + + + ); +}; diff --git a/packages/design/src/config/view/index.tsx b/packages/design/src/config/view/index.tsx index 0066eb4e..43aa1064 100644 --- a/packages/design/src/config/view/index.tsx +++ b/packages/design/src/config/view/index.tsx @@ -1,3 +1,4 @@ +import Address from './Address'; import Fieldset from './Fieldset'; import FormSummary from './FormSummary'; import Paragraph from './Paragraph'; @@ -7,6 +8,7 @@ import TextInput from './TextInput'; import { type ComponentForPattern } from '../../Form'; export const defaultPatternComponents: ComponentForPattern = { + address: Address, fieldset: Fieldset, 'form-summary': FormSummary, input: TextInput, diff --git a/packages/design/src/test-form.ts b/packages/design/src/test-form.ts index 4a77f193..fd090d74 100644 --- a/packages/design/src/test-form.ts +++ b/packages/design/src/test-form.ts @@ -24,10 +24,7 @@ export const createTestForm = () => { data: { patterns: ['element-1', 'element-2'], }, - initial: { - patterns: [], - }, - } as SequencePattern, + } satisfies SequencePattern, { type: 'input', id: 'element-1', @@ -37,13 +34,7 @@ export const createTestForm = () => { required: true, maxLength: 128, }, - initial: { - label: 'Pattern 1', - initial: '', - required: true, - maxLength: 128, - }, - } as InputPattern, + } satisfies InputPattern, { type: 'input', id: 'element-2', @@ -53,13 +44,7 @@ export const createTestForm = () => { required: true, maxLength: 128, }, - initial: { - label: 'Pattern 2', - initial: 'test', - required: true, - maxLength: 128, - }, - } as InputPattern, + } satisfies InputPattern, ], } ); diff --git a/packages/forms/src/builder/builder.test.ts b/packages/forms/src/builder/builder.test.ts index a99b32db..51fd144b 100644 --- a/packages/forms/src/builder/builder.test.ts +++ b/packages/forms/src/builder/builder.test.ts @@ -40,10 +40,7 @@ export const createTestBlueprint = () => { data: { patterns: ['element-1', 'element-2'], }, - initial: { - patterns: [], - }, - } as SequencePattern, + } satisfies SequencePattern, { type: 'input', id: 'element-1', @@ -53,13 +50,7 @@ export const createTestBlueprint = () => { required: true, maxLength: 128, }, - initial: { - label: 'Pattern 1', - initial: '', - required: true, - maxLength: 128, - }, - } as InputPattern, + } satisfies InputPattern, { type: 'input', id: 'element-2', @@ -69,13 +60,7 @@ export const createTestBlueprint = () => { required: true, maxLength: 128, }, - initial: { - label: 'Pattern 2', - initial: 'test', - required: true, - maxLength: 128, - }, - } as InputPattern, + } satisfies InputPattern, ], } ); diff --git a/packages/forms/src/components.ts b/packages/forms/src/components.ts index 51f79572..c3bdfb3c 100644 --- a/packages/forms/src/components.ts +++ b/packages/forms/src/components.ts @@ -35,12 +35,17 @@ export type ParagraphProps = PatternProps<{ export type FieldsetProps = PatternProps<{ type: 'fieldset'; - legend: string; + legend?: string; +}>; + +export type ZipcodeProps = PatternProps<{ + type: 'zipcode'; + inputId: string; + value: string; }>; export type PatternProps = { _patternId: PatternId; - _children: PromptPart[]; type: string; } & T; @@ -79,7 +84,7 @@ export const createPrompt = ( config, session.form.patterns[patternId].type ); - return elemConfig.acceptsInput; + return !!elemConfig.parseData; }) .map(([patternId, value]) => { return { diff --git a/packages/forms/src/documents/document.ts b/packages/forms/src/documents/document.ts index 53b9a6a4..bc15cf0a 100644 --- a/packages/forms/src/documents/document.ts +++ b/packages/forms/src/documents/document.ts @@ -7,6 +7,7 @@ import { updateFormSummary, } from '..'; import { InputPattern } from '../patterns/input'; +import { SequencePattern } from '../patterns/sequence'; import { PDFDocument, getDocumentFieldData } from './pdf'; import { getSuggestedPatternsFromCache } from './suggestions'; import { DocumentFieldMap } from './types'; @@ -76,9 +77,6 @@ export const addDocumentFieldsToForm = ( id: patternId, data: { label: field.label, - }, - initial: { - label: '', initial: '', required: false, maxLength: 128, @@ -90,9 +88,6 @@ export const addDocumentFieldsToForm = ( id: patternId, data: { label: field.label, - }, - initial: { - label: '', initial: '', required: false, maxLength: 128, @@ -104,9 +99,6 @@ export const addDocumentFieldsToForm = ( id: patternId, data: { label: field.label, - }, - initial: { - label: '', initial: '', required: false, maxLength: 128, @@ -118,9 +110,6 @@ export const addDocumentFieldsToForm = ( id: patternId, data: { label: field.label, - }, - initial: { - label: '', initial: '', required: false, maxLength: 128, @@ -132,9 +121,6 @@ export const addDocumentFieldsToForm = ( id: patternId, data: { label: field.label, - }, - initial: { - label: '', initial: '', required: false, maxLength: 128, @@ -154,7 +140,6 @@ export const addDocumentFieldsToForm = ( data: { patterns: patterns.map(pattern => pattern.id), }, - initial: [], - }); + } satisfies SequencePattern); return addPatterns(form, patterns, 'root'); }; diff --git a/packages/forms/src/documents/pdf/mock-api.ts b/packages/forms/src/documents/pdf/mock-api.ts index 118d9f5f..5dedc714 100644 --- a/packages/forms/src/documents/pdf/mock-api.ts +++ b/packages/forms/src/documents/pdf/mock-api.ts @@ -5,6 +5,7 @@ import { type Pattern, type PatternId, type PatternMap } from '../..'; import { type FieldsetPattern } from '../../patterns/fieldset'; import { type InputPattern } from '../../patterns/input'; import { type ParagraphPattern } from '../../patterns/paragraph'; +import { SequencePattern } from '../../patterns/sequence'; import { stringToBase64 } from '../util'; import { type DocumentFieldMap } from '../types'; @@ -111,13 +112,9 @@ export const parseAlabamaNameChangeForm = (): ParsedPdf => { parsedPdf.patterns[element.id] = { type: 'paragraph', id: element.id, - initial: { - text: '', - maxLength: 2048, - }, data: { text: element.element_params.text, - style: element.element_params.text_style, + maxLength: 2048, }, } satisfies ParagraphPattern; rootSequence.push(element.id); @@ -129,15 +126,12 @@ export const parseAlabamaNameChangeForm = (): ParsedPdf => { parsedPdf.patterns[id] = { type: 'input', id, - initial: { + data: { + label: input.input_params.instructions, required: false, - label: '', initial: '', maxLength: 128, }, - data: { - label: input.input_params.instructions, - }, } satisfies InputPattern; fieldsetPatterns.push(id); parsedPdf.outputs[id] = { @@ -158,10 +152,7 @@ export const parseAlabamaNameChangeForm = (): ParsedPdf => { legend: element.element_params.text, patterns: fieldsetPatterns, }, - initial: { - patterns: [], - }, - } as FieldsetPattern; + } satisfies FieldsetPattern; rootSequence.push(element.id); } } @@ -171,10 +162,7 @@ export const parseAlabamaNameChangeForm = (): ParsedPdf => { data: { patterns: rootSequence, }, - initial: { - patterns: [], - }, - }; + } satisfies SequencePattern; return parsedPdf; }; @@ -185,9 +173,11 @@ const getElementInputs = (element: ExtractedElement): Pattern[] => { return { type: 'input', id: input.input_params.output_id, - initial: {} as unknown as any, data: { label: input.input_params.instructions, + required: false, + maxLength: 256, + initial: '', }, } satisfies InputPattern; } diff --git a/packages/forms/src/index.ts b/packages/forms/src/index.ts index f42c39a2..e471939e 100644 --- a/packages/forms/src/index.ts +++ b/packages/forms/src/index.ts @@ -36,10 +36,7 @@ export const nullBlueprint: Blueprint = { data: { patterns: [], }, - initial: { - patterns: [], - }, - }, + } satisfies SequencePattern, }, outputs: [], }; @@ -69,9 +66,6 @@ export const createForm = ( data: { patterns: [], }, - initial: { - patterns: [], - }, } satisfies SequencePattern, ], root: 'root', diff --git a/packages/forms/src/pattern.ts b/packages/forms/src/pattern.ts index fb29aca5..fedf8a26 100644 --- a/packages/forms/src/pattern.ts +++ b/packages/forms/src/pattern.ts @@ -3,51 +3,49 @@ import { updatePattern, type Blueprint } from '..'; import { type CreatePrompt } from './components'; -export type Pattern = { +export type Pattern = { type: string; id: PatternId; data: C; - initial: T; }; export type PatternId = string; -export type PatternValue = T['initial']; +export type PatternValue = any; export type PatternValueMap = Record; export type PatternMap = Record; export type GetPattern = (form: Blueprint, id: PatternId) => Pattern; -export type ParsePatternData = ( - patternData: T['data'], - obj: string -) => Result; +type ParsePatternData = ( + patternData: PatternConfigData, + obj: unknown +) => Result; -export type ParsePatternConfigData = ( - patternData: T['data'] -) => Result; +type ParsePatternConfigData = ( + patternData: unknown +) => Result; export const getPattern: GetPattern = (form, patternId) => { return form.patterns[patternId]; }; -export type PatternConfig = { +export type PatternConfig< + ThisPattern extends Pattern = Pattern, + PatternOutput = unknown, +> = { displayName: string; - acceptsInput: boolean; initial: ThisPattern['data']; - parseData: ParsePatternData; - parseConfigData: ParsePatternConfigData; + parseData?: ParsePatternData; + parseConfigData: ParsePatternConfigData; getChildren: ( pattern: ThisPattern, patterns: Record ) => Pattern[]; createPrompt: CreatePrompt; }; -export type FormConfig = { - patterns: Record>; -}; -export type ConfigPatterns = ReturnType< - Config['patterns'][keyof Config['patterns']]['parseData'] ->; +export type FormConfig = { + patterns: Record>; +}; export const getPatternMap = (patterns: Pattern[]) => { return Object.fromEntries( @@ -65,24 +63,24 @@ export const getPatternConfig = ( }; export const validatePattern = ( - elementConfig: PatternConfig, - element: Pattern, + patternConfig: PatternConfig, + pattern: Pattern, value: any ): Result => { - if (!elementConfig.acceptsInput) { + if (!patternConfig.parseData) { return { success: true, data: value, }; } - const parseResult = elementConfig.parseData(element, value); + const parseResult = patternConfig.parseData(pattern, value); if (!parseResult.success) { return { success: false, error: parseResult.error, }; } - if (element.data.required && !parseResult.data) { + if (pattern.data.required && !parseResult.data) { return { success: false, error: 'Required value not provided.', @@ -139,6 +137,5 @@ export const createPattern = ( id: generatePatternId(), type: patternType, data: config.patterns[patternType].initial, - initial: {}, }; }; diff --git a/packages/forms/src/patterns/address/address.test.ts b/packages/forms/src/patterns/address/address.test.ts new file mode 100644 index 00000000..e69de29b diff --git a/packages/forms/src/patterns/address/index.ts b/packages/forms/src/patterns/address/index.ts new file mode 100644 index 00000000..14cb71f3 --- /dev/null +++ b/packages/forms/src/patterns/address/index.ts @@ -0,0 +1,175 @@ +import * as z from 'zod'; + +import { + validatePattern, + type Pattern, + type PatternConfig, +} from '../../pattern'; +import { PatternProps } from '../../components'; +import { safeZodParse } from '../../util/zod'; +import { + stateTerritoryOrMilitaryPostAbbreviations, + stateTerritoryOrMilitaryPostList, +} from './jurisdictions'; +import { getFormSessionValue } from '../../session'; + +export type AddressPattern = Pattern<{}>; + +export type AddressComponentProps = PatternProps<{ + childProps: { + streetAddress: { + inputId: string; + value: string; + label: string; + required: boolean; + error?: string; + }; + streetAddress2: { + inputId: string; + value: string; + label: string; + required: boolean; + error?: string; + }; + city: { + inputId: string; + value: string; + label: string; + required: boolean; + error?: string; + }; + stateTerritoryOrMilitaryPost: { + inputId: string; + value: string; + label: string; + required: boolean; + options: typeof stateTerritoryOrMilitaryPostList; + error?: string; + }; + zipCode: { + inputId: string; + value: string; + label: string; + required: boolean; + error?: string; + }; + urbanizationCode: { + inputId: string; + value: string; + label: string; + required: boolean; + error?: string; + }; + }; +}>; + +const AddressSchema = z.object({ + steetAddress: z.string().max(128), + steetAddress2: z.string().max(128).optional(), + city: z.string().max(64), + stateTerritoryOrMilitaryPost: stateTerritoryOrMilitaryPostAbbreviations, + zipCode: z.string().max(10), + urbanizationCode: z.string().max(128).optional(), + //googlePlusCode: z.string().max(8), +}); + +type AddressPatternOutput = z.infer; + +const configSchema = z.object({}); + +export const addressConfig: PatternConfig< + AddressPattern, + AddressPatternOutput +> = { + displayName: 'Physical address', + initial: { + patterns: [], + }, + parseData: (_, obj) => { + return safeZodParse(AddressSchema, obj); + }, + parseConfigData: obj => safeZodParse(configSchema, obj), + getChildren(pattern, patterns) { + return []; + }, + createPrompt(config, session, pattern, options) { + const sessionValue = getFormSessionValue(session, pattern.id); + const result = options.validate + ? AddressSchema.safeParse(sessionValue) + : null; + return { + pattern: { + _patternId: pattern.id, + type: 'address', + childProps: { + streetAddress: { + inputId: `${pattern.id}.streetAddress`, + value: sessionValue?.streetAddress, + label: 'Street address', + required: true, + error: + !result || result?.success + ? undefined + : result.error.formErrors.fieldErrors.steetAddress?.join(', '), + }, + streetAddress2: { + inputId: `${pattern.id}.streetAddress2`, + value: sessionValue?.streetAddress2, + label: 'Street address line 2', + required: false, + error: + !result || result?.success + ? undefined + : result.error.formErrors.fieldErrors.steetAddress2?.join(', '), + }, + city: { + inputId: `${pattern.id}.city`, + value: sessionValue?.city, + label: 'City', + required: true, + error: + !result || result?.success + ? undefined + : result.error.formErrors.fieldErrors.city?.join(', '), + }, + stateTerritoryOrMilitaryPost: { + inputId: `${pattern.id}.city`, + value: sessionValue?.stateTerritoryOrMilitaryPost, + label: 'State, territory, or military post', + required: true, + options: stateTerritoryOrMilitaryPostList, + error: + !result || result?.success + ? undefined + : result.error.formErrors.fieldErrors.stateTerritoryOrMilitaryPost?.join( + ', ' + ), + }, + zipCode: { + inputId: `${pattern.id}.zipCode`, + value: sessionValue?.zipCode, + label: 'ZIP code', + required: true, + error: + !result || result?.success + ? undefined + : result.error.formErrors.fieldErrors.zipCode?.join(', '), + }, + urbanizationCode: { + inputId: `${pattern.id}.urbanizationCode`, + value: sessionValue?.urbanizationCode, + label: 'Urbanization (Puerto Rico only)', + required: false, + error: + !result || result?.success + ? undefined + : result.error.formErrors.fieldErrors.urbanizationCode?.join( + ', ' + ), + }, + }, + } satisfies AddressComponentProps, + children: [], + }; + }, +}; diff --git a/packages/forms/src/patterns/address/jurisdictions.ts b/packages/forms/src/patterns/address/jurisdictions.ts new file mode 100644 index 00000000..bbc97ea1 --- /dev/null +++ b/packages/forms/src/patterns/address/jurisdictions.ts @@ -0,0 +1,138 @@ +import * as z from 'zod'; + +export const stateTerritoryOrMilitaryPostList = [ + { abbr: 'AL', label: 'AL - Alabama' }, + { abbr: 'AK', label: 'AK - Alaska' }, + { abbr: 'AS', label: 'AS - American' }, + { abbr: 'AZ', label: 'AZ - Arizona' }, + { abbr: 'AR', label: 'AR - Arkansas' }, + { abbr: 'CA', label: 'CA - California' }, + { abbr: 'CO', label: 'CO - Colorado' }, + { abbr: 'CT', label: 'CT - Connecticut' }, + { abbr: 'DE', label: 'DE - Delaware' }, + { abbr: 'DC', label: 'DC - District' }, + { abbr: 'FL', label: 'FL - Florida' }, + { abbr: 'GA', label: 'GA - Georgia' }, + { abbr: 'GU', label: 'GU - Guam' }, + { abbr: 'HI', label: 'HI - Hawaii' }, + { abbr: 'ID', label: 'ID - Idaho' }, + { abbr: 'IL', label: 'IL - Illinois' }, + { abbr: 'IN', label: 'IN - Indiana' }, + { abbr: 'IA', label: 'IA - Iowa' }, + { abbr: 'KS', label: 'KS - Kansas' }, + { abbr: 'KY', label: 'KY - Kentucky' }, + { abbr: 'LA', label: 'LA - Louisiana' }, + { abbr: 'ME', label: 'ME - Maine' }, + { abbr: 'MD', label: 'MD - Maryland' }, + { abbr: 'MA', label: 'MA - Massachusetts' }, + { abbr: 'MI', label: 'MI - Michigan' }, + { abbr: 'MN', label: 'MN - Minnesota' }, + { abbr: 'MS', label: 'MS - Mississippi' }, + { abbr: 'MO', label: 'MO - Missouri' }, + { abbr: 'MT', label: 'MT - Montana' }, + { abbr: 'NE', label: 'NE - Nebraska' }, + { abbr: 'NV', label: 'NV - Nevada' }, + { abbr: 'NH', label: 'NH - New' }, + { abbr: 'NJ', label: 'NJ - New' }, + { abbr: 'NM', label: 'NM - New' }, + { abbr: 'NY', label: 'NY - New' }, + { abbr: 'NC', label: 'NC - North' }, + { abbr: 'ND', label: 'ND - North' }, + { abbr: 'MP', label: 'MP - Northern' }, + { abbr: 'OH', label: 'OH - Ohio' }, + { abbr: 'OK', label: 'OK - Oklahoma' }, + { abbr: 'OR', label: 'OR - Oregon' }, + { abbr: 'PA', label: 'PA - Pennsylvania' }, + { abbr: 'PR', label: 'PR - Puerto' }, + { abbr: 'RI', label: 'RI - Rhode' }, + { abbr: 'SC', label: 'SC - South' }, + { abbr: 'SD', label: 'SD - South' }, + { abbr: 'TN', label: 'TN - Tennessee' }, + { abbr: 'TX', label: 'TX - Texas' }, + { abbr: 'UM', label: 'UM - United' }, + { abbr: 'UT', label: 'UT - Utah' }, + { abbr: 'VT', label: 'VT - Vermont' }, + { abbr: 'VI', label: 'VI - Virgin' }, + { abbr: 'VA', label: 'VA - Virginia' }, + { abbr: 'WA', label: 'WA - Washington' }, + { abbr: 'WV', label: 'WV - West' }, + { abbr: 'WI', label: 'WI - Wisconsin' }, + { abbr: 'WY', label: 'WY - Wyoming' }, + { abbr: 'AA', label: 'AA - Armed Forces Americas' }, + { abbr: 'AE', label: 'AE - Armed Forces Africa' }, + { abbr: 'AE', label: 'AE - Armed Forces Canada' }, + { abbr: 'AE', label: 'AE - Armed Forces Europe' }, + { abbr: 'AE', label: 'AE - Armed Forces Middle East' }, + { abbr: 'AP', label: 'AP - Armed Forces Pacific' }, +] as const; + +export const stateTerritoryOrMilitaryPostAbbreviations = z.union([ + z.literal('AL'), + z.literal('AK'), + z.literal('AS'), + z.literal('AZ'), + z.literal('AR'), + z.literal('CA'), + z.literal('CO'), + z.literal('CT'), + z.literal('DE'), + z.literal('DC'), + z.literal('FL'), + z.literal('GA'), + z.literal('GU'), + z.literal('HI'), + z.literal('ID'), + z.literal('IL'), + z.literal('IN'), + z.literal('IA'), + z.literal('KS'), + z.literal('KY'), + z.literal('LA'), + z.literal('ME'), + z.literal('MD'), + z.literal('MA'), + z.literal('MI'), + z.literal('MN'), + z.literal('MS'), + z.literal('MO'), + z.literal('MT'), + z.literal('NE'), + z.literal('NV'), + z.literal('NH'), + z.literal('NJ'), + z.literal('NM'), + z.literal('NY'), + z.literal('NC'), + z.literal('ND'), + z.literal('MP'), + z.literal('OH'), + z.literal('OK'), + z.literal('OR'), + z.literal('PA'), + z.literal('PR'), + z.literal('RI'), + z.literal('SC'), + z.literal('SD'), + z.literal('TN'), + z.literal('TX'), + z.literal('UM'), + z.literal('UT'), + z.literal('VT'), + z.literal('VI'), + z.literal('VA'), + z.literal('WA'), + z.literal('WV'), + z.literal('WI'), + z.literal('WY'), + z.literal('AA'), + z.literal('AE'), + z.literal('AP'), +]); + +/* +type JurisdictionAbbr = + (typeof stateTerritoryOrMilitaryPostList)[number]['abbr']; + +const getJurisdictionAbbreviations = (): JurisdictionAbbr[] => + stateTerritoryOrMilitaryPostList.map(j => j.abbr); +*/ diff --git a/packages/forms/src/patterns/fieldset.ts b/packages/forms/src/patterns/fieldset.ts index 96812523..a6b3cfdd 100644 --- a/packages/forms/src/patterns/fieldset.ts +++ b/packages/forms/src/patterns/fieldset.ts @@ -14,8 +14,6 @@ export type FieldsetPattern = Pattern<{ patterns: PatternId[]; }>; -const FieldsetSchema = z.array(z.string()); - const configSchema = z.object({ legend: z.string().optional(), patterns: z.array(z.string()), @@ -23,13 +21,9 @@ const configSchema = z.object({ export const fieldsetConfig: PatternConfig = { displayName: 'Fieldset', - acceptsInput: false, initial: { patterns: [], }, - parseData: (_, obj) => { - return safeZodParse(FieldsetSchema, obj); - }, parseConfigData: obj => safeZodParse(configSchema, obj), getChildren(pattern, patterns) { return pattern.data.patterns.map( @@ -43,7 +37,6 @@ export const fieldsetConfig: PatternConfig = { }); return { pattern: { - _children: children, _patternId: pattern.id, type: 'fieldset', legend: pattern.data.legend, diff --git a/packages/forms/src/patterns/form-summary.ts b/packages/forms/src/patterns/form-summary.ts index cb20aa0a..d17fe622 100644 --- a/packages/forms/src/patterns/form-summary.ts +++ b/packages/forms/src/patterns/form-summary.ts @@ -6,20 +6,16 @@ import { safeZodParse } from '../util/zod'; const configSchema = z.object({ title: z.string().max(128), - summary: z.string().max(2024), + description: z.string().max(2024), }); export type FormSummary = Pattern>; export const formSummaryConfig: PatternConfig = { displayName: 'Form summary', - acceptsInput: false, initial: { - text: '', - initial: '', - required: true, - maxLength: 128, + title: 'Form title', + description: 'Form extended description', }, - parseData: obj => safeZodParse(configSchema, obj), // make this optional? parseConfigData: obj => safeZodParse(configSchema, obj), getChildren() { return []; diff --git a/packages/forms/src/patterns/index.ts b/packages/forms/src/patterns/index.ts index 706e036b..5c827469 100644 --- a/packages/forms/src/patterns/index.ts +++ b/packages/forms/src/patterns/index.ts @@ -1,5 +1,6 @@ import { type FormConfig } from '../pattern'; +import { addressConfig } from './address'; import { fieldsetConfig } from './fieldset'; import { inputConfig } from './input'; import { paragraphConfig } from './paragraph'; @@ -10,6 +11,7 @@ import { sequenceConfig } from './sequence'; // understand the usage scenarios better. export const defaultFormConfig: FormConfig = { patterns: { + address: addressConfig, fieldset: fieldsetConfig, input: inputConfig, paragraph: paragraphConfig, diff --git a/packages/forms/src/patterns/input.ts b/packages/forms/src/patterns/input.ts index 54190d9a..329c2414 100644 --- a/packages/forms/src/patterns/input.ts +++ b/packages/forms/src/patterns/input.ts @@ -16,16 +16,19 @@ export type InputPattern = Pattern>; const createSchema = (data: InputPattern['data']) => z.string().max(data.maxLength); -export const inputConfig: PatternConfig = { +type InputPatternOutput = z.infer>; + +export const inputConfig: PatternConfig = { displayName: 'Text input', - acceptsInput: true, initial: { label: '', initial: '', required: true, maxLength: 128, }, - parseData: (patternData, obj) => safeZodParse(createSchema(patternData), obj), + parseData: (patternData, obj) => { + return safeZodParse(createSchema(patternData), obj); + }, parseConfigData: obj => safeZodParse(configSchema, obj), getChildren() { return []; diff --git a/packages/forms/src/patterns/paragraph.ts b/packages/forms/src/patterns/paragraph.ts index ac1e98c0..54701db0 100644 --- a/packages/forms/src/patterns/paragraph.ts +++ b/packages/forms/src/patterns/paragraph.ts @@ -10,17 +10,12 @@ const configSchema = z.object({ }); export type ParagraphPattern = Pattern>; -const createSchema = (data: ParagraphPattern['data']) => - z.string().max(data.maxLength); - export const paragraphConfig: PatternConfig = { displayName: 'Paragraph', - acceptsInput: false, initial: { - text: 'normal', + text: 'Paragraph text...', maxLength: 2048, }, - parseData: (patternData, obj) => safeZodParse(createSchema(patternData), obj), parseConfigData: obj => safeZodParse(configSchema, obj), getChildren() { return []; @@ -31,7 +26,6 @@ export const paragraphConfig: PatternConfig = { _patternId: pattern.id, type: 'paragraph' as const, text: pattern.data.text, - style: pattern.data.style, } as ParagraphProps, children: [], }; diff --git a/packages/forms/src/patterns/sequence.ts b/packages/forms/src/patterns/sequence.ts index 2701145a..e9245634 100644 --- a/packages/forms/src/patterns/sequence.ts +++ b/packages/forms/src/patterns/sequence.ts @@ -13,21 +13,15 @@ export type SequencePattern = Pattern<{ patterns: PatternId[]; }>; -const sequenceSchema = z.array(z.string()); - const configSchema = z.object({ patterns: z.array(z.string()), }); export const sequenceConfig: PatternConfig = { displayName: 'Sequence', - acceptsInput: false, initial: { patterns: [], }, - parseData: (_, obj) => { - return safeZodParse(sequenceSchema, obj); - }, parseConfigData: obj => safeZodParse(configSchema, obj), getChildren(pattern, patterns) { return pattern.data.patterns.map( @@ -41,7 +35,6 @@ export const sequenceConfig: PatternConfig = { }); return { pattern: { - _children: children, _patternId: pattern.id, type: 'sequence', }, diff --git a/packages/forms/src/response.ts b/packages/forms/src/response.ts index 3e3db137..46df833d 100644 --- a/packages/forms/src/response.ts +++ b/packages/forms/src/response.ts @@ -37,17 +37,6 @@ export const applyPromptResponse = ( }; }; -const parsePatternValue = ( - config: FormConfig, - session: FormSession, - patternId: PatternId, - promptValue: string -) => { - const pattern = session.form.patterns[patternId]; - const patternConfig = getPatternConfig(config, pattern.type); - return patternConfig.parseData(pattern, promptValue); -}; - const parsePromptResponse = ( session: FormSession, config: FormConfig, diff --git a/packages/forms/src/session.ts b/packages/forms/src/session.ts index 4b854366..e26063de 100644 --- a/packages/forms/src/session.ts +++ b/packages/forms/src/session.ts @@ -34,12 +34,10 @@ export const nullSession: FormSession = { root: { id: 'root', type: 'sequence', - required: false, - initial: { + data: { patterns: [], }, - data: {}, - } as SequencePattern, + } satisfies SequencePattern, }, root: 'root', summary: { @@ -54,11 +52,15 @@ export const createFormSession = (form: Blueprint): FormSession => { return { data: { errors: {}, + values: {}, + /* values: Object.fromEntries( - Object.values(form.patterns).map((pattern, index) => { + Object.values(form.patterns).map(pattern => { + //return [pattern.id, config.patterns[pattern.id].initial]; return [pattern.id, form.patterns[pattern.id].data.initial]; }) ), + */ }, form, }; diff --git a/packages/forms/src/util/zod.ts b/packages/forms/src/util/zod.ts index 262bfe5b..3a8060f3 100644 --- a/packages/forms/src/util/zod.ts +++ b/packages/forms/src/util/zod.ts @@ -6,8 +6,8 @@ import { type Pattern } from '..'; export const safeZodParse = ( schema: z.Schema, - obj: string -): Result => { + obj: unknown +): Result => { const result = schema.safeParse(obj); if (result.success) { return { diff --git a/packages/forms/tests/two-field-form.test.ts b/packages/forms/tests/two-field-form.test.ts index 42bc70b1..269b69cf 100644 --- a/packages/forms/tests/two-field-form.test.ts +++ b/packages/forms/tests/two-field-form.test.ts @@ -1,6 +1,8 @@ import { describe, expect, test } from 'vitest'; import * as forms from '../src'; +import { SequencePattern } from '../src/patterns/sequence'; +import { InputPattern } from '../src/patterns/input'; const patterns: forms.Pattern[] = [ { @@ -9,30 +11,27 @@ const patterns: forms.Pattern[] = [ data: { patterns: ['pattern-1', 'pattern-2'], }, - initial: { - patterns: [], - }, - }, + } satisfies SequencePattern, { type: 'input', id: 'pattern-1', data: { - text: 'What is your first name?', + label: 'What is your first name?', initial: '', required: true, + maxLength: 128, }, - initial: '', - }, + } satisfies InputPattern, { type: 'input', id: 'pattern-2', data: { - text: 'What is your favorite word?', + label: 'What is your favorite word?', initial: '', - required: false, + required: true, + maxLength: 128, }, - initial: '', - }, + } satisfies InputPattern, ]; const form = forms.createForm( { From 94e9ebf0eb48554e505c2edffd793e2b72271ef6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Apr 2024 21:35:01 -0500 Subject: [PATCH 4/6] Bump vite from 5.0.12 to 5.0.13 (#93) Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.0.12 to 5.0.13. - [Release notes](https://github.com/vitejs/vite/releases) - [Changelog](https://github.com/vitejs/vite/blob/v5.0.13/packages/vite/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite/commits/v5.0.13/packages/vite) --- updated-dependencies: - dependency-name: vite dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- packages/design/package.json | 2 +- pnpm-lock.yaml | 211 +++++++++-------------------------- 2 files changed, 53 insertions(+), 160 deletions(-) diff --git a/packages/design/package.json b/packages/design/package.json index c987b4dc..b7957dd9 100644 --- a/packages/design/package.json +++ b/packages/design/package.json @@ -56,7 +56,7 @@ "prop-types": "^15.8.1", "react": "^18.2.0", "react-dom": "^18.2.0", - "vite": "^5.0.12", + "vite": "^5.0.13", "vite-plugin-dts": "^3.7.1", "wait-on": "^7.2.0" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c216b7b7..bff1261f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -277,7 +277,7 @@ importers: version: 7.6.10(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.3) '@storybook/react-vite': specifier: ^7.6.10 - version: 7.6.10(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.3)(vite@5.0.12) + version: 7.6.10(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.3)(vite@5.1.4) '@storybook/test': specifier: ^7.6.10 version: 7.6.10(jest@29.7.0)(vitest@0.34.6) @@ -310,7 +310,7 @@ importers: version: github.com/danielnaab/uswds-compile/910cc2eeae7612d9ec0f56c40f6ea764f25ef2cf(ts-node@10.9.2) '@vitejs/plugin-react': specifier: ^4.2.1 - version: 4.2.1(vite@5.0.12) + version: 4.2.1(vite@5.1.4) concurrently: specifier: ^8.2.2 version: 8.2.2 @@ -345,11 +345,11 @@ importers: specifier: ^18.2.0 version: 18.2.0(react@18.2.0) vite: - specifier: ^5.0.12 - version: 5.0.12(@types/node@20.11.16) + specifier: ^5.0.13 + version: 5.1.4(@types/node@20.11.16) vite-plugin-dts: specifier: ^3.7.1 - version: 3.7.1(@types/node@20.11.16)(typescript@5.3.3)(vite@5.0.12) + version: 3.7.1(@types/node@20.11.16)(typescript@5.3.3)(vite@5.1.4) wait-on: specifier: ^7.2.0 version: 7.2.0 @@ -2470,6 +2470,7 @@ packages: cpu: [arm64] os: [android] requiresBuild: true + dev: true optional: true /@esbuild/android-arm@0.18.20: @@ -2494,6 +2495,7 @@ packages: cpu: [arm] os: [android] requiresBuild: true + dev: true optional: true /@esbuild/android-x64@0.18.20: @@ -2518,6 +2520,7 @@ packages: cpu: [x64] os: [android] requiresBuild: true + dev: true optional: true /@esbuild/darwin-arm64@0.18.20: @@ -2542,6 +2545,7 @@ packages: cpu: [arm64] os: [darwin] requiresBuild: true + dev: true optional: true /@esbuild/darwin-x64@0.18.20: @@ -2566,6 +2570,7 @@ packages: cpu: [x64] os: [darwin] requiresBuild: true + dev: true optional: true /@esbuild/freebsd-arm64@0.18.20: @@ -2590,6 +2595,7 @@ packages: cpu: [arm64] os: [freebsd] requiresBuild: true + dev: true optional: true /@esbuild/freebsd-x64@0.18.20: @@ -2614,6 +2620,7 @@ packages: cpu: [x64] os: [freebsd] requiresBuild: true + dev: true optional: true /@esbuild/linux-arm64@0.18.20: @@ -2638,6 +2645,7 @@ packages: cpu: [arm64] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-arm@0.18.20: @@ -2662,6 +2670,7 @@ packages: cpu: [arm] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-ia32@0.18.20: @@ -2686,6 +2695,7 @@ packages: cpu: [ia32] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-loong64@0.18.20: @@ -2710,6 +2720,7 @@ packages: cpu: [loong64] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-mips64el@0.18.20: @@ -2734,6 +2745,7 @@ packages: cpu: [mips64el] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-ppc64@0.18.20: @@ -2758,6 +2770,7 @@ packages: cpu: [ppc64] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-riscv64@0.18.20: @@ -2782,6 +2795,7 @@ packages: cpu: [riscv64] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-s390x@0.18.20: @@ -2806,6 +2820,7 @@ packages: cpu: [s390x] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-x64@0.18.20: @@ -2830,6 +2845,7 @@ packages: cpu: [x64] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/netbsd-x64@0.18.20: @@ -2854,6 +2870,7 @@ packages: cpu: [x64] os: [netbsd] requiresBuild: true + dev: true optional: true /@esbuild/openbsd-x64@0.18.20: @@ -2878,6 +2895,7 @@ packages: cpu: [x64] os: [openbsd] requiresBuild: true + dev: true optional: true /@esbuild/sunos-x64@0.18.20: @@ -2902,6 +2920,7 @@ packages: cpu: [x64] os: [sunos] requiresBuild: true + dev: true optional: true /@esbuild/win32-arm64@0.18.20: @@ -2926,6 +2945,7 @@ packages: cpu: [arm64] os: [win32] requiresBuild: true + dev: true optional: true /@esbuild/win32-ia32@0.18.20: @@ -2950,6 +2970,7 @@ packages: cpu: [ia32] os: [win32] requiresBuild: true + dev: true optional: true /@esbuild/win32-x64@0.18.20: @@ -2974,6 +2995,7 @@ packages: cpu: [x64] os: [win32] requiresBuild: true + dev: true optional: true /@eslint-community/eslint-utils@4.4.0(eslint@8.56.0): @@ -3502,7 +3524,7 @@ packages: chalk: 4.1.2 dev: true - /@joshwooding/vite-plugin-react-docgen-typescript@0.3.0(typescript@5.3.3)(vite@5.0.12): + /@joshwooding/vite-plugin-react-docgen-typescript@0.3.0(typescript@5.3.3)(vite@5.1.4): resolution: {integrity: sha512-2D6y7fNvFmsLmRt6UCOFJPvFoPMJGT0Uh1Wg0RaigUp7kdQPs6yYn8Dmx6GZkOH/NW0yMTwRz/p0SRMMRo50vA==} peerDependencies: typescript: '>= 4.3.x' @@ -3516,7 +3538,7 @@ packages: magic-string: 0.27.0 react-docgen-typescript: 2.2.2(typescript@5.3.3) typescript: 5.3.3 - vite: 5.0.12(@types/node@20.11.16) + vite: 5.1.4(@types/node@20.11.16) dev: true /@jridgewell/gen-mapping@0.3.3: @@ -4613,7 +4635,7 @@ packages: rollup: optional: true dependencies: - '@types/estree': 1.0.3 + '@types/estree': 1.0.5 estree-walker: 2.0.2 picomatch: 2.3.1 dev: true @@ -5087,7 +5109,7 @@ packages: - supports-color dev: false - /@storybook/builder-vite@7.6.10(typescript@5.3.3)(vite@5.0.12): + /@storybook/builder-vite@7.6.10(typescript@5.3.3)(vite@5.1.4): resolution: {integrity: sha512-qxe19axiNJVdIKj943e1ucAmADwU42fTGgMSdBzzrvfH3pSOmx2057aIxRzd8YtBRnj327eeqpgCHYIDTunMYQ==} peerDependencies: '@preact/preset-vite': '*' @@ -5119,7 +5141,7 @@ packages: magic-string: 0.30.7 rollup: 3.29.1 typescript: 5.3.3 - vite: 5.0.12(@types/node@20.11.16) + vite: 5.1.4(@types/node@20.11.16) transitivePeerDependencies: - encoding - supports-color @@ -5460,7 +5482,7 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: true - /@storybook/react-vite@7.6.10(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.3)(vite@5.0.12): + /@storybook/react-vite@7.6.10(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.3)(vite@5.1.4): resolution: {integrity: sha512-YE2+J1wy8nO+c6Nv/hBMu91Edew3K184L1KSnfoZV8vtq2074k1Me/8pfe0QNuq631AncpfCYNb37yBAXQ/80w==} engines: {node: '>=16'} peerDependencies: @@ -5468,16 +5490,16 @@ packages: react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 vite: ^3.0.0 || ^4.0.0 || ^5.0.0 dependencies: - '@joshwooding/vite-plugin-react-docgen-typescript': 0.3.0(typescript@5.3.3)(vite@5.0.12) + '@joshwooding/vite-plugin-react-docgen-typescript': 0.3.0(typescript@5.3.3)(vite@5.1.4) '@rollup/pluginutils': 5.1.0 - '@storybook/builder-vite': 7.6.10(typescript@5.3.3)(vite@5.0.12) + '@storybook/builder-vite': 7.6.10(typescript@5.3.3)(vite@5.1.4) '@storybook/react': 7.6.10(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.3) - '@vitejs/plugin-react': 3.1.0(vite@5.0.12) + '@vitejs/plugin-react': 3.1.0(vite@5.1.4) magic-string: 0.30.3 react: 18.2.0 react-docgen: 7.0.3 react-dom: 18.2.0(react@18.2.0) - vite: 5.0.12(@types/node@20.11.16) + vite: 5.1.4(@types/node@20.11.16) transitivePeerDependencies: - '@preact/preset-vite' - encoding @@ -5958,10 +5980,6 @@ packages: resolution: {integrity: sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==} dev: true - /@types/estree@1.0.3: - resolution: {integrity: sha512-CS2rOaoQ/eAgAfcTfq6amKG7bsN+EMcgGY4FAFQdvSj2y1ixvOZTUA9mOtCai7E1SYu283XNw7urKK30nP3wkQ==} - dev: true - /@types/estree@1.0.5: resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} @@ -6398,7 +6416,7 @@ packages: resolve-id-refs: 0.1.0 dev: false - /@vitejs/plugin-react@3.1.0(vite@5.0.12): + /@vitejs/plugin-react@3.1.0(vite@5.1.4): resolution: {integrity: sha512-AfgcRL8ZBhAlc3BFdigClmTUMISmmzHn7sB2h9U1odvc5U/MjWXsAaz18b/WoppUTDBzxOJwo2VdClfUcItu9g==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: @@ -6409,23 +6427,7 @@ packages: '@babel/plugin-transform-react-jsx-source': 7.23.3(@babel/core@7.23.7) magic-string: 0.27.0 react-refresh: 0.14.0 - vite: 5.0.12(@types/node@20.11.16) - transitivePeerDependencies: - - supports-color - dev: true - - /@vitejs/plugin-react@4.2.1(vite@5.0.12): - resolution: {integrity: sha512-oojO9IDc4nCUUi8qIR11KoQm0XFFLIwsRBwHRR4d/88IWghn1y6ckz/bJ8GHDCsYEJee8mDzqtJxh15/cisJNQ==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - vite: ^4.2.0 || ^5.0.0 - dependencies: - '@babel/core': 7.23.7 - '@babel/plugin-transform-react-jsx-self': 7.23.3(@babel/core@7.23.7) - '@babel/plugin-transform-react-jsx-source': 7.23.3(@babel/core@7.23.7) - '@types/babel__core': 7.20.5 - react-refresh: 0.14.0 - vite: 5.0.12(@types/node@20.11.16) + vite: 5.1.4(@types/node@20.11.16) transitivePeerDependencies: - supports-color dev: true @@ -6444,7 +6446,6 @@ packages: vite: 5.1.4(@types/node@20.11.16) transitivePeerDependencies: - supports-color - dev: false /@vitest/coverage-c8@0.33.0(vitest@0.34.6): resolution: {integrity: sha512-DaF1zJz4dcOZS4k/neiQJokmOWqsGXwhthfmUdPGorXIQHjdPvV6JQSYhQDI41MyI8c+IieQUdIDs5XAMHtDDw==} @@ -7369,8 +7370,8 @@ packages: tsconfck: 3.0.0(typescript@5.3.3) unist-util-visit: 5.0.0 vfile: 6.0.1 - vite: 5.0.12(@types/node@20.11.16) - vitefu: 0.2.5(vite@5.0.12) + vite: 5.1.4(@types/node@20.11.16) + vitefu: 0.2.5(vite@5.1.4) which-pm: 2.1.1 yargs-parser: 21.1.1 zod: 3.22.4 @@ -7452,8 +7453,8 @@ packages: tsconfck: 3.0.0(typescript@5.3.3) unist-util-visit: 5.0.0 vfile: 6.0.1 - vite: 5.1.3(@types/node@20.11.16) - vitefu: 0.2.5(vite@5.1.3) + vite: 5.1.4(@types/node@20.11.16) + vitefu: 0.2.5(vite@5.1.4) which-pm: 2.1.1 yargs-parser: 21.1.1 zod: 3.22.4 @@ -9546,7 +9547,7 @@ packages: dependencies: semver: 7.6.0 shelljs: 0.8.5 - typescript: 5.5.0-dev.20240321 + typescript: 5.5.0-dev.20240402 dev: false /dset@3.1.3: @@ -9903,6 +9904,7 @@ packages: '@esbuild/win32-arm64': 0.19.5 '@esbuild/win32-ia32': 0.19.5 '@esbuild/win32-x64': 0.19.5 + dev: true /escalade@3.1.1: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} @@ -14440,12 +14442,6 @@ packages: resolution: {integrity: sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==} requiresBuild: true - /nanoid@3.3.6: - resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - dev: true - /nanoid@3.3.7: resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -15579,19 +15575,11 @@ packages: /postcss@8.4.19: resolution: {integrity: sha512-h+pbPsyhlYj6N2ozBmHhHrs9DzGmbaarbLvWipMRO7RLS+v4onj26MPFXA5OBYFxyqYhUJK456SwDcY9H2/zsA==} engines: {node: ^10 || ^12 || >=14} - dependencies: - nanoid: 3.3.6 - picocolors: 1.0.0 - source-map-js: 1.0.2 - dev: true - - /postcss@8.4.33: - resolution: {integrity: sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==} - engines: {node: ^10 || ^12 || >=14} dependencies: nanoid: 3.3.7 picocolors: 1.0.0 source-map-js: 1.0.2 + dev: true /postcss@8.4.35: resolution: {integrity: sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==} @@ -15600,7 +15588,6 @@ packages: nanoid: 3.3.7 picocolors: 1.0.0 source-map-js: 1.0.2 - dev: false /prebuild-install@7.1.1: resolution: {integrity: sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==} @@ -18338,8 +18325,8 @@ packages: engines: {node: '>=14.17'} hasBin: true - /typescript@5.5.0-dev.20240321: - resolution: {integrity: sha512-QEUqMB18VAUQBHtHlq8BfjxGZzFkavH5fhZn+I97N67mDZTa86n1S8kiRKmrbxVwPAdvn3DYtJzcGq5OvdWkmQ==} + /typescript@5.5.0-dev.20240402: + resolution: {integrity: sha512-Dg6kiTZGVvuj9hqDzrqqUwZ3ruAcF1E7e9tSM53pP1XvZhPd4ZHLBF2rdbzjT2wIJOYL9Vl8OkAcL7dCJBB4Fg==} engines: {node: '>=14.17'} hasBin: true dev: false @@ -18900,7 +18887,7 @@ packages: mlly: 1.4.2 pathe: 1.1.1 picocolors: 1.0.0 - vite: 5.0.12(@types/node@20.11.16) + vite: 5.1.4(@types/node@20.11.16) transitivePeerDependencies: - '@types/node' - less @@ -18912,7 +18899,7 @@ packages: - terser dev: true - /vite-plugin-dts@3.7.1(@types/node@20.11.16)(typescript@5.3.3)(vite@5.0.12): + /vite-plugin-dts@3.7.1(@types/node@20.11.16)(typescript@5.3.3)(vite@5.1.4): resolution: {integrity: sha512-VZJckNFpVfRAkmOxhGT5OgTUVWVXxkNQqLpBUuiNGAr9HbtvmvsPLo2JB3Xhn+o/Z9+CT6YZfYa4bX9SGR5hNw==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: @@ -18928,7 +18915,7 @@ packages: debug: 4.3.4 kolorist: 1.8.0 typescript: 5.3.3 - vite: 5.0.12(@types/node@20.11.16) + vite: 5.1.4(@types/node@20.11.16) vue-tsc: 1.8.27(typescript@5.3.3) transitivePeerDependencies: - '@types/node' @@ -18947,77 +18934,6 @@ packages: - supports-color dev: true - /vite@5.0.12(@types/node@20.11.16): - resolution: {integrity: sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==} - engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true - peerDependencies: - '@types/node': ^18.0.0 || >=20.0.0 - less: '*' - lightningcss: ^1.21.0 - sass: '*' - stylus: '*' - sugarss: '*' - terser: ^5.4.0 - peerDependenciesMeta: - '@types/node': - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - dependencies: - '@types/node': 20.11.16 - esbuild: 0.19.5 - postcss: 8.4.33 - rollup: 4.9.5 - optionalDependencies: - fsevents: 2.3.3 - - /vite@5.1.3(@types/node@20.11.16): - resolution: {integrity: sha512-UfmUD36DKkqhi/F75RrxvPpry+9+tTkrXfMNZD+SboZqBCMsxKtO52XeGzzuh7ioz+Eo/SYDBbdb0Z7vgcDJew==} - engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true - peerDependencies: - '@types/node': ^18.0.0 || >=20.0.0 - less: '*' - lightningcss: ^1.21.0 - sass: '*' - stylus: '*' - sugarss: '*' - terser: ^5.4.0 - peerDependenciesMeta: - '@types/node': - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - dependencies: - '@types/node': 20.11.16 - esbuild: 0.19.12 - postcss: 8.4.35 - rollup: 4.9.5 - optionalDependencies: - fsevents: 2.3.3 - dev: false - /vite@5.1.4(@types/node@20.11.16): resolution: {integrity: sha512-n+MPqzq+d9nMVTKyewqw6kSt+R3CkvF9QAKY8obiQn8g1fwTscKxyfaYnC632HtBXAQGc1Yjomphwn1dtwGAHg==} engines: {node: ^18.0.0 || >=20.0.0} @@ -19052,29 +18968,6 @@ packages: rollup: 4.9.5 optionalDependencies: fsevents: 2.3.3 - dev: false - - /vitefu@0.2.5(vite@5.0.12): - resolution: {integrity: sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==} - peerDependencies: - vite: ^3.0.0 || ^4.0.0 || ^5.0.0 - peerDependenciesMeta: - vite: - optional: true - dependencies: - vite: 5.0.12(@types/node@20.11.16) - dev: false - - /vitefu@0.2.5(vite@5.1.3): - resolution: {integrity: sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==} - peerDependencies: - vite: ^3.0.0 || ^4.0.0 || ^5.0.0 - peerDependenciesMeta: - vite: - optional: true - dependencies: - vite: 5.1.3(@types/node@20.11.16) - dev: false /vitefu@0.2.5(vite@5.1.4): resolution: {integrity: sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==} @@ -19161,7 +19054,7 @@ packages: strip-literal: 1.3.0 tinybench: 2.6.0 tinypool: 0.7.0 - vite: 5.0.12(@types/node@20.11.16) + vite: 5.1.4(@types/node@20.11.16) vite-node: 0.34.6(@types/node@20.11.16) why-is-node-running: 2.2.2 transitivePeerDependencies: From 5959d97d002324dc1b77eeb843a093d7334523c1 Mon Sep 17 00:00:00 2001 From: Daniel Naab Date: Wed, 3 Apr 2024 21:51:33 -0500 Subject: [PATCH 5/6] Fix failing address pattern tests --- packages/design/src/FormManager/FormEdit/FormEdit.stories.tsx | 2 +- .../src/FormManager/FormList/CreateNew/PDFFileSelect.test.tsx | 2 +- packages/design/src/config/view/Address/Address.stories.tsx | 0 packages/design/src/config/view/Address/Address.test.tsx | 0 packages/forms/src/patterns/address/address.test.ts | 0 5 files changed, 2 insertions(+), 2 deletions(-) delete mode 100644 packages/design/src/config/view/Address/Address.stories.tsx delete mode 100644 packages/design/src/config/view/Address/Address.test.tsx delete mode 100644 packages/forms/src/patterns/address/address.test.ts diff --git a/packages/design/src/FormManager/FormEdit/FormEdit.stories.tsx b/packages/design/src/FormManager/FormEdit/FormEdit.stories.tsx index 9c7654b8..ce857197 100644 --- a/packages/design/src/FormManager/FormEdit/FormEdit.stories.tsx +++ b/packages/design/src/FormManager/FormEdit/FormEdit.stories.tsx @@ -46,7 +46,7 @@ export const FormEditAddPattern: StoryObj = { // Get the initial count of inputs const initialCount = (await canvas.getAllByRole('textbox')).length; - const select = canvas.getByLabelText('Add a pattern:'); + const select = canvas.getByLabelText('Add a pattern'); await userEvent.selectOptions(select, 'Text input'); const finalCount = (await canvas.getAllByRole('textbox')).length; diff --git a/packages/design/src/FormManager/FormList/CreateNew/PDFFileSelect.test.tsx b/packages/design/src/FormManager/FormList/CreateNew/PDFFileSelect.test.tsx index 621aa5d8..878875fb 100644 --- a/packages/design/src/FormManager/FormList/CreateNew/PDFFileSelect.test.tsx +++ b/packages/design/src/FormManager/FormList/CreateNew/PDFFileSelect.test.tsx @@ -2,6 +2,6 @@ * @vitest-environment jsdom */ import { describeStories } from '../../../test-helper'; -import meta, * as stories from './CreateNew.stories'; +import meta, * as stories from './PDFFileSelect.stories'; describeStories(meta.title, stories); diff --git a/packages/design/src/config/view/Address/Address.stories.tsx b/packages/design/src/config/view/Address/Address.stories.tsx deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/design/src/config/view/Address/Address.test.tsx b/packages/design/src/config/view/Address/Address.test.tsx deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/forms/src/patterns/address/address.test.ts b/packages/forms/src/patterns/address/address.test.ts deleted file mode 100644 index e69de29b..00000000 From 8eee124855fa36634cfa207d73b2fa3c98d78dbc Mon Sep 17 00:00:00 2001 From: Daniel Naab Date: Wed, 3 Apr 2024 21:57:38 -0500 Subject: [PATCH 6/6] Address cleanup (#95) * Fix failing address pattern tests * Linting --- .../SubmissionConfirmation/SubmissionConfirmation.stories.tsx | 1 - packages/design/src/config/view/TextInput/TestInput.stories.tsx | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/design/src/config/view/SubmissionConfirmation/SubmissionConfirmation.stories.tsx b/packages/design/src/config/view/SubmissionConfirmation/SubmissionConfirmation.stories.tsx index c6a195de..cc64b79c 100644 --- a/packages/design/src/config/view/SubmissionConfirmation/SubmissionConfirmation.stories.tsx +++ b/packages/design/src/config/view/SubmissionConfirmation/SubmissionConfirmation.stories.tsx @@ -1,7 +1,6 @@ import type { Meta, StoryObj } from '@storybook/react'; import SubmissionConfirmation from '.'; -import { type SubmissionConfirmationProps } from '@atj/forms'; export default { title: 'patterns/SubmissionConfirmation', diff --git a/packages/design/src/config/view/TextInput/TestInput.stories.tsx b/packages/design/src/config/view/TextInput/TestInput.stories.tsx index 42d95a5b..dd40362c 100644 --- a/packages/design/src/config/view/TextInput/TestInput.stories.tsx +++ b/packages/design/src/config/view/TextInput/TestInput.stories.tsx @@ -2,7 +2,6 @@ import React from 'react'; import { FormProvider, useForm } from 'react-hook-form'; import type { Meta, StoryObj } from '@storybook/react'; -import { type TextInputProps } from '@atj/forms'; import TextInput from '.'; export default {