Skip to content

Commit

Permalink
docs: Add docs for <Form> (#3674)
Browse files Browse the repository at this point in the history
* start form docs

* submit example

* add server errror section

* add focus state management example

* change message to an error

* full server example

* demo

* done

* udpate

* update pnpm

* update form recipe

* add comment about react query

* Create moody-humans-yawn.md

---------

Co-authored-by: Marcel Köhler <[email protected]>
  • Loading branch information
sebald and aromko authored Feb 9, 2024
1 parent ecd881c commit 3d66a58
Show file tree
Hide file tree
Showing 16 changed files with 623 additions and 125 deletions.
6 changes: 6 additions & 0 deletions .changeset/moody-humans-yawn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@marigold/docs": minor
"@marigold/components": patch
---

docs: Add docs for `<Form>`
39 changes: 39 additions & 0 deletions docs/app/api/demo/subscribe/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { z } from 'zod';

import { NextResponse } from 'next/server';

// Helpers
// ---------------
const ALREADY_REGISTERED_EMAILS = [
'[email protected]',
'[email protected]',
];

const schema = z.object({
email: z
.string()
.email()
.refine(
val => {
return !ALREADY_REGISTERED_EMAILS.includes(val);
},
{
message: 'This email is already subscribed.',
}
),
});

// POST
// ---------------
export const POST = async (req: Request) => {
const body = await req.json();
const result = schema.safeParse(body);

if (!result.success) {
return NextResponse.json(result.error.flatten().fieldErrors, {
status: 400,
});
}

return NextResponse.json(result.data);
};
22 changes: 22 additions & 0 deletions docs/content/components/form/form/form-base.demo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Button, Form, Inset, Stack, TextField } from '@marigold/components';

export default () => {
return (
<Form>
<Inset space={8}>
<Stack space={2} alignX="left">
<TextField label="User Name" name="user" type="name" width="1/2" />
<TextField
label="Password"
name="password"
type="password"
width="1/2"
/>
<Button variant="primary" type="submit">
Login
</Button>
</Stack>
</Inset>
</Form>
);
};
68 changes: 68 additions & 0 deletions docs/content/components/form/form/form-focus-management.demo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { useState } from 'react';

import {
Button,
Form,
Inline,
Inset,
Message,
Stack,
TextField,
} from '@marigold/components';

export default () => {
let [invalid, setInvalid] = useState(false);
return (
<Form
onInvalid={e => {
e.preventDefault();
setInvalid(true);
}}
onSubmit={e => {
e.preventDefault();
setInvalid(false);
}}
onReset={() => setInvalid(false)}
>
<Inset space={8}>
<Stack space={4}>
{invalid ? (
<Message
variant="error"
messageTitle="Whoopsies!"
role="alert"
tabIndex={-1}
ref={e => e?.focus()}
>
Please enter both your email address and password to proceed.
Ensure that all required fields are filled correctly before
attempting to log in.
</Message>
) : null}
<Stack space={2} alignX="left">
<TextField
label="User Name"
name="user"
type="name"
width="1/2"
required
/>
<TextField
label="Password"
name="password"
type="password"
width="1/2"
required
/>
<Inline space={2}>
<Button variant="primary" type="submit">
Login
</Button>
<Button type="reset">Reset</Button>
</Inline>
</Stack>
</Stack>
</Inset>
</Form>
);
};
63 changes: 63 additions & 0 deletions docs/content/components/form/form/form-submission.demo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { useState } from 'react';

import {
Button,
Form,
Inline,
Inset,
Stack,
Text,
TextField,
} from '@marigold/components';

export default () => {
let [action, setAction] = useState<string | null>(null);
return (
<Form
onSubmit={e => {
// This will prevent the native form submission
e.preventDefault();

// Read the form values and convert it to a regular object
const data = Object.fromEntries(new FormData(e.currentTarget));
setAction(`data: ${JSON.stringify(data, null, 2)}`);
}}
onReset={() => setAction('reset')}
>
<Inset space={8}>
<Stack space={4}>
<Stack space={2} alignX="left">
<TextField
label="User Name"
name="user"
type="name"
width="1/2"
required
/>
<TextField
label="Password"
name="password"
type="password"
width="1/2"
required
/>
<Inline space={2}>
<Button variant="primary" type="submit">
Login
</Button>
<Button type="reset">Reset</Button>
</Inline>
</Stack>
{action && (
<div className="bg-secondary-200 rounded-lg p-4">
<Text weight="bold">Action:</Text>
<pre>
<code>{action}</code>
</pre>
</div>
)}
</Stack>
</Inset>
</Form>
);
};
22 changes: 22 additions & 0 deletions docs/content/components/form/form/form-validation-errors.demo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Button, Form, Inset, Stack, TextField } from '@marigold/components';

export default () => {
return (
<Form validationErrors={{ password: 'Incorrect password.' }}>
<Inset space={8}>
<Stack space={2} alignX="left">
<TextField label="User Name" name="user" type="name" width="1/2" />
<TextField
label="Password"
name="password"
type="password"
width="1/2"
/>
<Button variant="primary" type="submit">
Login
</Button>
</Stack>
</Inset>
</Form>
);
};
109 changes: 109 additions & 0 deletions docs/content/components/form/form/form.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
---
title: Form
caption: Wrap your fields to submit user data and enable input validation.
badge: new
---

The `<Form>` component acts as a container for a set of fields, enabling data transmission to a server. It operates like a standard HTML form, initiating either a request based on the specified `method` attribute.

Additionally, the `<Form>` allows to validate user input and offers feedback for incorrect data entries, enhancing the overall resilience and user-friendliness of the form submission process. See the [Validation](/concepts/validation) guide to learn more about form validation.

## Usage

### Import

To import the component you just have to use this code below.

```tsx
import { Form } from '@marigold/components';
```

### Appearance

<AppearanceTable component={title} />

### Props

<PropsTable
props={[
{
property: 'validationErrors',
type: 'Record<string, string | string[]>',
description:
'Validation errors for the form, typically returned by a server. This should be set to an object mapping from input names to errors.',
default: '-',
},
{
property: 'action',
type: `string | FormHTMLAttributes<HTMLFormElement>['action']`,
description:
'Endpoint where to send the form-data when the form is submitted.',
default: '-',
},
{
property: 'method',
type: `'get' | 'post' | 'dialog'`,
description: 'The HTTP method to submit the form with.',
default: '-',
},
{
property: 'onSubmit',
type: `(event: FormEvent<HTMLFormElement>) => void`,
description: 'Triggered when a user submits the form.',
default: '-',
},
{
property: 'onReset',
type: `(event: FormEvent<HTMLFormElement>) => void`,
description: 'Triggered when a user resets the form.',
default: '-',
},
{
property: 'onInvalid',
type: `(event: FormEvent<HTMLFormElement>) => void`,
description:
'Triggered for each invalid field when a user submits the form.',
default: '-',
},
{
property: '...',
type: '',
description: 'Yes you can use all regular attributes of `form`!',
default: '',
},
]}
/>

## Examples

### Setup

This is a simple setup how to use a `<Form>`.

<ComponentDemo file="./form-base.demo.tsx" />

### Handling submission

The `onSubmit` event is useful for custom form actions, such as calling a REST API, instead of relying on the native form submission. It triggeres when a user submits the form using the Enter key or clicks the submit button. The `onReset` event is triggered when a user presses a reset button (`[type=reset]`).

<ComponentDemo file="./form-submission.demo.tsx" />

### Server Errors

The `<Form>` component handles passed errors, typically received from a server after form submission. To display validation errors, set the `validationErrors` prop as an object mapping each field's name prop to a string or array of strings representing errors. These errors appear to the user as soon as the `validationErrors` prop is set and are cleared when the user modifies the corresponding field's value.

<ComponentDemo file="./form-validation-errors.demo.tsx" />

For more information about form validation, see the [Validation](/concepts/validation) guide.

### Focus Management

As you can see in the previous server errors example, when a user submits a form with validation errors, the first invalid field is automatically focused. This behavior can be customized using `e.preventDefault` during the `onInvalid` event and manage the focus manually.

<ComponentDemo file="./form-focus-management.demo.tsx" />

<p />

<Message messageTitle="Want more?!">
You can find more examples and usages of the `<Form>` component on the [Validation](/concepts/validation) page and in the [Forms](/recipes/form-recipes) recipes.
</Message>
Loading

0 comments on commit 3d66a58

Please sign in to comment.