From 9f91553061a558a9ad72bed5409949c1d13ecc9d Mon Sep 17 00:00:00 2001 From: Jake Ruesink Date: Mon, 25 Nov 2024 21:54:15 -0600 Subject: [PATCH 1/9] chore: adding a remix text area story --- .github/workflows/release.yml | 4 - README.md | 4 +- apps/docs/CHANGELOG.md | 9 -- .../docs/src/remix/remix-textarea.stories.tsx | 135 ++++++++++++++++++ .../components/src/remix/remix-textarea.tsx | 27 ++-- packages/components/src/ui/calendar.tsx | 8 +- packages/components/src/ui/form.tsx | 2 +- packages/components/src/ui/radio-group.tsx | 4 +- packages/components/src/ui/textarea.tsx | 64 +++++++-- 9 files changed, 204 insertions(+), 53 deletions(-) delete mode 100644 apps/docs/CHANGELOG.md create mode 100644 apps/docs/src/remix/remix-textarea.stories.tsx diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ff5df6cb..5743076d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -38,7 +38,3 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - - - name: Send a Slack notification if a publish happens - if: steps.changesets.outputs.published == 'true' - run: my-slack-bot send-notification --message "A new version of ${GITHUB_REPOSITORY} was published!" diff --git a/README.md b/README.md index f4aee75e..d7e71b87 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Welcome! -Checkout our [Storybook Documentation](https://lambda-curry.github.io/forms/?path=/docs/helloworld-start-here--docs) to see the components in action and get started. +Checkout our [Storybook Documentation](https://lambda-curry.github.io/forms/?path=/docs/0-1-hello-world-start-here--docs) to see the components in action and get started. ## Getting Started @@ -10,7 +10,7 @@ Step 1: Install the dependencies yarn install ``` -Note: You may need to enable corepack for yarn v4 by running `corepack enable` before intstalling the dependencies. +Note: You may need to enable corepack for yarn v4 by running `corepack enable` before installing the dependencies. Step 2: Start Storybook diff --git a/apps/docs/CHANGELOG.md b/apps/docs/CHANGELOG.md deleted file mode 100644 index ca501d40..00000000 --- a/apps/docs/CHANGELOG.md +++ /dev/null @@ -1,9 +0,0 @@ -# @lambdacurry/forms-docs - -## 0.1.1 - -### Patch Changes - -- d3ab83e: Fixed errors and versioning differences -- Updated dependencies [d3ab83e] - - @lambdacurry/forms@0.0.2 diff --git a/apps/docs/src/remix/remix-textarea.stories.tsx b/apps/docs/src/remix/remix-textarea.stories.tsx new file mode 100644 index 00000000..24943122 --- /dev/null +++ b/apps/docs/src/remix/remix-textarea.stories.tsx @@ -0,0 +1,135 @@ +import { zodResolver } from '@hookform/resolvers/zod'; +import type { ActionFunctionArgs } from '@remix-run/node'; +import { useFetcher } from '@remix-run/react'; +import type { Meta, StoryObj } from '@storybook/react'; +import { expect, userEvent } from '@storybook/test'; +import { RemixFormProvider, getValidatedFormData, useRemixForm } from 'remix-hook-form'; +import { z } from 'zod'; +import { withRemixStubDecorator } from '../lib/storybook/remix-stub'; +import { RemixTextarea } from '@lambdacurry/forms/remix/remix-textarea'; +import { Button } from '@lambdacurry/forms/ui/button'; + +const formSchema = z.object({ + description: z.string().min(10, 'Description must be at least 10 characters'), +}); + +type FormData = z.infer; + +const INITIAL_DESCRIPTION = 'Initial description text'; +const INVALID_CONTENT = 'This description is too long and will be rejected by the server'; +const CONTENT_ERROR = 'This description is not allowed'; + +function ControlledTextareaExample() { + const fetcher = useFetcher<{ message: string }>(); + const methods = useRemixForm({ + resolver: zodResolver(formSchema), + defaultValues: { + description: INITIAL_DESCRIPTION, + }, + fetcher, + }); + + return ( + + +
+ + + {fetcher.data?.message && ( +

{fetcher.data.message}

+ )} +
+
+
+ ); +} + +async function handleFormSubmission(request: Request) { + const { + errors, + data, + receivedValues: defaultValues, + } = await getValidatedFormData(request, zodResolver(formSchema)); + + if (errors) { + return { errors, defaultValues }; + } + + if (data.description === INVALID_CONTENT) { + return { + errors: { + description: { + type: 'manual', + message: CONTENT_ERROR, + }, + }, + defaultValues, + }; + } + + return { message: 'Form submitted successfully' }; +} + +const meta = { + title: 'Remix/RemixTextarea', + component: RemixTextarea, + parameters: { + layout: 'centered', + docs: { + description: { + component: 'A textarea component that integrates with Remix Form and provides validation feedback.', + }, + }, + }, + tags: ['autodocs'], + decorators: [ + withRemixStubDecorator([ + { + path: '/', + Component: ControlledTextareaExample, + action: async ({ request }: ActionFunctionArgs) => handleFormSubmission(request), + }, + ]), + ], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Example: Story = { + args: { + name: 'description', + }, + play: async ({ canvasElement }) => { + // Test initial state + const textarea = canvasElement.querySelector('textarea[name="description"]'); + expect(textarea).toHaveValue(INITIAL_DESCRIPTION); + + // Test validation error + await userEvent.clear(textarea as HTMLTextAreaElement); + await userEvent.type(textarea as HTMLTextAreaElement, 'short'); + await userEvent.click(canvasElement.querySelector('button[type="submit"]') as HTMLButtonElement); + expect(canvasElement.querySelector('.form-message')).toHaveTextContent( + 'Description must be at least 10 characters' + ); + + // Test server error + await userEvent.clear(textarea as HTMLTextAreaElement); + await userEvent.type(textarea as HTMLTextAreaElement, INVALID_CONTENT); + await userEvent.click(canvasElement.querySelector('button[type="submit"]') as HTMLButtonElement); + expect(canvasElement.querySelector('.form-message')).toHaveTextContent(CONTENT_ERROR); + + // Test successful submission + await userEvent.clear(textarea as HTMLTextAreaElement); + await userEvent.type(textarea as HTMLTextAreaElement, 'This is a valid description text'); + await userEvent.click(canvasElement.querySelector('button[type="submit"]') as HTMLButtonElement); + expect(canvasElement.querySelector('.text-green-600')).toHaveTextContent( + 'Form submitted successfully' + ); + }, +}; \ No newline at end of file diff --git a/packages/components/src/remix/remix-textarea.tsx b/packages/components/src/remix/remix-textarea.tsx index 19d025a9..c48ea263 100644 --- a/packages/components/src/remix/remix-textarea.tsx +++ b/packages/components/src/remix/remix-textarea.tsx @@ -1,23 +1,18 @@ import { useRemixFormContext } from 'remix-hook-form'; import { Textarea, type TextareaProps } from '../ui/textarea'; -import { FormField, FormItem } from '../ui/form'; -export interface RemixTextareaProps extends Omit { - label?: string; - description?: string; -} +import { RemixFormControl, RemixFormDescription, RemixFormLabel, RemixFormMessage } from './remix-form'; + +export type RemixTextareaProps = Omit; export function RemixTextarea(props: RemixTextareaProps) { const { control } = useRemixFormContext(); - return ( - ( - -