From f9fcaf6da764a6c93869d361a31c47eab109a71e Mon Sep 17 00:00:00 2001 From: "codegen-sh[bot]" <131295404+codegen-sh[bot]@users.noreply.github.com> Date: Thu, 18 Sep 2025 03:59:08 +0000 Subject: [PATCH 1/8] feat: add comprehensive Storybook examples for scroll-to-error functionality - Created scroll-to-error.stories.tsx with hook and component examples - Demonstrates both useScrollToErrorOnSubmit hook and ScrollToErrorOnSubmit component - Includes comprehensive form with multiple sections for scroll testing - Added interactive test scenarios for validation and success cases - Fixed TypeScript issues and accessibility concerns - Optimized performance with regex constants --- .../scroll-to-error.stories.tsx | 699 +++++ .../hooks/useScrollToErrorOnSubmit.ts | 29 +- yarn.lock | 2388 ++++++++--------- 3 files changed, 1864 insertions(+), 1252 deletions(-) create mode 100644 apps/docs/src/remix-hook-form/scroll-to-error.stories.tsx diff --git a/apps/docs/src/remix-hook-form/scroll-to-error.stories.tsx b/apps/docs/src/remix-hook-form/scroll-to-error.stories.tsx new file mode 100644 index 00000000..5e46480d --- /dev/null +++ b/apps/docs/src/remix-hook-form/scroll-to-error.stories.tsx @@ -0,0 +1,699 @@ +import { zodResolver } from '@hookform/resolvers/zod'; +import { Checkbox } from '@lambdacurry/forms/remix-hook-form/checkbox'; +import { ScrollToErrorOnSubmit } from '@lambdacurry/forms/remix-hook-form/components/ScrollToErrorOnSubmit'; +import { useScrollToErrorOnSubmit } from '@lambdacurry/forms/remix-hook-form/hooks/useScrollToErrorOnSubmit'; +import { RadioGroup, RadioGroupItem } from '@lambdacurry/forms/remix-hook-form/radio-group'; +import { Select } from '@lambdacurry/forms/remix-hook-form/select'; +import { TextField } from '@lambdacurry/forms/remix-hook-form/text-field'; +import { Textarea } from '@lambdacurry/forms/remix-hook-form/textarea'; +import { Button } from '@lambdacurry/forms/ui/button'; +import type { Meta, StoryContext, StoryObj } from '@storybook/react-vite'; +import { expect, userEvent } from '@storybook/test'; +import { type ActionFunctionArgs, useFetcher } from 'react-router'; +import { RemixFormProvider, getValidatedFormData, useRemixForm } from 'remix-hook-form'; +import { z } from 'zod'; +import { withReactRouterStubDecorator } from '../lib/storybook/react-router-stub'; + +// Regex constants for performance +const SUBMIT_FORM_REGEX = /submit form/i; + +const formSchema = z.object({ + // Personal Information Section + firstName: z.string().min(2, 'First name must be at least 2 characters'), + lastName: z.string().min(2, 'Last name must be at least 2 characters'), + email: z.string().email('Please enter a valid email address'), + phone: z.string().min(10, 'Phone number must be at least 10 digits'), + + // Address Section + address: z.string().min(5, 'Address must be at least 5 characters'), + city: z.string().min(2, 'City must be at least 2 characters'), + state: z.string().min(1, 'Please select a state'), + zipCode: z.string().min(5, 'ZIP code must be at least 5 characters'), + + // Preferences Section + newsletter: z.boolean(), + notifications: z.enum(['email', 'sms', 'none'], { + errorMap: () => ({ message: 'Please select a notification preference' }), + }), + + // Additional Information + company: z.string().min(2, 'Company name must be at least 2 characters'), + jobTitle: z.string().min(2, 'Job title must be at least 2 characters'), + experience: z.string().min(1, 'Please select your experience level'), + bio: z.string().min(10, 'Bio must be at least 10 characters'), + + // Terms and Conditions + terms: z.boolean().refine((val) => val === true, { + message: 'You must accept the terms and conditions', + }), + privacy: z.boolean().refine((val) => val === true, { + message: 'You must accept the privacy policy', + }), +}); + +type FormData = z.infer; + +// Component using the hook approach +const ScrollToErrorHookExample = () => { + const fetcher = useFetcher<{ message: string; errors?: Record }>(); + const methods = useRemixForm({ + resolver: zodResolver(formSchema), + defaultValues: { + firstName: '', + lastName: '', + email: '', + phone: '', + address: '', + city: '', + state: '', + zipCode: '', + newsletter: false, + notifications: undefined, + company: '', + jobTitle: '', + experience: '', + bio: '', + terms: false, + privacy: false, + }, + fetcher, + submitConfig: { + action: '/', + method: 'post', + }, + }); + + // Use the scroll-to-error hook with custom options + useScrollToErrorOnSubmit({ + offset: 100, // Account for fixed header + behavior: 'smooth', + shouldFocus: true, + scrollOnServerErrors: true, + scrollOnMount: true, + retryAttempts: 3, + delay: 150, + }); + + return ( +
+
+
+

Scroll-to-Error Demo (Hook)

+

+ This form demonstrates the scroll-to-error functionality using the{' '} + useScrollToErrorOnSubmit hook. Try submitting + the form without filling out required fields to see the scroll behavior. +

+
+ + + + {/* Personal Information Section */} +
+

Personal Information

+
+ + +
+
+ + +
+
+ + {/* Address Section */} +
+

Address Information

+
+ +
+ + +