Skip to content

Commit

Permalink
UI rendering with dnd controls, test passing; handles inactive.
Browse files Browse the repository at this point in the history
  • Loading branch information
danielnaab committed Apr 15, 2024
1 parent 51eb8ab commit 91ed5df
Show file tree
Hide file tree
Showing 7 changed files with 131 additions and 125 deletions.
6 changes: 2 additions & 4 deletions packages/design/src/Form/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -259,16 +259,14 @@ const PromptComponent = ({
component: PromptComponent;
}) => {
const Component = context.components[component.props.type];
console.log('rendering', component);
return (
<Component {...component.props}>
{component.children?.map((child, index) => {
console.log('rendering', index, child.props.type);
{component.children?.map((childPromptComponent, index) => {
return (
<PromptComponent
key={index}
context={context}
component={component}
component={childPromptComponent}
/>
);
})}
Expand Down
7 changes: 4 additions & 3 deletions packages/design/src/FormManager/FormEdit/DraggableList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ import {
getPattern,
type Blueprint,
type Pattern,
PatternId,
type PatternId,
} from '@atj/forms';

import { SequencePattern } from '@atj/forms/src/patterns/sequence';
import { type SequencePattern } from '@atj/forms/src/patterns/sequence';

const SortableItem = ({
id,
Expand Down Expand Up @@ -111,8 +111,9 @@ export const DraggableList: React.FC<DraggableListProps> = ({
>
<SortableContext items={patterns} strategy={verticalListSortingStrategy}>
{arrayChildren.map((child, index) => {
const patternId = child.props._patternId;
return (
<SortableItem key={index} id={patterns[index].id}>
<SortableItem key={index} id={patternId}>
{child}
</SortableItem>
);
Expand Down
16 changes: 10 additions & 6 deletions packages/design/src/FormManager/FormEdit/FormEdit.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ export default {

export const FormEditTest: StoryObj<typeof FormEdit> = {
play: async ({ canvasElement }) => {
await editFieldLabel(canvasElement, 0, 'First field label');
await editFieldLabel(canvasElement, 1, 'Second field label');
await editFieldLabel(canvasElement, 1, 'First field label');
await editFieldLabel(canvasElement, 2, 'Second field label');
},
};

Expand All @@ -40,16 +40,18 @@ export const FormEditAddPattern: StoryObj<typeof FormEdit> = {
const canvas = within(canvasElement);

// Select the first pattern for editing
const button = (await canvas.findAllByRole('button'))[0];
const button = (await canvas.findAllByLabelText('Edit form group'))[0];
await userEvent.click(button);

// Get the initial count of inputs
const initialCount = (await canvas.getAllByRole('textbox')).length;
const initialCount = (await canvas.findAllByLabelText('Edit form group'))
.length;

const select = canvas.getByLabelText('Add a pattern');
await userEvent.selectOptions(select, 'Text input');

const finalCount = (await canvas.getAllByRole('textbox')).length;
const finalCount = (await canvas.findAllByLabelText('Edit form group'))
.length;
expect(finalCount).toEqual(initialCount + 1);
},
};
Expand All @@ -62,7 +64,9 @@ const editFieldLabel = async (
const canvas = within(element);

// Click "edit form" button for first field
await userEvent.click(canvas.getAllByRole('button')[buttonIndex]);
await userEvent.click(
(await canvas.findAllByLabelText('Edit form group'))[buttonIndex]
);

// Enter new text for first field
const input = canvas.getByLabelText('Field label');
Expand Down
121 changes: 16 additions & 105 deletions packages/design/src/FormManager/FormEdit/Preview.tsx
Original file line number Diff line number Diff line change
@@ -1,136 +1,47 @@
import React from 'react';

import { createFormSession, getPattern } from '@atj/forms';
import { createFormSession } from '@atj/forms';

import Form, {
type ComponentForPattern,
type PatternComponent,
type FormUIContext,
} from '../../Form';
import Form, { PatternComponent, type ComponentForPattern } from '../../Form';

import { PreviewPattern } from './PreviewPattern';
import { PatternPreviewSequence } from './PreviewSequencePattern';
import { useFormEditStore } from './store';
import DraggableList from './DraggableList';

export const PreviewForm = () => {
const uiContext = useFormEditStore(state => state.context);
const form = useFormEditStore(state => state.form);

const previewUiContext: FormUIContext = {
config: uiContext.config,
// TODO: We'll want to hoist this definition up to a higher level, so we
// don't have to regenerate it every time we render the form.
components: createPreviewComponents(
uiContext.components,
uiContext.uswdsRoot
),
uswdsRoot: uiContext.uswdsRoot,
};
const disposable = createFormSession(form); // nullSession instead?

return (
<Form
isPreview={true}
context={previewUiContext}
context={{
config: uiContext.config,
// TODO: We might want to hoist this definition up to a higher level,
// so we don't have to regenerate it every time we render the form.
components: createPreviewComponents(uiContext.components),
uswdsRoot: uiContext.uswdsRoot,
}}
session={disposable}
></Form>
);
};

const createSequencePatternPreviewComponent = (
Component: PatternComponent,
previewComponents: ComponentForPattern
) => {
const PatternPreviewSequenceComponent: PatternComponent = props => {
const form = useFormEditStore(state => state.form);
const setSelectedPattern = useFormEditStore(
state => state.setSelectedPattern
);
const pattern = getPattern(form, props._patternId);
return (
<DraggableList
form={form}
pattern={pattern}
setSelectedPattern={setSelectedPattern}
>
<Component _patternId={pattern.id} {...pattern}></Component>
</DraggableList>
);
};
return PatternPreviewSequenceComponent;
};

const createPatternPreviewComponent = (
Component: PatternComponent,
uswdsRoot: string
) => {
const PatternPreviewComponent: PatternComponent = props => {
const context = useFormEditStore(state => state.context);
const selectedPattern = useFormEditStore(state => state.selectedPattern);
const handleEditClick = useFormEditStore(state => state.handleEditClick);

const isSelected = selectedPattern?.id === props._patternId;
const divClassNames = isSelected
? 'form-group-row field-selected'
: 'form-group-row';
const EditComponent = context.editComponents[props.type];

return (
<div className={divClassNames} data-id={props._patternId}>
<Component {...props} />
<span className="edit-button-icon">
{EditComponent ? (
<button
className="usa-button usa-button--secondary usa-button--unstyled"
onClick={() => handleEditClick(props._patternId)}
onKeyDown={e => {
if (e.key === 'Enter') {
handleEditClick(props._patternId);
}
}}
tabIndex={0}
aria-label="Edit form group"
>
<svg
className="usa-icon"
aria-hidden="true"
focusable="false"
role="img"
>
<use xlinkHref={`${uswdsRoot}img/sprite.svg#settings`}></use>
</svg>
</button>
) : null}
</span>
</div>
);
};
return PatternPreviewComponent;
};

const createPreviewComponents = (
components: ComponentForPattern,
uswdsRoot: string
components: ComponentForPattern
): ComponentForPattern => {
const previewComponents: ComponentForPattern = {};
// TODO: Create a configurable way to to define preview components.
for (const [patternType, Component] of Object.entries(components)) {
previewComponents[patternType] = Component;
if (patternType === 'sequence') {
previewComponents[patternType] = createSequencePatternPreviewComponent(
Component,
previewComponents
);
}
/*
} else if (patternType === 'form-summary') {
previewComponents[patternType] = Component;
previewComponents[patternType] =
PatternPreviewSequence as PatternComponent;
} else {
//previewComponents[patternType] = Component;
previewComponents[patternType] = createPatternPreviewComponent(
Component,
uswdsRoot
);
previewComponents[patternType] = PreviewPattern;
}
*/
}
return previewComponents;
};
49 changes: 49 additions & 0 deletions packages/design/src/FormManager/FormEdit/PreviewPattern.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React from 'react';

import { PatternComponent } from '../../Form';
import { useFormEditStore } from './store';

export const PreviewPattern: PatternComponent = function PreviewPattern(props) {
const context = useFormEditStore(state => state.context);
const selectedPattern = useFormEditStore(state => state.selectedPattern);
const handleEditClick = useFormEditStore(state => state.handleEditClick);

const isSelected = selectedPattern?.id === props._patternId;
const divClassNames = isSelected
? 'form-group-row field-selected'
: 'form-group-row';
const Component = context.components[props.type];
const EditComponent = context.editComponents[props.type];

return (
<div className={divClassNames} data-id={props._patternId}>
<Component {...props} />
<span className="edit-button-icon">
{EditComponent ? (
<button
className="usa-button usa-button--secondary usa-button--unstyled"
onClick={() => handleEditClick(props._patternId)}
onKeyDown={e => {
if (e.key === 'Enter') {
handleEditClick(props._patternId);
}
}}
tabIndex={0}
aria-label="Edit form group"
>
<svg
className="usa-icon"
aria-hidden="true"
focusable="false"
role="img"
>
<use
xlinkHref={`${context.uswdsRoot}img/sprite.svg#settings`}
></use>
</svg>
</button>
) : null}
</span>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from 'react';

import { PatternProps, getPattern } from '@atj/forms';

import { PatternComponent } from '../../Form';
import DraggableList from './DraggableList';
import { useFormEditStore } from './store';
import { SequencePattern } from '@atj/forms/src/patterns/sequence';

// TODO: consider merging this component with DraggableList, to clean up
// sematics around how its children are handled.
export const PatternPreviewSequence: PatternComponent<
PatternProps<SequencePattern>
> = function PatternPreviewSequence(props) {
const form = useFormEditStore(state => state.form);
const setSelectedPattern = useFormEditStore(
state => state.setSelectedPattern
);

const pattern = getPattern(form, props._patternId);

/**
* Here, we assume that we are rendering a "sequence" pattern, and that
* sequences have no styling of their own. If sequences were to get their
* own styling (like other components), this component would need to be
* updated to replicate the styles, or the wrapping structure would need to
* be updated to ensure that we pass the correct children to DraggableList.
*
* In other words, we'd want to render:
* const Component = context.components[props.type];
* ... and then something like:
* <Component _patternId={pattern.id} {...pattern}>{props.children}</Component>
*/
return (
<DraggableList
form={form}
pattern={pattern}
setSelectedPattern={setSelectedPattern}
>
{props.children}
</DraggableList>
);
};
14 changes: 7 additions & 7 deletions packages/forms/src/documents/document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,21 @@ export const addDocument = async (
}
) => {
const fields = await getDocumentFieldData(fileDetails.data);
const cachedPdf = await getSuggestedPatterns(fileDetails.data);
const parsedPdf = await getSuggestedPatterns(fileDetails.data);

if (cachedPdf) {
if (parsedPdf) {
form = updateFormSummary(form, {
title: cachedPdf.title,
title: parsedPdf.title,
description: '',
});
form = addPatternMap(form, cachedPdf.patterns, cachedPdf.root);
form = addPatternMap(form, parsedPdf.patterns, parsedPdf.root);
const updatedForm = addFormOutput(form, {
data: fileDetails.data,
path: fileDetails.name,
fields: cachedPdf.outputs,
fields: parsedPdf.outputs,
formFields: Object.fromEntries(
Object.keys(cachedPdf.outputs).map(output => {
return [output, cachedPdf.outputs[output].name];
Object.keys(parsedPdf.outputs).map(output => {
return [output, parsedPdf.outputs[output].name];
})
),
});
Expand Down

0 comments on commit 91ed5df

Please sign in to comment.