Skip to content

Commit

Permalink
feat(conform-react): add support of descriptionId
Browse files Browse the repository at this point in the history
  • Loading branch information
edmundhung committed Mar 24, 2023
1 parent 18b71e9 commit 8a63450
Show file tree
Hide file tree
Showing 5 changed files with 256 additions and 55 deletions.
1 change: 1 addition & 0 deletions packages/conform-dom/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export interface FieldConfig<Schema = unknown> extends FieldConstraint<Schema> {
defaultValue?: FieldValue<Schema>;
initialError?: Record<string, string | string[]>;
form?: string;
descriptionId?: string;
errorId?: string;

/**
Expand Down
44 changes: 28 additions & 16 deletions packages/conform-react/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,17 +45,22 @@ interface TextareaProps extends FormControlProps {
defaultValue?: string;
}

type InputOptions =
| {
type: 'checkbox' | 'radio';
hidden?: boolean;
value?: string;
}
| {
type?: Exclude<HTMLInputTypeAttribute, 'button' | 'submit' | 'hidden'>;
hidden?: boolean;
value?: never;
};
type BaseOptions = {
description?: boolean;
hidden?: boolean;
};

type InputOptions = BaseOptions &
(
| {
type: 'checkbox' | 'radio';
value?: string;
}
| {
type?: Exclude<HTMLInputTypeAttribute, 'button' | 'submit' | 'hidden'>;
value?: never;
}
);

/**
* Style to make the input element visually hidden
Expand All @@ -75,7 +80,7 @@ const hiddenStyle: CSSProperties = {

function getFormControlProps(
config: FieldConfig<any>,
options?: { hidden?: boolean },
options?: BaseOptions,
): FormControlProps {
const props: FormControlProps = {
id: config.id,
Expand All @@ -86,11 +91,18 @@ function getFormControlProps(

if (config.id) {
props.id = config.id;
props['aria-describedby'] = config.errorId;
}

if (config.descriptionId && options?.description) {
props['aria-describedby'] = config.descriptionId;
}

if (config.errorId && config.error?.length) {
props['aria-invalid'] = true;
props['aria-describedby'] =
config.descriptionId && options?.description
? `${config.errorId} ${config.descriptionId}`
: config.errorId;
}

if (config.initialError && Object.entries(config.initialError).length > 0) {
Expand All @@ -108,7 +120,7 @@ function getFormControlProps(

export function input<Schema extends File | File[]>(
config: FieldConfig<Schema>,
options: { type: 'file' },
options: InputOptions & { type: 'file' },
): InputProps<Schema>;
export function input<Schema extends Primitive>(
config: FieldConfig<Schema>,
Expand Down Expand Up @@ -142,7 +154,7 @@ export function input<Schema extends Primitive | File | File[]>(

export function select(
config: FieldConfig<Primitive | Primitive[]>,
options?: { hidden?: boolean },
options?: BaseOptions,
): SelectProps {
const props: SelectProps = {
...getFormControlProps(config, options),
Expand All @@ -155,7 +167,7 @@ export function select(

export function textarea(
config: FieldConfig<Primitive>,
options?: { hidden?: boolean },
options?: BaseOptions,
): TextareaProps {
const props: TextareaProps = {
...getFormControlProps(config, options),
Expand Down
4 changes: 3 additions & 1 deletion packages/conform-react/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -404,11 +404,11 @@ export function useForm<
form.id = config.id;
form.errorId = `${config.id}-error`;
form.props.id = form.id;
form.props['aria-describedby'] = form.errorId;
}

if (form.errorId && form.errors.length > 0) {
form.props['aria-invalid'] = 'true';
form.props['aria-describedby'] = form.errorId;
}

return [form, fieldset];
Expand Down Expand Up @@ -615,6 +615,7 @@ export function useFieldset<Schema extends Record<string, any>>(
field.form = fieldsetConfig.form;
field.id = `${fieldsetConfig.form}-${field.name}`;
field.errorId = `${field.id}-error`;
field.descriptionId = `${field.id}-description`;
}

return field;
Expand Down Expand Up @@ -811,6 +812,7 @@ export function useFieldList<Payload = any>(
fieldConfig.form = config.form;
fieldConfig.id = `${config.form}-${config.name}`;
fieldConfig.errorId = `${fieldConfig.id}-error`;
fieldConfig.descriptionId = `${fieldConfig.id}-description`;
}

return {
Expand Down
42 changes: 35 additions & 7 deletions playground/app/routes/input-attributes.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { conform, useForm, parse } from '@conform-to/react';
import { json, type ActionArgs } from '@remix-run/node';
import { Form, useActionData } from '@remix-run/react';
import { json, type ActionArgs, type LoaderArgs } from '@remix-run/node';
import { Form, useActionData, useLoaderData } from '@remix-run/react';
import { useRef } from 'react';
import { Playground, Field, Alert } from '~/components';

Expand All @@ -12,6 +12,14 @@ interface Schema {
tags: string[];
}

export async function loader({ request }: LoaderArgs) {
const url = new URL(request.url);

return json({
enableDescription: url.searchParams.get('enableDescription') === 'yes',
});
}

export async function action({ request }: ActionArgs) {
const formData = await request.formData();
const submission = parse(formData);
Expand All @@ -25,6 +33,7 @@ export async function action({ request }: ActionArgs) {
}

export default function Example() {
const { enableDescription } = useLoaderData<typeof loader>();
const lastSubmission = useActionData<typeof action>();
const ref = useRef<HTMLFormElement>(null);
const [form, { title, description, images, rating, tags }] = useForm<Schema>({
Expand Down Expand Up @@ -69,16 +78,30 @@ export default function Example() {
<Playground title="Input attributes" lastSubmission={lastSubmission}>
<Alert id={form.errorId} errors={form.errors} />
<Field label="Title" config={title}>
<input {...conform.input(title, { type: 'text' })} />
<input
{...conform.input(title, {
type: 'text',
description: enableDescription,
})}
/>
</Field>
<Field label="Description" config={description}>
<textarea {...conform.textarea(description)} />
<textarea
{...conform.textarea(description, {
description: enableDescription,
})}
/>
</Field>
<Field label="Image" config={images}>
<input {...conform.input(images, { type: 'file' })} />
<input
{...conform.input(images, {
type: 'file',
description: enableDescription,
})}
/>
</Field>
<Field label="Tags" config={tags}>
<select {...conform.select(tags)}>
<select {...conform.select(tags, { description: enableDescription })}>
<option value="">Please select</option>
<option value="action">Action</option>
<option value="adventure">Adventure</option>
Expand All @@ -90,7 +113,12 @@ export default function Example() {
</select>
</Field>
<Field label="Rating" config={rating}>
<input {...conform.input(rating, { type: 'number' })} />
<input
{...conform.input(rating, {
type: 'number',
description: enableDescription,
})}
/>
</Field>
</Playground>
</Form>
Expand Down
Loading

0 comments on commit 8a63450

Please sign in to comment.