@classytic/formkit 1.0.2 → 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,44 +1,82 @@
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)
7
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue.svg)](https://www.typescriptlang.org/)
7
8
 
8
- ## Installation
9
+ ## Features
9
10
 
10
- ```bash
11
- npm install @classytic/formkit react-hook-form
12
- ```
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
13
24
 
14
- ## Quick Start (Shadcn UI)
25
+ ## Requirements
15
26
 
16
- **Important:** You must use `Controller` from react-hook-form. Raw Shadcn components won't work.
27
+ - **React 19.0+** (React 18 is not supported)
28
+ - **React Hook Form 7.54.0+**
17
29
 
18
- ### 1. Install Shadcn Components
30
+ ## Installation
19
31
 
20
32
  ```bash
21
- npx shadcn@latest add input label select
33
+ npm install @classytic/formkit react-hook-form
34
+ # or
35
+ pnpm add @classytic/formkit react-hook-form
36
+ # or
37
+ yarn add @classytic/formkit react-hook-form
22
38
  ```
23
39
 
24
- ### 2. Create Form Component
40
+ ## Quick Start
41
+
42
+ ### 1. Create Field Components
43
+
44
+ Each field component receives `FieldComponentProps` including `error`, `fieldId`, and the full `field` config:
25
45
 
26
46
  ```tsx
27
- // components/form-input.tsx
47
+ // components/form/form-input.tsx
48
+ "use client";
49
+
28
50
  import { Controller } from "react-hook-form";
51
+ import type { FieldComponentProps } from "@classytic/formkit";
29
52
  import { Input } from "@/components/ui/input";
30
53
  import { Label } from "@/components/ui/label";
31
54
 
32
- export function FormInput({ control, name, label, placeholder, required, ...props }) {
55
+ export function FormInput({
56
+ control,
57
+ field,
58
+ label,
59
+ placeholder,
60
+ required,
61
+ error,
62
+ fieldId,
63
+ }: FieldComponentProps) {
33
64
  return (
34
65
  <Controller
35
- name={name}
66
+ name={field.name}
36
67
  control={control}
37
- render={({ field, fieldState }) => (
68
+ render={({ field: rhfField }) => (
38
69
  <div className="space-y-2">
39
- {label && <Label>{label}{required && '*'}</Label>}
40
- <Input {...field} placeholder={placeholder} />
41
- {fieldState.error && <p className="text-sm text-red-500">{fieldState.error.message}</p>}
70
+ {label && (
71
+ <Label htmlFor={fieldId}>
72
+ {label}
73
+ {required && <span className="text-red-500 ml-1">*</span>}
74
+ </Label>
75
+ )}
76
+ <Input {...rhfField} id={fieldId} placeholder={placeholder} />
77
+ {error && (
78
+ <p className="text-sm text-red-500">{error.message}</p>
79
+ )}
42
80
  </div>
43
81
  )}
44
82
  />
@@ -46,110 +84,655 @@ export function FormInput({ control, name, label, placeholder, required, ...prop
46
84
  }
47
85
  ```
48
86
 
49
- ### 3. Create Adapter
87
+ ### 2. Create Form Adapter
88
+
89
+ Register your components and layouts:
50
90
 
51
91
  ```tsx
52
92
  // lib/form-adapter.tsx
53
- import { FormSystemProvider } from "@classytic/formkit";
54
- import { FormInput } from "@/components/form-input";
93
+ "use client";
55
94
 
56
- const components = {
95
+ import {
96
+ FormSystemProvider,
97
+ type ComponentRegistry,
98
+ type LayoutRegistry,
99
+ } from "@classytic/formkit";
100
+ import { FormInput } from "@/components/form/form-input";
101
+
102
+ const components: ComponentRegistry = {
57
103
  text: FormInput,
58
104
  email: FormInput,
105
+ password: FormInput,
106
+ // Add more field types...
59
107
  };
60
108
 
61
- const layouts = {
62
- section: ({ title, children }) => <div><h3>{title}</h3>{children}</div>,
63
- grid: ({ children, cols = 1 }) => <div className={`grid grid-cols-${cols} gap-4`}>{children}</div>,
109
+ const layouts: LayoutRegistry = {
110
+ section: ({ title, description, children }) => (
111
+ <div className="space-y-4">
112
+ {title && <h3 className="text-lg font-semibold">{title}</h3>}
113
+ {description && <p className="text-muted-foreground">{description}</p>}
114
+ {children}
115
+ </div>
116
+ ),
117
+ grid: ({ children, cols = 1 }) => (
118
+ <div className={`grid grid-cols-${cols} gap-4`}>{children}</div>
119
+ ),
64
120
  };
65
121
 
66
- export function FormProvider({ children }) {
67
- return <FormSystemProvider components={components} layouts={layouts}>{children}</FormSystemProvider>;
122
+ export function FormProvider({ children }: { children: React.ReactNode }) {
123
+ return (
124
+ <FormSystemProvider components={components} layouts={layouts}>
125
+ {children}
126
+ </FormSystemProvider>
127
+ );
68
128
  }
69
129
  ```
70
130
 
71
- ### 4. Use in Your App
131
+ ### 3. Use FormGenerator
72
132
 
73
133
  ```tsx
74
- import { useForm } from "react-hook-form";
75
- import { FormGenerator } from "@classytic/formkit";
76
- import { FormProvider } from "./form-adapter";
134
+ // app/signup/page.tsx
135
+ "use client";
136
+
137
+ import { zodResolver } from "@hookform/resolvers/zod";
138
+ import { z } from "zod";
139
+ import { FormGenerator, useFormKit, type FormSchema } from "@classytic/formkit";
140
+ import { FormProvider } from "@/lib/form-adapter";
141
+
142
+ const signupSchema = z.object({
143
+ firstName: z.string().min(2),
144
+ lastName: z.string().min(2),
145
+ email: z.string().email(),
146
+ password: z.string().min(8),
147
+ });
148
+
149
+ type SignupData = z.infer<typeof signupSchema>;
150
+
151
+ const formSchema: FormSchema<SignupData> = {
152
+ sections: [
153
+ {
154
+ title: "Personal Information",
155
+ cols: 2,
156
+ fields: [
157
+ { name: "firstName", type: "text", label: "First Name", required: true, defaultValue: "" },
158
+ { name: "lastName", type: "text", label: "Last Name", required: true, defaultValue: "" },
159
+ ],
160
+ },
161
+ {
162
+ title: "Account",
163
+ fields: [
164
+ { name: "email", type: "email", label: "Email", required: true, defaultValue: "" },
165
+ { name: "password", type: "password", label: "Password", required: true, defaultValue: "" },
166
+ ],
167
+ },
168
+ ],
169
+ };
77
170
 
78
- export function MyForm() {
79
- const form = useForm();
171
+ export default function SignupPage() {
172
+ const { handleSubmit, generatorProps } = useFormKit({
173
+ schema: formSchema,
174
+ resolver: zodResolver(signupSchema),
175
+ });
80
176
 
81
177
  return (
82
178
  <FormProvider>
83
- <form onSubmit={form.handleSubmit(console.log)}>
84
- <FormGenerator
85
- schema={{
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>
179
+ <form onSubmit={handleSubmit(console.log)} className="space-y-8">
180
+ <FormGenerator {...generatorProps} />
181
+ <button type="submit">Sign Up</button>
96
182
  </form>
97
183
  </FormProvider>
98
184
  );
99
185
  }
100
186
  ```
101
187
 
102
- ## Complete Examples
188
+ ## API Reference
103
189
 
104
- See `example/shadcn/` directory for full working examples with:
105
- - FormInput, FormSelect, FormCheckbox
106
- - Validation with Zod
107
- - Conditional fields
108
- - Error handling
190
+ ### useFormKit
109
191
 
110
- ## Key Points
192
+ Convenience hook that combines schema default extraction with react-hook-form setup. Returns all `useForm` methods plus ready-to-spread `generatorProps`.
111
193
 
112
- **You MUST use Controller:**
113
194
  ```tsx
114
- import { Controller } from "react-hook-form";
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
+
217
+ ### FormGenerator
115
218
 
116
- <Controller
117
- name={name}
118
- control={control}
119
- render={({ field, fieldState }) => (
120
- <Input {...field} /> // Spread field
121
- )}
219
+ The main component that renders forms from a schema. Supports React 19 `ref` as a regular prop.
220
+
221
+ ```tsx
222
+ <FormGenerator
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)
122
229
  />
123
230
  ```
124
231
 
125
- **Component receives these props:**
232
+ ### FormSchema
233
+
234
+ ```ts
235
+ interface FormSchema<T extends FieldValues = FieldValues> {
236
+ sections: Section<T>[];
237
+ }
238
+ ```
239
+
240
+ ### Section
241
+
242
+ ```ts
243
+ interface Section<T> {
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
254
+ defaultCollapsed?: boolean;
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;
262
+ }
263
+ ```
264
+
265
+ ### BaseField
266
+
267
+ ```ts
268
+ interface BaseField<T> {
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
+
282
+ // Conditional rendering
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
+
293
+ // For select/radio/checkbox
294
+ options?: FieldOption[];
295
+
296
+ // HTML input attributes
297
+ min?: number | string;
298
+ max?: number | string;
299
+ step?: number;
300
+ pattern?: string;
301
+ minLength?: number;
302
+ maxLength?: number;
303
+ rows?: number;
304
+ multiple?: boolean;
305
+ accept?: string;
306
+ autoComplete?: string;
307
+ autoFocus?: boolean;
308
+
309
+ // Custom render override
310
+ render?: (props: FieldComponentProps<T>) => ReactNode;
311
+
312
+ // Arbitrary extra props for custom components
313
+ customProps?: Record<string, unknown>;
314
+ }
315
+ ```
316
+
317
+ ### FieldComponentProps
318
+
319
+ Props passed to your field components:
320
+
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")
337
+ }
338
+ ```
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
+
371
+ ### ComponentRegistry
372
+
373
+ ```ts
374
+ const components: ComponentRegistry = {
375
+ // Simple mapping
376
+ text: FormInput,
377
+ select: FormSelect,
378
+
379
+ // Variant-specific components
380
+ compact: {
381
+ text: CompactInput,
382
+ select: CompactSelect,
383
+ },
384
+ };
385
+ ```
386
+
387
+ ### LayoutRegistry
388
+
389
+ ```ts
390
+ const layouts: LayoutRegistry = {
391
+ section: SectionLayout,
392
+ grid: GridLayout,
393
+
394
+ // Variant-specific layouts
395
+ compact: {
396
+ section: CompactSection,
397
+ },
398
+ };
399
+ ```
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
+
467
+ ## Advanced Features
468
+
469
+ ### Conditional Fields (Function)
470
+
471
+ ```ts
472
+ {
473
+ name: "companyName",
474
+ type: "text",
475
+ label: "Company Name",
476
+ condition: (values) => values.accountType === "business",
477
+ }
478
+ ```
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
+
547
+ ### Variants
548
+
549
+ Apply different styles based on context:
550
+
126
551
  ```tsx
552
+ // Register variant-specific components
553
+ const components = {
554
+ text: DefaultInput,
555
+ compact: {
556
+ text: CompactInput,
557
+ },
558
+ };
559
+
560
+ // Use variant on the whole form
561
+ <FormGenerator schema={schema} variant="compact" />
562
+
563
+ // Or per-section
564
+ { variant: "compact", fields: [...] }
565
+
566
+ // Or per-field
567
+ { name: "notes", type: "text", variant: "compact" }
568
+ ```
569
+
570
+ ### Dynamic Options Loading
571
+
572
+ ```ts
127
573
  {
128
- field: {...}, // Config object
129
- control: {...}, // React Hook Form control
130
- name: "email", // Plus all field props spread
131
- label: "Email",
132
- // ...
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,
133
582
  }
134
583
  ```
135
584
 
136
- ## API
585
+ ### Custom Section Render
137
586
 
138
- ### FormGenerator
587
+ ```ts
588
+ {
589
+ title: "Payment",
590
+ render: ({ control, disabled }) => (
591
+ <StripeElements>
592
+ <CardElement />
593
+ <FormInput name="billingName" control={control} />
594
+ </StripeElements>
595
+ ),
596
+ }
597
+ ```
139
598
 
140
- | Prop | Type | Required |
141
- |------|------|----------|
142
- | schema | FormSchema | Yes |
143
- | control | Control | Yes |
144
- | disabled | boolean | No |
145
- | variant | string | No |
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
+ ```
146
626
 
147
- ### Types
627
+ Access in your component:
148
628
 
149
629
  ```tsx
150
- import type { FormSchema, FieldComponentProps } from "@classytic/formkit";
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
639
+ {
640
+ name: "country",
641
+ type: "select",
642
+ options: [
643
+ {
644
+ label: "North America",
645
+ options: [
646
+ { value: "us", label: "United States" },
647
+ { value: "ca", label: "Canada" },
648
+ ],
649
+ },
650
+ {
651
+ label: "Europe",
652
+ options: [
653
+ { value: "uk", label: "United Kingdom" },
654
+ { value: "de", label: "Germany" },
655
+ ],
656
+ },
657
+ ],
658
+ }
151
659
  ```
152
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
+
686
+ ## Type Exports
687
+
688
+ ```ts
689
+ import type {
690
+ // Core
691
+ FormSchema,
692
+ FormGeneratorProps,
693
+ BaseField,
694
+ Section,
695
+
696
+ // Components
697
+ FieldComponentProps,
698
+ FieldComponent,
699
+ ComponentRegistry,
700
+
701
+ // Layouts
702
+ SectionLayoutProps,
703
+ GridLayoutProps,
704
+ LayoutComponent,
705
+ LayoutRegistry,
706
+
707
+ // Options
708
+ FieldOption,
709
+ FieldOptionGroup,
710
+
711
+ // Conditions
712
+ ConditionRule,
713
+ ConditionConfig,
714
+ Condition,
715
+
716
+ // Hook types
717
+ UseFormKitOptions,
718
+ UseFormKitReturn,
719
+
720
+ // Utility types
721
+ FieldType,
722
+ LayoutType,
723
+ Variant,
724
+ DefineField,
725
+ InferSchemaValues,
726
+ SchemaFieldNames,
727
+ FormElement,
728
+ } from "@classytic/formkit";
729
+ ```
730
+
731
+ ## Browser Support
732
+
733
+ - React 19.0+
734
+ - All modern browsers
735
+
153
736
  ## License
154
737
 
155
738
  MIT © [Classytic](https://github.com/classytic)
@@ -157,5 +740,6 @@ MIT © [Classytic](https://github.com/classytic)
157
740
  ## Links
158
741
 
159
742
  - [GitHub](https://github.com/classytic/formkit)
743
+ - [npm](https://www.npmjs.com/package/@classytic/formkit)
160
744
  - [Examples](https://github.com/classytic/formkit/tree/main/example/shadcn)
161
745
  - [Issues](https://github.com/classytic/formkit/issues)