Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
95b318f
LC-173: Migrate LambdaCurry/forms to ShadCN tailwind V4
Mar 18, 2025
1230d08
yarn lock update
jaruesink Mar 20, 2025
0ccd091
LC-177: Migrate LambdaCurry/forms to latest React Router and React ve…
codegen-sh[bot] Mar 20, 2025
0cfe04d
yarn lock update
jaruesink Mar 20, 2025
8257a8d
Merge pull request #38 from lambda-curry/LC-177-migrate-react-router
jaruesink Mar 20, 2025
fa26df4
Fix Storybook build error for @remix-run/react imports (LC-178)
codegen-sh[bot] Mar 20, 2025
2c82715
Remove @remix-run dependencies from docs app (LC-178)
codegen-sh[bot] Mar 20, 2025
b71b825
Fix build error by removing @remix-run dependencies in docs app
codegen-sh[bot] Mar 20, 2025
a442311
Refactor docs app to remove Remix dependencies and update to React Ro…
jaruesink Mar 20, 2025
416fdf7
Enhance radio group story by wrapping radio options in a flex contain…
jaruesink Mar 20, 2025
d17db52
Update configuration and documentation for improved component customi…
jaruesink Mar 21, 2025
fc8df8d
Update dependencies and refactor storybook decorators for React Route…
jaruesink Mar 21, 2025
746a9d9
fix: add .yarn folder because it was missing
dwene Mar 21, 2025
e214107
chore: move remix-router to dev dep so we can use with remix OR react…
dwene Mar 21, 2025
6727627
merge conflicts
jaruesink Mar 27, 2025
38ddefa
Implement Data Table Component with ShadCN and TanStack (LC-187)
codegen-sh[bot] Apr 1, 2025
d6ca858
Implement DataTableRouterForm and remove Input component (LC-187)
codegen-sh[bot] Apr 1, 2025
5969c41
add tanstack to package json
jaruesink Apr 1, 2025
99c3e80
Refactor data table components to use type imports for React and TanS…
jaruesink Apr 1, 2025
0bdc864
Update dependencies in package.json and yarn.lock; add new Radix UI c…
jaruesink Apr 1, 2025
a447b90
Refactor ControlledTextFieldExample in text-field stories; remove unu…
jaruesink Apr 1, 2025
546f305
Implement DataTable with nuqs and zod filters
codegen-sh[bot] Apr 1, 2025
6582d67
Implement nuqs and zod filters for DataTableRouterForm
codegen-sh[bot] Apr 1, 2025
a10a823
Update nuqs to version 2.4.1; integrate nuqs for URL state management…
jaruesink Apr 2, 2025
64b88e8
Update story title for DataTableRouterForm to follow naming conventio…
jaruesink Apr 2, 2025
f160ce7
Merge branch 'codegen-data-table-lc187' of github.com:lambda-curry/fo…
jaruesink Apr 2, 2025
b93512e
merge conflicts
jaruesink Apr 2, 2025
decae8a
merge conflicts
jaruesink Apr 2, 2025
3221e2c
Merge pull request #48 from lambda-curry/codegen-data-table-nuqs-zod-…
jaruesink Apr 2, 2025
643eb29
Update package.json and turbo.json scripts; add new serve and prerele…
jaruesink Apr 2, 2025
4bbad97
Refactor custom components in form stories to use functional componen…
jaruesink Apr 4, 2025
2dae38f
Enhance radio group component with options prop for automatic item ge…
jaruesink Apr 5, 2025
4765a95
Refactor OTP input story to simplify OTP entry by directly typing int…
jaruesink Apr 5, 2025
fe3d78a
Enhance date picker story with improved button selection and dialog h…
jaruesink Apr 5, 2025
19f5069
Add build-storybook task to turbo.json and remove Storybook build ste…
jaruesink Apr 5, 2025
1ede9c2
Add build-storybook script to package.json for improved Storybook bui…
jaruesink Apr 5, 2025
7b3388a
Update remix-hook-form to v7.0.0 for React Router 7 compatibility
codegen-sh[bot] Apr 6, 2025
3cd7a1c
Remove middleware.ts file and update imports to use remix-hook-form/m…
codegen-sh[bot] Apr 6, 2025
0b4822e
Address PR review comments: Use direct imports from remix-hook-form/m…
codegen-sh[bot] Apr 6, 2025
f0c0fd5
Merge pull request #50 from lambda-curry/codegen-update-remix-hook-fo…
jaruesink Apr 8, 2025
2f065e2
Replace nuqs with React Router 7 features in data table component
codegen-sh[bot] Apr 16, 2025
fa05b58
Address PR feedback: restore original package.json and create custom …
codegen-sh[bot] Apr 16, 2025
30d609f
yarn lock update
jaruesink Apr 16, 2025
835242a
Replace nuqs with React Router 7 features in all components
codegen-sh[bot] Apr 16, 2025
f2b051b
Fix build error and improve data table component
codegen-sh[bot] Apr 16, 2025
d21afd9
Remove action function from data table story and rely solely on loade…
codegen-sh[bot] Apr 17, 2025
08a4244
Fix data table storybook story to show mock data
Apr 17, 2025
176e787
Merge pull request #52 from lambda-curry/codegen-bot/3a34e1bf
jaruesink Apr 17, 2025
09d5381
Fix data table storybook story to show mock data
codegen-sh[bot] Apr 17, 2025
76b4578
Merge pull request #54 from lambda-curry/codegen-bot/fix-data-table-s…
jaruesink Apr 17, 2025
0bf634e
Refactor data table components to improve loading state and paginatio…
jaruesink Apr 17, 2025
222fc4b
Enhance DropdownMenuSelect component with context for item selection …
jaruesink Apr 17, 2025
b256fa8
Merge pull request #51 from lambda-curry/codegen-replace-nuqs-with-re…
jaruesink Apr 17, 2025
6de63ad
Merge pull request #44 from lambda-curry/codegen-data-table-lc187
jaruesink Apr 17, 2025
540446c
chore(release): bump minor version
jaruesink Apr 17, 2025
e03a750
chore(release): bump minor version
jaruesink Apr 17, 2025
875ee45
chore(release): bump minor version
jaruesink Apr 17, 2025
3b4a49b
chore(release): bump minor version
jaruesink Apr 17, 2025
4085523
fix(date-picker): update date schema to use z.coerce.date and enhance…
jaruesink Apr 17, 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
11 changes: 0 additions & 11 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,6 @@ jobs:
- name: Install Playwright Chromium
run: npx playwright install chromium

- name: Build Storybook
run: yarn build-storybook

- name: Start Storybook server
run: yarn workspace @lambdacurry/forms-docs storybook --ci --port 6006 &
env:
NODE_OPTIONS: --max-old-space-size=4096

- name: Wait for Storybook to be ready
run: npx wait-on http://localhost:6006

- name: Run tests
run: yarn test

Expand Down
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -179,4 +179,7 @@ dist
.turbo
*storybook.log

storybook-static
storybook-static

# React Router v7
.react-router/
4 changes: 3 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"hookform",
"isbot",
"lucide",
"Nuqs",
"shadcn",
"sonner"
],
Expand All @@ -20,5 +21,6 @@
"editor.codeActionsOnSave": {
"source.fixAll.biome": "explicit",
"source.organizeImports.biome": "explicit"
}
},
"tailwindCSS.classAttributes": ["class", "className", "ngClass", "class:list", "wrapperClassName"]
}
2 changes: 1 addition & 1 deletion .yarnrc.yml
Original file line number Diff line number Diff line change
@@ -1 +1 @@
nodeLinker: node-modules
nodeLinker: node-modules
15 changes: 14 additions & 1 deletion ai/CustomInputsProject.md
Original file line number Diff line number Diff line change
Expand Up @@ -484,7 +484,8 @@ import { expect, userEvent } from '@storybook/test';
import * as React from 'react';
import { RemixFormProvider, getValidatedFormData, useRemixForm } from 'remix-hook-form';
import { z } from 'zod';
import { withRemixStubDecorator } from '../lib/storybook/remix-stub';

import { withReactRouterStubDecorator } from '../lib/storybook/react-router-stub';

// Custom checkbox component
const CustomCheckbox = React.forwardRef<
Expand Down Expand Up @@ -597,6 +598,18 @@ const handleFormSubmission = async (request: Request) => {
// Story definition
export const CustomComponents: Story = {
render: () => <CustomCheckboxExample />,
decorators: [
withReactRouterStubDecorator({
routes: [
{
path: '/',
action: async ({ request }: ActionFunctionArgs) => {
return handleFormSubmission(request);
},
},
],
}),
],
parameters: {
docs: {
description: {
Expand Down
6 changes: 3 additions & 3 deletions apps/docs/.storybook/main.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { dirname, join } from 'node:path';
import type { StorybookConfig } from '@storybook/react-vite';
import { mergeConfig } from 'vite';
import viteConfig from '../vite.config';

/**
* This function is used to resolve the absolute path of a package.
Expand All @@ -21,7 +19,9 @@ const config: StorybookConfig = {
name: getAbsolutePath('@storybook/react-vite'),
options: {},
},
viteFinal: (config) => {
viteFinal: async (config) => {
const { mergeConfig } = await import('vite');
const viteConfig = await import('../vite.config.mjs');
return mergeConfig(config, viteConfig);
},
};
Expand Down
44 changes: 24 additions & 20 deletions apps/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,39 @@
"build": "storybook build",
"build-storybook": "storybook build",
"storybook": "storybook dev -p 6006",
"test": "test-storybook"
"serve": "http-server ./storybook-static -p 6006 -s",
"test": "start-server-and-test serve http://127.0.0.1:6006 'test-storybook --url http://127.0.0.1:6006'",
"test:local": "test-storybook"
},
"dependencies": {
"@lambdacurry/forms": "*",
"@storybook/addon-essentials": "^8.4.7",
"@storybook/addon-interactions": "^8.4.7",
"@storybook/addon-links": "^8.4.7",
"@storybook/blocks": "^8.4.7",
"@storybook/react": "^8.4.7",
"@storybook/react-vite": "^8.4.7",
"@storybook/test": "^8.4.7",
"storybook": "^8.4.7"
"@storybook/addon-essentials": "^8.6.7",
"@storybook/addon-interactions": "^8.6.7",
"@storybook/addon-links": "^8.6.7",
"@storybook/blocks": "^8.6.7",
"@storybook/react": "^8.6.7",
"@storybook/react-vite": "^8.6.7",
"@storybook/test": "^8.6.7",
"storybook": "^8.6.7"
},
"devDependencies": {
"@remix-run/dev": "^2.15.1",
"@remix-run/testing": "^2.15.1",
"@storybook/test-runner": "^0.20.1",
"@react-router/dev": "^7.0.0",
"@storybook/test-runner": "^0.22.0",
"@storybook/testing-library": "^0.2.2",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"@tailwindcss/vite": "^4.0.0",
"@types/react": "^19.0.0",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0",
"autoprefixer": "^10.4.20",
"postcss": "^8.4.49",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"tailwindcss": "^3.4.17",
"http-server": "^14.1.1",
"react": "^19.0.0",
"react-router": "^7.0.0",
"react-router-dom": "^7.0.0",
"start-server-and-test": "^2.0.11",
"tailwindcss": "^4.0.0",
"typescript": "^5.7.2",
"vite": "^5.4.11",
"vite-tsconfig-paths": "^5.1.4"
"vite": "^6.2.2",
"vite-tsconfig-paths": "^5.1.4",
"wait-on": "^8.0.3"
}
}
6 changes: 0 additions & 6 deletions apps/docs/postcss.config.js

This file was deleted.

79 changes: 79 additions & 0 deletions apps/docs/src/examples/middleware-example.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Example of using the new middleware feature in remix-hook-form v7.0.0
import { Form } from 'react-router';
import { RemixFormProvider, useRemixForm } from 'remix-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import * as zod from 'zod';
import { TextField } from '@lambdacurry/forms/remix-hook-form';
import { getValidatedFormData } from 'remix-hook-form/middleware';
import type { ActionFunctionArgs } from 'react-router';

// Define schema and types
const schema = zod.object({
name: zod.string().min(1, "Name is required"),
email: zod.string().email("Invalid email format").min(1, "Email is required"),
});

type FormData = zod.infer<typeof schema>;
const resolver = zodResolver(schema);

// Action function using the new middleware
export const action = async ({ context }: ActionFunctionArgs) => {
// Use the middleware to extract and validate form data
const { errors, data, receivedValues } = await getValidatedFormData<FormData>(
context,
resolver
);

if (errors) {
return { errors, defaultValues: receivedValues };
}

// Process the validated data
console.log('Processing data:', data);

return { success: true, data };
};

// Component
export default function MiddlewareExample() {
const {
handleSubmit,
formState: { errors },
register,
} = useRemixForm<FormData>({
mode: "onSubmit",
resolver,
});

return (
<div className="p-4">
<h1 className="text-2xl font-bold mb-4">Remix Hook Form v7 Middleware Example</h1>

<RemixFormProvider>
<Form method="POST" onSubmit={handleSubmit}>
<div className="space-y-4">
<TextField
label="Name"
{...register("name")}
error={errors.name?.message}
/>

<TextField
label="Email"
type="email"
{...register("email")}
error={errors.email?.message}
/>

<button
type="submit"
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
>
Submit
</button>
</div>
</Form>
</RemixFormProvider>
</div>
);
}
23 changes: 23 additions & 0 deletions apps/docs/src/examples/root-example.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Example of setting up the middleware in root.tsx
import { unstable_extractFormDataMiddleware } from 'remix-hook-form/middleware';
import { Outlet } from 'react-router-dom';

// Export the middleware for React Router 7
export const unstable_middleware = [unstable_extractFormDataMiddleware()];

export default function Root() {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Remix Hook Form v7 Example</title>
</head>
<body>
<div className="container mx-auto">
<Outlet />
</div>
</body>
</html>
);
}
85 changes: 85 additions & 0 deletions apps/docs/src/lib/storybook/react-router-stub.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import type { Decorator } from '@storybook/react';
import type { ComponentType } from 'react';
import {
type ActionFunction,
type IndexRouteObject,
type LinksFunction,
type LoaderFunction,
type MetaFunction,
type NonIndexRouteObject,
RouterProvider,
createMemoryRouter,
} from 'react-router-dom';

export type StubRouteObject = StubIndexRouteObject | StubNonIndexRouteObject;

interface StubNonIndexRouteObject
extends Omit<NonIndexRouteObject, 'loader' | 'action' | 'element' | 'errorElement' | 'children'> {
loader?: LoaderFunction;
action?: ActionFunction;
children?: StubRouteObject[];
meta?: MetaFunction;
links?: LinksFunction;
// biome-ignore lint/suspicious/noExplicitAny: allow any here
Component?: ComponentType<any>;
}

interface StubIndexRouteObject
extends Omit<IndexRouteObject, 'loader' | 'action' | 'element' | 'errorElement' | 'children'> {
loader?: LoaderFunction;
action?: ActionFunction;
children?: StubRouteObject[];
meta?: MetaFunction;
links?: LinksFunction;
// biome-ignore lint/suspicious/noExplicitAny: allow any here
Component?: ComponentType<any>;
}

interface RemixStubOptions {
routes: StubRouteObject[];
initialPath?: string;
}

export const withReactRouterStubDecorator = (options: RemixStubOptions): Decorator => {
const { routes, initialPath = '/' } = options;
// This outer function runs once when Storybook loads the story meta

return (Story, context) => {
// This inner function runs when the story component actually renders
const mappedRoutes = routes.map((route) => ({
...route,
Component: route.Component ?? (() => <Story {...context.args} />),
}));

// Get the base path (without existing query params from options)
const basePath = initialPath.split('?')[0];

// Get the current search string from the actual browser window, if available
// If not available, use a default search string with parameters needed for the data table
const currentWindowSearch = typeof window !== 'undefined'
? window.location.search
: '?page=0&pageSize=10';

// Combine them for the initial entry
const actualInitialPath = `${basePath}${currentWindowSearch}`;

// Create a memory router, initializing it with the path derived from the window's search params
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
const router = createMemoryRouter(mappedRoutes as any, {
initialEntries: [actualInitialPath], // Use the path combined with window.location.search
});

return <RouterProvider router={router} />;
};
};

/**
* A decorator that provides URL state management for stories
* Use this when you need URL query parameters in your stories
*/
export const withURLState = (initialPath = '/'): Decorator => {
return withReactRouterStubDecorator({
routes: [{ path: '/' }],
initialPath,
});
};
Loading