Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
6410659
feat: enhance checkbox components with custom label support and impro…
jaruesink Mar 15, 2025
511dc49
chore: update package dependencies and configuration
jaruesink Mar 15, 2025
c6be185
feat: enhance radio group and checkbox components with custom impleme…
jaruesink Mar 15, 2025
bf932a1
feat: enhance radio group and checkbox components with new props and …
jaruesink Mar 15, 2025
5e766e1
feat: enhance Switch component with customizable components and impro…
jaruesink Mar 15, 2025
b67816c
feat: enhance textarea and text field components with customizable st…
jaruesink Mar 15, 2025
b184404
feat: enhance text field stories with forwardRef support and improved…
jaruesink Mar 15, 2025
8d57a6c
feat: refine IconInput component in text field stories for improved a…
jaruesink Mar 15, 2025
9573a46
Add comprehensive documentation to checkbox-custom.stories.tsx
Mar 18, 2025
6167a4f
Add prefix and suffix props to TextField component
codegen-sh[bot] Mar 21, 2025
07f423c
feat: add measurement field to text field stories and enhance TextFie…
jaruesink Mar 21, 2025
6329095
Merge pull request #41 from lambda-curry/feature/mkt-159-text-field-p…
jaruesink Mar 21, 2025
4060182
refactor: improve structure of TextField component by consolidating F…
jaruesink Mar 21, 2025
cbff156
Merge branch 'feature/mkt-159-text-field-prefix-suffix' into custom-i…
jaruesink Mar 21, 2025
72adb4c
chore: downgrade React and TypeScript dependencies to version 18
jaruesink Mar 21, 2025
9b9ef7d
Merge branch 'main' of github.com:lambda-curry/forms into custom-inputs
jaruesink Mar 21, 2025
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
935 changes: 935 additions & 0 deletions ai/CustomInputsProject.md

Large diffs are not rendered by default.

509 changes: 509 additions & 0 deletions apps/docs/src/remix-hook-form/checkbox-custom.stories.tsx

Large diffs are not rendered by default.

65 changes: 59 additions & 6 deletions apps/docs/src/remix-hook-form/checkbox-list.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Form } from '@remix-run/react';
import type { Meta, StoryContext, StoryObj } from '@storybook/react';
import { expect, userEvent } from '@storybook/test';
import type {} from '@testing-library/dom';
import * as React from 'react';
import { RemixFormProvider, createFormData, getValidatedFormData, useRemixForm } from 'remix-hook-form';
import { z } from 'zod';
import { withRemixStubDecorator } from '../lib/storybook/remix-stub';
Expand All @@ -28,6 +29,23 @@ const formSchema = z.object({

type FormData = z.infer<typeof formSchema>;

// Custom FormLabel component that makes the entire area clickable
const FullWidthLabel = React.forwardRef<HTMLLabelElement, React.ComponentPropsWithoutRef<'label'>>(
({ className, children, htmlFor, ...props }, ref) => {
return (
<label
ref={ref}
htmlFor={htmlFor}
className={`absolute inset-0 cursor-pointer flex items-center py-4 px-8 ${className}`}
{...props}
>
<span className="ml-2">{children}</span>
</label>
);
},
);
FullWidthLabel.displayName = 'FullWidthLabel';

const ControlledCheckboxListExample = () => {
const fetcher = useFetcher<{ message: string; selectedColors: string[] }>();
const methods = useRemixForm<FormData>({
Expand Down Expand Up @@ -58,16 +76,22 @@ const ControlledCheckboxListExample = () => {
},
});

console.log(methods.formState);

return (
<RemixFormProvider {...methods}>
<Form onSubmit={methods.handleSubmit}>
<div className="space-y-4">
<p className="text-sm text-gray-500">Select your favorite colors:</p>
<div className="grid gap-4">
{AVAILABLE_COLORS.map(({ value, label }) => (
<Checkbox key={value} className="rounded-md border p-4" name={`colors.${value}`} label={label} />
<Checkbox
key={value}
className="relative rounded-md border p-4 hover:bg-gray-50"
name={`colors.${value}`}
label={label}
components={{
FormLabel: FullWidthLabel,
}}
/>
))}
</div>
<FormMessage error={methods.formState.errors.colors?.root?.message} />
Expand Down Expand Up @@ -101,7 +125,7 @@ const handleFormSubmission = async (request: Request) => {
};

const meta: Meta<typeof Checkbox> = {
title: 'RemixHookForm/CheckboxList',
title: 'RemixHookForm/Checkbox List',
component: Checkbox,
parameters: { layout: 'centered' },
tags: ['autodocs'],
Expand Down Expand Up @@ -153,7 +177,36 @@ export const Tests: Story = {
parameters: {
docs: {
description: {
story: 'A checkbox list component for selecting multiple colors.',
story: 'A checkbox list component for selecting multiple colors with full-width clickable area.',
},
source: {
code: `
// Custom FormLabel component that makes the entire area clickable
const FullWidthLabel = React.forwardRef<HTMLLabelElement, React.ComponentPropsWithoutRef<'label'>>(
({ className, children, htmlFor, ...props }, ref) => {
return (
<label
ref={ref}
htmlFor={htmlFor}
className={\`absolute inset-0 cursor-pointer flex items-center py-4 px-8 \${className}\`}
{...props}
>
<span className="ml-2">{children}</span>
</label>
);
},
);

// Usage in your component
<Checkbox
className="relative rounded-md border p-4 hover:bg-gray-50"
name="colors.red"
label="Red"
components={{
FormLabel: FullWidthLabel,
}}
/>
`,
},
},
},
Expand All @@ -162,4 +215,4 @@ export const Tests: Story = {
await testErrorState(storyContext);
await testColorSelection(storyContext);
},
};
};
82 changes: 42 additions & 40 deletions apps/docs/src/remix-hook-form/checkbox.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import type { ActionFunctionArgs } from '@remix-run/node';
import { useFetcher } from '@remix-run/react';
import type { Meta, StoryContext, StoryObj } from '@storybook/react';
import { expect, userEvent } from '@storybook/test';
import type {} from '@testing-library/dom';
import { RemixFormProvider, getValidatedFormData, useRemixForm } from 'remix-hook-form';
import { z } from 'zod';
import { withRemixStubDecorator } from '../lib/storybook/remix-stub';
Expand Down Expand Up @@ -37,17 +36,16 @@ const ControlledCheckboxExample = () => {
return (
<RemixFormProvider {...methods}>
<fetcher.Form onSubmit={methods.handleSubmit}>
<div className="grid gap-4">
<Checkbox className="rounded-md border p-4" name="terms" label="Accept terms and conditions" />
<div className="grid gap-8">
<Checkbox name="terms" label="Accept terms and conditions" />
<Checkbox
className="rounded-md border p-4"
name="marketing"
label="Receive marketing emails"
description="We will send you hourly updates about our products"
/>
<Checkbox className="rounded-md border p-4" name="required" label="This is a required checkbox" />
<Checkbox name="required" label="This is a required checkbox" />
</div>
<Button type="submit" className="mt-4">
<Button type="submit" className="mt-8">
Submit
</Button>
{fetcher.data?.message && <p className="mt-2 text-green-600">{fetcher.data.message}</p>}
Expand Down Expand Up @@ -112,48 +110,52 @@ const testValidSubmission = async ({ canvas }: StoryContext) => {
await expect(await canvas.findByText('Form submitted successfully')).toBeInTheDocument();
};

export const Tests: Story = {
export const Default: Story = {
parameters: {
docs: {
description: {
story: 'The default checkbox component.',
},
source: {
code: `
const formSchema = z.object({
terms: z.boolean().optional().refine(val => val === true, 'You must accept the terms and conditions'),
marketing: z.boolean().optional(),
required: z.boolean().optional().refine(val => val === true, 'This field is required'),
const formSchema = z.object({
terms: z.boolean().refine(val => val === true, 'You must accept the terms and conditions'),
marketing: z.boolean().optional(),
required: z.boolean().refine(val => val === true, 'This field is required'),
});

const ControlledCheckboxExample = () => {
const fetcher = useFetcher<{ message: string }>();
const methods = useRemixForm<FormData>({
resolver: zodResolver(formSchema),
defaultValues: {
terms: false as true, // Note: ZOD Schema expects a true value
marketing: false,
required: false as true //Note: ZOD Schema expects a true value
},
fetcher,
submitConfig: {
action: '/',
method: 'post',
},
});

const ControlledCheckboxExample = () => {
const fetcher = useFetcher<{ message: string }>();
const methods = useRemixForm<FormData>({
resolver: zodResolver(formSchema),
defaultValues: {
terms: false as true, // Note: ZOD Schema expects a true value
marketing: false,
required: false as true //Note: ZOD Schema expects a true value
},
fetcher,
});

return (
<RemixFormProvider {...methods}>
<fetcher.Form onSubmit={methods.handleSubmit} method="post" action="/">
<div className='grid gap-4'>
<Checkbox className='rounded-md border p-4' name="terms" label="Accept terms and conditions" />
<Checkbox className='rounded-md border p-4' name="marketing" label="Receive marketing emails" description="We will send you hourly updates about our products" />
<Checkbox className='rounded-md border p-4' name="required" label="This is a required checkbox" />
</div>
<Button type="submit" className="mt-4">
Submit
</Button>
{fetcher.data?.message && <p className="mt-2 text-green-600">{fetcher.data.message}</p>}
</fetcher.Form>
</RemixFormProvider>
);
};`,
return (
<RemixFormProvider {...methods}>
<fetcher.Form onSubmit={methods.handleSubmit}>
<div className='grid gap-8'>
<Checkbox name="terms" label="Accept terms and conditions" />
<Checkbox name="marketing" label="Receive marketing emails" description="We will send you hourly updates about our products" />
<Checkbox name="required" label="This is a required checkbox" />
</div>
<Button type="submit" className="mt-8">
Submit
</Button>
{fetcher.data?.message && <p className="mt-2 text-green-600">{fetcher.data.message}</p>}
</fetcher.Form>
</RemixFormProvider>
);
};`,
},
},
},
Expand All @@ -162,4 +164,4 @@ export const Tests: Story = {
await testInvalidSubmission(storyContext);
await testValidSubmission(storyContext);
},
};
};
4 changes: 2 additions & 2 deletions apps/docs/src/remix-hook-form/date-picker.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const handleFormSubmission = async (request: Request) => {

// Storybook configuration
const meta: Meta<typeof DatePicker> = {
title: 'RemixHookForm/DatePicker',
title: 'RemixHookForm/Date Picker',
component: DatePicker,
parameters: { layout: 'centered' },
tags: ['autodocs'],
Expand Down Expand Up @@ -119,4 +119,4 @@ export const Tests: Story = {
await testDateSelection(storyContext);
await testSubmission(storyContext);
},
};
};
Loading