@classytic/formkit 1.0.3 → 1.2.0

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,6 +1,6 @@
1
1
  # @classytic/formkit
2
2
 
3
- Headless, type-safe form generation engine for React 18/19. Schema-driven with full TypeScript support.
3
+ Headless, type-safe form generation engine for React 19. Schema-driven with full TypeScript support.
4
4
 
5
5
  [![npm](https://img.shields.io/npm/v/@classytic/formkit.svg)](https://www.npmjs.com/package/@classytic/formkit)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
@@ -8,14 +8,24 @@ Headless, type-safe form generation engine for React 18/19. Schema-driven with f
8
8
 
9
9
  ## Features
10
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
11
+ - **Minimal boilerplate** - `useFormKit` hook: 5 lines to set up a complete form
12
+ - **Headless** - Bring your own UI components (Shadcn, MUI, Chakra, etc.)
13
+ - **Schema-driven** - Define forms with JSON/TypeScript schemas, defaults extracted automatically
14
+ - **Type-safe** - Full TypeScript support with generics
15
+ - **React Hook Form** - Built on top of the best form library
16
+ - **React 19** - Uses modern React 19 patterns (Context as provider, ref as prop)
17
+ - **Server Components** - Dedicated `@classytic/formkit/server` entry point for RSC
18
+ - **Variants** - Support for multiple component variants
19
+ - **Conditional fields** - Show/hide fields based on form values (function, DSL rules, AND/OR logic)
20
+ - **Responsive layouts** - Multi-column grid layouts
21
+ - **Accessibility** - Auto-generated `fieldId`, `error`, and `fieldState` props
22
+ - **Validation helpers** - `buildValidationRules` generates RHF rules from schema props
23
+ - **Lightweight** - ~7KB gzipped, tree-shakeable
24
+
25
+ ## Requirements
26
+
27
+ - **React 19.0+** (React 18 is not supported)
28
+ - **React Hook Form 7.54.0+**
19
29
 
20
30
  ## Installation
21
31
 
@@ -31,7 +41,7 @@ yarn add @classytic/formkit react-hook-form
31
41
 
32
42
  ### 1. Create Field Components
33
43
 
34
- Each field component wraps your UI library with `react-hook-form`'s Controller:
44
+ Each field component receives `FieldComponentProps` including `error`, `fieldId`, and the full `field` config:
35
45
 
36
46
  ```tsx
37
47
  // components/form/form-input.tsx
@@ -42,22 +52,30 @@ import type { FieldComponentProps } from "@classytic/formkit";
42
52
  import { Input } from "@/components/ui/input";
43
53
  import { Label } from "@/components/ui/label";
44
54
 
45
- export function FormInput({ control, name, label, placeholder, required }: FieldComponentProps) {
55
+ export function FormInput({
56
+ control,
57
+ field,
58
+ label,
59
+ placeholder,
60
+ required,
61
+ error,
62
+ fieldId,
63
+ }: FieldComponentProps) {
46
64
  return (
47
65
  <Controller
48
- name={name}
66
+ name={field.name}
49
67
  control={control}
50
- render={({ field, fieldState }) => (
68
+ render={({ field: rhfField }) => (
51
69
  <div className="space-y-2">
52
70
  {label && (
53
- <Label htmlFor={name}>
71
+ <Label htmlFor={fieldId}>
54
72
  {label}
55
73
  {required && <span className="text-red-500 ml-1">*</span>}
56
74
  </Label>
57
75
  )}
58
- <Input {...field} id={name} placeholder={placeholder} />
59
- {fieldState.error && (
60
- <p className="text-sm text-red-500">{fieldState.error.message}</p>
76
+ <Input {...rhfField} id={fieldId} placeholder={placeholder} />
77
+ {error && (
78
+ <p className="text-sm text-red-500">{error.message}</p>
61
79
  )}
62
80
  </div>
63
81
  )}
@@ -74,7 +92,11 @@ Register your components and layouts:
74
92
  // lib/form-adapter.tsx
75
93
  "use client";
76
94
 
77
- import { FormSystemProvider, type ComponentRegistry, type LayoutRegistry } from "@classytic/formkit";
95
+ import {
96
+ FormSystemProvider,
97
+ type ComponentRegistry,
98
+ type LayoutRegistry,
99
+ } from "@classytic/formkit";
78
100
  import { FormInput } from "@/components/form/form-input";
79
101
 
80
102
  const components: ComponentRegistry = {
@@ -112,13 +134,11 @@ export function FormProvider({ children }: { children: React.ReactNode }) {
112
134
  // app/signup/page.tsx
113
135
  "use client";
114
136
 
115
- import { useForm } from "react-hook-form";
116
137
  import { zodResolver } from "@hookform/resolvers/zod";
117
138
  import { z } from "zod";
118
- import { FormGenerator, type FormSchema } from "@classytic/formkit";
139
+ import { FormGenerator, useFormKit, type FormSchema } from "@classytic/formkit";
119
140
  import { FormProvider } from "@/lib/form-adapter";
120
141
 
121
- // Validation schema
122
142
  const signupSchema = z.object({
123
143
  firstName: z.string().min(2),
124
144
  lastName: z.string().min(2),
@@ -128,36 +148,36 @@ const signupSchema = z.object({
128
148
 
129
149
  type SignupData = z.infer<typeof signupSchema>;
130
150
 
131
- // Form schema (type-safe!)
132
151
  const formSchema: FormSchema<SignupData> = {
133
152
  sections: [
134
153
  {
135
154
  title: "Personal Information",
136
155
  cols: 2,
137
156
  fields: [
138
- { name: "firstName", type: "text", label: "First Name", required: true },
139
- { name: "lastName", type: "text", label: "Last Name", required: true },
157
+ { name: "firstName", type: "text", label: "First Name", required: true, defaultValue: "" },
158
+ { name: "lastName", type: "text", label: "Last Name", required: true, defaultValue: "" },
140
159
  ],
141
160
  },
142
161
  {
143
162
  title: "Account",
144
163
  fields: [
145
- { name: "email", type: "email", label: "Email", required: true },
146
- { name: "password", type: "password", label: "Password", required: true },
164
+ { name: "email", type: "email", label: "Email", required: true, defaultValue: "" },
165
+ { name: "password", type: "password", label: "Password", required: true, defaultValue: "" },
147
166
  ],
148
167
  },
149
168
  ],
150
169
  };
151
170
 
152
171
  export default function SignupPage() {
153
- const form = useForm<SignupData>({
172
+ const { handleSubmit, generatorProps } = useFormKit({
173
+ schema: formSchema,
154
174
  resolver: zodResolver(signupSchema),
155
175
  });
156
176
 
157
177
  return (
158
178
  <FormProvider>
159
- <form onSubmit={form.handleSubmit(console.log)} className="space-y-8">
160
- <FormGenerator schema={formSchema} control={form.control} />
179
+ <form onSubmit={handleSubmit(console.log)} className="space-y-8">
180
+ <FormGenerator {...generatorProps} />
161
181
  <button type="submit">Sign Up</button>
162
182
  </form>
163
183
  </FormProvider>
@@ -167,67 +187,112 @@ export default function SignupPage() {
167
187
 
168
188
  ## API Reference
169
189
 
190
+ ### useFormKit
191
+
192
+ Convenience hook that combines schema default extraction with react-hook-form setup. Returns all `useForm` methods plus ready-to-spread `generatorProps`.
193
+
194
+ ```tsx
195
+ import { useFormKit, FormGenerator } from "@classytic/formkit";
196
+
197
+ const { handleSubmit, generatorProps, formState, reset, ...rest } = useFormKit({
198
+ schema: formSchema,
199
+ resolver: zodResolver(validationSchema), // optional
200
+ defaultValues: { email: "pre@fill.com" }, // optional overrides
201
+ disabled: false, // optional
202
+ variant: "compact", // optional
203
+ className: "my-form", // optional
204
+ mode: "onBlur", // any useForm option
205
+ });
206
+
207
+ return (
208
+ <form onSubmit={handleSubmit(onSubmit)}>
209
+ <FormGenerator {...generatorProps} />
210
+ <button type="submit">Submit</button>
211
+ </form>
212
+ );
213
+ ```
214
+
215
+ Schema `defaultValue` fields are automatically extracted and merged with any explicit `defaultValues` you provide (explicit values take priority).
216
+
170
217
  ### FormGenerator
171
218
 
172
- The main component that renders forms from a schema.
219
+ The main component that renders forms from a schema. Supports React 19 `ref` as a regular prop.
173
220
 
174
221
  ```tsx
175
222
  <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
223
+ schema={formSchema} // Required: Form schema
224
+ control={form.control} // Optional: React Hook Form control (or wrap in <FormProvider>)
225
+ disabled={false} // Optional: Disable all fields
226
+ variant="default" // Optional: Global variant
227
+ className="my-form" // Optional: Root element class
228
+ ref={formRef} // Optional: Ref to the root <div> (React 19 ref-as-prop)
181
229
  />
182
230
  ```
183
231
 
184
232
  ### FormSchema
185
233
 
186
- ```tsx
234
+ ```ts
187
235
  interface FormSchema<T extends FieldValues = FieldValues> {
188
236
  sections: Section<T>[];
189
237
  }
238
+ ```
239
+
240
+ ### Section
190
241
 
242
+ ```ts
191
243
  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
244
+ id?: string; // Unique identifier
245
+ title?: string; // Section title
246
+ description?: string; // Section description
247
+ icon?: ReactNode; // Section icon
248
+ fields?: BaseField<T>[]; // Fields in this section
249
+ cols?: number; // Grid columns (1-6)
250
+ gap?: number; // Grid gap
251
+ variant?: string; // Section variant
252
+ className?: string; // Custom class
253
+ collapsible?: boolean; // Make section collapsible
202
254
  defaultCollapsed?: boolean;
203
- condition?: (control) => boolean; // Conditional rendering
204
- render?: (props) => ReactNode; // Custom render function
255
+ nameSpace?: string; // Prefix for nested object fields (e.g. "address")
256
+
257
+ // Conditional rendering (function, DSL rule, or ConditionConfig)
258
+ condition?: Condition<T>;
259
+
260
+ // Custom render function (bypasses grid layout)
261
+ render?: (props: SectionRenderProps<T>) => ReactNode;
205
262
  }
206
263
  ```
207
264
 
208
265
  ### BaseField
209
266
 
210
- ```tsx
267
+ ```ts
211
268
  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
-
269
+ name: string; // Field name (required)
270
+ type: FieldType; // Field type (required)
271
+ label?: string; // Field label
272
+ placeholder?: string; // Placeholder text
273
+ helperText?: string; // Helper text below field
274
+ disabled?: boolean; // Disable field
275
+ required?: boolean; // Mark as required
276
+ readOnly?: boolean; // Read-only field
277
+ variant?: string; // Field variant
278
+ fullWidth?: boolean; // Span full grid width
279
+ className?: string; // Custom class
280
+ defaultValue?: unknown; // Default value
281
+
225
282
  // Conditional rendering
226
- condition?: (formValues: T) => boolean;
227
-
283
+ condition?: Condition<T>;
284
+ watchNames?: string | string[]; // Optimize useWatch performance
285
+
286
+ // Dynamic options loading
287
+ loadOptions?: (formValues: Partial<T>) => Promise<FieldOption[]> | FieldOption[];
288
+ debounceMs?: number;
289
+
290
+ // For array/grouped types
291
+ itemFields?: BaseField<T>[];
292
+
228
293
  // For select/radio/checkbox
229
294
  options?: FieldOption[];
230
-
295
+
231
296
  // HTML input attributes
232
297
  min?: number | string;
233
298
  max?: number | string;
@@ -235,14 +300,17 @@ interface BaseField<T> {
235
300
  pattern?: string;
236
301
  minLength?: number;
237
302
  maxLength?: number;
238
- rows?: number; // For textarea
239
- multiple?: boolean; // For select/file
240
- accept?: string; // For file input
303
+ rows?: number;
304
+ multiple?: boolean;
305
+ accept?: string;
241
306
  autoComplete?: string;
242
307
  autoFocus?: boolean;
243
-
244
- // Custom props
245
- [key: string]: unknown;
308
+
309
+ // Custom render override
310
+ render?: (props: FieldComponentProps<T>) => ReactNode;
311
+
312
+ // Arbitrary extra props for custom components
313
+ customProps?: Record<string, unknown>;
246
314
  }
247
315
  ```
248
316
 
@@ -250,23 +318,64 @@ interface BaseField<T> {
250
318
 
251
319
  Props passed to your field components:
252
320
 
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
321
+ ```ts
322
+ interface FieldComponentProps<T extends FieldValues = FieldValues>
323
+ extends BaseField<T> {
324
+ field: BaseField<T>; // Full field config
325
+ control: Control<T>; // React Hook Form control
326
+ disabled?: boolean; // Merged disabled state
327
+ variant?: string; // Active variant
328
+ error?: FieldError; // Field error from react-hook-form
329
+ fieldState?: { // Field state metadata
330
+ invalid: boolean;
331
+ isDirty: boolean;
332
+ isTouched: boolean;
333
+ isValidating: boolean;
334
+ error?: FieldError;
335
+ };
336
+ fieldId: string; // Generated ID for label-input association (e.g. "formkit-field-email")
259
337
  }
260
338
  ```
261
339
 
340
+ ### Condition Types
341
+
342
+ Conditions can be a function, a DSL rule, an array of rules (AND), or a `ConditionConfig` (AND/OR):
343
+
344
+ ```ts
345
+ // Function condition
346
+ condition: (values) => values.accountType === "business"
347
+
348
+ // Single DSL rule
349
+ condition: { watch: "country", operator: "===", value: "US" }
350
+
351
+ // Array of rules (AND - all must match)
352
+ condition: [
353
+ { watch: "country", operator: "===", value: "US" },
354
+ { watch: "age", operator: "truthy" },
355
+ ]
356
+
357
+ // ConditionConfig with OR logic
358
+ condition: {
359
+ rules: [
360
+ { watch: "country", operator: "===", value: "US" },
361
+ { watch: "country", operator: "===", value: "CA" },
362
+ ],
363
+ logic: "or",
364
+ }
365
+ ```
366
+
367
+ **Supported operators:** `===`, `!==`, `in`, `not-in`, `truthy`, `falsy`
368
+
369
+ **Nested paths:** DSL rules support dot-notation paths like `"address.city"` for nested form values.
370
+
262
371
  ### ComponentRegistry
263
372
 
264
- ```tsx
373
+ ```ts
265
374
  const components: ComponentRegistry = {
266
375
  // Simple mapping
267
376
  text: FormInput,
268
377
  select: FormSelect,
269
-
378
+
270
379
  // Variant-specific components
271
380
  compact: {
272
381
  text: CompactInput,
@@ -277,11 +386,11 @@ const components: ComponentRegistry = {
277
386
 
278
387
  ### LayoutRegistry
279
388
 
280
- ```tsx
389
+ ```ts
281
390
  const layouts: LayoutRegistry = {
282
391
  section: SectionLayout,
283
392
  grid: GridLayout,
284
-
393
+
285
394
  // Variant-specific layouts
286
395
  compact: {
287
396
  section: CompactSection,
@@ -289,11 +398,77 @@ const layouts: LayoutRegistry = {
289
398
  };
290
399
  ```
291
400
 
401
+ ### extractDefaultValues
402
+
403
+ Extracts default values from a schema. Server-safe (no hooks).
404
+
405
+ ```ts
406
+ import { extractDefaultValues } from "@classytic/formkit"; // or /server
407
+
408
+ const defaults = extractDefaultValues(formSchema);
409
+ // { firstName: "", lastName: "", email: "", password: "" }
410
+
411
+ // Use with react-hook-form
412
+ const form = useForm({ defaultValues: defaults });
413
+ ```
414
+
415
+ Respects `nameSpace` prefixes and group `itemFields` defaults.
416
+
417
+ ### buildValidationRules
418
+
419
+ Generates react-hook-form validation rules from a field's schema props. Server-safe (no hooks).
420
+
421
+ ```ts
422
+ import { buildValidationRules } from "@classytic/formkit"; // or /server
423
+
424
+ function FormInput({ field, control, error, fieldId }: FieldComponentProps) {
425
+ const rules = buildValidationRules(field);
426
+ return (
427
+ <Controller
428
+ name={field.name}
429
+ control={control}
430
+ rules={rules}
431
+ render={({ field: rhf }) => <input {...rhf} id={fieldId} />}
432
+ />
433
+ );
434
+ }
435
+ ```
436
+
437
+ Maps `required`, `min`, `max`, `minLength`, `maxLength`, and `pattern` from the field schema to RHF-compatible rules with auto-generated error messages.
438
+
439
+ ## Server Components
440
+
441
+ The `@classytic/formkit/server` entry point exports server-safe utilities with no React hooks or client-side code:
442
+
443
+ ```ts
444
+ import {
445
+ cn,
446
+ defineSchema,
447
+ defineField,
448
+ defineSection,
449
+ evaluateCondition,
450
+ extractWatchNames,
451
+ extractDefaultValues,
452
+ buildValidationRules,
453
+ } from "@classytic/formkit/server";
454
+
455
+ // Type-only imports also available
456
+ import type {
457
+ FormSchema,
458
+ BaseField,
459
+ Section,
460
+ ConditionRule,
461
+ ConditionConfig,
462
+ } from "@classytic/formkit/server";
463
+ ```
464
+
465
+ Use this entry point in React Server Components to define schemas, evaluate conditions, or use `cn` without pulling in client-side code.
466
+
292
467
  ## Advanced Features
293
468
 
294
- ### Conditional Fields
469
+ ### Conditional Fields (Function)
295
470
 
296
- ```tsx
471
+ ```ts
297
472
  {
298
473
  name: "companyName",
299
474
  type: "text",
@@ -302,6 +477,73 @@ const layouts: LayoutRegistry = {
302
477
  }
303
478
  ```
304
479
 
480
+ ### Conditional Fields (DSL Rules)
481
+
482
+ ```ts
483
+ {
484
+ name: "stateField",
485
+ type: "select",
486
+ label: "State",
487
+ condition: { watch: "country", operator: "===", value: "US" },
488
+ watchNames: ["country"], // Optimizes re-renders
489
+ }
490
+ ```
491
+
492
+ ### Conditional Sections
493
+
494
+ ```ts
495
+ {
496
+ title: "Business Details",
497
+ condition: (values) => values.accountType === "business",
498
+ fields: [
499
+ { name: "companyName", type: "text", label: "Company" },
500
+ { name: "taxId", type: "text", label: "Tax ID" },
501
+ ],
502
+ }
503
+ ```
504
+
505
+ ### OR Conditions
506
+
507
+ ```ts
508
+ {
509
+ name: "taxField",
510
+ type: "text",
511
+ condition: {
512
+ rules: [
513
+ { watch: "country", operator: "===", value: "US" },
514
+ { watch: "country", operator: "===", value: "CA" },
515
+ ],
516
+ logic: "or",
517
+ },
518
+ }
519
+ ```
520
+
521
+ ### Nested Path Conditions
522
+
523
+ DSL rules resolve dot-notation paths for nested form values:
524
+
525
+ ```ts
526
+ {
527
+ name: "zipCode",
528
+ type: "text",
529
+ condition: { watch: "address.country", operator: "===", value: "US" },
530
+ }
531
+ ```
532
+
533
+ ### Namespace Support
534
+
535
+ Prefix all field names in a section with a namespace for nested objects:
536
+
537
+ ```ts
538
+ {
539
+ nameSpace: "address",
540
+ fields: [
541
+ { name: "street", type: "text" }, // Becomes "address.street"
542
+ { name: "city", type: "text" }, // Becomes "address.city"
543
+ ],
544
+ }
545
+ ```
546
+
305
547
  ### Variants
306
548
 
307
549
  Apply different styles based on context:
@@ -315,25 +557,34 @@ const components = {
315
557
  },
316
558
  };
317
559
 
318
- // Use variant in schema
319
- const schema = {
320
- sections: [{
321
- variant: "compact", // All fields use compact variant
322
- fields: [...]
323
- }]
324
- };
560
+ // Use variant on the whole form
561
+ <FormGenerator schema={schema} variant="compact" />
562
+
563
+ // Or per-section
564
+ { variant: "compact", fields: [...] }
325
565
 
326
566
  // Or per-field
567
+ { name: "notes", type: "text", variant: "compact" }
568
+ ```
569
+
570
+ ### Dynamic Options Loading
571
+
572
+ ```ts
327
573
  {
328
- name: "notes",
329
- type: "text",
330
- variant: "compact",
574
+ name: "city",
575
+ type: "select",
576
+ watchNames: ["country"],
577
+ loadOptions: async (values) => {
578
+ const cities = await fetchCities(values.country);
579
+ return cities.map(c => ({ label: c.name, value: c.id }));
580
+ },
581
+ debounceMs: 300,
331
582
  }
332
583
  ```
333
584
 
334
585
  ### Custom Section Render
335
586
 
336
- ```tsx
587
+ ```ts
337
588
  {
338
589
  title: "Payment",
339
590
  render: ({ control, disabled }) => (
@@ -345,9 +596,46 @@ const schema = {
345
596
  }
346
597
  ```
347
598
 
348
- ### Grouped Select Options
599
+ ### Custom Field Render
600
+
601
+ ```ts
602
+ {
603
+ name: "avatar",
604
+ type: "file",
605
+ render: ({ field, control, error, fieldId }) => (
606
+ <AvatarUploader fieldId={fieldId} error={error} />
607
+ ),
608
+ }
609
+ ```
610
+
611
+ ### Custom Props
612
+
613
+ Pass arbitrary props to your field components via `customProps`:
614
+
615
+ ```ts
616
+ {
617
+ name: "bio",
618
+ type: "textarea",
619
+ label: "Biography",
620
+ customProps: {
621
+ maxCharacters: 500,
622
+ showCounter: true,
623
+ },
624
+ }
625
+ ```
626
+
627
+ Access in your component:
349
628
 
350
629
  ```tsx
630
+ function FormTextarea({ field, customProps, ...props }: FieldComponentProps) {
631
+ const maxChars = customProps?.maxCharacters as number;
632
+ // ...
633
+ }
634
+ ```
635
+
636
+ ### Grouped Select Options
637
+
638
+ ```ts
351
639
  {
352
640
  name: "country",
353
641
  type: "select",
@@ -370,31 +658,65 @@ const schema = {
370
658
  }
371
659
  ```
372
660
 
661
+ ### Schema Builder Utilities
662
+
663
+ Type-safe helpers for defining schemas outside of components:
664
+
665
+ ```ts
666
+ import { defineSchema, defineField, defineSection } from "@classytic/formkit/server";
667
+
668
+ const emailField = defineField<MyFormData>({
669
+ name: "email",
670
+ type: "email",
671
+ label: "Email Address",
672
+ required: true,
673
+ });
674
+
675
+ const personalSection = defineSection<MyFormData>({
676
+ title: "Personal Info",
677
+ cols: 2,
678
+ fields: [emailField],
679
+ });
680
+
681
+ const schema = defineSchema<MyFormData>({
682
+ sections: [personalSection],
683
+ });
684
+ ```
685
+
373
686
  ## Type Exports
374
687
 
375
- ```tsx
688
+ ```ts
376
689
  import type {
377
690
  // Core
378
691
  FormSchema,
379
692
  FormGeneratorProps,
380
693
  BaseField,
381
694
  Section,
382
-
695
+
383
696
  // Components
384
697
  FieldComponentProps,
385
698
  FieldComponent,
386
699
  ComponentRegistry,
387
-
700
+
388
701
  // Layouts
389
702
  SectionLayoutProps,
390
703
  GridLayoutProps,
391
704
  LayoutComponent,
392
705
  LayoutRegistry,
393
-
706
+
394
707
  // Options
395
708
  FieldOption,
396
709
  FieldOptionGroup,
397
-
710
+
711
+ // Conditions
712
+ ConditionRule,
713
+ ConditionConfig,
714
+ Condition,
715
+
716
+ // Hook types
717
+ UseFormKitOptions,
718
+ UseFormKitReturn,
719
+
398
720
  // Utility types
399
721
  FieldType,
400
722
  LayoutType,
@@ -402,23 +724,12 @@ import type {
402
724
  DefineField,
403
725
  InferSchemaValues,
404
726
  SchemaFieldNames,
727
+ FormElement,
405
728
  } from "@classytic/formkit";
406
729
  ```
407
730
 
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
731
  ## Browser Support
420
732
 
421
- - React 18.0+
422
733
  - React 19.0+
423
734
  - All modern browsers
424
735