@classytic/formkit 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,273 +1,434 @@
1
- # @classytic/formkit
2
-
3
- A headless, type-safe form generation engine for React 18+ and React 19. Build dynamic forms from schemas with full TypeScript support and zero UI opinions.
4
-
5
- [![npm version](https://img.shields.io/npm/v/@classytic/formkit.svg)](https://www.npmjs.com/package/@classytic/formkit)
6
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
-
8
- ## ✨ Features
9
-
10
- - 🎯 **Headless & UI-Agnostic**: Bring your own components (Shadcn, Material UI, Ant Design, or custom)
11
- - 📝 **Schema-Driven**: Generate complex forms from simple JSON schemas
12
- - 🔒 **Type-Safe**: Built with TypeScript, full type inference and autocomplete
13
- - **Performance**: Optimized rendering with React 18/19 concurrent features
14
- - 🎨 **Variants**: Support multiple component styles (compact, default, custom)
15
- - 🔀 **Conditional Fields**: Show/hide fields based on form values
16
- - 🧩 **Composable**: Modular architecture with pluggable components
17
- - 🪝 **React Hook Form**: Built on top of the industry-standard form library
18
- - 🌲 **Tree-Shakeable**: Only bundle what you use
19
-
20
- ## 📦 Installation
21
-
22
- ```bash
23
- npm install @classytic/formkit react-hook-form
24
- # or
25
- yarn add @classytic/formkit react-hook-form
26
- # or
27
- pnpm add @classytic/formkit react-hook-form
28
- ```
29
-
30
- ## 🚀 Quick Start
31
-
32
- ### 1. Create a Component Adapter
33
-
34
- Map your UI components to form field types:
35
-
36
- ```tsx
37
- // form-adapter.tsx
38
- import { FormSystemProvider } from "@classytic/formkit";
39
- import { Input } from "@/components/ui/input";
40
- import { Select } from "@/components/ui/select";
41
- import { Checkbox } from "@/components/ui/checkbox";
42
-
43
- const components = {
44
- text: Input,
45
- email: Input,
46
- select: Select,
47
- checkbox: Checkbox,
48
- // Add more field types as needed
49
- };
50
-
51
- const layouts = {
52
- grid: ({ children, cols = 1 }) => (
53
- <div className={`grid grid-cols-${cols} gap-4`}>{children}</div>
54
- ),
55
- section: ({ title, description, children }) => (
56
- <div className="space-y-4">
57
- {title && <h3 className="text-lg font-semibold">{title}</h3>}
58
- {description && <p className="text-sm text-muted-foreground">{description}</p>}
59
- {children}
60
- </div>
61
- ),
62
- };
63
-
64
- export function FormProvider({ children }: { children: React.ReactNode }) {
65
- return (
66
- <FormSystemProvider components={components} layouts={layouts}>
67
- {children}
68
- </FormSystemProvider>
69
- );
70
- }
71
- ```
72
-
73
- ### 2. Define Your Schema
74
-
75
- ```tsx
76
- import type { FormSchema } from "@classytic/formkit";
77
-
78
- const userFormSchema: FormSchema = {
79
- sections: [
80
- {
81
- title: "Personal Information",
82
- cols: 2,
83
- fields: [
84
- {
85
- name: "firstName",
86
- type: "text",
87
- label: "First Name",
88
- required: true,
89
- },
90
- {
91
- name: "lastName",
92
- type: "text",
93
- label: "Last Name",
94
- required: true,
95
- },
96
- {
97
- name: "email",
98
- type: "email",
99
- label: "Email",
100
- fullWidth: true,
101
- },
102
- ],
103
- },
104
- {
105
- title: "Account Settings",
106
- fields: [
107
- {
108
- name: "role",
109
- type: "select",
110
- label: "Role",
111
- options: [
112
- { value: "user", label: "User" },
113
- { value: "admin", label: "Admin" },
114
- ],
115
- },
116
- {
117
- name: "notifications",
118
- type: "checkbox",
119
- label: "Enable notifications",
120
- // Conditional field - only show for admins
121
- condition: (values) => values.role === "admin",
122
- },
123
- ],
124
- },
125
- ],
126
- };
127
- ```
128
-
129
- ### 3. Render Your Form
130
-
131
- ```tsx
132
- import { useForm } from "react-hook-form";
133
- import { FormGenerator } from "@classytic/formkit";
134
- import { FormProvider } from "./form-adapter";
135
-
136
- function UserForm() {
137
- const form = useForm();
138
-
139
- const onSubmit = (data: any) => {
140
- console.log("Form data:", data);
141
- };
142
-
143
- return (
144
- <FormProvider>
145
- <form onSubmit={form.handleSubmit(onSubmit)}>
146
- <FormGenerator schema={userFormSchema} control={form.control} />
147
- <button type="submit">Submit</button>
148
- </form>
149
- </FormProvider>
150
- );
151
- }
152
- ```
153
-
154
- ## 📖 API Reference
155
-
156
- ### `<FormSystemProvider>`
157
-
158
- The root provider that registers your components.
159
-
160
- ```tsx
161
- <FormSystemProvider components={componentsMap} layouts={layoutsMap}>
162
- {children}
163
- </FormSystemProvider>
164
- ```
165
-
166
- **Props:**
167
- - `components`: Object mapping field types to React components
168
- - `layouts`: Object mapping layout types to React components
169
- - `children`: React children
170
-
171
- ### `<FormGenerator>`
172
-
173
- Renders forms from schemas.
174
-
175
- ```tsx
176
- <FormGenerator schema={schema} control={control} disabled={false} variant="default" />
177
- ```
178
-
179
- **Props:**
180
- - `schema`: Form schema object
181
- - `control?`: React Hook Form control (optional if inside FormProvider)
182
- - `disabled?`: Global disabled state
183
- - `variant?`: Component variant to use
184
-
185
- ### Type Exports
186
-
187
- ```tsx
188
- import type {
189
- FormSchema,
190
- BaseField,
191
- Section,
192
- FieldComponentProps,
193
- ComponentRegistry,
194
- } from "@classytic/formkit";
195
- ```
196
-
197
- ## 🎨 Advanced Usage
198
-
199
- ### Variants
200
-
201
- Support multiple UI styles:
202
-
203
- ```tsx
204
- const components = {
205
- text: DefaultInput,
206
- compact: {
207
- text: CompactInput,
208
- select: CompactSelect,
209
- },
210
- };
211
-
212
- // Use compact variant
213
- <FormGenerator schema={schema} variant="compact" />
214
- ```
215
-
216
- ### Conditional Fields
217
-
218
- ```tsx
219
- {
220
- name: "vatNumber",
221
- type: "text",
222
- label: "VAT Number",
223
- condition: (values) => values.country === "EU",
224
- }
225
- ```
226
-
227
- ### Custom Renderers
228
-
229
- ```tsx
230
- {
231
- title: "Custom Section",
232
- render: ({ control, disabled }) => (
233
- <div>Your custom JSX here</div>
234
- ),
235
- }
236
- ```
237
-
238
- ### Full Width Fields
239
-
240
- ```tsx
241
- {
242
- name: "description",
243
- type: "textarea",
244
- label: "Description",
245
- fullWidth: true, // Spans all columns
246
- }
247
- ```
248
-
249
- ## 🧪 Testing
250
-
251
- ```bash
252
- npm test
253
- ```
254
-
255
- ## 📄 License
256
-
257
- MIT © [Classytic](https://github.com/classytic)
258
-
259
- ## 🤝 Contributing
260
-
261
- Contributions are welcome! Please feel free to submit a Pull Request.
262
-
263
- ## 🔗 Links
264
-
265
- - [GitHub Repository](https://github.com/classytic/formkit)
266
- - [Issue Tracker](https://github.com/classytic/formkit/issues)
267
- - [npm Package](https://www.npmjs.com/package/@classytic/formkit)
268
-
269
- ## 🌟 Related Packages
270
-
271
- - [@classytic/notifications](https://www.npmjs.com/package/@classytic/notifications) - Multi-channel notification system
272
- - [@classytic/mongokit](https://www.npmjs.com/package/@classytic/mongokit) - Event-driven MongoDB repositories
273
-
1
+ # @classytic/formkit
2
+
3
+ Headless, type-safe form generation engine for React 18/19. Schema-driven with full TypeScript support.
4
+
5
+ [![npm](https://img.shields.io/npm/v/@classytic/formkit.svg)](https://www.npmjs.com/package/@classytic/formkit)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue.svg)](https://www.typescriptlang.org/)
8
+
9
+ ## Features
10
+
11
+ - 🎯 **Headless** - Bring your own UI components (Shadcn, MUI, Chakra, etc.)
12
+ - 📝 **Schema-driven** - Define forms with JSON/TypeScript schemas
13
+ - 🔒 **Type-safe** - Full TypeScript support with generics
14
+ - **React Hook Form** - Built on top of the best form library
15
+ - 🎨 **Variants** - Support for multiple component variants
16
+ - 🔀 **Conditional fields** - Show/hide fields based on form values
17
+ - 📱 **Responsive layouts** - Multi-column grid layouts
18
+ - 🪶 **Lightweight** - ~6KB minified, tree-shakeable
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ npm install @classytic/formkit react-hook-form
24
+ # or
25
+ pnpm add @classytic/formkit react-hook-form
26
+ # or
27
+ yarn add @classytic/formkit react-hook-form
28
+ ```
29
+
30
+ ## Quick Start
31
+
32
+ ### 1. Create Field Components
33
+
34
+ Each field component wraps your UI library with `react-hook-form`'s Controller:
35
+
36
+ ```tsx
37
+ // components/form/form-input.tsx
38
+ "use client";
39
+
40
+ import { Controller } from "react-hook-form";
41
+ import type { FieldComponentProps } from "@classytic/formkit";
42
+ import { Input } from "@/components/ui/input";
43
+ import { Label } from "@/components/ui/label";
44
+
45
+ export function FormInput({ control, name, label, placeholder, required }: FieldComponentProps) {
46
+ return (
47
+ <Controller
48
+ name={name}
49
+ control={control}
50
+ render={({ field, fieldState }) => (
51
+ <div className="space-y-2">
52
+ {label && (
53
+ <Label htmlFor={name}>
54
+ {label}
55
+ {required && <span className="text-red-500 ml-1">*</span>}
56
+ </Label>
57
+ )}
58
+ <Input {...field} id={name} placeholder={placeholder} />
59
+ {fieldState.error && (
60
+ <p className="text-sm text-red-500">{fieldState.error.message}</p>
61
+ )}
62
+ </div>
63
+ )}
64
+ />
65
+ );
66
+ }
67
+ ```
68
+
69
+ ### 2. Create Form Adapter
70
+
71
+ Register your components and layouts:
72
+
73
+ ```tsx
74
+ // lib/form-adapter.tsx
75
+ "use client";
76
+
77
+ import { FormSystemProvider, type ComponentRegistry, type LayoutRegistry } from "@classytic/formkit";
78
+ import { FormInput } from "@/components/form/form-input";
79
+
80
+ const components: ComponentRegistry = {
81
+ text: FormInput,
82
+ email: FormInput,
83
+ password: FormInput,
84
+ // Add more field types...
85
+ };
86
+
87
+ const layouts: LayoutRegistry = {
88
+ section: ({ title, description, children }) => (
89
+ <div className="space-y-4">
90
+ {title && <h3 className="text-lg font-semibold">{title}</h3>}
91
+ {description && <p className="text-muted-foreground">{description}</p>}
92
+ {children}
93
+ </div>
94
+ ),
95
+ grid: ({ children, cols = 1 }) => (
96
+ <div className={`grid grid-cols-${cols} gap-4`}>{children}</div>
97
+ ),
98
+ };
99
+
100
+ export function FormProvider({ children }: { children: React.ReactNode }) {
101
+ return (
102
+ <FormSystemProvider components={components} layouts={layouts}>
103
+ {children}
104
+ </FormSystemProvider>
105
+ );
106
+ }
107
+ ```
108
+
109
+ ### 3. Use FormGenerator
110
+
111
+ ```tsx
112
+ // app/signup/page.tsx
113
+ "use client";
114
+
115
+ import { useForm } from "react-hook-form";
116
+ import { zodResolver } from "@hookform/resolvers/zod";
117
+ import { z } from "zod";
118
+ import { FormGenerator, type FormSchema } from "@classytic/formkit";
119
+ import { FormProvider } from "@/lib/form-adapter";
120
+
121
+ // Validation schema
122
+ const signupSchema = z.object({
123
+ firstName: z.string().min(2),
124
+ lastName: z.string().min(2),
125
+ email: z.string().email(),
126
+ password: z.string().min(8),
127
+ });
128
+
129
+ type SignupData = z.infer<typeof signupSchema>;
130
+
131
+ // Form schema (type-safe!)
132
+ const formSchema: FormSchema<SignupData> = {
133
+ sections: [
134
+ {
135
+ title: "Personal Information",
136
+ cols: 2,
137
+ fields: [
138
+ { name: "firstName", type: "text", label: "First Name", required: true },
139
+ { name: "lastName", type: "text", label: "Last Name", required: true },
140
+ ],
141
+ },
142
+ {
143
+ title: "Account",
144
+ fields: [
145
+ { name: "email", type: "email", label: "Email", required: true },
146
+ { name: "password", type: "password", label: "Password", required: true },
147
+ ],
148
+ },
149
+ ],
150
+ };
151
+
152
+ export default function SignupPage() {
153
+ const form = useForm<SignupData>({
154
+ resolver: zodResolver(signupSchema),
155
+ });
156
+
157
+ return (
158
+ <FormProvider>
159
+ <form onSubmit={form.handleSubmit(console.log)} className="space-y-8">
160
+ <FormGenerator schema={formSchema} control={form.control} />
161
+ <button type="submit">Sign Up</button>
162
+ </form>
163
+ </FormProvider>
164
+ );
165
+ }
166
+ ```
167
+
168
+ ## API Reference
169
+
170
+ ### FormGenerator
171
+
172
+ The main component that renders forms from a schema.
173
+
174
+ ```tsx
175
+ <FormGenerator
176
+ schema={formSchema} // Required: Form schema
177
+ control={form.control} // Required: React Hook Form control
178
+ disabled={false} // Optional: Disable all fields
179
+ variant="default" // Optional: Global variant
180
+ className="my-form" // Optional: Root element class
181
+ />
182
+ ```
183
+
184
+ ### FormSchema
185
+
186
+ ```tsx
187
+ interface FormSchema<T extends FieldValues = FieldValues> {
188
+ sections: Section<T>[];
189
+ }
190
+
191
+ interface Section<T> {
192
+ id?: string; // Unique identifier
193
+ title?: string; // Section title
194
+ description?: string; // Section description
195
+ icon?: ReactNode; // Section icon
196
+ fields?: BaseField<T>[]; // Fields in this section
197
+ cols?: number; // Grid columns (1-6)
198
+ gap?: number; // Grid gap
199
+ variant?: string; // Section variant
200
+ className?: string; // Custom class
201
+ collapsible?: boolean; // Make section collapsible
202
+ defaultCollapsed?: boolean;
203
+ condition?: (control) => boolean; // Conditional rendering
204
+ render?: (props) => ReactNode; // Custom render function
205
+ }
206
+ ```
207
+
208
+ ### BaseField
209
+
210
+ ```tsx
211
+ interface BaseField<T> {
212
+ name: string; // Field name (required)
213
+ type: FieldType; // Field type (required)
214
+ label?: string; // Field label
215
+ placeholder?: string; // Placeholder text
216
+ helperText?: string; // Helper text below field
217
+ disabled?: boolean; // Disable field
218
+ required?: boolean; // Mark as required
219
+ readOnly?: boolean; // Read-only field
220
+ variant?: string; // Field variant
221
+ fullWidth?: boolean; // Span full grid width
222
+ className?: string; // Custom class
223
+ defaultValue?: unknown; // Default value
224
+
225
+ // Conditional rendering
226
+ condition?: (formValues: T) => boolean;
227
+
228
+ // For select/radio/checkbox
229
+ options?: FieldOption[];
230
+
231
+ // HTML input attributes
232
+ min?: number | string;
233
+ max?: number | string;
234
+ step?: number;
235
+ pattern?: string;
236
+ minLength?: number;
237
+ maxLength?: number;
238
+ rows?: number; // For textarea
239
+ multiple?: boolean; // For select/file
240
+ accept?: string; // For file input
241
+ autoComplete?: string;
242
+ autoFocus?: boolean;
243
+
244
+ // Custom props
245
+ [key: string]: unknown;
246
+ }
247
+ ```
248
+
249
+ ### FieldComponentProps
250
+
251
+ Props passed to your field components:
252
+
253
+ ```tsx
254
+ interface FieldComponentProps<T extends FieldValues = FieldValues> extends BaseField<T> {
255
+ field: BaseField<T>; // Full field config
256
+ control: Control<T>; // React Hook Form control
257
+ disabled?: boolean; // Merged disabled state
258
+ variant?: string; // Active variant
259
+ }
260
+ ```
261
+
262
+ ### ComponentRegistry
263
+
264
+ ```tsx
265
+ const components: ComponentRegistry = {
266
+ // Simple mapping
267
+ text: FormInput,
268
+ select: FormSelect,
269
+
270
+ // Variant-specific components
271
+ compact: {
272
+ text: CompactInput,
273
+ select: CompactSelect,
274
+ },
275
+ };
276
+ ```
277
+
278
+ ### LayoutRegistry
279
+
280
+ ```tsx
281
+ const layouts: LayoutRegistry = {
282
+ section: SectionLayout,
283
+ grid: GridLayout,
284
+
285
+ // Variant-specific layouts
286
+ compact: {
287
+ section: CompactSection,
288
+ },
289
+ };
290
+ ```
291
+
292
+ ## Advanced Features
293
+
294
+ ### Conditional Fields
295
+
296
+ ```tsx
297
+ {
298
+ name: "companyName",
299
+ type: "text",
300
+ label: "Company Name",
301
+ condition: (values) => values.accountType === "business",
302
+ }
303
+ ```
304
+
305
+ ### Variants
306
+
307
+ Apply different styles based on context:
308
+
309
+ ```tsx
310
+ // Register variant-specific components
311
+ const components = {
312
+ text: DefaultInput,
313
+ compact: {
314
+ text: CompactInput,
315
+ },
316
+ };
317
+
318
+ // Use variant in schema
319
+ const schema = {
320
+ sections: [{
321
+ variant: "compact", // All fields use compact variant
322
+ fields: [...]
323
+ }]
324
+ };
325
+
326
+ // Or per-field
327
+ {
328
+ name: "notes",
329
+ type: "text",
330
+ variant: "compact",
331
+ }
332
+ ```
333
+
334
+ ### Custom Section Render
335
+
336
+ ```tsx
337
+ {
338
+ title: "Payment",
339
+ render: ({ control, disabled }) => (
340
+ <StripeElements>
341
+ <CardElement />
342
+ <FormInput name="billingName" control={control} />
343
+ </StripeElements>
344
+ ),
345
+ }
346
+ ```
347
+
348
+ ### Grouped Select Options
349
+
350
+ ```tsx
351
+ {
352
+ name: "country",
353
+ type: "select",
354
+ options: [
355
+ {
356
+ label: "North America",
357
+ options: [
358
+ { value: "us", label: "United States" },
359
+ { value: "ca", label: "Canada" },
360
+ ],
361
+ },
362
+ {
363
+ label: "Europe",
364
+ options: [
365
+ { value: "uk", label: "United Kingdom" },
366
+ { value: "de", label: "Germany" },
367
+ ],
368
+ },
369
+ ],
370
+ }
371
+ ```
372
+
373
+ ## Type Exports
374
+
375
+ ```tsx
376
+ import type {
377
+ // Core
378
+ FormSchema,
379
+ FormGeneratorProps,
380
+ BaseField,
381
+ Section,
382
+
383
+ // Components
384
+ FieldComponentProps,
385
+ FieldComponent,
386
+ ComponentRegistry,
387
+
388
+ // Layouts
389
+ SectionLayoutProps,
390
+ GridLayoutProps,
391
+ LayoutComponent,
392
+ LayoutRegistry,
393
+
394
+ // Options
395
+ FieldOption,
396
+ FieldOptionGroup,
397
+
398
+ // Utility types
399
+ FieldType,
400
+ LayoutType,
401
+ Variant,
402
+ DefineField,
403
+ InferSchemaValues,
404
+ SchemaFieldNames,
405
+ } from "@classytic/formkit";
406
+ ```
407
+
408
+ ## Examples
409
+
410
+ See the [`example/shadcn`](./example/shadcn) directory for complete working examples with:
411
+
412
+ - Form components (Input, Select, Checkbox)
413
+ - Full adapter configuration
414
+ - Zod validation
415
+ - Conditional fields
416
+ - Multi-column layouts
417
+ - TypeScript integration
418
+
419
+ ## Browser Support
420
+
421
+ - React 18.0+
422
+ - React 19.0+
423
+ - All modern browsers
424
+
425
+ ## License
426
+
427
+ MIT © [Classytic](https://github.com/classytic)
428
+
429
+ ## Links
430
+
431
+ - [GitHub](https://github.com/classytic/formkit)
432
+ - [npm](https://www.npmjs.com/package/@classytic/formkit)
433
+ - [Examples](https://github.com/classytic/formkit/tree/main/example/shadcn)
434
+ - [Issues](https://github.com/classytic/formkit/issues)