From 3d66a58ca0843a9586e37a87cdfb41b6a6318fd6 Mon Sep 17 00:00:00 2001 From: Sebastian Sebald Date: Fri, 9 Feb 2024 13:40:53 +0100 Subject: [PATCH] docs: Add docs for `
` (#3674) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 <77496890+aromko@users.noreply.github.com> --- .changeset/moody-humans-yawn.md | 6 + docs/app/api/demo/subscribe/route.ts | 39 +++++ .../components/form/form/form-base.demo.tsx | 22 +++ .../form/form/form-focus-management.demo.tsx | 68 +++++++++ .../form/form/form-submission.demo.tsx | 63 ++++++++ .../form/form/form-validation-errors.demo.tsx | 22 +++ docs/content/components/form/form/form.mdx | 109 ++++++++++++++ .../concepts/validation-server-error.demo.tsx | 82 +++++++++++ docs/content/concepts/validation.mdx | 10 ++ docs/content/recipes/bookings-form.demo.tsx | 136 ++++++++++-------- .../recipes/organization-search.demo.tsx | 63 ++++---- docs/lib/fetch.ts | 32 +++++ docs/package.json | 3 +- .../components/src/Message/Message.test.tsx | 14 ++ packages/components/src/Message/Message.tsx | 55 +++---- pnpm-lock.yaml | 24 +++- 16 files changed, 623 insertions(+), 125 deletions(-) create mode 100644 .changeset/moody-humans-yawn.md create mode 100644 docs/app/api/demo/subscribe/route.ts create mode 100644 docs/content/components/form/form/form-base.demo.tsx create mode 100644 docs/content/components/form/form/form-focus-management.demo.tsx create mode 100644 docs/content/components/form/form/form-submission.demo.tsx create mode 100644 docs/content/components/form/form/form-validation-errors.demo.tsx create mode 100644 docs/content/components/form/form/form.mdx create mode 100644 docs/content/concepts/validation-server-error.demo.tsx create mode 100644 docs/lib/fetch.ts diff --git a/.changeset/moody-humans-yawn.md b/.changeset/moody-humans-yawn.md new file mode 100644 index 0000000000..6898801327 --- /dev/null +++ b/.changeset/moody-humans-yawn.md @@ -0,0 +1,6 @@ +--- +"@marigold/docs": minor +"@marigold/components": patch +--- + +docs: Add docs for `` diff --git a/docs/app/api/demo/subscribe/route.ts b/docs/app/api/demo/subscribe/route.ts new file mode 100644 index 0000000000..6eb09544ad --- /dev/null +++ b/docs/app/api/demo/subscribe/route.ts @@ -0,0 +1,39 @@ +import { z } from 'zod'; + +import { NextResponse } from 'next/server'; + +// Helpers +// --------------- +const ALREADY_REGISTERED_EMAILS = [ + 'support@reservix.de', + 'marigold@reservix.de', +]; + +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); +}; diff --git a/docs/content/components/form/form/form-base.demo.tsx b/docs/content/components/form/form/form-base.demo.tsx new file mode 100644 index 0000000000..9e0c2fc341 --- /dev/null +++ b/docs/content/components/form/form/form-base.demo.tsx @@ -0,0 +1,22 @@ +import { Button, Form, Inset, Stack, TextField } from '@marigold/components'; + +export default () => { + return ( + + + + + + + + +
+ ); +}; diff --git a/docs/content/components/form/form/form-focus-management.demo.tsx b/docs/content/components/form/form/form-focus-management.demo.tsx new file mode 100644 index 0000000000..1445bd6966 --- /dev/null +++ b/docs/content/components/form/form/form-focus-management.demo.tsx @@ -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 ( +
{ + e.preventDefault(); + setInvalid(true); + }} + onSubmit={e => { + e.preventDefault(); + setInvalid(false); + }} + onReset={() => setInvalid(false)} + > + + + {invalid ? ( + 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. + + ) : null} + + + + + + + + + + +
+ ); +}; diff --git a/docs/content/components/form/form/form-submission.demo.tsx b/docs/content/components/form/form/form-submission.demo.tsx new file mode 100644 index 0000000000..640f975e83 --- /dev/null +++ b/docs/content/components/form/form/form-submission.demo.tsx @@ -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(null); + return ( +
{ + // 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')} + > + + + + + + + + + + + {action && ( +
+ Action: +
+                {action}
+              
+
+ )} +
+
+
+ ); +}; diff --git a/docs/content/components/form/form/form-validation-errors.demo.tsx b/docs/content/components/form/form/form-validation-errors.demo.tsx new file mode 100644 index 0000000000..a847d83df5 --- /dev/null +++ b/docs/content/components/form/form/form-validation-errors.demo.tsx @@ -0,0 +1,22 @@ +import { Button, Form, Inset, Stack, TextField } from '@marigold/components'; + +export default () => { + return ( +
+ + + + + + + +
+ ); +}; diff --git a/docs/content/components/form/form/form.mdx b/docs/content/components/form/form/form.mdx new file mode 100644 index 0000000000..d243aec39b --- /dev/null +++ b/docs/content/components/form/form/form.mdx @@ -0,0 +1,109 @@ +--- +title: Form +caption: Wrap your fields to submit user data and enable input validation. +badge: new +--- + +The `
` 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 `` 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 + + + +### Props + +', + 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['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) => void`, + description: 'Triggered when a user submits the form.', + default: '-', + }, + { + property: 'onReset', + type: `(event: FormEvent) => void`, + description: 'Triggered when a user resets the form.', + default: '-', + }, + { + property: 'onInvalid', + type: `(event: FormEvent) => 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 ``. + + + +### 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]`). + + + +### Server Errors + +The `` 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. + + + +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. + + + +

+ + + You can find more examples and usages of the `` component on the [Validation](/concepts/validation) page and in the [Forms](/recipes/form-recipes) recipes. + diff --git a/docs/content/concepts/validation-server-error.demo.tsx b/docs/content/concepts/validation-server-error.demo.tsx new file mode 100644 index 0000000000..09091002e1 --- /dev/null +++ b/docs/content/concepts/validation-server-error.demo.tsx @@ -0,0 +1,82 @@ +import { ValidationError, post } from '@/lib/fetch'; +import { + QueryClient, + QueryClientProvider, + useMutation, +} from '@tanstack/react-query'; +import type { FormEvent } from 'react'; + +import { + Button, + Form, + Inline, + Inset, + Stack, + Text, + TextField, +} from '@marigold/components'; +import { Check } from '@marigold/icons'; + +const SuccessMessage = () => ( + + Successfully subscribed! + +); + +const App = () => { + /** + * Server communication + * + * (We are using `@tanstack/react-query` in this example to interact + * with a server. Regular form request via the `action` attribute work too!) + */ + const mutation = useMutation({ + mutationFn: (email: string) => post('/api/demo/subscribe', { email }), + }); + + // Form handling + const subscribe = (e: FormEvent) => { + e.preventDefault(); + + const email = new FormData(e.currentTarget).get('email') as string; + mutation.mutate(email); + }; + + // Show form errors from server + const validationErrors = mutation.error ? mutation.error.cause : undefined; + + return ( + + + + + + Subscribe to our Newsletter! + + Stay updated with our latest news and updates. + + + } + required + /> + + + + + ); +}; + +const queryClient = new QueryClient(); + +export default () => ( + + + +); diff --git a/docs/content/concepts/validation.mdx b/docs/content/concepts/validation.mdx index 3691643407..a286fa90d9 100644 --- a/docs/content/concepts/validation.mdx +++ b/docs/content/concepts/validation.mdx @@ -82,3 +82,13 @@ In specific situations though, choosing real-time validation proves beneficial, Here's an example where real-time validation is employed to check password requirements, immediately informing the user when a criteria is met. + +### Hanlding Server Errors + +Server-side validation is essential alongside client-side validation to ensure robust and secure applications. While client-side validation offers immediate feedback and a smoother user experience, server-side validation is crucial for maintaining data integrity and security. + +Marigold supports displaying server validation errors via the `validationErrors` prop on the `

` component. The errors should be an object, where each field's `name`is mapped to a string or array of strings representing error messages. The errors are immediately shown to the user upon setting the `validationErrors` and are cleared when the user modifies the field's value. + +The subscription example now involves sending and receiving the provided email to and from the server. While most email subscriptions will be successful, attempting to use `support@reservix.de` serves as a negative example, triggering an error response from the server. + + diff --git a/docs/content/recipes/bookings-form.demo.tsx b/docs/content/recipes/bookings-form.demo.tsx index 337109d8e9..4beb6bf306 100644 --- a/docs/content/recipes/bookings-form.demo.tsx +++ b/docs/content/recipes/bookings-form.demo.tsx @@ -2,6 +2,7 @@ import { Button, FieldBase, FieldGroup, + Form, Inline, Radio, Select, @@ -15,68 +16,77 @@ import coreTheme from '@marigold/theme-core'; // eslint-disable-next-line import/no-anonymous-default-export export default () => ( - - - - - Herr - Frau - keine - - - - - - - - - - - - - - - - - - - - - - - - {/* This shouldn't be used, we need to fix it */} - - - - - - - -