Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@autoform v2 #117

Merged
merged 18 commits into from
Oct 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .github/workflows/cypress.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Cypress Tests

on: push

jobs:
cypress-run:
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: "20"

- name: Install dependencies
run: npm ci

- name: Build packages
run: npm run build

- name: Cypress run
uses: cypress-io/github-action@v6
with:
install-command: npm install
project: apps/web
start: npm run cypress
browser: chrome
component: true
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ For releases, AutoForm uses changesets. To create a new release, run:

```bash
npm run build
npm run cypress # Run the component tests
npx changeset
```

Expand Down
1 change: 1 addition & 0 deletions apps/docs/pages/docs/react/_meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ export default {
migration: "Migration from v1",
customization: "Customization",
"custom-integration": "Custom integration",
api: "API",
};
37 changes: 37 additions & 0 deletions apps/docs/pages/docs/react/api.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# API

## schema: SchemaProvider

The `schema` prop is used to pass the schema to the form. It can be a `ZodProvider` or a `YupProvider`.

## onSubmit?: (values: T, form: FormInstance) => void

The `onSubmit` prop is called when the form is submitted and the data is valid. It receives the form data and the form instance from react-hook-form.

## onFormInit?: (form: FormInstance) => void

The `onFormInit` prop is called when the form is initialized. It receives the form instance from react-hook-form.

## withSubmit?: boolean

The `withSubmit` prop is used to automatically add a submit button to the form. It defaults to `false`.

## defaultValues?: Partial\<T>

The `defaultValues` prop is used to set the initial values of the form.

## values: Partial\<T>

The `values` prop is used to set the values of the form. It is a controlled input.

## children: React.ReactNode

All children passed to the `AutoForm` component will be rendered below the form.

## formComponents: Partial\<AutoFormFieldComponents>

Additional, custom form components can be passed to the `formComponents` prop. This allows you to add custom field types to the form.

## uiComponents: Partial\<AutoFormUIComponents>

Override the default UI components with custom components. This allows you to customize the look and feel of the form.
63 changes: 6 additions & 57 deletions apps/docs/pages/docs/react/custom-integration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -129,19 +129,10 @@ import { AutoFormFieldProps } from "@autoform/react";

export const StringField: React.FC<AutoFormFieldProps> = ({
field,
value,
onChange,
inputProps,
error,
id,
}) => (
<input
id={id}
type="text"
value={value || ""}
onChange={(e) => onChange(e.target.value)}
{...field.fieldConfig?.inputProps}
/>
);
}) => <input id={id} {...inputProps} />;
```

```tsx
Expand All @@ -151,19 +142,9 @@ import { AutoFormFieldProps } from "@autoform/react";

export const NumberField: React.FC<AutoFormFieldProps> = ({
field,
value,
onChange,
error,
inputProps,
id,
}) => (
<input
id={id}
type="number"
value={value || ""}
onChange={(e) => onChange(Number(e.target.value))}
{...field.fieldConfig?.inputProps}
/>
);
}) => <input id={id} type="number" {...inputProps} />;
```

```tsx
Expand All @@ -173,19 +154,9 @@ import { AutoFormFieldProps } from "@autoform/react";

export const DateField: React.FC<AutoFormFieldProps> = ({
field,
value,
onChange,
error,
inputProps,
id,
}) => (
<input
id={id}
type="date"
value={value ? new Date(value).toISOString().split("T")[0] : ""}
onChange={(e) => onChange(new Date(e.target.value))}
{...field.fieldConfig?.inputProps}
/>
);
}) => <input id={id} type="date" {...inputProps} />;
```

Additionally, you can create custom field components for other types like checkboxes, radio buttons, etc. These can later be used by setting a custom `fieldType` in the schema.
Expand Down Expand Up @@ -234,28 +205,6 @@ export function AutoForm<T extends Record<string, any>>(
}
```

## Implement utility functions

To provide type-safety when selecting field types, you should create a custom wrapper for the `fieldConfig` function.

Create a new file called `src/utils.ts`:

```tsx
// src/utils.ts
import { FieldConfig } from "@autoform/core";
import { SuperRefineFunction } from "@autoform/zod";
import { fieldConfig as baseFieldConfig } from "@autoform/react";
import { ReactNode } from "react";

type FieldTypes = "string" | "number" | "date" | string;

export function fieldConfig(
config: FieldConfig<ReactNode, FieldTypes>
): SuperRefineFunction {
return baseFieldConfig<FieldTypes>(config);
}
```

## Add custom field types (optional)

If you want to add custom field types, you can create new components and add them to the `formComponents` object in `src/AutoForm.tsx`. For example:
Expand Down
139 changes: 132 additions & 7 deletions apps/docs/pages/docs/react/customization.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,23 @@

The customization of the components is done by providing a `fieldConfig` to your schema fields. This allows you to customize the rendering of the field, add additional props, and more.

With zod, you can use the `superRefine` method to add a `fieldConfig` to your schema field. This method is used to add additional validation and configuration to your field.
With zod, you can use the `superRefine` method to add a `fieldConfig` to your schema field. For more information, see the [Zod documentation](/docs/schema/zod).

You should import `fieldConfig` from your AutoForm UI-specific package (e.g. `@autoform/mui`) so it will be type-safe for your specific UI. If you use a custom UI, you can import `fieldConfig` from `@autoform/react` or `@autoform/core`.
With yup, you can use the `transform` method to add a `fieldConfig` to your schema field. For more information, see the [Yup documentation](/docs/schema/yup). In these examples, we will use Zod.

You should import `buildZodFieldConfig` or `buildYupFieldConfig` from `@autoform/react` and customize it.

```tsx
import * as z from "zod";
import { fieldConfig } from "@autoform/mui"; // or your UI library
import { buildZodFieldConfig } from "@autoform/react";
import { FieldTypes } from "@autoform/mui";

const fieldConfig = buildZodFieldConfig<
FieldTypes, // You should provide the "FieldTypes" type from the UI library you use
{
isImportant?: boolean; // You can add custom props here
}
>();

const schema = z.object({
username: z.string().superRefine(
Expand All @@ -17,7 +27,10 @@ const schema = z.object({
inputProps: {
placeholder: "Enter your username",
},
}),
customData: {
isImportant: true, // You can add custom data here
},
})
),
// ...
});
Expand All @@ -35,7 +48,7 @@ const schema = z.object({
type: "text",
placeholder: "Username",
},
}),
})
),
});
// This will be rendered as:
Expand All @@ -51,13 +64,54 @@ const schema = z.object({
username: z.string().superRefine(
fieldConfig({
fieldType: "textarea",
}),
})
),
});
```

The list of available fields depends on the UI library you use - use the autocomplete in your IDE to see the available options.

### Custom field types

You can also add your own custom field types. To do this, you need to extend the `formComponents` prop of your AutoForm component and add your custom field type.

```tsx
<AutoForm
formComponents={{
custom: ({ field, label, inputProps }: AutoFormFieldProps) => {
return (
<div>
<input
type="text"
className="bg-red-400 rounded-lg p-4"
// You should always pass the "inputProps" to the input component
// This includes the handlers for "onChange", "onBlur", etc.
{...inputProps}
/>
</div>
);
},
}}
/>;

const fieldConfig = buildZodFieldConfig<
FieldTypes | "custom",
{
isImportant?: boolean;
}
>();

const schema = z.object({
username: z.string().superRefine(
fieldConfig({
fieldType: "custom",
})
),
});
```

Please note that this will still render the default `FieldWrapper` around your input field, which contains the label and error message. If you want to customize this, you can use the `fieldWrapper` property ([see below](#custom-field-wrapper)).

## Description

You can use the `description` property to add a description below the field.
Expand All @@ -68,9 +122,80 @@ const schema = z.object({
fieldConfig({
description:
"Enter a unique username. This will be shown to other users.",
}),
})
),
});
```

You can use JSX in the description.

## Order

If you want to change the order of fields, use the order config. You can pass an arbitrary number where smaller numbers will be displayed first. All fields without a defined order use "0" so they appear in the same order they are defined in

```tsx
const schema = z.object({
termsOfService: z.boolean().superRefine(
fieldConfig({
order: 1, // This will be displayed after other fields with order 0
})
),

username: z.string().superRefine(
fieldConfig({
order: -1, // This will be displayed first
})
),

email: z.string().superRefine(
fieldConfig({
// Without specifying an order, this will have order 0
})
),
});
```

## Custom field wrapper

You can use the `fieldWrapper` property to wrap the field in a custom component. This is useful if you want to add additional elements to the field.

The `fieldWrapper` is responsible for rendering the field label and error, so when you use a custom `fieldWrapper`, you need to handle these yourself. You can take a look at the `FieldWrapperProps` type to see what props are passed to the `fieldWrapper`.

```tsx
const schema = z.object({
email: z.string().superRefine(
fieldConfig({
fieldWrapper: (props: FieldWrapperProps) => {
return (
<>
{props.children}
<p className="text-muted-foreground text-sm">
Don't worry, we won't share your email with anyone!
</p>
</>
);
},
})
),
});
```

## Override UI components

You can also override the default UI components with custom components. This allows you to customize the look and feel of the form.

```tsx
<AutoForm
uiComponents={{
FieldWrapper: ({ children, label, error }) => {
return (
<div>
<label>{label}</label>
{children}
{error}
</div>
);
},
}}
/>
```
Loading
Loading