Skip to content

Commit 67fe131

Browse files
authored
Merge pull request #85 from lambda-curry/codegen/lc-240-medusa-forms-controlled-components-documentation-stories-and
2 parents c2e0790 + a790cdc commit 67fe131

File tree

7 files changed

+2435
-0
lines changed

7 files changed

+2435
-0
lines changed
Lines changed: 396 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,396 @@
1+
import { ControlledCheckbox } from '@lambdacurry/medusa-forms/controlled/ControlledCheckbox';
2+
import type { Meta, StoryObj } from '@storybook/react-vite';
3+
import React from 'react';
4+
import { FormProvider, useForm } from 'react-hook-form';
5+
6+
const meta = {
7+
title: 'Medusa Forms/Controlled Checkbox',
8+
component: ControlledCheckbox,
9+
parameters: {
10+
layout: 'centered',
11+
},
12+
tags: ['autodocs'],
13+
} satisfies Meta<typeof ControlledCheckbox>;
14+
15+
export default meta;
16+
type Story = StoryObj<typeof meta>;
17+
18+
// Basic Usage Story
19+
const BasicCheckboxForm = () => {
20+
const form = useForm({
21+
defaultValues: {
22+
acceptTerms: false,
23+
},
24+
});
25+
26+
return (
27+
<FormProvider {...form}>
28+
<div className="w-[400px] space-y-4">
29+
<ControlledCheckbox
30+
name="acceptTerms"
31+
label="I accept the terms and conditions"
32+
/>
33+
<div className="text-sm text-gray-600">
34+
Current value: {form.watch('acceptTerms') ? 'true' : 'false'}
35+
</div>
36+
</div>
37+
</FormProvider>
38+
);
39+
};
40+
41+
export const BasicUsage: Story = {
42+
render: () => <BasicCheckboxForm />,
43+
};
44+
45+
// Default Checked State Story
46+
const DefaultCheckedForm = () => {
47+
const form = useForm({
48+
defaultValues: {
49+
newsletter: true,
50+
},
51+
});
52+
53+
return (
54+
<FormProvider {...form}>
55+
<div className="w-[400px] space-y-4">
56+
<ControlledCheckbox
57+
name="newsletter"
58+
label="Subscribe to newsletter"
59+
/>
60+
<div className="text-sm text-gray-600">
61+
Current value: {form.watch('newsletter') ? 'true' : 'false'}
62+
</div>
63+
</div>
64+
</FormProvider>
65+
);
66+
};
67+
68+
export const DefaultChecked: Story = {
69+
render: () => <DefaultCheckedForm />,
70+
};
71+
72+
// Default Unchecked State Story
73+
const DefaultUncheckedForm = () => {
74+
const form = useForm({
75+
defaultValues: {
76+
marketing: false,
77+
},
78+
});
79+
80+
return (
81+
<FormProvider {...form}>
82+
<div className="w-[400px] space-y-4">
83+
<ControlledCheckbox
84+
name="marketing"
85+
label="Receive marketing emails"
86+
/>
87+
<div className="text-sm text-gray-600">
88+
Current value: {form.watch('marketing') ? 'true' : 'false'}
89+
</div>
90+
</div>
91+
</FormProvider>
92+
);
93+
};
94+
95+
export const DefaultUnchecked: Story = {
96+
render: () => <DefaultUncheckedForm />,
97+
};
98+
99+
// Required Field Validation Story
100+
const RequiredValidationForm = () => {
101+
const form = useForm({
102+
defaultValues: {
103+
requiredField: false,
104+
},
105+
mode: 'onChange',
106+
});
107+
108+
const onSubmit = (data: any) => {
109+
console.log('Form submitted:', data);
110+
};
111+
112+
return (
113+
<FormProvider {...form}>
114+
<form onSubmit={form.handleSubmit(onSubmit)} className="w-[400px] space-y-4">
115+
<ControlledCheckbox
116+
name="requiredField"
117+
label="This field is required"
118+
rules={{
119+
required: 'You must check this box to continue'
120+
}}
121+
/>
122+
<div className="text-sm text-gray-600">
123+
Form valid: {form.formState.isValid ? 'Yes' : 'No'}
124+
</div>
125+
<button
126+
type="submit"
127+
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 disabled:opacity-50"
128+
disabled={!form.formState.isValid}
129+
>
130+
Submit
131+
</button>
132+
</form>
133+
</FormProvider>
134+
);
135+
};
136+
137+
export const RequiredValidation: Story = {
138+
render: () => <RequiredValidationForm />,
139+
};
140+
141+
// Custom Validation Message Story
142+
const CustomValidationForm = () => {
143+
const form = useForm({
144+
defaultValues: {
145+
agreement: false,
146+
},
147+
mode: 'onChange',
148+
});
149+
150+
return (
151+
<FormProvider {...form}>
152+
<div className="w-[400px] space-y-4">
153+
<ControlledCheckbox
154+
name="agreement"
155+
label="I agree to the privacy policy"
156+
rules={{
157+
required: 'Please accept our privacy policy to continue',
158+
validate: (value) => value === true || 'You must agree to the privacy policy'
159+
}}
160+
/>
161+
<div className="text-sm text-gray-600">
162+
Has errors: {form.formState.errors.agreement ? 'Yes' : 'No'}
163+
</div>
164+
{form.formState.errors.agreement && (
165+
<div className="text-sm text-red-600">
166+
Error: {form.formState.errors.agreement.message}
167+
</div>
168+
)}
169+
</div>
170+
</FormProvider>
171+
);
172+
};
173+
174+
export const CustomValidationMessage: Story = {
175+
render: () => <CustomValidationForm />,
176+
};
177+
178+
// Error State Display Story
179+
const ErrorStateForm = () => {
180+
const form = useForm({
181+
defaultValues: {
182+
errorField: false,
183+
},
184+
});
185+
186+
// Manually trigger an error for demonstration
187+
React.useEffect(() => {
188+
form.setError('errorField', {
189+
type: 'manual',
190+
message: 'This is an example error message'
191+
});
192+
}, [form]);
193+
194+
return (
195+
<FormProvider {...form}>
196+
<div className="w-[400px] space-y-4">
197+
<ControlledCheckbox
198+
name="errorField"
199+
label="Checkbox with error state"
200+
/>
201+
<div className="text-sm text-gray-600">
202+
This checkbox demonstrates the error state styling
203+
</div>
204+
</div>
205+
</FormProvider>
206+
);
207+
};
208+
209+
export const ErrorState: Story = {
210+
render: () => <ErrorStateForm />,
211+
};
212+
213+
// Disabled State Story
214+
const DisabledStateForm = () => {
215+
const form = useForm({
216+
defaultValues: {
217+
disabledUnchecked: false,
218+
disabledChecked: true,
219+
},
220+
});
221+
222+
return (
223+
<FormProvider {...form}>
224+
<div className="w-[400px] space-y-4">
225+
<ControlledCheckbox
226+
name="disabledUnchecked"
227+
label="Disabled unchecked checkbox"
228+
disabled
229+
/>
230+
<ControlledCheckbox
231+
name="disabledChecked"
232+
label="Disabled checked checkbox"
233+
disabled
234+
/>
235+
<div className="text-sm text-gray-600">
236+
These checkboxes are disabled and cannot be interacted with
237+
</div>
238+
</div>
239+
</FormProvider>
240+
);
241+
};
242+
243+
export const DisabledState: Story = {
244+
render: () => <DisabledStateForm />,
245+
};
246+
247+
// Multiple Checkboxes with State Management Story
248+
const MultipleCheckboxesForm = () => {
249+
const form = useForm({
250+
defaultValues: {
251+
option1: false,
252+
option2: true,
253+
option3: false,
254+
selectAll: false,
255+
},
256+
});
257+
258+
const watchedValues = form.watch(['option1', 'option2', 'option3']);
259+
const allSelected = watchedValues.every(Boolean);
260+
const someSelected = watchedValues.some(Boolean);
261+
262+
React.useEffect(() => {
263+
form.setValue('selectAll', allSelected);
264+
}, [allSelected, form]);
265+
266+
const handleSelectAll = (checked: boolean) => {
267+
form.setValue('option1', checked);
268+
form.setValue('option2', checked);
269+
form.setValue('option3', checked);
270+
form.setValue('selectAll', checked);
271+
};
272+
273+
return (
274+
<FormProvider {...form}>
275+
<div className="w-[400px] space-y-4">
276+
<ControlledCheckbox
277+
name="selectAll"
278+
label="Select All"
279+
checked={allSelected ? true : someSelected ? 'indeterminate' : false}
280+
onChange={handleSelectAll}
281+
/>
282+
<div className="ml-4 space-y-2">
283+
<ControlledCheckbox
284+
name="option1"
285+
label="Option 1"
286+
/>
287+
<ControlledCheckbox
288+
name="option2"
289+
label="Option 2"
290+
/>
291+
<ControlledCheckbox
292+
name="option3"
293+
label="Option 3"
294+
/>
295+
</div>
296+
<div className="text-sm text-gray-600">
297+
Selected: {watchedValues.filter(Boolean).length} of {watchedValues.length}
298+
</div>
299+
</div>
300+
</FormProvider>
301+
);
302+
};
303+
304+
export const MultipleCheckboxes: Story = {
305+
render: () => <MultipleCheckboxesForm />,
306+
};
307+
308+
// Form Integration Example Story
309+
const CompleteFormExampleComponent = () => {
310+
const form = useForm({
311+
defaultValues: {
312+
username: '',
313+
email: '',
314+
acceptTerms: false,
315+
newsletter: false,
316+
marketing: false,
317+
},
318+
mode: 'onChange',
319+
});
320+
321+
const onSubmit = (data: any) => {
322+
alert(`Form submitted with data: ${JSON.stringify(data, null, 2)}`);
323+
};
324+
325+
return (
326+
<FormProvider {...form}>
327+
<form onSubmit={form.handleSubmit(onSubmit)} className="w-[400px] space-y-4">
328+
<div>
329+
<label className="block text-sm font-medium mb-1">Username</label>
330+
<input
331+
{...form.register('username', { required: 'Username is required' })}
332+
className="w-full px-3 py-2 border border-gray-300 rounded-md"
333+
placeholder="Enter username"
334+
/>
335+
</div>
336+
337+
<div>
338+
<label className="block text-sm font-medium mb-1">Email</label>
339+
<input
340+
{...form.register('email', {
341+
required: 'Email is required',
342+
pattern: {
343+
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
344+
message: 'Invalid email address'
345+
}
346+
})}
347+
className="w-full px-3 py-2 border border-gray-300 rounded-md"
348+
placeholder="Enter email"
349+
type="email"
350+
/>
351+
</div>
352+
353+
<div className="space-y-2">
354+
<ControlledCheckbox
355+
name="acceptTerms"
356+
label="I accept the terms and conditions"
357+
rules={{ required: 'You must accept the terms' }}
358+
/>
359+
<ControlledCheckbox
360+
name="newsletter"
361+
label="Subscribe to newsletter"
362+
/>
363+
<ControlledCheckbox
364+
name="marketing"
365+
label="Receive marketing emails"
366+
/>
367+
</div>
368+
369+
<div className="flex space-x-2">
370+
<button
371+
type="submit"
372+
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 disabled:opacity-50"
373+
disabled={!form.formState.isValid}
374+
>
375+
Submit
376+
</button>
377+
<button
378+
type="button"
379+
onClick={() => form.reset()}
380+
className="px-4 py-2 bg-gray-500 text-white rounded hover:bg-gray-600"
381+
>
382+
Reset
383+
</button>
384+
</div>
385+
386+
<div className="text-sm text-gray-600">
387+
Form valid: {form.formState.isValid ? 'Yes' : 'No'}
388+
</div>
389+
</form>
390+
</FormProvider>
391+
);
392+
};
393+
394+
export const CompleteFormExample: Story = {
395+
render: () => <CompleteFormExampleComponent />,
396+
};

0 commit comments

Comments
 (0)