Skip to content

Commit aed0312

Browse files
authored
Merge pull request #69 from lambda-curry/feature/bazza-ui-filters
2 parents 59a2678 + 454ad1c commit aed0312

83 files changed

Lines changed: 24948 additions & 1044 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.cursor/rules/storybook-testing.mdc

Lines changed: 457 additions & 2 deletions
Large diffs are not rendered by default.

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ on:
44
push:
55
branches: [main]
66
pull_request:
7-
branches: [main]
7+
branches: ["*"]
88
workflow_dispatch:
99

1010
jobs:

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"cSpell.words": [
33
"autodocs",
4+
"Bazza",
45
"biomejs",
56
"cleanbuild",
67
"Filenaming",

README.md

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,69 @@
22

33
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.
44

5-
A form library for React applications.
5+
A comprehensive form library for React applications with modern data table filtering capabilities.
6+
7+
## ✨ New: Bazza UI Data Table Filters
8+
9+
We've added a powerful, accessible filtering system inspired by Linear's interface:
10+
11+
- 🎛️ **Multiple Filter Types**: Text, option, date, and number filters
12+
- 🔗 **URL State Synchronization**: Filter state persists across page refreshes
13+
- 📊 **Faceted Filtering**: Dynamic option counts based on current filters
14+
-**Client & Server-Side**: Flexible filtering strategies for any dataset size
15+
-**Accessibility**: Full WCAG 2.1 AA compliance
16+
- 🎨 **Modern UI**: Clean, Linear-inspired design
17+
18+
### Quick Example
19+
20+
```typescript
21+
import { DataTableFilter } from '@lambdacurry/forms/ui/data-table-filter';
22+
import { useDataTableFilters } from '@lambdacurry/forms/ui/data-table-filter/hooks/use-data-table-filters';
23+
import { createColumnConfigHelper } from '@lambdacurry/forms/ui/data-table-filter/core/filters';
24+
25+
const dtf = createColumnConfigHelper<YourDataType>();
26+
27+
const columnConfigs = [
28+
dtf.text().id('title').accessor(row => row.title).displayName('Title').build(),
29+
dtf.option().id('status').accessor(row => row.status).displayName('Status')
30+
.options([
31+
{ value: 'active', label: 'Active' },
32+
{ value: 'inactive', label: 'Inactive' },
33+
]).build(),
34+
];
35+
36+
const MyTable = () => {
37+
const [filters, setFilters] = useFilterSync();
38+
const { columns, actions, strategy } = useDataTableFilters({
39+
columnsConfig: columnConfigs,
40+
filters,
41+
onFiltersChange: setFilters,
42+
strategy: 'client',
43+
data: yourData,
44+
});
45+
46+
return <DataTableFilter columns={columns} filters={filters} actions={actions} strategy={strategy} />;
47+
};
48+
```
49+
50+
📖 **[View Complete Filter Documentation](./packages/components/src/ui/data-table-filter/README.md)**
51+
52+
## Features
53+
54+
### Form Components
55+
- Comprehensive form field components with validation
56+
- React Hook Form integration
57+
- Remix integration for server-side forms
58+
- TypeScript support with excellent IntelliSense
659

60+
### Data Table Filtering
61+
- Modern Linear-inspired filter interface
62+
- Multiple filter types (text, option, date, number)
63+
- URL state synchronization for filter persistence
64+
- Faceted filtering with dynamic option counts
65+
- Client-side and server-side filtering strategies
66+
- Full accessibility support (WCAG 2.1 AA)
67+
- Comprehensive test coverage
768

869
## Getting Started
970

apps/docs/.storybook/main.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,7 @@ function getAbsolutePath(value: string): string {
1010
}
1111
const config: StorybookConfig = {
1212
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
13-
addons: [
14-
getAbsolutePath('@storybook/addon-links'),
15-
getAbsolutePath("@storybook/addon-docs")
16-
],
13+
addons: [getAbsolutePath('@storybook/addon-links'), getAbsolutePath('@storybook/addon-docs')],
1714
framework: {
1815
name: getAbsolutePath('@storybook/react-vite'),
1916
options: {},

apps/docs/src/examples/middleware-example.tsx

Lines changed: 18 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1+
import { zodResolver } from '@hookform/resolvers/zod';
2+
import { TextField } from '@lambdacurry/forms/remix-hook-form';
13
// Example of using the new middleware feature in remix-hook-form v7.0.0
24
import { Form } from 'react-router';
5+
import type { ActionFunctionArgs } from 'react-router';
36
import { RemixFormProvider, useRemixForm } from 'remix-hook-form';
4-
import { zodResolver } from '@hookform/resolvers/zod';
5-
import * as zod from 'zod';
6-
import { TextField } from '@lambdacurry/forms/remix-hook-form';
77
import { getValidatedFormData } from 'remix-hook-form/middleware';
8-
import type { ActionFunctionArgs } from 'react-router';
8+
import * as zod from 'zod';
99

1010
// Define schema and types
1111
const schema = zod.object({
12-
name: zod.string().min(1, "Name is required"),
13-
email: zod.string().email("Invalid email format").min(1, "Email is required"),
12+
name: zod.string().min(1, 'Name is required'),
13+
email: zod.string().email('Invalid email format').min(1, 'Email is required'),
1414
});
1515

1616
type FormData = zod.infer<typeof schema>;
@@ -19,18 +19,15 @@ const resolver = zodResolver(schema);
1919
// Action function using the new middleware
2020
export const action = async ({ context }: ActionFunctionArgs) => {
2121
// Use the middleware to extract and validate form data
22-
const { errors, data, receivedValues } = await getValidatedFormData<FormData>(
23-
context,
24-
resolver
25-
);
26-
22+
const { errors, data, receivedValues } = await getValidatedFormData<FormData>(context, resolver);
23+
2724
if (errors) {
2825
return { errors, defaultValues: receivedValues };
2926
}
30-
27+
3128
// Process the validated data
3229
console.log('Processing data:', data);
33-
30+
3431
return { success: true, data };
3532
};
3633

@@ -41,34 +38,22 @@ export default function MiddlewareExample() {
4138
formState: { errors },
4239
register,
4340
} = useRemixForm<FormData>({
44-
mode: "onSubmit",
41+
mode: 'onSubmit',
4542
resolver,
4643
});
47-
44+
4845
return (
4946
<div className="p-4">
5047
<h1 className="text-2xl font-bold mb-4">Remix Hook Form v7 Middleware Example</h1>
51-
48+
5249
<RemixFormProvider>
5350
<Form method="POST" onSubmit={handleSubmit}>
5451
<div className="space-y-4">
55-
<TextField
56-
label="Name"
57-
{...register("name")}
58-
error={errors.name?.message}
59-
/>
60-
61-
<TextField
62-
label="Email"
63-
type="email"
64-
{...register("email")}
65-
error={errors.email?.message}
66-
/>
67-
68-
<button
69-
type="submit"
70-
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
71-
>
52+
<TextField label="Name" {...register('name')} error={errors.name?.message} />
53+
54+
<TextField label="Email" type="email" {...register('email')} error={errors.email?.message} />
55+
56+
<button type="submit" className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">
7257
Submit
7358
</button>
7459
</div>

apps/docs/src/main.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
@import 'tailwindcss';
1+
@import "tailwindcss";
22
@source "../../../packages/components";
33

44
:root {

apps/docs/src/remix-hook-form/checkbox.stories.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -141,11 +141,11 @@ const ControlledCheckboxExample = () => {
141141
const termsCheckbox = canvas.getByLabelText('Accept terms and conditions');
142142
const marketingCheckbox = canvas.getByLabelText('Receive marketing emails');
143143
const requiredCheckbox = canvas.getByLabelText('This is a required checkbox');
144-
144+
145145
expect(termsCheckbox).not.toBeChecked();
146146
expect(marketingCheckbox).not.toBeChecked();
147147
expect(requiredCheckbox).not.toBeChecked();
148-
148+
149149
// Verify submit button is present
150150
const submitButton = canvas.getByRole('button', { name: 'Submit' });
151151
expect(submitButton).toBeInTheDocument();
@@ -155,7 +155,7 @@ const ControlledCheckboxExample = () => {
155155
// Submit form without checking required checkboxes
156156
const submitButton = canvas.getByRole('button', { name: 'Submit' });
157157
await userEvent.click(submitButton);
158-
158+
159159
// Verify validation error messages appear
160160
await expect(canvas.findByText('You must accept the terms and conditions')).resolves.toBeInTheDocument();
161161
await expect(canvas.findByText('This field is required')).resolves.toBeInTheDocument();
@@ -165,14 +165,14 @@ const ControlledCheckboxExample = () => {
165165
// Check required checkboxes
166166
const termsCheckbox = canvas.getByLabelText('Accept terms and conditions');
167167
const requiredCheckbox = canvas.getByLabelText('This is a required checkbox');
168-
168+
169169
await userEvent.click(termsCheckbox);
170170
await userEvent.click(requiredCheckbox);
171-
171+
172172
// Submit form
173173
const submitButton = canvas.getByRole('button', { name: 'Submit' });
174174
await userEvent.click(submitButton);
175-
175+
176176
// Verify success message
177177
await expect(canvas.findByText('Form submitted successfully')).resolves.toBeInTheDocument();
178178
});

0 commit comments

Comments
 (0)