Skip to content

Commit

Permalink
feat: introduce Multi-step Forms (#3436)
Browse files Browse the repository at this point in the history
Co-authored-by: adrians5j <adrian@webiny.com>
Co-authored-by: Adrian Smijulj <adrian1358@gmail.com>
  • Loading branch information
3 people committed Sep 29, 2023
1 parent 30e7f85 commit d512ba4
Show file tree
Hide file tree
Showing 47 changed files with 1,547 additions and 494 deletions.
101 changes: 83 additions & 18 deletions apps/theme/layouts/forms/DefaultFormLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,35 @@ import { Row } from "./DefaultFormLayout/Row";
import { Cell } from "./DefaultFormLayout/Cell";
import { Field } from "./DefaultFormLayout/Field";
import { SuccessMessage } from "./DefaultFormLayout/SuccessMessage";
import { SubmitButton } from "./DefaultFormLayout/SubmitButton";
import { TermsOfServiceSection } from "./DefaultFormLayout/TermsOfServiceSection";
import { ReCaptchaSection } from "./DefaultFormLayout/ReCaptchaSection";

import { Button } from "./DefaultFormLayout/buttons/Button";
const Wrapper = styled.div`
width: 100%;
padding: 0 5px 5px 5px;
box-sizing: border-box;
background-color: ${props => props.theme.styles.colors["color6"]};
`;

const ButtonsWrapper = styled.div`
display: flex;
justify-content: center;
align-items: center;
& button {
height: 45px;
}
& button:first-of-type {
margin-right: 15px;
}
`;

const StepTitle = styled.div`
font-size: 1.2em;
height: 1.2em;
`;

/**
* This is the default form layout component, in which we render all the form fields. We also render terms of service
* and reCAPTCHA (if enabled in form settings), and, at the very bottom, the submit button.
Expand All @@ -27,6 +45,13 @@ const DefaultFormLayout: FormLayoutComponent = ({
getFields,
getDefaultValues,
submit,
goToNextStep,
goToPreviousStep,
isLastStep,
isFirstStep,
isMultiStepForm,
currentStepIndex,
currentStep,
formData,
ReCaptcha,
reCaptchaEnabled,
Expand All @@ -40,17 +65,20 @@ const DefaultFormLayout: FormLayoutComponent = ({
const [formSuccess, setFormSuccess] = useState(false);

// All form fields - an array of rows where each row is an array that contain fields.
const fields = getFields();

const fields = getFields(currentStepIndex);
/**
* Once the data is successfully submitted, we show a success message.
*/
const submitForm = async (data: Record<string, any>): Promise<void> => {
setLoading(true);
const result = await submit(data);
setLoading(false);
if (result.error === null) {
setFormSuccess(true);
if (isLastStep) {
setLoading(true);
const result = await submit(data);
setLoading(false);
if (result.error === null) {
setFormSuccess(true);
}
} else {
goToNextStep();
}
};

Expand All @@ -64,6 +92,7 @@ const DefaultFormLayout: FormLayoutComponent = ({
<Form onSubmit={submitForm} data={getDefaultValues()}>
{({ submit }) => (
<Wrapper>
<StepTitle>{currentStep?.title}</StepTitle>
{fields.map((row, rowIndex) => (
<Row key={rowIndex}>
{row.map(field => (
Expand All @@ -73,17 +102,53 @@ const DefaultFormLayout: FormLayoutComponent = ({
))}
</Row>
))}

{termsOfServiceEnabled && <TermsOfServiceSection component={TermsOfService} />}
{reCaptchaEnabled && <ReCaptchaSection component={ReCaptcha} />}

<SubmitButton
onClick={submit}
loading={loading}
fullWidth={formData.settings.fullWidthSubmitButton}
>
{formData.settings.submitButtonLabel || "Submit"}
</SubmitButton>
{/*
If the form has more than one step then the form will be recognized as a Multi Step Form,
so it means that we need to render form step handlers to switch between steps.
*/}
{isMultiStepForm && (
<ButtonsWrapper>
<Button
fullWidth={false}
onClick={goToPreviousStep}
disabled={isFirstStep}
>
Previous Step
</Button>
{currentStepIndex === formData.steps.length - 1 ? (
<Button
type="primary"
onClick={submit}
disabled={loading}
fullWidth={false}
>
{formData.settings.submitButtonLabel || "Submit"}
</Button>
) : (
<Button
type="primary"
onClick={submit}
disabled={loading}
fullWidth={false}
>
Next Step
</Button>
)}
</ButtonsWrapper>
)}
{/* If form is single step then we just render submit button */}
{!isMultiStepForm && (
<Button
type="primary"
onClick={submit}
disabled={loading}
fullWidth={formData.settings.fullWidthSubmitButton}
>
{formData.settings.submitButtonLabel || "Submit"}
</Button>
)}
</Wrapper>
)}
</Form>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React from "react";
import { FormRenderPropParamsSubmit } from "@webiny/form";
import styled from "@emotion/styled";
import { FormRenderPropParamsSubmit } from "@webiny/form";

export const Wrapper = styled.div<{ fullWidth: boolean }>`
${props => props.theme.styles.elements["button"]["primary"]}
export const Wrapper = styled.div<{ fullWidth: boolean; type: "primary" | "default" }>`
${({ theme, type }) => theme.styles.elements["button"][`${type}`]}
.button-body {
width: ${props => (props.fullWidth ? "100%" : "auto")};
margin-left: auto;
Expand All @@ -16,15 +16,22 @@ export const Wrapper = styled.div<{ fullWidth: boolean }>`

interface Props {
fullWidth: boolean;
onClick: FormRenderPropParamsSubmit;
loading: boolean;
disabled: boolean;
children: React.ReactNode;
type?: "primary" | "default";
onClick: FormRenderPropParamsSubmit | (() => void);
}

export const SubmitButton: React.FC<Props> = ({ fullWidth, onClick, loading, children }) => {
export const Button: React.FC<Props> = ({
fullWidth,
disabled,
children,
type = "default",
onClick
}) => {
return (
<Wrapper fullWidth={fullWidth}>
<button className={"button-body"} onClick={onClick} disabled={loading}>
<Wrapper fullWidth={fullWidth} type={type}>
<button className={"button-body"} onClick={onClick} disabled={disabled}>
{children}
</button>
</Wrapper>
Expand Down

0 comments on commit d512ba4

Please sign in to comment.