Skip to content

Commit

Permalink
Wire drag and drop back up (#107)
Browse files Browse the repository at this point in the history
* Cleanup

* In-progress

* UI rendering with dnd controls, test passing; handles inactive.

* dnd wired up to blueprint state. Added a test, which unfortunately does not work with JSDOM, so it is commented out

* Remove unused import

* Removed dist folder, which is in .gitignore
  • Loading branch information
danielnaab authored Apr 15, 2024
1 parent f9b8afa commit 6fc1d89
Show file tree
Hide file tree
Showing 26 changed files with 277 additions and 400 deletions.
6 changes: 3 additions & 3 deletions packages/design/sass/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -464,19 +464,19 @@ iframe:focus {
.grid-col-8 {
width: 100%;
}

.usa-button{
margin-left: 0;
}

.grid-col-4 {
position: static;
}

.form-group-row {
padding: 0 0 1rem;
}

.settingsContainer {
position: fixed;
top: 10%;
Expand Down
18 changes: 12 additions & 6 deletions packages/design/src/AvailableFormList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,27 @@ const FormList = ({
urlForForm: UrlForForm;
}) => {
return (

<table className="usa-table usa-table--stacked">
<caption>Available forms</caption>
<thead>
<tr>
<th className="column1" scope="col">Form title</th>
<th className="column2" scope="col">Description</th>
<th className="column3" scope="col">Actions</th>
<th className="column1" scope="col">
Form title
</th>
<th className="column2" scope="col">
Description
</th>
<th className="column3" scope="col">
Actions
</th>
</tr>
</thead>
<tbody>
{forms.map((form, index) => (
<tr key={index}>
<th data-label="Form title" scope="row">{form.title}</th>
<th data-label="Form title" scope="row">
{form.title}
</th>
<td data-label="Description">{form.description}</td>
<td data-label="Actions">
<a href={urlForForm(form.id)} title={form.title}>
Expand All @@ -58,6 +65,5 @@ const FormList = ({
))}
</tbody>
</table>

);
};
22 changes: 13 additions & 9 deletions packages/design/src/Form/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
type FormSession,
type PatternProps,
type Prompt,
type PromptPart,
type PromptComponent,
} from '@atj/forms';

import ActionBar from './ActionBar';
Expand Down Expand Up @@ -232,12 +232,12 @@ export default function Form({
)}

<fieldset className="usa-fieldset">
{prompt.parts.map((part, index) => {
{prompt.components.map((component, index) => {
return (
<PromptComponent
key={index}
context={context}
promptPart={part}
component={component}
/>
);
})}
Expand All @@ -253,17 +253,21 @@ export default function Form({

const PromptComponent = ({
context,
promptPart,
component,
}: {
context: FormUIContext;
promptPart: PromptPart;
component: PromptComponent;
}) => {
const Component = context.components[promptPart.pattern.type];
const Component = context.components[component.props.type];
return (
<Component {...promptPart.pattern}>
{promptPart.children?.map((child, index) => {
<Component {...component.props}>
{component.children?.map((childPromptComponent, index) => {
return (
<PromptComponent key={index} context={context} promptPart={child} />
<PromptComponent
key={index}
context={context}
component={childPromptComponent}
/>
);
})}
</Component>
Expand Down
4 changes: 3 additions & 1 deletion packages/design/src/FormManager/FormDelete/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ export default function FormDelete({
<h1>Delete form</h1>
<h2>Are you sure you want to delete the form with id: `{formId}`?</h2>
<p className="padding-bottom-3">
<button className="usa-button" onClick={deleteForm}>Delete form</button>
<button className="usa-button" onClick={deleteForm}>
Delete form
</button>
</p>
<code>{JSON.stringify(form, null, 4)}</code>
</div>
Expand Down
77 changes: 27 additions & 50 deletions packages/design/src/FormManager/FormEdit/DraggableList.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React, { Children, useState } from 'react';
import React, { Children } from 'react';
import {
DndContext,
closestCenter,
KeyboardSensor,
PointerSensor,
useSensor,
useSensors,
UniqueIdentifier,
} from '@dnd-kit/core';
import {
arrayMove,
Expand All @@ -16,32 +17,24 @@ import {
} from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';

import {
getPattern,
type Blueprint,
type Pattern,
PatternId,
} from '@atj/forms';

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

const SortableItem = ({
id,
children,
}: {
id: string;
id: UniqueIdentifier;
children: React.ReactNode;
}) => {
const { attributes, listeners, setNodeRef, transform, transition } =
useSortable({ id });

const style = {
transform: CSS.Transform.toString(transform),
transition,
};

return (
<li ref={setNodeRef} style={style}>
<div
ref={setNodeRef}
style={{
transform: CSS.Transform.toString(transform),
transition,
}}
>
<div className="editFieldsRowWrapper grid-row grid-gap">
<div
className="editPageGrabButtonWrapper grid-col-1 grid-col"
Expand All @@ -54,32 +47,26 @@ const SortableItem = ({
</div>
<div className="editFieldsWrapper grid-col-11 grid-col">{children}</div>
</div>
</li>
</div>
);
};

type DraggableListProps = React.PropsWithChildren<{
pattern: Pattern<SequencePattern>;
form: Blueprint;
setSelectedPattern: (pattern: Pattern) => void;
order: UniqueIdentifier[];
updateOrder: (order: UniqueIdentifier[]) => void;
}>;
export const DraggableList: React.FC<DraggableListProps> = ({
pattern,
form,
setSelectedPattern,
children,
order,
updateOrder,
}) => {
const [patterns, setPatterns] = useState<Pattern[]>(
pattern.data.patterns.map((patternId: PatternId) => {
return getPattern(form, patternId);
})
);
const sensors = useSensors(
useSensor(PointerSensor),
useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })
);

const arrayChildren = Children.toArray(children);

return (
<DndContext
sensors={sensors}
Expand All @@ -90,32 +77,22 @@ export const DraggableList: React.FC<DraggableListProps> = ({
return;
}
if (active.id !== over.id) {
const oldIndex = patterns.findIndex(pattern => {
return pattern.id === active.id;
});
const newIndex = patterns.findIndex(pattern => {
return pattern.id === over.id;
});
const newOrder = arrayMove(patterns, oldIndex, newIndex);
setPatterns(newOrder);
setSelectedPattern({
id: pattern.id,
type: pattern.type,
data: {
patterns: newOrder.map(pattern => pattern.id),
},
} satisfies SequencePattern);
const oldIndex = order.indexOf(active.id);
const newIndex = order.indexOf(over.id);
const newOrder = arrayMove(order, oldIndex, newIndex);
updateOrder(newOrder);
}
}}
>
<SortableContext items={patterns} strategy={verticalListSortingStrategy}>
<ul className="editFormWrapper">
{arrayChildren.map((child, index) => (
<SortableItem key={index} id={patterns[index].id}>
<SortableContext items={order} strategy={verticalListSortingStrategy}>
{arrayChildren.map((child, index) => {
const patternId = order[index];
return (
<SortableItem key={patternId} id={patternId}>
{child}
</SortableItem>
))}
</ul>
);
})}
</SortableContext>
</DndContext>
);
Expand Down
47 changes: 41 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,20 +40,53 @@ 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);
},
};

// This test only works in a real browser, not via JSDOM as we use it.
/*
export const FormEditReorderPattern: StoryObj<typeof FormEdit> = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const grabber = await canvas.getAllByText(':::')[0];
await grabber.focus();
// Enter reordering mode with the spacebar
await userEvent.type(grabber, ' ');
await new Promise(r => setTimeout(r, 100));
// Press the arrow down, to move the first pattern to the second position
await userEvent.type(grabber, '[ArrowDown]');
await new Promise(r => setTimeout(r, 100));
// Press the spacebar to exit reordering mode
await userEvent.type(grabber, ' ');
await new Promise(r => setTimeout(r, 100));
// Pattern 1 should be after pattern 2 in the document
const pattern1 = canvas.getByText('Pattern 1');
const pattern2 = canvas.getByText('Pattern 2');
expect(pattern2.compareDocumentPosition(pattern1)).toBe(
Node.DOCUMENT_POSITION_FOLLOWING
);
},
};
*/

const editFieldLabel = async (
element: HTMLElement,
buttonIndex: number,
Expand All @@ -62,7 +95,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
Loading

0 comments on commit 6fc1d89

Please sign in to comment.