From d406581205db11c3d62e610f37fd995b1ec6b592 Mon Sep 17 00:00:00 2001 From: Daniel Naab Date: Mon, 22 Apr 2024 13:09:54 -0500 Subject: [PATCH 1/2] Dependencies updates - downgrade Storybook (#112) * Downgrade dependencies * Upgrade uswds-compile * Upgrade vite and minor deps * Upgrade misc deps * upgrade eslint-plugin-react version * styling tweak * update lock file --- packages/design/package.json | 29 +- .../components/common/PatternEditActions.tsx | 6 +- .../FormManagerLayout/TopNavigation.tsx | 4 +- pnpm-lock.yaml | 2351 +++++++++++------ 4 files changed, 1579 insertions(+), 811 deletions(-) diff --git a/packages/design/package.json b/packages/design/package.json index 9f8ccd631..ea357ce94 100644 --- a/packages/design/package.json +++ b/packages/design/package.json @@ -25,18 +25,18 @@ ], "devDependencies": { "@playwright/test": "^1.43.1", - "@storybook/addon-a11y": "^8.0.8", - "@storybook/addon-coverage": "^1.0.1", - "@storybook/addon-essentials": "^8.0.8", - "@storybook/addon-interactions": "^8.0.8", - "@storybook/addon-links": "^8.0.8", - "@storybook/blocks": "^8.0.8", - "@storybook/preview-api": "^8.0.8", - "@storybook/react": "^8.0.8", - "@storybook/react-vite": "^8.0.8", - "@storybook/test": "^8.0.8", - "@storybook/test-runner": "^0.17.0", - "@storybook/types": "^8.0.8", + "@storybook/addon-a11y": "^7.6.10", + "@storybook/addon-coverage": "^1.0.0", + "@storybook/addon-essentials": "^7.6.10", + "@storybook/addon-interactions": "^7.6.10", + "@storybook/addon-links": "^7.6.10", + "@storybook/blocks": "^7.6.10", + "@storybook/preview-api": "^7.6.10", + "@storybook/react": "^7.6.10", + "@storybook/react-vite": "^7.6.10", + "@storybook/test": "^7.6.10", + "@storybook/test-runner": "^0.16.0", + "@storybook/types": "^7.6.10", "@testing-library/react": "^15.0.2", "@types/deep-equal": "^1.0.4", "@types/prop-types": "^15.7.12", @@ -52,9 +52,7 @@ "gulp": "^5.0.0", "http-server": "^14.1.1", "install": "^0.13.0", - "npm": "^10.5.2", "prop-types": "^15.8.1", - "react": "^18.2.0", "react-dom": "^18.2.0", "vite": "^5.2.9", "vite-plugin-dts": "^3.8.3", @@ -70,9 +68,10 @@ "@uswds/uswds": "^3.8.0", "classnames": "^2.5.1", "deep-equal": "^2.2.3", + "react": "^18.2.0", "react-hook-form": "^7.51.3", "react-router-dom": "^6.22.3", - "storybook": "^8.0.8", + "storybook": "^7.6.10", "zustand": "^4.5.2", "zustand-utils": "^1.3.2" } diff --git a/packages/design/src/FormManager/FormEdit/components/common/PatternEditActions.tsx b/packages/design/src/FormManager/FormEdit/components/common/PatternEditActions.tsx index 7b6c855a2..aa6d1be0b 100644 --- a/packages/design/src/FormManager/FormEdit/components/common/PatternEditActions.tsx +++ b/packages/design/src/FormManager/FormEdit/components/common/PatternEditActions.tsx @@ -35,8 +35,7 @@ export const PatternEditActions = ({ children }: PatternEditActionsProps) => { }} >
    -
  1. +
  2. (
  3. - - {title} - {children} - -
  4. - ); - } else if (current < step) { - return ( -
  5. - - {title} - {children} - not completed - -
  6. - ); - } else { - return ( -
  7. - -
  8. - ); - } - }; - const CreateNew = () => { return (
    @@ -168,13 +128,6 @@ const DocumentImporter = ({ return (

    Import a PDF

    -
    -
      - - - -
    -
    {state.page === 1 && } {state.page === 2 && } {state.page === 3 && } @@ -292,7 +245,7 @@ const useDocumentImporter = ( }, stepThreeSaveForm(formId: string) { formService.saveForm(formId, state.previewForm); - navigate(`/${formId}/edit`); + navigate(`/${formId}/create`); }, gotoPage(step: number) { dispatch({ type: 'GOTO_PAGE', page: step }); diff --git a/packages/design/src/FormManager/import-document.tsx b/packages/design/src/FormManager/FormDocumentImport/index.tsx similarity index 93% rename from packages/design/src/FormManager/import-document.tsx rename to packages/design/src/FormManager/FormDocumentImport/index.tsx index 73314e5ae..95234fd4c 100644 --- a/packages/design/src/FormManager/import-document.tsx +++ b/packages/design/src/FormManager/FormDocumentImport/index.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { type FormService } from '@atj/form-service'; -import { type FormUIContext } from '../Form'; +import { type FormUIContext } from '../../Form'; import DocumentImporter from './DocumentImporter'; export const FormDocumentImport = ({ diff --git a/packages/design/src/FormManager/FormEdit/AddPatternDropdown.tsx b/packages/design/src/FormManager/FormEdit/AddPatternDropdown.tsx index fe40ebc03..eb9392c56 100644 --- a/packages/design/src/FormManager/FormEdit/AddPatternDropdown.tsx +++ b/packages/design/src/FormManager/FormEdit/AddPatternDropdown.tsx @@ -1,8 +1,8 @@ import React from 'react'; -import { useFormEditStore } from './store'; +import { useFormManagerStore } from '../store'; export const AddPatternDropdown = () => { - const store = useFormEditStore(state => ({ + const store = useFormManagerStore(state => ({ availablePatterns: state.availablePatterns, addPattern: state.addPattern, })); diff --git a/packages/design/src/FormManager/FormEdit/FormEdit.stories.tsx b/packages/design/src/FormManager/FormEdit/FormEdit.stories.tsx index 1eac1316d..103454155 100644 --- a/packages/design/src/FormManager/FormEdit/FormEdit.stories.tsx +++ b/packages/design/src/FormManager/FormEdit/FormEdit.stories.tsx @@ -1,12 +1,12 @@ import React from 'react'; import { MemoryRouter } from 'react-router-dom'; import type { Meta, StoryObj } from '@storybook/react'; +import { expect, userEvent, waitFor, within } from '@storybook/test'; -import { createTestFormService } from '@atj/form-service'; +import { FormManagerProvider } from '../store'; import FormEdit from '.'; -import { createTestForm, createTestFormEditContext } from '../../test-form'; -import { expect, userEvent, waitFor, within } from '@storybook/test'; +import { createTestForm, createTestFormManagerContext } from '../../test-form'; export default { title: 'FormManager/FormEdit', @@ -14,16 +14,17 @@ export default { decorators: [ (Story, args) => ( - + + + ), ], args: { - context: createTestFormEditContext(), formId: 'test-form', - formService: createTestFormService({ - 'test-form': createTestForm(), - }), }, tags: ['autodocs'], } satisfies Meta; diff --git a/packages/design/src/FormManager/FormEdit/FormEdit.test.ts b/packages/design/src/FormManager/FormEdit/FormEdit.test.ts index 6de8c0a40..a65a8099e 100644 --- a/packages/design/src/FormManager/FormEdit/FormEdit.test.ts +++ b/packages/design/src/FormManager/FormEdit/FormEdit.test.ts @@ -4,4 +4,4 @@ import { describeStories } from '../../test-helper'; import meta, * as stories from './FormEdit.stories'; -describeStories(meta.title, stories); +describeStories(meta, stories); diff --git a/packages/design/src/FormManager/FormEdit/PreviewPattern.tsx b/packages/design/src/FormManager/FormEdit/PreviewPattern.tsx index 2d3d85cce..7b109534a 100644 --- a/packages/design/src/FormManager/FormEdit/PreviewPattern.tsx +++ b/packages/design/src/FormManager/FormEdit/PreviewPattern.tsx @@ -1,10 +1,10 @@ import React from 'react'; import { PatternComponent } from '../../Form'; -import { useFormEditStore } from './store'; +import { useFormManagerStore } from '../store'; export const PreviewPattern: PatternComponent = function PreviewPattern(props) { - const { context, setFocus } = useFormEditStore(state => ({ + const { context, setFocus } = useFormManagerStore(state => ({ context: state.context, setFocus: state.setFocus, updatePatternById: state.updatePatternById, diff --git a/packages/design/src/FormManager/FormEdit/components/FieldsetEdit.tsx b/packages/design/src/FormManager/FormEdit/components/FieldsetEdit.tsx index a17134cab..1109ea7a2 100644 --- a/packages/design/src/FormManager/FormEdit/components/FieldsetEdit.tsx +++ b/packages/design/src/FormManager/FormEdit/components/FieldsetEdit.tsx @@ -1,17 +1,18 @@ import React from 'react'; -import { PatternId, type FieldsetProps } from '@atj/forms'; -import { FieldsetPattern } from '@atj/forms/src/patterns/fieldset'; +import { type PatternId, type FieldsetProps } from '@atj/forms'; import Fieldset from '../../../Form/components/Fieldset'; -import { useIsPatternSelected, usePattern } from '../store'; +import { useFormManagerStore } from '../../store'; import { PatternEditComponent } from '../types'; import { PatternEditActions } from './common/PatternEditActions'; import { PatternEditForm } from './common/PatternEditForm'; const FieldsetEdit: PatternEditComponent = props => { - const isSelected = useIsPatternSelected(props.previewProps._patternId); + const isSelected = useFormManagerStore( + state => state.focusedPattern?.id === props.previewProps._patternId + ); return ( <> {isSelected ? ( @@ -29,7 +30,9 @@ const FieldsetEdit: PatternEditComponent = props => { }; const FieldsetPreview = (props: FieldsetProps) => { - const pattern = usePattern(props._patternId); + const pattern = useFormManagerStore( + state => state.form.patterns[props._patternId] + ); return ( <> {pattern.data.patterns.length === 0 && [Empty fieldset]} @@ -39,7 +42,7 @@ const FieldsetPreview = (props: FieldsetProps) => { }; const EditComponent = ({ patternId }: { patternId: PatternId }) => { - const pattern = usePattern(patternId); + const pattern = useFormManagerStore(state => state.form.patterns[patternId]); //const { register } = usePatternEditFormContext(); return (
    diff --git a/packages/design/src/FormManager/FormEdit/components/FormSummaryEdit.tsx b/packages/design/src/FormManager/FormEdit/components/FormSummaryEdit.tsx index ebe3dfe46..66be73bbd 100644 --- a/packages/design/src/FormManager/FormEdit/components/FormSummaryEdit.tsx +++ b/packages/design/src/FormManager/FormEdit/components/FormSummaryEdit.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { type FormSummaryProps, type PatternId } from '@atj/forms'; import FormSummary from '../../../Form/components/FormSummary'; -import { useIsPatternSelected } from '../store'; +import { useFormManagerStore } from '../../store'; import { PatternEditComponent } from '../types'; import { @@ -12,7 +12,9 @@ import { } from './common/PatternEditForm'; const FormSummaryEdit: PatternEditComponent = props => { - const isSelected = useIsPatternSelected(props.previewProps._patternId); + const isSelected = useFormManagerStore( + state => state.focusedPattern?.id === props.previewProps._patternId + ); return ( <> {isSelected ? ( diff --git a/packages/design/src/FormManager/FormEdit/components/InputPatternEdit.tsx b/packages/design/src/FormManager/FormEdit/components/InputPatternEdit.tsx index 617cdc721..14e6837e6 100644 --- a/packages/design/src/FormManager/FormEdit/components/InputPatternEdit.tsx +++ b/packages/design/src/FormManager/FormEdit/components/InputPatternEdit.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { PatternId, TextInputProps } from '@atj/forms'; import TextInput from '../../../Form/components/TextInput'; -import { useIsPatternSelected, usePattern } from '../store'; +import { useFormManagerStore } from '../../store'; import { PatternEditComponent } from '../types'; import { PatternEditActions } from './common/PatternEditActions'; @@ -13,7 +13,9 @@ import { } from './common/PatternEditForm'; const InputPatternEdit: PatternEditComponent = props => { - const isSelected = useIsPatternSelected(props.previewProps._patternId); + const isSelected = useFormManagerStore( + state => state.focusedPattern?.id === props.previewProps._patternId + ); return ( <> {isSelected ? ( @@ -31,7 +33,7 @@ const InputPatternEdit: PatternEditComponent = props => { }; const EditComponent = ({ patternId }: { patternId: PatternId }) => { - const pattern = usePattern(patternId); + const pattern = useFormManagerStore(state => state.form.patterns[patternId]); const methods = usePatternEditFormContext(); return ( diff --git a/packages/design/src/FormManager/FormEdit/components/ParagraphPatternEdit.tsx b/packages/design/src/FormManager/FormEdit/components/ParagraphPatternEdit.tsx index f2133a5b6..43b3bd77c 100644 --- a/packages/design/src/FormManager/FormEdit/components/ParagraphPatternEdit.tsx +++ b/packages/design/src/FormManager/FormEdit/components/ParagraphPatternEdit.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { PatternId, type ParagraphProps } from '@atj/forms'; import Paragraph from '../../../Form/components/Paragraph'; -import { useIsPatternSelected } from '../store'; +import { useFormManagerStore } from '../../store'; import { PatternEditComponent } from '../types'; import { PatternEditActions } from './common/PatternEditActions'; @@ -13,7 +13,9 @@ import { } from './common/PatternEditForm'; const ParagraphPatternEdit: PatternEditComponent = props => { - const isSelected = useIsPatternSelected(props.previewProps._patternId); + const isSelected = useFormManagerStore( + state => state.focusedPattern?.id === props.previewProps._patternId + ); return ( <> {isSelected ? ( diff --git a/packages/design/src/FormManager/FormEdit/components/PreviewSequencePattern/DraggableList.tsx b/packages/design/src/FormManager/FormEdit/components/PreviewSequencePattern/DraggableList.tsx index 215b6d168..b9e695a5d 100644 --- a/packages/design/src/FormManager/FormEdit/components/PreviewSequencePattern/DraggableList.tsx +++ b/packages/design/src/FormManager/FormEdit/components/PreviewSequencePattern/DraggableList.tsx @@ -16,7 +16,7 @@ import { verticalListSortingStrategy, } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; -import { useFormEditStore } from '../../store'; +import { useFormManagerStore } from '../../../store'; type DraggableListProps = React.PropsWithChildren<{ order: UniqueIdentifier[]; @@ -74,7 +74,7 @@ const SortableItem = ({ }) => { const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id }); - const context = useFormEditStore(state => state.context); + const context = useFormManagerStore(state => state.context); return (
    > = function PatternPreviewSequence(props) { - const form = useFormEditStore(state => state.form); - const updatePattern = useFormEditStore(state => state.updatePattern); + const form = useFormManagerStore(state => state.form); + const updatePattern = useFormManagerStore(state => state.updatePattern); const pattern = getPattern(form, props._patternId); /** diff --git a/packages/design/src/FormManager/FormEdit/components/SubmissionConfirmationEdit.tsx b/packages/design/src/FormManager/FormEdit/components/SubmissionConfirmationEdit.tsx index 7353932a4..2865e07d6 100644 --- a/packages/design/src/FormManager/FormEdit/components/SubmissionConfirmationEdit.tsx +++ b/packages/design/src/FormManager/FormEdit/components/SubmissionConfirmationEdit.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { type PatternId, SubmissionConfirmationProps } from '@atj/forms'; import SubmissionConfirmation from '../../../Form/components/SubmissionConfirmation'; -import { useIsPatternSelected } from '../store'; +import { useFormManagerStore } from '../../store'; import { PatternEditComponent } from '../types'; import { @@ -14,7 +14,9 @@ import { const SubmissionConfirmationEdit: PatternEditComponent< SubmissionConfirmationProps > = props => { - const isSelected = useIsPatternSelected(props.previewProps._patternId); + const isSelected = useFormManagerStore( + state => state.focusedPattern?.id === props.previewProps._patternId + ); return ( <> {isSelected ? ( diff --git a/packages/design/src/FormManager/FormEdit/components/common/PatternEditActions.tsx b/packages/design/src/FormManager/FormEdit/components/common/PatternEditActions.tsx index aa6d1be0b..43ed4b4a2 100644 --- a/packages/design/src/FormManager/FormEdit/components/common/PatternEditActions.tsx +++ b/packages/design/src/FormManager/FormEdit/components/common/PatternEditActions.tsx @@ -1,7 +1,7 @@ import React, { PropsWithChildren, ReactElement } from 'react'; import classNames from 'classnames'; -import { useFormEditStore } from '../../store'; +import { useFormManagerStore } from '../../../store'; type PatternEditActionsProps = PropsWithChildren<{ children?: ReactElement; @@ -9,8 +9,8 @@ type PatternEditActionsProps = PropsWithChildren<{ export const PatternEditActions = ({ children }: PatternEditActionsProps) => { children; - const context = useFormEditStore(state => state.context); - const { deleteSelectedPattern } = useFormEditStore(state => ({ + const context = useFormManagerStore(state => state.context); + const { deleteSelectedPattern } = useFormManagerStore(state => ({ deleteSelectedPattern: state.deleteSelectedPattern, })); diff --git a/packages/design/src/FormManager/FormEdit/components/common/PatternEditForm.tsx b/packages/design/src/FormManager/FormEdit/components/common/PatternEditForm.tsx index 7a17a3fa5..c18832a34 100644 --- a/packages/design/src/FormManager/FormEdit/components/common/PatternEditForm.tsx +++ b/packages/design/src/FormManager/FormEdit/components/common/PatternEditForm.tsx @@ -3,7 +3,7 @@ import { FormProvider, useForm, useFormContext } from 'react-hook-form'; import { type PatternId, type PatternMap } from '@atj/forms'; -import { useFormEditStore, usePattern } from '../../store'; +import { useFormManagerStore } from '../../../store'; type PatternEditFormProps = { patternId: PatternId; @@ -14,10 +14,10 @@ export const PatternEditForm = ({ patternId, editComponent, }: PatternEditFormProps) => { - const { updatePatternById } = useFormEditStore(state => ({ + const { updatePatternById } = useFormManagerStore(state => ({ updatePatternById: state.updatePatternById, })); - const pattern = usePattern(patternId); + const pattern = useFormManagerStore(state => state.form.patterns[patternId]); const methods = useForm({ defaultValues: { [patternId]: pattern, diff --git a/packages/design/src/FormManager/FormEdit/index.tsx b/packages/design/src/FormManager/FormEdit/index.tsx index 1e260161d..4513c5d4f 100644 --- a/packages/design/src/FormManager/FormEdit/index.tsx +++ b/packages/design/src/FormManager/FormEdit/index.tsx @@ -1,45 +1,31 @@ import React, { useEffect } from 'react'; import { Blueprint, createFormSession } from '@atj/forms'; -import { type FormService } from '@atj/form-service'; -import Form, { ComponentForPattern, PatternComponent } from '../../Form'; +import Form, { + type ComponentForPattern, + type PatternComponent, +} from '../../Form'; import { AddPatternDropdown } from './AddPatternDropdown'; import { PreviewPattern } from './PreviewPattern'; import { PatternPreviewSequence } from './components/PreviewSequencePattern'; -import { FormEditProvider, useFormEditStore } from './store'; -import { type FormEditUIContext } from './types'; - -export default function FormEdit({ - context, - formId, - formService, -}: { - context: FormEditUIContext; - formId: string; - formService: FormService; -}) { - const result = formService.getForm(formId); - if (!result.success) { - return 'Form not found'; - } - const form = result.data; +import { useFormManagerStore } from '../store'; +export default function FormEdit({ formId }: { formId: string }) { + const saveForm = useFormManagerStore(state => state.saveForm); return ( <>

    Edit form

    Your form has been imported for web delivery.

    - - formService.saveForm(formId, form)} /> - + saveForm(formId, form)} /> ); } const EditForm = ({ saveForm }: { saveForm: (form: Blueprint) => void }) => { - const { form } = useFormEditStore(); - const uiContext = useFormEditStore(state => state.context); + const { form } = useFormManagerStore(); + const uiContext = useFormManagerStore(state => state.context); const disposable = createFormSession(form); // nullSession instead? useEffect(() => { saveForm(form); diff --git a/packages/design/src/FormManager/FormEdit/store.tsx b/packages/design/src/FormManager/FormEdit/store.ts similarity index 75% rename from packages/design/src/FormManager/FormEdit/store.tsx rename to packages/design/src/FormManager/FormEdit/store.ts index c5241ca98..8fe8c7421 100644 --- a/packages/design/src/FormManager/FormEdit/store.tsx +++ b/packages/design/src/FormManager/FormEdit/store.ts @@ -1,6 +1,4 @@ -import React from 'react'; -import { StoreApi, create } from 'zustand'; -import { createContext } from 'zustand-utils'; +import { StateCreator } from 'zustand'; import { type Blueprint, @@ -10,32 +8,10 @@ import { getPattern, BlueprintBuilder, } from '@atj/forms'; -import { type FormEditUIContext } from './types'; +import { type FormManagerContext } from '..'; -const { Provider, useStore } = createContext>(); - -export const useFormEditStore = useStore; - -export const usePattern = (id: PatternId) => - useFormEditStore(state => state.form.patterns[id] as T); - -export const useIsPatternSelected = (id: PatternId) => - useFormEditStore(state => state.focusedPattern?.id === id); - -export const FormEditProvider = (props: { - context: FormEditUIContext; - form: Blueprint; - children: React.ReactNode; -}) => { - return ( - createFormEditStore(props)}> - {props.children} - - ); -}; - -type FormEditState = { - context: FormEditUIContext; +export type FormEditSlice = { + context: FormManagerContext; form: Blueprint; focusedPattern?: Pattern; availablePatterns: { @@ -52,14 +28,16 @@ type FormEditState = { updateSelectedPattern: (formData: PatternMap) => void; }; -const createFormEditStore = ({ - context, - form, -}: { - context: FormEditUIContext; +type FormEditStoreContext = { + context: FormManagerContext; form: Blueprint; -}) => - create((set, get) => ({ +}; + +type FormEditStoreCreator = StateCreator; + +export const createFormEditSlice = + ({ context, form }: FormEditStoreContext): FormEditStoreCreator => + (set, get) => ({ context, form, availablePatterns: Object.entries(context.config.patterns).map( @@ -134,4 +112,4 @@ const createFormEditStore = ({ set({ form: builder.form, focusedPattern: undefined }); } }, - })); + }); diff --git a/packages/design/src/FormManager/FormEdit/types.ts b/packages/design/src/FormManager/FormEdit/types.ts index f74b02d7c..f69fa383c 100644 --- a/packages/design/src/FormManager/FormEdit/types.ts +++ b/packages/design/src/FormManager/FormEdit/types.ts @@ -1,17 +1,9 @@ -import { type FormConfig, type PatternProps } from '@atj/forms'; - -import { type ComponentForPattern } from '../../Form'; - -export type FormEditUIContext = { - config: FormConfig; - components: ComponentForPattern; - editComponents: EditComponentForPattern; - uswdsRoot: `${string}/`; -}; +import { type PatternProps } from '@atj/forms'; +import { FormManagerContext } from '..'; export type PatternEditComponent = React.ComponentType<{ - context: FormEditUIContext; + context: FormManagerContext; previewProps: T; }>; diff --git a/packages/design/src/FormManager/FormList/CreateNew/PDFFileSelect.stories.tsx b/packages/design/src/FormManager/FormList/CreateNew/PDFFileSelect.stories.tsx index b72d5603e..a737ee6c4 100644 --- a/packages/design/src/FormManager/FormList/CreateNew/PDFFileSelect.stories.tsx +++ b/packages/design/src/FormManager/FormList/CreateNew/PDFFileSelect.stories.tsx @@ -2,6 +2,11 @@ import React from 'react'; import { MemoryRouter } from 'react-router-dom'; import type { Meta, StoryObj } from '@storybook/react'; +import { + createTestForm, + createTestFormManagerContext, +} from '../../../test-form'; +import { FormManagerProvider } from '../../store'; import CreateNew from '.'; export default { @@ -10,7 +15,12 @@ export default { decorators: [ (Story, args) => ( - + + + ), ], diff --git a/packages/design/src/FormManager/FormList/CreateNew/PDFFileSelect.test.tsx b/packages/design/src/FormManager/FormList/CreateNew/PDFFileSelect.test.tsx index 878875fb0..5810470f3 100644 --- a/packages/design/src/FormManager/FormList/CreateNew/PDFFileSelect.test.tsx +++ b/packages/design/src/FormManager/FormList/CreateNew/PDFFileSelect.test.tsx @@ -4,4 +4,4 @@ import { describeStories } from '../../../test-helper'; import meta, * as stories from './PDFFileSelect.stories'; -describeStories(meta.title, stories); +describeStories(meta, stories); diff --git a/packages/design/src/FormManager/FormList/CreateNew/hooks.ts b/packages/design/src/FormManager/FormList/CreateNew/hooks.ts deleted file mode 100644 index f42a568fd..000000000 --- a/packages/design/src/FormManager/FormList/CreateNew/hooks.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { useNavigate } from 'react-router-dom'; - -import { BlueprintBuilder } from '@atj/forms'; -import { type FormService } from '@atj/form-service'; - -export const useDocumentImporter = ( - formService: FormService, - baseUrl: string -) => { - const navigate = useNavigate(); - - return { - actions: { - stepOneSelectPdfByUrl: async (url: string) => { - const data = await fetchUint8Array(`${baseUrl}${url}`); - - const builder = new BlueprintBuilder(); - builder.setFormSummary({ - title: url, - description: '', - }); - await builder.addDocument({ - name: url, - data, - }); - - const result = formService.addForm(builder.form); - if (result.success) { - navigate(`/${result.data}/edit`); - } - }, - stepOneSelectPdfByUpload: async (fileDetails: { - name: string; - data: Uint8Array; - }) => { - const builder = new BlueprintBuilder(); - builder.setFormSummary({ - title: fileDetails.name, - description: '', - }); - await builder.addDocument(fileDetails); - const result = await formService.addForm(builder.form); - if (result.success) { - navigate(`/${result.data}/edit`); - } - }, - createNewForm: async () => { - const builder = new BlueprintBuilder(); - builder.setFormSummary({ - title: `My form - ${new Date().toISOString()}`, - description: '', - }); - const result = await formService.addForm(builder.form); - if (result.success) { - navigate(`/${result.data}/edit`); - } - }, - }, - }; -}; - -const fetchUint8Array = async (url: string) => { - const response = await fetch(url); - const blob = await response.blob(); - return new Uint8Array(await blob.arrayBuffer()); -}; diff --git a/packages/design/src/FormManager/FormList/CreateNew/index.tsx b/packages/design/src/FormManager/FormList/CreateNew/index.tsx index a54a1cafd..ed1842ce5 100644 --- a/packages/design/src/FormManager/FormList/CreateNew/index.tsx +++ b/packages/design/src/FormManager/FormList/CreateNew/index.tsx @@ -1,23 +1,31 @@ import React from 'react'; +import { useNavigate } from 'react-router-dom'; import { SAMPLE_DOCUMENTS } from '@atj/forms'; -import { FormService } from '@atj/form-service'; +import { useFormManagerStore } from '../../store'; import { onFileInputChangeGetFile } from './file-input'; -import { useDocumentImporter } from './hooks'; -export default function CreateNew({ - formService, - baseUrl, -}: { - formService: FormService; - baseUrl: string; -}) { - const { actions } = useDocumentImporter(formService, baseUrl); +export default function CreateNew() { + const navigate = useNavigate(); + const actions = useFormManagerStore(state => ({ + context: state.context, + createNewForm: state.createNewForm, + createNewFormByPDFUpload: state.createNewFormByPDFUpload, + createNewFormByPDFUrl: state.createNewFormByPDFUrl, + })); return (
    -
    @@ -54,7 +66,12 @@ export default function CreateNew({ {SAMPLE_DOCUMENTS.map((document, index) => ( { + const result = await actions.createNewFormByPDFUrl(url); + if (result.success) { + navigate(`/${result.data}/create`); + } + }} documentPath={document.path} /> ))} diff --git a/packages/design/src/FormManager/FormList/FormList.stories.tsx b/packages/design/src/FormManager/FormList/FormList.stories.tsx index fca9faea1..8ca2c6925 100644 --- a/packages/design/src/FormManager/FormList/FormList.stories.tsx +++ b/packages/design/src/FormManager/FormList/FormList.stories.tsx @@ -5,7 +5,8 @@ import type { Meta, StoryObj } from '@storybook/react'; import { createTestFormService } from '@atj/form-service'; import FormList from '.'; -import { createTestForm } from '../../test-form'; +import { createTestForm, createTestFormManagerContext } from '../../test-form'; +import { FormManagerProvider } from '../store'; export default { title: 'FormManager/FormList', @@ -13,17 +14,21 @@ export default { decorators: [ (Story, args) => ( - + + + ), ], args: { - baseUrl: '/', formService: createTestFormService({ 'test-form': createTestForm(), }), }, tags: ['autodocs'], -} satisfies Meta; +} as Meta; export const FormListFilled = {} satisfies StoryObj; diff --git a/packages/design/src/FormManager/FormList/FormList.test.ts b/packages/design/src/FormManager/FormList/FormList.test.ts index 0bdac0ecf..7a7014ddd 100644 --- a/packages/design/src/FormManager/FormList/FormList.test.ts +++ b/packages/design/src/FormManager/FormList/FormList.test.ts @@ -4,4 +4,4 @@ import { describeStories } from '../../test-helper'; import meta, * as stories from './FormList.stories'; -describeStories(meta.title, stories); +describeStories(meta, stories); diff --git a/packages/design/src/FormManager/FormList/ManageFormsTable.tsx b/packages/design/src/FormManager/FormList/ManageFormsTable.tsx new file mode 100644 index 000000000..53124c3a1 --- /dev/null +++ b/packages/design/src/FormManager/FormList/ManageFormsTable.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; + +import { FormListItem } from '@atj/form-service/src/operations/get-form-list'; + +type ManageFormsTableProps = { + formListItems: FormListItem[]; +}; + +export const ManageFormsTable = ({ formListItems }: ManageFormsTableProps) => { + return ( + <> + {' '} +

    Manage Forms

    + + + + + + + + + + {formListItems.map((form, index) => ( + + + + + + ))} + +
    + Form title + + Description + + Actions +
    + {form.title} + {form.description} + {/* Preview */} + + Edit + + + Delete + +
    + + ); +}; diff --git a/packages/design/src/FormManager/FormList/index.tsx b/packages/design/src/FormManager/FormList/index.tsx index 695898965..b0a99f252 100644 --- a/packages/design/src/FormManager/FormList/index.tsx +++ b/packages/design/src/FormManager/FormList/index.tsx @@ -1,14 +1,12 @@ import React from 'react'; -import { Link } from 'react-router-dom'; - import { FormService } from '@atj/form-service'; + import CreateNew from './CreateNew'; +import { ManageFormsTable } from './ManageFormsTable'; export default function FormList({ - baseUrl, formService, }: { - baseUrl: string; formService: FormService; }) { const result = formService.getFormList(); @@ -17,43 +15,8 @@ export default function FormList({ } return ( <> - {' '} -

    Manage Forms

    - - - - - - - - - - {result.data.map((form, index) => ( - - - - - - ))} - -
    - Form title - - Description - - Actions -
    - {form.title} - {form.description} - {/* Preview */} - - Edit - - - Delete - -
    - + + ); } diff --git a/packages/design/src/FormManager/FormList/store.ts b/packages/design/src/FormManager/FormList/store.ts new file mode 100644 index 000000000..81ba251e0 --- /dev/null +++ b/packages/design/src/FormManager/FormList/store.ts @@ -0,0 +1,54 @@ +import { type StateCreator } from 'zustand'; + +import { BlueprintBuilder } from '@atj/forms'; +import { type FormManagerContext } from '../../FormManager'; +import { type Result } from '@atj/common'; + +type StoreContext = { + context: FormManagerContext; +}; + +export type FormListSlice = { + context: FormManagerContext; + createNewFormByPDFUrl: (url: string) => Promise>; + createNewFormByPDFUpload: (fileDetails: { + name: string; + data: Uint8Array; + }) => Promise>; +}; + +type FormListSliceCreator = StateCreator; +export const createFormListSlice = + ({ context }: StoreContext): FormListSliceCreator => + () => ({ + context, + createNewFormByPDFUrl: async url => { + const data = await fetchUint8Array(`${context.baseUrl}${url}`); + + const builder = new BlueprintBuilder(); + builder.setFormSummary({ + title: url, + description: '', + }); + await builder.addDocument({ + name: url, + data, + }); + return context.formService.addForm(builder.form); + }, + createNewFormByPDFUpload: async fileDetails => { + const builder = new BlueprintBuilder(); + builder.setFormSummary({ + title: fileDetails.name, + description: '', + }); + await builder.addDocument(fileDetails); + return await context.formService.addForm(builder.form); + }, + }); + +const fetchUint8Array = async (url: string) => { + const response = await fetch(url); + const blob = await response.blob(); + return new Uint8Array(await blob.arrayBuffer()); +}; diff --git a/packages/design/src/FormManager/FormManager.stories.ts b/packages/design/src/FormManager/FormManager.stories.ts index 57e898c9b..538433e1b 100644 --- a/packages/design/src/FormManager/FormManager.stories.ts +++ b/packages/design/src/FormManager/FormManager.stories.ts @@ -1,18 +1,14 @@ // Replace your-framework with the name of your framework import type { Meta, StoryObj } from '@storybook/react'; -import { createTestFormService } from '@atj/form-service'; - import FormManager from '.'; -import { createTestFormEditContext } from '../test-form'; +import { createTestFormManagerContext } from '../test-form'; export default { title: 'form/FormManager', component: FormManager, args: { - baseUrl: '/', - context: createTestFormEditContext(), - formService: createTestFormService(), + context: createTestFormManagerContext(), }, tags: ['autodocs'], } satisfies Meta; diff --git a/packages/design/src/FormManager/FormManager.test.ts b/packages/design/src/FormManager/FormManager.test.ts index 95e2a6d7f..f573850ae 100644 --- a/packages/design/src/FormManager/FormManager.test.ts +++ b/packages/design/src/FormManager/FormManager.test.ts @@ -4,4 +4,4 @@ import { describeStories } from '../test-helper'; import meta, * as stories from './FormManager.stories'; -describeStories(meta.title, stories); +describeStories(meta, stories); diff --git a/packages/design/src/FormManager/FormManagerLayout/FormManagerLayout.stories.tsx b/packages/design/src/FormManager/FormManagerLayout/FormManagerLayout.stories.tsx new file mode 100644 index 000000000..92f5976fc --- /dev/null +++ b/packages/design/src/FormManager/FormManagerLayout/FormManagerLayout.stories.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import { MemoryRouter } from 'react-router-dom'; +import type { Meta, StoryObj } from '@storybook/react'; + +import { FormManagerLayout, NavPage } from '.'; +import { createTestForm, createTestFormManagerContext } from '../../test-form'; +import { FormManagerProvider } from '../store'; + +export default { + title: 'FormManagerLayout', + component: FormManagerLayout, + decorators: [ + (Story, args) => ( + + + + + + ), + ], + args: {}, + tags: ['autodocs'], +} satisfies Meta; + +export const Configure = { + args: { + step: NavPage.configure, + next: '#', + }, +} satisfies StoryObj; + +export const Create = { + args: { + step: NavPage.create, + next: '#', + back: '#', + preview: '#', + }, +} satisfies StoryObj; + +export const Publish = { + args: { + step: NavPage.publish, + next: '#', + back: '#', + preview: '#', + }, +} satisfies StoryObj; + +export const Upload = { + args: { + step: NavPage.upload, + next: '#', + back: '#', + preview: '#', + }, +} satisfies StoryObj; diff --git a/packages/design/src/FormManager/FormManagerLayout/FormManagerLayout.test.tsx b/packages/design/src/FormManager/FormManagerLayout/FormManagerLayout.test.tsx new file mode 100644 index 000000000..4c2928ad6 --- /dev/null +++ b/packages/design/src/FormManager/FormManagerLayout/FormManagerLayout.test.tsx @@ -0,0 +1,7 @@ +/** + * @vitest-environment jsdom + */ +import { describeStories } from '../../test-helper'; +import meta, * as stories from './FormManagerLayout.stories'; + +describeStories(meta, stories); diff --git a/packages/design/src/FormManager/FormManagerLayout/TopNavigation.tsx b/packages/design/src/FormManager/FormManagerLayout/TopNavigation.tsx index efdb59a56..88f0074ed 100644 --- a/packages/design/src/FormManager/FormManagerLayout/TopNavigation.tsx +++ b/packages/design/src/FormManager/FormManagerLayout/TopNavigation.tsx @@ -1,5 +1,8 @@ -import classNames from 'classnames'; import React from 'react'; +import classNames from 'classnames'; + +import { MyForms } from '../routes'; +import { useFormManagerStore } from '../store'; export enum NavPage { upload = 1, @@ -30,92 +33,102 @@ const srHint = (page: NavPage, curPage: NavPage) => { export const TopNavigation = ({ curPage, - uswdsRoot, + preview, }: { curPage: NavPage; - uswdsRoot: `${string}/`; + preview?: string; }) => { + const uswdsRoot = useFormManagerStore(state => state.context.uswdsRoot); + const lastSaved = useFormManagerStore(state => state.lastSaved); return (
    -
    - - - Saved - - +
    +
    +
    + +
    +
    + Saved + {preview && } +
    +
    -
    -
    -
      -
    1. - -
    2. -
    3. - - Upload {srHint(NavPage.upload, curPage)} - -
    4. -
    5. - - Create {srHint(NavPage.create, curPage)} - -
    6. -
    7. - - Configure {srHint(NavPage.configure, curPage)} - -
    8. -
    9. - - Publish {srHint(NavPage.publish, curPage)} - -
    10. -
    11. - - Saved at 11:00:03 am on Thur Mar 28 - - - Preview - -
    12. -
    - {/*
      - {(isPreviewPage || isEditPage || isImportDocuments) && ( -
    • - - {isImportDocuments ? 'Back to Edit Page' : 'Edit'} - -
    • - )} -
    • - View all Forms -
    • -
    */} +
    @@ -123,7 +136,7 @@ export const TopNavigation = ({ }; const MyFormsLink = ({ uswdsRoot }: { uswdsRoot: `${string}/` }) => ( - + ); -const PreviewIconLink = ({ uswdsRoot }: { uswdsRoot: `${string}/` }) => ( - - - -); +const PreviewIconLink = ({ + url, + uswdsRoot, +}: { + url: string; + uswdsRoot: `${string}/`; +}) => { + return ( + + + + ); +}; const MobileStepIndicator = () => (
    diff --git a/packages/design/src/FormManager/FormManagerLayout/index.tsx b/packages/design/src/FormManager/FormManagerLayout/index.tsx index b886c1acc..546953522 100644 --- a/packages/design/src/FormManager/FormManagerLayout/index.tsx +++ b/packages/design/src/FormManager/FormManagerLayout/index.tsx @@ -6,25 +6,25 @@ import { BottomNavigation } from './BottomNavigation'; export { NavPage } from './TopNavigation'; type FormManagerLayoutProps = { - uswdsRoot: `${string}/`; children: React.ReactNode; step?: NavPage; - next?: string; back?: string; close?: string; + next?: string; + preview?: string; }; export const FormManagerLayout = ({ - uswdsRoot, children, step, - next, back, close, + next, + preview, }: FormManagerLayoutProps) => { return ( <> - {step && } + {step && }
    diff --git a/packages/design/src/FormManager/FormPreview/FormPreview.stories.tsx b/packages/design/src/FormManager/FormPreview/FormPreview.stories.tsx index 8921ae789..cbd65e48e 100644 --- a/packages/design/src/FormManager/FormPreview/FormPreview.stories.tsx +++ b/packages/design/src/FormManager/FormPreview/FormPreview.stories.tsx @@ -2,8 +2,13 @@ import React from 'react'; import { MemoryRouter } from 'react-router-dom'; import type { Meta, StoryObj } from '@storybook/react'; -import FormPreview from '.'; -import { createTestForm, createTestFormContext } from '../../test-form'; +import { FormPreview } from '.'; +import { + createTestForm, + createTestFormContext, + createTestFormManagerContext, +} from '../../test-form'; +import { FormManagerProvider } from '../store'; export default { title: 'FormManager/FormPreview', @@ -11,7 +16,12 @@ export default { decorators: [ (Story, args) => ( - + + + ), ], diff --git a/packages/design/src/FormManager/FormPreview/FormPreview.test.ts b/packages/design/src/FormManager/FormPreview/FormPreview.test.ts index 182e9f2fe..8724ac545 100644 --- a/packages/design/src/FormManager/FormPreview/FormPreview.test.ts +++ b/packages/design/src/FormManager/FormPreview/FormPreview.test.ts @@ -4,4 +4,4 @@ import { describeStories } from '../../test-helper'; import meta, * as stories from './FormPreview.stories'; -describeStories(meta.title, stories); +describeStories(meta, stories); diff --git a/packages/design/src/FormManager/FormPreview/index.tsx b/packages/design/src/FormManager/FormPreview/index.tsx index cb429bad2..d6c47e229 100644 --- a/packages/design/src/FormManager/FormPreview/index.tsx +++ b/packages/design/src/FormManager/FormPreview/index.tsx @@ -1,33 +1,13 @@ import React from 'react'; -import { type Blueprint, createFormSession } from '@atj/forms'; -import { FormService } from '@atj/form-service'; +import { createFormSession } from '@atj/forms'; -import Form, { type FormUIContext } from '../../Form'; +import Form from '../../Form'; +import { useFormManagerStore } from '../store'; -export default function FormPreview({ - context, - form, -}: { - context: FormUIContext; - form: Blueprint; -}) { +export const FormPreview = () => { + const context = useFormManagerStore(state => state.context); + const form = useFormManagerStore(state => state.form); const session = createFormSession(form); return
    ; -} - -export const FormPreviewById = ({ - context, - formService, - formId, -}: { - context: FormUIContext; - formService: FormService; - formId: string; -}) => { - const form = formService.getForm(formId); - if (!form.success) { - return
    Error loading form preview
    ; - } - return ; }; diff --git a/packages/design/src/FormManager/index.tsx b/packages/design/src/FormManager/index.tsx index 8adbaf157..772e67953 100644 --- a/packages/design/src/FormManager/index.tsx +++ b/packages/design/src/FormManager/index.tsx @@ -1,118 +1,186 @@ import React from 'react'; import { useParams, HashRouter, Route, Routes } from 'react-router-dom'; -import { type FormService } from '@atj/form-service'; +import { type FormConfig, nullBlueprint } from '@atj/forms'; +import { FormService } from '@atj/form-service'; + +import { type ComponentForPattern } from '../Form'; import FormDelete from './FormDelete'; +import { FormDocumentImport } from './FormDocumentImport'; import FormEdit from './FormEdit'; +import { type EditComponentForPattern } from './FormEdit/types'; import FormList from './FormList'; -import { FormPreviewById } from './FormPreview'; -import { FormDocumentImport } from './import-document'; -import { type FormEditUIContext } from './FormEdit/types'; import { FormManagerLayout, NavPage } from './FormManagerLayout'; +import { FormPreview } from './FormPreview'; +import * as AppRoutes from './routes'; +import { FormManagerProvider } from './store'; -export default function FormManager({ - context, - baseUrl, - formService, -}: { - context: FormEditUIContext; - baseUrl: string; +export type FormManagerContext = { + baseUrl: `${string}/`; + components: ComponentForPattern; + config: FormConfig; + editComponents: EditComponentForPattern; formService: FormService; -}) { + uswdsRoot: `${string}/`; +}; + +type FormManagerProps = { + context: FormManagerContext; +}; + +export default function FormManager({ context }: FormManagerProps) { return ( ( - - - + + + + + )} /> { const { formId } = useParams(); if (formId === undefined) { return
    formId is undefined
    ; } + const form = context.formService.getForm(formId); + if (!form.success) { + return
    Error loading form preview
    ; + } return ( - + + + + + ); }} /> { const { formId } = useParams(); if (formId === undefined) { return
    formId is undefined
    ; } + const result = context.formService.getForm(formId); + if (!result.success) { + return
    Form not found
    ; + } + const form = result.data; return ( - - - + + + + + ); }} /> { const { formId } = useParams(); if (formId === undefined) { return
    formId is undefined
    ; } + const result = context.formService.getForm(formId); + if (!result.success) { + return
    Form not found
    ; + } + const form = result.data; return ( - - Publish - + + + + + ); }} /> { const { formId } = useParams(); if (formId === undefined) { return
    formId is undefined
    ; } - return ; + const result = context.formService.getForm(formId); + if (!result.success) { + return 'Form not found'; + } + const form = result.data; + return ( + + + Publish + + + ); + }} + /> + { + const { formId } = useParams(); + if (formId === undefined) { + return
    formId is undefined
    ; + } + const result = context.formService.getForm(formId); + if (!result.success) { + return 'Form not found'; + } + const form = result.data; + return ( + + + Publish + + + ); }} /> { const { formId } = useParams(); if (formId === undefined) { return
    formId is undefined
    ; } return ( - + ); }} /> diff --git a/packages/design/src/FormManager/routes.ts b/packages/design/src/FormManager/routes.ts new file mode 100644 index 000000000..df65d2aa2 --- /dev/null +++ b/packages/design/src/FormManager/routes.ts @@ -0,0 +1,39 @@ +type Route = { + path: string; + getUrl: (...args: UrlParams) => string; +}; + +export const MyForms: Route<[]> = { + path: '/', + getUrl: () => `#`, +}; + +export const Preview: Route = { + path: '/:formId/preview', + getUrl: (formId: string) => `#/${formId}/preview`, +}; + +export const Upload: Route = { + path: '/:formId/upload', + getUrl: (formId: string) => `#/${formId}/upload`, +}; + +export const Create: Route = { + path: '/:formId/create', + getUrl: (formId: string) => `#/${formId}/create`, +}; + +export const Configure: Route = { + path: '/:formId/configure', + getUrl: (formId: string) => `#/${formId}/configure`, +}; + +export const Publish: Route = { + path: '/:formId/publish', + getUrl: (formId: string) => `#/${formId}/publish`, +}; + +export const Delete: Route = { + path: '/:formId/delete', + getUrl: (formId: string) => `#/${formId}/delete`, +}; diff --git a/packages/design/src/FormManager/store.tsx b/packages/design/src/FormManager/store.tsx new file mode 100644 index 000000000..5aa8d4217 --- /dev/null +++ b/packages/design/src/FormManager/store.tsx @@ -0,0 +1,72 @@ +import React from 'react'; +import { type StoreApi, type StateCreator, create } from 'zustand'; +import { createContext } from 'zustand-utils'; + +import { BlueprintBuilder, type Blueprint } from '@atj/forms'; + +import { type FormEditSlice, createFormEditSlice } from './FormEdit/store'; +import { type FormListSlice, createFormListSlice } from './FormList/store'; +import { type FormManagerContext } from '.'; +import { Result } from '@atj/common'; + +type StoreContext = { + context: FormManagerContext; + form: Blueprint; +}; + +type FormManagerStore = FormEditSlice & FormListSlice & FormManagerSlice; +const { Provider, useStore } = createContext>(); +export const useFormManagerStore = useStore; + +const createStore = ({ context, form }: StoreContext) => + create((...args) => ({ + ...createFormEditSlice({ context, form })(...args), + ...createFormListSlice({ context })(...args), + ...createFormManagerSlice({ context, form })(...args), + })); + +export const FormManagerProvider = (props: { + context: FormManagerContext; + form: Blueprint; + children: React.ReactNode; +}) => { + return ( + createStore(props)}>{props.children} + ); +}; + +type FormManagerSlice = { + context: FormManagerContext; + form: Blueprint; + lastSaved?: Date; + createNewForm: () => Promise>; + saveForm: (formId: string, blueprint: Blueprint) => void; +}; + +type FormManagerSliceCreator = StateCreator< + FormManagerSlice, + [], + [], + FormManagerSlice +>; +const createFormManagerSlice = + ({ context, form }: StoreContext): FormManagerSliceCreator => + (set, get) => ({ + context, + form, + createNewForm: async () => { + const builder = new BlueprintBuilder(); + builder.setFormSummary({ + title: `My form - ${new Date().toISOString()}`, + description: '', + }); + return await context.formService.addForm(builder.form); + }, + saveForm: async (formId, blueprint) => { + const { context } = get(); + const result = await context.formService.saveForm(formId, blueprint); + if (result.success) { + set({ lastSaved: result.data.timestamp }); + } + }, + }); diff --git a/packages/design/src/FormRouter/FormRouter.test.ts b/packages/design/src/FormRouter/FormRouter.test.ts index 6adc7446a..fec5f0fc7 100644 --- a/packages/design/src/FormRouter/FormRouter.test.ts +++ b/packages/design/src/FormRouter/FormRouter.test.ts @@ -4,4 +4,4 @@ import { describeStories } from '../test-helper'; import meta, * as stories from './FormRouter.stories'; -describeStories(meta.title, stories); +describeStories(meta, stories); diff --git a/packages/design/src/test-form.ts b/packages/design/src/test-form.ts index c9cad413d..139a606ba 100644 --- a/packages/design/src/test-form.ts +++ b/packages/design/src/test-form.ts @@ -1,11 +1,12 @@ import { createForm, createFormSession, defaultFormConfig } from '@atj/forms'; -import { SequencePattern } from '@atj/forms/src/patterns/sequence'; -import { InputPattern } from '@atj/forms/src/patterns/input'; +import { type SequencePattern } from '@atj/forms/src/patterns/sequence'; +import { type InputPattern } from '@atj/forms/src/patterns/input'; +import { createTestFormService } from '@atj/form-service'; import { type FormUIContext } from './Form'; import { defaultPatternComponents } from './Form/components'; -import { type FormEditUIContext } from './FormManager/FormEdit/types'; import { defaultPatternEditComponents } from './FormManager/FormEdit/components'; +import { type FormManagerContext } from './FormManager'; export const createTestForm = () => { return createForm( @@ -64,11 +65,13 @@ export const createTestFormContext = (): FormUIContext => { }; }; -export const createTestFormEditContext = (): FormEditUIContext => { +export const createTestFormManagerContext = (): FormManagerContext => { return { - config: defaultFormConfig, + baseUrl: '/', components: defaultPatternComponents, + config: defaultFormConfig, editComponents: defaultPatternEditComponents, + formService: createTestFormService(), uswdsRoot: `/static/uswds/`, }; }; diff --git a/packages/design/src/test-helper.ts b/packages/design/src/test-helper.ts index b4b0d3397..77ebf4c13 100644 --- a/packages/design/src/test-helper.ts +++ b/packages/design/src/test-helper.ts @@ -1,8 +1,13 @@ +import { ReactElement } from 'react'; +import { describe, test } from 'vitest'; +import { type ReactRenderer, composeStories, Meta } from '@storybook/react'; import { type Store_CSFExports } from '@storybook/types'; -import { type ReactRenderer, composeStories } from '@storybook/react'; import { render } from '@testing-library/react'; -import { type Entries } from 'type-fest'; -import { describe, test } from 'vitest'; + +type Story = { + (): ReactElement; + play?: (args: { canvasElement: HTMLElement }) => Promise; +}; /** * Wrap the Component Story Format (CSF) exports for a component with Vitest @@ -11,19 +16,18 @@ import { describe, test } from 'vitest'; * @param csfExports */ export const describeStories = < - // eslint-disable-next-line + // eslint-disable-next-line @typescript-eslint/no-explicit-any TModule extends Store_CSFExports, >( - componentName: string, + meta: Meta, csfExports: TModule ) => { const composedStories = composeStories(csfExports); - describe(componentName, () => { - const entries = Object.entries(composedStories) as Entries< - typeof composedStories - >; + describe(`Storybook stories: ${meta.title || meta.id}`, () => { + type Entry = [string, Story]; + const entries = Object.entries(composedStories) as Entry[]; entries.forEach(([name, Story]) => { - test(name, async () => { + test(name as string, async () => { const { container } = render(Story()); if (Story.play) { await Story.play({ canvasElement: container }); diff --git a/packages/form-service/src/operations/save-form.ts b/packages/form-service/src/operations/save-form.ts index d689b1dfb..2b17cdd8a 100644 --- a/packages/form-service/src/operations/save-form.ts +++ b/packages/form-service/src/operations/save-form.ts @@ -1,4 +1,4 @@ -import { VoidResult } from '@atj/common'; +import { Result } from '@atj/common'; import { Blueprint } from '@atj/forms'; import { saveFormToStorage } from '../context/browser/form-repo'; @@ -7,7 +7,7 @@ export const saveForm = ( ctx: { storage: Storage }, formId: string, form: Blueprint -): VoidResult => { +): Result<{ timestamp: Date }> => { const result = saveFormToStorage(ctx.storage, formId, form); if (result.success === false) { return { @@ -17,5 +17,8 @@ export const saveForm = ( } return { success: true, + data: { + timestamp: new Date(), + }, }; }; diff --git a/packages/form-service/src/types.ts b/packages/form-service/src/types.ts index 8cc51342b..380a815a3 100644 --- a/packages/form-service/src/types.ts +++ b/packages/form-service/src/types.ts @@ -8,7 +8,7 @@ export type FormService = { deleteForm: (formId: string) => VoidResult; getForm: (formId: string) => Result; getFormList: () => Result; - saveForm: (formId: string, form: Blueprint) => VoidResult; + saveForm: (formId: string, form: Blueprint) => Result<{ timestamp: Date }>; 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 153e93166..2a7836159 100644 --- a/packages/forms/src/builder/index.ts +++ b/packages/forms/src/builder/index.ts @@ -8,11 +8,11 @@ import { addDocument, addPatternToRoot, createPattern, + getPattern, nullBlueprint, removePatternFromBlueprint, updateFormSummary, updatePatternFromFormData, - getPattern, } from '..'; export class BlueprintBuilder { diff --git a/packages/forms/src/components.ts b/packages/forms/src/components.ts index 49ba74f66..663f5e24a 100644 --- a/packages/forms/src/components.ts +++ b/packages/forms/src/components.ts @@ -36,6 +36,7 @@ export type ParagraphProps = PatternProps<{ export type FieldsetProps = PatternProps<{ type: 'fieldset'; legend?: string; + subHeading?: string; }>; export type ZipcodeProps = PatternProps<{