@classytic/formkit 1.0.2 â 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/CHANGELOG.md +56 -0
- package/README.md +346 -73
- package/dist/index.cjs +122 -58
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +304 -81
- package/dist/index.d.ts +304 -81
- package/dist/index.js +119 -60
- package/dist/index.js.map +1 -1
- package/package.json +49 -16
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [1.0.3] - 2024-12-04
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- **Complete TypeScript Migration**: Rewrote entire codebase with strict TypeScript
|
|
13
|
+
- **Improved Type Definitions**: Added comprehensive type exports for better DX
|
|
14
|
+
- **Modern Build System**: Updated tsup configuration for better tree-shaking
|
|
15
|
+
- **Enhanced Types**: Added `FieldOption`, `FieldOptionGroup`, `DefineField` utility types
|
|
16
|
+
- **Better Error Messages**: Improved development warnings with actionable messages
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- New type exports: `FieldOption`, `FieldOptionGroup`, `DefineField`, `SchemaFieldNames`, `InferSchemaValues`
|
|
21
|
+
- Subcomponent exports: `SectionRenderer`, `GridRenderer`, `FieldWrapper`
|
|
22
|
+
- Hook exports: `useFieldComponent`, `useLayoutComponent`
|
|
23
|
+
- Support for `gap` property in sections
|
|
24
|
+
- Support for `collapsible` and `defaultCollapsed` section properties
|
|
25
|
+
- `FormElement` type for consistent JSX return types
|
|
26
|
+
- `ClassValue` re-export from utils
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
- Package exports configuration for proper ESM/CJS interop
|
|
31
|
+
- TypeScript strict mode compatibility
|
|
32
|
+
- Proper peer dependency declarations
|
|
33
|
+
|
|
34
|
+
## [1.0.2] - 2024-12-03
|
|
35
|
+
|
|
36
|
+
### Added
|
|
37
|
+
|
|
38
|
+
- Initial public release
|
|
39
|
+
- FormGenerator component
|
|
40
|
+
- FormSystemProvider context
|
|
41
|
+
- Schema-driven form generation
|
|
42
|
+
- Variant support for components
|
|
43
|
+
- Conditional field rendering
|
|
44
|
+
|
|
45
|
+
## [1.0.1] - 2024-12-02
|
|
46
|
+
|
|
47
|
+
### Fixed
|
|
48
|
+
|
|
49
|
+
- Package configuration fixes
|
|
50
|
+
|
|
51
|
+
## [1.0.0] - 2024-12-01
|
|
52
|
+
|
|
53
|
+
### Added
|
|
54
|
+
|
|
55
|
+
- Initial release
|
|
56
|
+
|
package/README.md
CHANGED
|
@@ -4,41 +4,61 @@ Headless, type-safe form generation engine for React 18/19. Schema-driven with f
|
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@classytic/formkit)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
[](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
|
|
7
19
|
|
|
8
20
|
## Installation
|
|
9
21
|
|
|
10
22
|
```bash
|
|
11
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
|
|
12
28
|
```
|
|
13
29
|
|
|
14
|
-
## Quick Start
|
|
15
|
-
|
|
16
|
-
**Important:** You must use `Controller` from react-hook-form. Raw Shadcn components won't work.
|
|
30
|
+
## Quick Start
|
|
17
31
|
|
|
18
|
-
### 1.
|
|
19
|
-
|
|
20
|
-
```bash
|
|
21
|
-
npx shadcn@latest add input label select
|
|
22
|
-
```
|
|
32
|
+
### 1. Create Field Components
|
|
23
33
|
|
|
24
|
-
|
|
34
|
+
Each field component wraps your UI library with `react-hook-form`'s Controller:
|
|
25
35
|
|
|
26
36
|
```tsx
|
|
27
|
-
// components/form-input.tsx
|
|
37
|
+
// components/form/form-input.tsx
|
|
38
|
+
"use client";
|
|
39
|
+
|
|
28
40
|
import { Controller } from "react-hook-form";
|
|
41
|
+
import type { FieldComponentProps } from "@classytic/formkit";
|
|
29
42
|
import { Input } from "@/components/ui/input";
|
|
30
43
|
import { Label } from "@/components/ui/label";
|
|
31
44
|
|
|
32
|
-
export function FormInput({ control, name, label, placeholder, required
|
|
45
|
+
export function FormInput({ control, name, label, placeholder, required }: FieldComponentProps) {
|
|
33
46
|
return (
|
|
34
47
|
<Controller
|
|
35
48
|
name={name}
|
|
36
49
|
control={control}
|
|
37
50
|
render={({ field, fieldState }) => (
|
|
38
51
|
<div className="space-y-2">
|
|
39
|
-
{label &&
|
|
40
|
-
|
|
41
|
-
|
|
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
|
+
)}
|
|
42
62
|
</div>
|
|
43
63
|
)}
|
|
44
64
|
/>
|
|
@@ -46,110 +66,362 @@ export function FormInput({ control, name, label, placeholder, required, ...prop
|
|
|
46
66
|
}
|
|
47
67
|
```
|
|
48
68
|
|
|
49
|
-
###
|
|
69
|
+
### 2. Create Form Adapter
|
|
70
|
+
|
|
71
|
+
Register your components and layouts:
|
|
50
72
|
|
|
51
73
|
```tsx
|
|
52
74
|
// lib/form-adapter.tsx
|
|
53
|
-
|
|
54
|
-
import { FormInput } from "@/components/form-input";
|
|
75
|
+
"use client";
|
|
55
76
|
|
|
56
|
-
|
|
77
|
+
import { FormSystemProvider, type ComponentRegistry, type LayoutRegistry } from "@classytic/formkit";
|
|
78
|
+
import { FormInput } from "@/components/form/form-input";
|
|
79
|
+
|
|
80
|
+
const components: ComponentRegistry = {
|
|
57
81
|
text: FormInput,
|
|
58
82
|
email: FormInput,
|
|
83
|
+
password: FormInput,
|
|
84
|
+
// Add more field types...
|
|
59
85
|
};
|
|
60
86
|
|
|
61
|
-
const layouts = {
|
|
62
|
-
section: ({ title, children }) =>
|
|
63
|
-
|
|
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
|
+
),
|
|
64
98
|
};
|
|
65
99
|
|
|
66
|
-
export function FormProvider({ children }) {
|
|
67
|
-
return
|
|
100
|
+
export function FormProvider({ children }: { children: React.ReactNode }) {
|
|
101
|
+
return (
|
|
102
|
+
<FormSystemProvider components={components} layouts={layouts}>
|
|
103
|
+
{children}
|
|
104
|
+
</FormSystemProvider>
|
|
105
|
+
);
|
|
68
106
|
}
|
|
69
107
|
```
|
|
70
108
|
|
|
71
|
-
###
|
|
109
|
+
### 3. Use FormGenerator
|
|
72
110
|
|
|
73
111
|
```tsx
|
|
112
|
+
// app/signup/page.tsx
|
|
113
|
+
"use client";
|
|
114
|
+
|
|
74
115
|
import { useForm } from "react-hook-form";
|
|
75
|
-
import {
|
|
76
|
-
import {
|
|
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
|
+
};
|
|
77
151
|
|
|
78
|
-
export function
|
|
79
|
-
const form = useForm(
|
|
152
|
+
export default function SignupPage() {
|
|
153
|
+
const form = useForm<SignupData>({
|
|
154
|
+
resolver: zodResolver(signupSchema),
|
|
155
|
+
});
|
|
80
156
|
|
|
81
157
|
return (
|
|
82
158
|
<FormProvider>
|
|
83
|
-
<form onSubmit={form.handleSubmit(console.log)}>
|
|
84
|
-
<FormGenerator
|
|
85
|
-
|
|
86
|
-
sections: [{
|
|
87
|
-
fields: [
|
|
88
|
-
{ name: "email", type: "email", label: "Email", required: true },
|
|
89
|
-
{ name: "password", type: "text", label: "Password" },
|
|
90
|
-
]
|
|
91
|
-
}]
|
|
92
|
-
}}
|
|
93
|
-
control={form.control}
|
|
94
|
-
/>
|
|
95
|
-
<button type="submit">Submit</button>
|
|
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>
|
|
96
162
|
</form>
|
|
97
163
|
</FormProvider>
|
|
98
164
|
);
|
|
99
165
|
}
|
|
100
166
|
```
|
|
101
167
|
|
|
102
|
-
##
|
|
168
|
+
## API Reference
|
|
103
169
|
|
|
104
|
-
|
|
105
|
-
- FormInput, FormSelect, FormCheckbox
|
|
106
|
-
- Validation with Zod
|
|
107
|
-
- Conditional fields
|
|
108
|
-
- Error handling
|
|
170
|
+
### FormGenerator
|
|
109
171
|
|
|
110
|
-
|
|
172
|
+
The main component that renders forms from a schema.
|
|
111
173
|
|
|
112
|
-
**You MUST use Controller:**
|
|
113
174
|
```tsx
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
<Input {...field} /> // â Spread field
|
|
121
|
-
)}
|
|
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
|
|
122
181
|
/>
|
|
123
182
|
```
|
|
124
183
|
|
|
125
|
-
|
|
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
|
+
|
|
126
296
|
```tsx
|
|
127
297
|
{
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
// ...
|
|
298
|
+
name: "companyName",
|
|
299
|
+
type: "text",
|
|
300
|
+
label: "Company Name",
|
|
301
|
+
condition: (values) => values.accountType === "business",
|
|
133
302
|
}
|
|
134
303
|
```
|
|
135
304
|
|
|
136
|
-
|
|
305
|
+
### Variants
|
|
137
306
|
|
|
138
|
-
|
|
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
|
|
139
335
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
+
```
|
|
146
347
|
|
|
147
|
-
###
|
|
348
|
+
### Grouped Select Options
|
|
148
349
|
|
|
149
350
|
```tsx
|
|
150
|
-
|
|
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
|
+
}
|
|
151
371
|
```
|
|
152
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
|
+
|
|
153
425
|
## License
|
|
154
426
|
|
|
155
427
|
MIT Š [Classytic](https://github.com/classytic)
|
|
@@ -157,5 +429,6 @@ MIT Š [Classytic](https://github.com/classytic)
|
|
|
157
429
|
## Links
|
|
158
430
|
|
|
159
431
|
- [GitHub](https://github.com/classytic/formkit)
|
|
432
|
+
- [npm](https://www.npmjs.com/package/@classytic/formkit)
|
|
160
433
|
- [Examples](https://github.com/classytic/formkit/tree/main/example/shadcn)
|
|
161
434
|
- [Issues](https://github.com/classytic/formkit/issues)
|