@classytic/formkit 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Classytic
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,273 @@
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
+
package/dist/index.cjs ADDED
@@ -0,0 +1,169 @@
1
+ 'use strict';
2
+
3
+ var reactHookForm = require('react-hook-form');
4
+ var react = require('react');
5
+ var jsxRuntime = require('react/jsx-runtime');
6
+ var clsx = require('clsx');
7
+ var tailwindMerge = require('tailwind-merge');
8
+
9
+ // src/FormGenerator.tsx
10
+ var FormSystemContext = react.createContext(null);
11
+ function FormSystemProvider({
12
+ components,
13
+ layouts,
14
+ children
15
+ }) {
16
+ const value = react.useMemo(
17
+ () => ({
18
+ components: components || {},
19
+ layouts: layouts || {}
20
+ }),
21
+ [components, layouts]
22
+ );
23
+ return /* @__PURE__ */ jsxRuntime.jsx(FormSystemContext.Provider, { value, children });
24
+ }
25
+ function useFormSystem() {
26
+ const context = react.useContext(FormSystemContext);
27
+ if (!context) {
28
+ throw new Error("useFormSystem must be used within a FormSystemProvider");
29
+ }
30
+ return context;
31
+ }
32
+ function useFieldComponent(type, variant) {
33
+ const { components } = useFormSystem();
34
+ if (variant && typeof components[variant] === "object" && components[variant]) {
35
+ const variantComponents = components[variant];
36
+ if (variantComponents[type]) {
37
+ return variantComponents[type];
38
+ }
39
+ }
40
+ const Component = components[type] || components["default"] || components["text"];
41
+ if (!Component) {
42
+ if (process.env.NODE_ENV === "development") {
43
+ console.warn(`FormKit: No component found for type "${type}" (variant: ${variant})`);
44
+ return () => /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { color: "red", padding: 4, border: "1px dashed red" }, children: [
45
+ "Missing: ",
46
+ type
47
+ ] });
48
+ }
49
+ return () => null;
50
+ }
51
+ return Component;
52
+ }
53
+ function useLayoutComponent(type, variant) {
54
+ const { layouts } = useFormSystem();
55
+ if (variant && typeof layouts[variant] === "object" && layouts[variant]) {
56
+ const variantLayouts = layouts[variant];
57
+ if (variantLayouts[type]) {
58
+ return variantLayouts[type];
59
+ }
60
+ }
61
+ return layouts[type] || layouts["default"] || DefaultLayout;
62
+ }
63
+ var DefaultLayout = ({ children, className }) => /* @__PURE__ */ jsxRuntime.jsx("div", { className, children });
64
+ function cn(...inputs) {
65
+ return tailwindMerge.twMerge(clsx.clsx(inputs));
66
+ }
67
+ function FormGenerator({
68
+ schema,
69
+ control,
70
+ disabled = false,
71
+ variant
72
+ }) {
73
+ const formContext = reactHookForm.useFormContext();
74
+ const activeControl = control || formContext?.control;
75
+ if (!schema?.sections) return null;
76
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("form-generator-root", variant && `form-variant-${variant}`), children: schema.sections.map((section, idx) => /* @__PURE__ */ jsxRuntime.jsx(
77
+ SectionRenderer,
78
+ {
79
+ section,
80
+ control: activeControl,
81
+ disabled,
82
+ variant
83
+ },
84
+ section.id || idx
85
+ )) });
86
+ }
87
+ function SectionRenderer({
88
+ section,
89
+ control,
90
+ disabled,
91
+ variant
92
+ }) {
93
+ const activeVariant = section.variant || variant;
94
+ const SectionLayout = useLayoutComponent("section", activeVariant);
95
+ if (section.condition && !section.condition(control)) {
96
+ return null;
97
+ }
98
+ return /* @__PURE__ */ jsxRuntime.jsx(
99
+ SectionLayout,
100
+ {
101
+ title: section.title,
102
+ description: section.description,
103
+ icon: section.icon,
104
+ variant: activeVariant,
105
+ className: section.className,
106
+ children: section.render ? section.render({ control, disabled, section }) : /* @__PURE__ */ jsxRuntime.jsx(
107
+ GridRenderer,
108
+ {
109
+ fields: section.fields,
110
+ cols: section.cols,
111
+ control,
112
+ disabled,
113
+ variant: activeVariant
114
+ }
115
+ )
116
+ }
117
+ );
118
+ }
119
+ function GridRenderer({
120
+ fields,
121
+ cols = 1,
122
+ control,
123
+ disabled,
124
+ variant
125
+ }) {
126
+ const GridLayout = useLayoutComponent("grid", variant);
127
+ if (!fields || fields.length === 0) return null;
128
+ return /* @__PURE__ */ jsxRuntime.jsx(GridLayout, { cols, children: fields.map((field, idx) => /* @__PURE__ */ jsxRuntime.jsx(
129
+ FieldWrapper,
130
+ {
131
+ field,
132
+ control,
133
+ disabled,
134
+ variant
135
+ },
136
+ field.name || idx
137
+ )) });
138
+ }
139
+ function FieldWrapper({
140
+ field,
141
+ control,
142
+ disabled,
143
+ variant
144
+ }) {
145
+ const formValues = reactHookForm.useWatch({ control });
146
+ if (field.condition && !field.condition(formValues)) {
147
+ return null;
148
+ }
149
+ const activeVariant = field.variant || variant;
150
+ const FieldComponent = useFieldComponent(field.type, activeVariant);
151
+ if (!FieldComponent) return null;
152
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: field.fullWidth ? "col-span-full" : "", children: /* @__PURE__ */ jsxRuntime.jsx(
153
+ FieldComponent,
154
+ {
155
+ field,
156
+ control,
157
+ disabled: disabled || field.disabled,
158
+ variant: activeVariant,
159
+ ...field
160
+ }
161
+ ) });
162
+ }
163
+
164
+ exports.FormGenerator = FormGenerator;
165
+ exports.FormSystemProvider = FormSystemProvider;
166
+ exports.cn = cn;
167
+ exports.useFormSystem = useFormSystem;
168
+ //# sourceMappingURL=index.cjs.map
169
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/FormSystemContext.tsx","../src/utils.ts","../src/FormGenerator.tsx"],"names":["createContext","useMemo","jsx","useContext","jsxs","twMerge","clsx","useFormContext","useWatch"],"mappings":"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","file":"index.cjs","sourcesContent":["\"use client\";\r\n\r\nimport { createContext, useContext, useMemo } from \"react\";\r\nimport type {\r\n FormSystemContextValue,\r\n FormSystemProviderProps,\r\n FieldComponent,\r\n LayoutComponent,\r\n FieldType,\r\n LayoutType,\r\n Variant,\r\n} from \"./types\";\r\n\r\nconst FormSystemContext = createContext<FormSystemContextValue | null>(null);\r\n\r\n/**\r\n * FormSystemProvider\r\n *\r\n * Provides the component registry to the form system.\r\n * This is the root provider that enables FormGenerator to work.\r\n *\r\n * @example\r\n * ```tsx\r\n * import { FormSystemProvider } from '@classytic/formkit';\r\n *\r\n * const components = {\r\n * text: TextInput,\r\n * select: SelectInput,\r\n * };\r\n *\r\n * const layouts = {\r\n * section: SectionLayout,\r\n * grid: GridLayout,\r\n * };\r\n *\r\n * function App() {\r\n * return (\r\n * <FormSystemProvider components={components} layouts={layouts}>\r\n * <YourFormComponent />\r\n * </FormSystemProvider>\r\n * );\r\n * }\r\n * ```\r\n */\r\nexport function FormSystemProvider({\r\n components,\r\n layouts,\r\n children,\r\n}: FormSystemProviderProps): JSX.Element {\r\n const value = useMemo<FormSystemContextValue>(\r\n () => ({\r\n components: components || {},\r\n layouts: layouts || {},\r\n }),\r\n [components, layouts]\r\n );\r\n\r\n return <FormSystemContext.Provider value={value}>{children}</FormSystemContext.Provider>;\r\n}\r\n\r\n/**\r\n * Hook to access the form system context\r\n *\r\n * @throws {Error} If used outside FormSystemProvider\r\n * @returns Form system context value\r\n *\r\n * @example\r\n * ```tsx\r\n * const { components, layouts } = useFormSystem();\r\n * ```\r\n */\r\nexport function useFormSystem(): FormSystemContextValue {\r\n const context = useContext(FormSystemContext);\r\n if (!context) {\r\n throw new Error(\"useFormSystem must be used within a FormSystemProvider\");\r\n }\r\n return context;\r\n}\r\n\r\n/**\r\n * Helper to resolve a component for a specific field type and variant\r\n *\r\n * @param type - Field type identifier\r\n * @param variant - Optional variant name\r\n * @returns Field component or fallback\r\n *\r\n * @internal\r\n */\r\nexport function useFieldComponent(type: FieldType, variant?: Variant): FieldComponent {\r\n const { components } = useFormSystem();\r\n\r\n // 1. Try variant-specific component\r\n if (variant && typeof components[variant] === \"object\" && components[variant]) {\r\n const variantComponents = components[variant] as Record<string, FieldComponent>;\r\n if (variantComponents[type]) {\r\n return variantComponents[type];\r\n }\r\n }\r\n\r\n // 2. Fallback to standard component\r\n const Component =\r\n (components[type] as FieldComponent) ||\r\n (components[\"default\"] as FieldComponent) ||\r\n (components[\"text\"] as FieldComponent);\r\n\r\n if (!Component) {\r\n // Library-safe fallback: Don't crash, just warn and render nothing or a placeholder\r\n if (process.env.NODE_ENV === \"development\") {\r\n console.warn(`FormKit: No component found for type \"${type}\" (variant: ${variant})`);\r\n return () => (\r\n <div style={{ color: \"red\", padding: 4, border: \"1px dashed red\" }}>\r\n Missing: {type}\r\n </div>\r\n );\r\n }\r\n return () => null;\r\n }\r\n\r\n return Component;\r\n}\r\n\r\n/**\r\n * Helper to resolve a layout component\r\n *\r\n * @param type - Layout type identifier\r\n * @param variant - Optional variant name\r\n * @returns Layout component or fallback\r\n *\r\n * @internal\r\n */\r\nexport function useLayoutComponent(type: LayoutType, variant?: Variant): LayoutComponent {\r\n const { layouts } = useFormSystem();\r\n\r\n // 1. Try variant-specific layout\r\n if (variant && typeof layouts[variant] === \"object\" && layouts[variant]) {\r\n const variantLayouts = layouts[variant] as Record<string, LayoutComponent>;\r\n if (variantLayouts[type]) {\r\n return variantLayouts[type];\r\n }\r\n }\r\n\r\n // 2. Fallback to standard layout\r\n return (\r\n (layouts[type] as LayoutComponent) ||\r\n (layouts[\"default\"] as LayoutComponent) ||\r\n DefaultLayout\r\n );\r\n}\r\n\r\n/**\r\n * Default layout component - simple div wrapper\r\n */\r\nconst DefaultLayout: LayoutComponent = ({ children, className }: any) => (\r\n <div className={className}>{children}</div>\r\n);\r\n","import { type ClassValue, clsx } from \"clsx\";\r\nimport { twMerge } from \"tailwind-merge\";\r\n\r\n/**\r\n * Utility function to merge Tailwind CSS classes\r\n * Combines clsx and tailwind-merge for conflict-free class merging\r\n *\r\n * @param inputs - Class values to merge\r\n * @returns Merged class string\r\n */\r\nexport function cn(...inputs: ClassValue[]): string {\r\n return twMerge(clsx(inputs));\r\n}\r\n","\"use client\";\r\n\r\nimport { useFormContext, useWatch } from \"react-hook-form\";\r\nimport type { Control, FieldValues } from \"react-hook-form\";\r\nimport { useFieldComponent, useLayoutComponent } from \"./FormSystemContext\";\r\nimport { cn } from \"./utils\";\r\nimport type { FormGeneratorProps, Section, BaseField, Variant } from \"./types\";\r\n\r\n/**\r\n * FormGenerator - Headless Form Generator Component\r\n *\r\n * Renders a form based on a schema, using components injected via FormSystemProvider.\r\n * Supports conditional fields, dynamic layouts, and variants.\r\n *\r\n * @template TFieldValues - Form field values type\r\n *\r\n * @example\r\n * ```tsx\r\n * import { useForm } from 'react-hook-form';\r\n * import { FormGenerator } from '@classytic/formkit';\r\n *\r\n * function MyForm() {\r\n * const { control } = useForm();\r\n *\r\n * const schema = {\r\n * sections: [\r\n * {\r\n * title: \"User Details\",\r\n * fields: [\r\n * { name: \"firstName\", type: \"text\", label: \"First Name\" },\r\n * { name: \"email\", type: \"email\", label: \"Email\" }\r\n * ]\r\n * }\r\n * ]\r\n * };\r\n *\r\n * return <FormGenerator schema={schema} control={control} />;\r\n * }\r\n * ```\r\n */\r\nexport function FormGenerator<TFieldValues extends FieldValues = FieldValues>({\r\n schema,\r\n control,\r\n disabled = false,\r\n variant,\r\n}: FormGeneratorProps<TFieldValues>): JSX.Element | null {\r\n // Use provided control or fall back to context\r\n const formContext = useFormContext<TFieldValues>();\r\n const activeControl = control || formContext?.control;\r\n\r\n if (!schema?.sections) return null;\r\n\r\n return (\r\n <div className={cn(\"form-generator-root\", variant && `form-variant-${variant}`)}>\r\n {schema.sections.map((section, idx) => (\r\n <SectionRenderer\r\n key={section.id || idx}\r\n section={section}\r\n control={activeControl}\r\n disabled={disabled}\r\n variant={variant}\r\n />\r\n ))}\r\n </div>\r\n );\r\n}\r\n\r\n/**\r\n * Section Renderer Props\r\n */\r\ninterface SectionRendererProps<TFieldValues extends FieldValues = FieldValues> {\r\n section: Section;\r\n control?: Control<TFieldValues>;\r\n disabled?: boolean;\r\n variant?: Variant;\r\n}\r\n\r\n/**\r\n * Renders a single section with its fields\r\n */\r\nfunction SectionRenderer<TFieldValues extends FieldValues = FieldValues>({\r\n section,\r\n control,\r\n disabled,\r\n variant,\r\n}: SectionRendererProps<TFieldValues>): JSX.Element | null {\r\n // Allow section to override variant, or use global variant\r\n const activeVariant = section.variant || variant;\r\n const SectionLayout = useLayoutComponent(\"section\", activeVariant);\r\n\r\n // Check condition if present\r\n if (section.condition && !section.condition(control)) {\r\n return null;\r\n }\r\n\r\n return (\r\n <SectionLayout\r\n title={section.title}\r\n description={section.description}\r\n icon={section.icon}\r\n variant={activeVariant}\r\n className={section.className}\r\n >\r\n {section.render ? (\r\n section.render({ control, disabled, section })\r\n ) : (\r\n <GridRenderer\r\n fields={section.fields}\r\n cols={section.cols}\r\n control={control}\r\n disabled={disabled}\r\n variant={activeVariant}\r\n />\r\n )}\r\n </SectionLayout>\r\n );\r\n}\r\n\r\n/**\r\n * Grid Renderer Props\r\n */\r\ninterface GridRendererProps<TFieldValues extends FieldValues = FieldValues> {\r\n fields?: BaseField[];\r\n cols?: number;\r\n control?: Control<TFieldValues>;\r\n disabled?: boolean;\r\n variant?: Variant;\r\n}\r\n\r\n/**\r\n * Renders a grid of fields with specified column layout\r\n */\r\nfunction GridRenderer<TFieldValues extends FieldValues = FieldValues>({\r\n fields,\r\n cols = 1,\r\n control,\r\n disabled,\r\n variant,\r\n}: GridRendererProps<TFieldValues>): JSX.Element | null {\r\n const GridLayout = useLayoutComponent(\"grid\", variant);\r\n\r\n if (!fields || fields.length === 0) return null;\r\n\r\n return (\r\n <GridLayout cols={cols}>\r\n {fields.map((field, idx) => (\r\n <FieldWrapper\r\n key={field.name || idx}\r\n field={field}\r\n control={control}\r\n disabled={disabled}\r\n variant={variant}\r\n />\r\n ))}\r\n </GridLayout>\r\n );\r\n}\r\n\r\n/**\r\n * Field Wrapper Props\r\n */\r\ninterface FieldWrapperProps<TFieldValues extends FieldValues = FieldValues> {\r\n field: BaseField;\r\n control?: Control<TFieldValues>;\r\n disabled?: boolean;\r\n variant?: Variant;\r\n}\r\n\r\n/**\r\n * Renders a single field wrapper\r\n * Handles conditional rendering and field visibility\r\n */\r\nfunction FieldWrapper<TFieldValues extends FieldValues = FieldValues>({\r\n field,\r\n control,\r\n disabled,\r\n variant,\r\n}: FieldWrapperProps<TFieldValues>): JSX.Element | null {\r\n // Watch values for conditional rendering\r\n const formValues = useWatch({ control });\r\n\r\n if (field.condition && !field.condition(formValues)) {\r\n return null;\r\n }\r\n\r\n // Allow field to override variant\r\n const activeVariant = field.variant || variant;\r\n const FieldComponent = useFieldComponent(field.type, activeVariant);\r\n\r\n if (!FieldComponent) return null;\r\n\r\n return (\r\n <div className={field.fullWidth ? \"col-span-full\" : \"\"}>\r\n <FieldComponent\r\n field={field}\r\n control={control as any}\r\n disabled={disabled || field.disabled}\r\n variant={activeVariant}\r\n // Pass all field props to the component\r\n {...field}\r\n />\r\n </div>\r\n );\r\n}\r\n"]}
@@ -0,0 +1,265 @@
1
+ import { FieldValues, Control } from 'react-hook-form';
2
+ import { ReactNode, ComponentType } from 'react';
3
+ import { ClassValue } from 'clsx';
4
+
5
+ /**
6
+ * Field type identifier
7
+ */
8
+ type FieldType = string;
9
+ /**
10
+ * Layout type identifier
11
+ */
12
+ type LayoutType = "section" | "grid" | "default" | string;
13
+ /**
14
+ * Variant identifier for component styling
15
+ */
16
+ type Variant = string | undefined;
17
+ /**
18
+ * Base field configuration
19
+ */
20
+ interface BaseField {
21
+ /** Field name (maps to form field) */
22
+ name: string;
23
+ /** Field type (text, select, checkbox, etc.) */
24
+ type: FieldType;
25
+ /** Field label */
26
+ label?: string;
27
+ /** Placeholder text */
28
+ placeholder?: string;
29
+ /** Whether field is disabled */
30
+ disabled?: boolean;
31
+ /** Whether field is required */
32
+ required?: boolean;
33
+ /** Field variant */
34
+ variant?: Variant;
35
+ /** Whether field should span full width */
36
+ fullWidth?: boolean;
37
+ /** Custom CSS class name */
38
+ className?: string;
39
+ /** Conditional rendering function */
40
+ condition?: (formValues: FieldValues) => boolean;
41
+ /** Default value */
42
+ defaultValue?: any;
43
+ /** Additional field-specific props */
44
+ [key: string]: any;
45
+ }
46
+ /**
47
+ * Field component props
48
+ */
49
+ interface FieldComponentProps<TFieldValues extends FieldValues = FieldValues> {
50
+ /** Field configuration */
51
+ field: BaseField;
52
+ /** React Hook Form control */
53
+ control?: Control<TFieldValues>;
54
+ /** Whether field is disabled */
55
+ disabled?: boolean;
56
+ /** Field variant */
57
+ variant?: Variant;
58
+ }
59
+ /**
60
+ * Section configuration
61
+ */
62
+ interface Section {
63
+ /** Section ID (optional) */
64
+ id?: string;
65
+ /** Section title */
66
+ title?: string;
67
+ /** Section description */
68
+ description?: string;
69
+ /** Section icon */
70
+ icon?: ReactNode;
71
+ /** Section fields */
72
+ fields?: BaseField[];
73
+ /** Number of columns in grid */
74
+ cols?: number;
75
+ /** Custom render function */
76
+ render?: (props: {
77
+ control?: Control<any>;
78
+ disabled?: boolean;
79
+ section: Section;
80
+ }) => ReactNode;
81
+ /** Section variant */
82
+ variant?: Variant;
83
+ /** Custom CSS class name */
84
+ className?: string;
85
+ /** Conditional rendering function */
86
+ condition?: (control?: Control<any>) => boolean;
87
+ }
88
+ /**
89
+ * Form schema configuration
90
+ */
91
+ interface FormSchema {
92
+ /** Form sections */
93
+ sections: Section[];
94
+ }
95
+ /**
96
+ * Section layout component props
97
+ */
98
+ interface SectionLayoutProps {
99
+ /** Section title */
100
+ title?: string;
101
+ /** Section description */
102
+ description?: string;
103
+ /** Section icon */
104
+ icon?: ReactNode;
105
+ /** Layout variant */
106
+ variant?: Variant;
107
+ /** Custom CSS class name */
108
+ className?: string;
109
+ /** Children content */
110
+ children: ReactNode;
111
+ }
112
+ /**
113
+ * Grid layout component props
114
+ */
115
+ interface GridLayoutProps {
116
+ /** Number of columns */
117
+ cols?: number;
118
+ /** Children content */
119
+ children: ReactNode;
120
+ }
121
+ /**
122
+ * Layout component props union
123
+ */
124
+ type LayoutComponentProps = SectionLayoutProps | GridLayoutProps;
125
+ /**
126
+ * Field component type
127
+ */
128
+ type FieldComponent<TFieldValues extends FieldValues = FieldValues> = ComponentType<FieldComponentProps<TFieldValues>>;
129
+ /**
130
+ * Layout component type
131
+ */
132
+ type LayoutComponent = ComponentType<any>;
133
+ /**
134
+ * Component registry mapping field types to components
135
+ */
136
+ interface ComponentRegistry {
137
+ [key: string]: FieldComponent | ComponentRegistry;
138
+ }
139
+ /**
140
+ * Layout registry mapping layout types to components
141
+ */
142
+ interface LayoutRegistry {
143
+ [key: string]: LayoutComponent | LayoutRegistry;
144
+ }
145
+ /**
146
+ * Form system context value
147
+ */
148
+ interface FormSystemContextValue {
149
+ /** Registered field components */
150
+ components: ComponentRegistry;
151
+ /** Registered layout components */
152
+ layouts: LayoutRegistry;
153
+ }
154
+ /**
155
+ * Form system provider props
156
+ */
157
+ interface FormSystemProviderProps {
158
+ /** Field component registry */
159
+ components?: ComponentRegistry;
160
+ /** Layout component registry */
161
+ layouts?: LayoutRegistry;
162
+ /** Children content */
163
+ children: ReactNode;
164
+ }
165
+ /**
166
+ * Form generator props
167
+ */
168
+ interface FormGeneratorProps<TFieldValues extends FieldValues = FieldValues> {
169
+ /** Form schema */
170
+ schema: FormSchema;
171
+ /** React Hook Form control */
172
+ control?: Control<TFieldValues>;
173
+ /** Global disabled state */
174
+ disabled?: boolean;
175
+ /** Global variant */
176
+ variant?: Variant;
177
+ }
178
+
179
+ /**
180
+ * FormGenerator - Headless Form Generator Component
181
+ *
182
+ * Renders a form based on a schema, using components injected via FormSystemProvider.
183
+ * Supports conditional fields, dynamic layouts, and variants.
184
+ *
185
+ * @template TFieldValues - Form field values type
186
+ *
187
+ * @example
188
+ * ```tsx
189
+ * import { useForm } from 'react-hook-form';
190
+ * import { FormGenerator } from '@classytic/formkit';
191
+ *
192
+ * function MyForm() {
193
+ * const { control } = useForm();
194
+ *
195
+ * const schema = {
196
+ * sections: [
197
+ * {
198
+ * title: "User Details",
199
+ * fields: [
200
+ * { name: "firstName", type: "text", label: "First Name" },
201
+ * { name: "email", type: "email", label: "Email" }
202
+ * ]
203
+ * }
204
+ * ]
205
+ * };
206
+ *
207
+ * return <FormGenerator schema={schema} control={control} />;
208
+ * }
209
+ * ```
210
+ */
211
+ declare function FormGenerator<TFieldValues extends FieldValues = FieldValues>({ schema, control, disabled, variant, }: FormGeneratorProps<TFieldValues>): JSX.Element | null;
212
+
213
+ /**
214
+ * FormSystemProvider
215
+ *
216
+ * Provides the component registry to the form system.
217
+ * This is the root provider that enables FormGenerator to work.
218
+ *
219
+ * @example
220
+ * ```tsx
221
+ * import { FormSystemProvider } from '@classytic/formkit';
222
+ *
223
+ * const components = {
224
+ * text: TextInput,
225
+ * select: SelectInput,
226
+ * };
227
+ *
228
+ * const layouts = {
229
+ * section: SectionLayout,
230
+ * grid: GridLayout,
231
+ * };
232
+ *
233
+ * function App() {
234
+ * return (
235
+ * <FormSystemProvider components={components} layouts={layouts}>
236
+ * <YourFormComponent />
237
+ * </FormSystemProvider>
238
+ * );
239
+ * }
240
+ * ```
241
+ */
242
+ declare function FormSystemProvider({ components, layouts, children, }: FormSystemProviderProps): JSX.Element;
243
+ /**
244
+ * Hook to access the form system context
245
+ *
246
+ * @throws {Error} If used outside FormSystemProvider
247
+ * @returns Form system context value
248
+ *
249
+ * @example
250
+ * ```tsx
251
+ * const { components, layouts } = useFormSystem();
252
+ * ```
253
+ */
254
+ declare function useFormSystem(): FormSystemContextValue;
255
+
256
+ /**
257
+ * Utility function to merge Tailwind CSS classes
258
+ * Combines clsx and tailwind-merge for conflict-free class merging
259
+ *
260
+ * @param inputs - Class values to merge
261
+ * @returns Merged class string
262
+ */
263
+ declare function cn(...inputs: ClassValue[]): string;
264
+
265
+ export { type BaseField, type ComponentRegistry, type FieldComponent, type FieldComponentProps, type FieldType, FormGenerator, type FormGeneratorProps, type FormSchema, type FormSystemContextValue, FormSystemProvider, type FormSystemProviderProps, type GridLayoutProps, type LayoutComponent, type LayoutComponentProps, type LayoutRegistry, type LayoutType, type Section, type SectionLayoutProps, type Variant, cn, useFormSystem };
@@ -0,0 +1,265 @@
1
+ import { FieldValues, Control } from 'react-hook-form';
2
+ import { ReactNode, ComponentType } from 'react';
3
+ import { ClassValue } from 'clsx';
4
+
5
+ /**
6
+ * Field type identifier
7
+ */
8
+ type FieldType = string;
9
+ /**
10
+ * Layout type identifier
11
+ */
12
+ type LayoutType = "section" | "grid" | "default" | string;
13
+ /**
14
+ * Variant identifier for component styling
15
+ */
16
+ type Variant = string | undefined;
17
+ /**
18
+ * Base field configuration
19
+ */
20
+ interface BaseField {
21
+ /** Field name (maps to form field) */
22
+ name: string;
23
+ /** Field type (text, select, checkbox, etc.) */
24
+ type: FieldType;
25
+ /** Field label */
26
+ label?: string;
27
+ /** Placeholder text */
28
+ placeholder?: string;
29
+ /** Whether field is disabled */
30
+ disabled?: boolean;
31
+ /** Whether field is required */
32
+ required?: boolean;
33
+ /** Field variant */
34
+ variant?: Variant;
35
+ /** Whether field should span full width */
36
+ fullWidth?: boolean;
37
+ /** Custom CSS class name */
38
+ className?: string;
39
+ /** Conditional rendering function */
40
+ condition?: (formValues: FieldValues) => boolean;
41
+ /** Default value */
42
+ defaultValue?: any;
43
+ /** Additional field-specific props */
44
+ [key: string]: any;
45
+ }
46
+ /**
47
+ * Field component props
48
+ */
49
+ interface FieldComponentProps<TFieldValues extends FieldValues = FieldValues> {
50
+ /** Field configuration */
51
+ field: BaseField;
52
+ /** React Hook Form control */
53
+ control?: Control<TFieldValues>;
54
+ /** Whether field is disabled */
55
+ disabled?: boolean;
56
+ /** Field variant */
57
+ variant?: Variant;
58
+ }
59
+ /**
60
+ * Section configuration
61
+ */
62
+ interface Section {
63
+ /** Section ID (optional) */
64
+ id?: string;
65
+ /** Section title */
66
+ title?: string;
67
+ /** Section description */
68
+ description?: string;
69
+ /** Section icon */
70
+ icon?: ReactNode;
71
+ /** Section fields */
72
+ fields?: BaseField[];
73
+ /** Number of columns in grid */
74
+ cols?: number;
75
+ /** Custom render function */
76
+ render?: (props: {
77
+ control?: Control<any>;
78
+ disabled?: boolean;
79
+ section: Section;
80
+ }) => ReactNode;
81
+ /** Section variant */
82
+ variant?: Variant;
83
+ /** Custom CSS class name */
84
+ className?: string;
85
+ /** Conditional rendering function */
86
+ condition?: (control?: Control<any>) => boolean;
87
+ }
88
+ /**
89
+ * Form schema configuration
90
+ */
91
+ interface FormSchema {
92
+ /** Form sections */
93
+ sections: Section[];
94
+ }
95
+ /**
96
+ * Section layout component props
97
+ */
98
+ interface SectionLayoutProps {
99
+ /** Section title */
100
+ title?: string;
101
+ /** Section description */
102
+ description?: string;
103
+ /** Section icon */
104
+ icon?: ReactNode;
105
+ /** Layout variant */
106
+ variant?: Variant;
107
+ /** Custom CSS class name */
108
+ className?: string;
109
+ /** Children content */
110
+ children: ReactNode;
111
+ }
112
+ /**
113
+ * Grid layout component props
114
+ */
115
+ interface GridLayoutProps {
116
+ /** Number of columns */
117
+ cols?: number;
118
+ /** Children content */
119
+ children: ReactNode;
120
+ }
121
+ /**
122
+ * Layout component props union
123
+ */
124
+ type LayoutComponentProps = SectionLayoutProps | GridLayoutProps;
125
+ /**
126
+ * Field component type
127
+ */
128
+ type FieldComponent<TFieldValues extends FieldValues = FieldValues> = ComponentType<FieldComponentProps<TFieldValues>>;
129
+ /**
130
+ * Layout component type
131
+ */
132
+ type LayoutComponent = ComponentType<any>;
133
+ /**
134
+ * Component registry mapping field types to components
135
+ */
136
+ interface ComponentRegistry {
137
+ [key: string]: FieldComponent | ComponentRegistry;
138
+ }
139
+ /**
140
+ * Layout registry mapping layout types to components
141
+ */
142
+ interface LayoutRegistry {
143
+ [key: string]: LayoutComponent | LayoutRegistry;
144
+ }
145
+ /**
146
+ * Form system context value
147
+ */
148
+ interface FormSystemContextValue {
149
+ /** Registered field components */
150
+ components: ComponentRegistry;
151
+ /** Registered layout components */
152
+ layouts: LayoutRegistry;
153
+ }
154
+ /**
155
+ * Form system provider props
156
+ */
157
+ interface FormSystemProviderProps {
158
+ /** Field component registry */
159
+ components?: ComponentRegistry;
160
+ /** Layout component registry */
161
+ layouts?: LayoutRegistry;
162
+ /** Children content */
163
+ children: ReactNode;
164
+ }
165
+ /**
166
+ * Form generator props
167
+ */
168
+ interface FormGeneratorProps<TFieldValues extends FieldValues = FieldValues> {
169
+ /** Form schema */
170
+ schema: FormSchema;
171
+ /** React Hook Form control */
172
+ control?: Control<TFieldValues>;
173
+ /** Global disabled state */
174
+ disabled?: boolean;
175
+ /** Global variant */
176
+ variant?: Variant;
177
+ }
178
+
179
+ /**
180
+ * FormGenerator - Headless Form Generator Component
181
+ *
182
+ * Renders a form based on a schema, using components injected via FormSystemProvider.
183
+ * Supports conditional fields, dynamic layouts, and variants.
184
+ *
185
+ * @template TFieldValues - Form field values type
186
+ *
187
+ * @example
188
+ * ```tsx
189
+ * import { useForm } from 'react-hook-form';
190
+ * import { FormGenerator } from '@classytic/formkit';
191
+ *
192
+ * function MyForm() {
193
+ * const { control } = useForm();
194
+ *
195
+ * const schema = {
196
+ * sections: [
197
+ * {
198
+ * title: "User Details",
199
+ * fields: [
200
+ * { name: "firstName", type: "text", label: "First Name" },
201
+ * { name: "email", type: "email", label: "Email" }
202
+ * ]
203
+ * }
204
+ * ]
205
+ * };
206
+ *
207
+ * return <FormGenerator schema={schema} control={control} />;
208
+ * }
209
+ * ```
210
+ */
211
+ declare function FormGenerator<TFieldValues extends FieldValues = FieldValues>({ schema, control, disabled, variant, }: FormGeneratorProps<TFieldValues>): JSX.Element | null;
212
+
213
+ /**
214
+ * FormSystemProvider
215
+ *
216
+ * Provides the component registry to the form system.
217
+ * This is the root provider that enables FormGenerator to work.
218
+ *
219
+ * @example
220
+ * ```tsx
221
+ * import { FormSystemProvider } from '@classytic/formkit';
222
+ *
223
+ * const components = {
224
+ * text: TextInput,
225
+ * select: SelectInput,
226
+ * };
227
+ *
228
+ * const layouts = {
229
+ * section: SectionLayout,
230
+ * grid: GridLayout,
231
+ * };
232
+ *
233
+ * function App() {
234
+ * return (
235
+ * <FormSystemProvider components={components} layouts={layouts}>
236
+ * <YourFormComponent />
237
+ * </FormSystemProvider>
238
+ * );
239
+ * }
240
+ * ```
241
+ */
242
+ declare function FormSystemProvider({ components, layouts, children, }: FormSystemProviderProps): JSX.Element;
243
+ /**
244
+ * Hook to access the form system context
245
+ *
246
+ * @throws {Error} If used outside FormSystemProvider
247
+ * @returns Form system context value
248
+ *
249
+ * @example
250
+ * ```tsx
251
+ * const { components, layouts } = useFormSystem();
252
+ * ```
253
+ */
254
+ declare function useFormSystem(): FormSystemContextValue;
255
+
256
+ /**
257
+ * Utility function to merge Tailwind CSS classes
258
+ * Combines clsx and tailwind-merge for conflict-free class merging
259
+ *
260
+ * @param inputs - Class values to merge
261
+ * @returns Merged class string
262
+ */
263
+ declare function cn(...inputs: ClassValue[]): string;
264
+
265
+ export { type BaseField, type ComponentRegistry, type FieldComponent, type FieldComponentProps, type FieldType, FormGenerator, type FormGeneratorProps, type FormSchema, type FormSystemContextValue, FormSystemProvider, type FormSystemProviderProps, type GridLayoutProps, type LayoutComponent, type LayoutComponentProps, type LayoutRegistry, type LayoutType, type Section, type SectionLayoutProps, type Variant, cn, useFormSystem };
package/dist/index.js ADDED
@@ -0,0 +1,164 @@
1
+ import { useFormContext, useWatch } from 'react-hook-form';
2
+ import { createContext, useMemo, useContext } from 'react';
3
+ import { jsx, jsxs } from 'react/jsx-runtime';
4
+ import { clsx } from 'clsx';
5
+ import { twMerge } from 'tailwind-merge';
6
+
7
+ // src/FormGenerator.tsx
8
+ var FormSystemContext = createContext(null);
9
+ function FormSystemProvider({
10
+ components,
11
+ layouts,
12
+ children
13
+ }) {
14
+ const value = useMemo(
15
+ () => ({
16
+ components: components || {},
17
+ layouts: layouts || {}
18
+ }),
19
+ [components, layouts]
20
+ );
21
+ return /* @__PURE__ */ jsx(FormSystemContext.Provider, { value, children });
22
+ }
23
+ function useFormSystem() {
24
+ const context = useContext(FormSystemContext);
25
+ if (!context) {
26
+ throw new Error("useFormSystem must be used within a FormSystemProvider");
27
+ }
28
+ return context;
29
+ }
30
+ function useFieldComponent(type, variant) {
31
+ const { components } = useFormSystem();
32
+ if (variant && typeof components[variant] === "object" && components[variant]) {
33
+ const variantComponents = components[variant];
34
+ if (variantComponents[type]) {
35
+ return variantComponents[type];
36
+ }
37
+ }
38
+ const Component = components[type] || components["default"] || components["text"];
39
+ if (!Component) {
40
+ if (process.env.NODE_ENV === "development") {
41
+ console.warn(`FormKit: No component found for type "${type}" (variant: ${variant})`);
42
+ return () => /* @__PURE__ */ jsxs("div", { style: { color: "red", padding: 4, border: "1px dashed red" }, children: [
43
+ "Missing: ",
44
+ type
45
+ ] });
46
+ }
47
+ return () => null;
48
+ }
49
+ return Component;
50
+ }
51
+ function useLayoutComponent(type, variant) {
52
+ const { layouts } = useFormSystem();
53
+ if (variant && typeof layouts[variant] === "object" && layouts[variant]) {
54
+ const variantLayouts = layouts[variant];
55
+ if (variantLayouts[type]) {
56
+ return variantLayouts[type];
57
+ }
58
+ }
59
+ return layouts[type] || layouts["default"] || DefaultLayout;
60
+ }
61
+ var DefaultLayout = ({ children, className }) => /* @__PURE__ */ jsx("div", { className, children });
62
+ function cn(...inputs) {
63
+ return twMerge(clsx(inputs));
64
+ }
65
+ function FormGenerator({
66
+ schema,
67
+ control,
68
+ disabled = false,
69
+ variant
70
+ }) {
71
+ const formContext = useFormContext();
72
+ const activeControl = control || formContext?.control;
73
+ if (!schema?.sections) return null;
74
+ return /* @__PURE__ */ jsx("div", { className: cn("form-generator-root", variant && `form-variant-${variant}`), children: schema.sections.map((section, idx) => /* @__PURE__ */ jsx(
75
+ SectionRenderer,
76
+ {
77
+ section,
78
+ control: activeControl,
79
+ disabled,
80
+ variant
81
+ },
82
+ section.id || idx
83
+ )) });
84
+ }
85
+ function SectionRenderer({
86
+ section,
87
+ control,
88
+ disabled,
89
+ variant
90
+ }) {
91
+ const activeVariant = section.variant || variant;
92
+ const SectionLayout = useLayoutComponent("section", activeVariant);
93
+ if (section.condition && !section.condition(control)) {
94
+ return null;
95
+ }
96
+ return /* @__PURE__ */ jsx(
97
+ SectionLayout,
98
+ {
99
+ title: section.title,
100
+ description: section.description,
101
+ icon: section.icon,
102
+ variant: activeVariant,
103
+ className: section.className,
104
+ children: section.render ? section.render({ control, disabled, section }) : /* @__PURE__ */ jsx(
105
+ GridRenderer,
106
+ {
107
+ fields: section.fields,
108
+ cols: section.cols,
109
+ control,
110
+ disabled,
111
+ variant: activeVariant
112
+ }
113
+ )
114
+ }
115
+ );
116
+ }
117
+ function GridRenderer({
118
+ fields,
119
+ cols = 1,
120
+ control,
121
+ disabled,
122
+ variant
123
+ }) {
124
+ const GridLayout = useLayoutComponent("grid", variant);
125
+ if (!fields || fields.length === 0) return null;
126
+ return /* @__PURE__ */ jsx(GridLayout, { cols, children: fields.map((field, idx) => /* @__PURE__ */ jsx(
127
+ FieldWrapper,
128
+ {
129
+ field,
130
+ control,
131
+ disabled,
132
+ variant
133
+ },
134
+ field.name || idx
135
+ )) });
136
+ }
137
+ function FieldWrapper({
138
+ field,
139
+ control,
140
+ disabled,
141
+ variant
142
+ }) {
143
+ const formValues = useWatch({ control });
144
+ if (field.condition && !field.condition(formValues)) {
145
+ return null;
146
+ }
147
+ const activeVariant = field.variant || variant;
148
+ const FieldComponent = useFieldComponent(field.type, activeVariant);
149
+ if (!FieldComponent) return null;
150
+ return /* @__PURE__ */ jsx("div", { className: field.fullWidth ? "col-span-full" : "", children: /* @__PURE__ */ jsx(
151
+ FieldComponent,
152
+ {
153
+ field,
154
+ control,
155
+ disabled: disabled || field.disabled,
156
+ variant: activeVariant,
157
+ ...field
158
+ }
159
+ ) });
160
+ }
161
+
162
+ export { FormGenerator, FormSystemProvider, cn, useFormSystem };
163
+ //# sourceMappingURL=index.js.map
164
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/FormSystemContext.tsx","../src/utils.ts","../src/FormGenerator.tsx"],"names":["jsx"],"mappings":"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","file":"index.js","sourcesContent":["\"use client\";\r\n\r\nimport { createContext, useContext, useMemo } from \"react\";\r\nimport type {\r\n FormSystemContextValue,\r\n FormSystemProviderProps,\r\n FieldComponent,\r\n LayoutComponent,\r\n FieldType,\r\n LayoutType,\r\n Variant,\r\n} from \"./types\";\r\n\r\nconst FormSystemContext = createContext<FormSystemContextValue | null>(null);\r\n\r\n/**\r\n * FormSystemProvider\r\n *\r\n * Provides the component registry to the form system.\r\n * This is the root provider that enables FormGenerator to work.\r\n *\r\n * @example\r\n * ```tsx\r\n * import { FormSystemProvider } from '@classytic/formkit';\r\n *\r\n * const components = {\r\n * text: TextInput,\r\n * select: SelectInput,\r\n * };\r\n *\r\n * const layouts = {\r\n * section: SectionLayout,\r\n * grid: GridLayout,\r\n * };\r\n *\r\n * function App() {\r\n * return (\r\n * <FormSystemProvider components={components} layouts={layouts}>\r\n * <YourFormComponent />\r\n * </FormSystemProvider>\r\n * );\r\n * }\r\n * ```\r\n */\r\nexport function FormSystemProvider({\r\n components,\r\n layouts,\r\n children,\r\n}: FormSystemProviderProps): JSX.Element {\r\n const value = useMemo<FormSystemContextValue>(\r\n () => ({\r\n components: components || {},\r\n layouts: layouts || {},\r\n }),\r\n [components, layouts]\r\n );\r\n\r\n return <FormSystemContext.Provider value={value}>{children}</FormSystemContext.Provider>;\r\n}\r\n\r\n/**\r\n * Hook to access the form system context\r\n *\r\n * @throws {Error} If used outside FormSystemProvider\r\n * @returns Form system context value\r\n *\r\n * @example\r\n * ```tsx\r\n * const { components, layouts } = useFormSystem();\r\n * ```\r\n */\r\nexport function useFormSystem(): FormSystemContextValue {\r\n const context = useContext(FormSystemContext);\r\n if (!context) {\r\n throw new Error(\"useFormSystem must be used within a FormSystemProvider\");\r\n }\r\n return context;\r\n}\r\n\r\n/**\r\n * Helper to resolve a component for a specific field type and variant\r\n *\r\n * @param type - Field type identifier\r\n * @param variant - Optional variant name\r\n * @returns Field component or fallback\r\n *\r\n * @internal\r\n */\r\nexport function useFieldComponent(type: FieldType, variant?: Variant): FieldComponent {\r\n const { components } = useFormSystem();\r\n\r\n // 1. Try variant-specific component\r\n if (variant && typeof components[variant] === \"object\" && components[variant]) {\r\n const variantComponents = components[variant] as Record<string, FieldComponent>;\r\n if (variantComponents[type]) {\r\n return variantComponents[type];\r\n }\r\n }\r\n\r\n // 2. Fallback to standard component\r\n const Component =\r\n (components[type] as FieldComponent) ||\r\n (components[\"default\"] as FieldComponent) ||\r\n (components[\"text\"] as FieldComponent);\r\n\r\n if (!Component) {\r\n // Library-safe fallback: Don't crash, just warn and render nothing or a placeholder\r\n if (process.env.NODE_ENV === \"development\") {\r\n console.warn(`FormKit: No component found for type \"${type}\" (variant: ${variant})`);\r\n return () => (\r\n <div style={{ color: \"red\", padding: 4, border: \"1px dashed red\" }}>\r\n Missing: {type}\r\n </div>\r\n );\r\n }\r\n return () => null;\r\n }\r\n\r\n return Component;\r\n}\r\n\r\n/**\r\n * Helper to resolve a layout component\r\n *\r\n * @param type - Layout type identifier\r\n * @param variant - Optional variant name\r\n * @returns Layout component or fallback\r\n *\r\n * @internal\r\n */\r\nexport function useLayoutComponent(type: LayoutType, variant?: Variant): LayoutComponent {\r\n const { layouts } = useFormSystem();\r\n\r\n // 1. Try variant-specific layout\r\n if (variant && typeof layouts[variant] === \"object\" && layouts[variant]) {\r\n const variantLayouts = layouts[variant] as Record<string, LayoutComponent>;\r\n if (variantLayouts[type]) {\r\n return variantLayouts[type];\r\n }\r\n }\r\n\r\n // 2. Fallback to standard layout\r\n return (\r\n (layouts[type] as LayoutComponent) ||\r\n (layouts[\"default\"] as LayoutComponent) ||\r\n DefaultLayout\r\n );\r\n}\r\n\r\n/**\r\n * Default layout component - simple div wrapper\r\n */\r\nconst DefaultLayout: LayoutComponent = ({ children, className }: any) => (\r\n <div className={className}>{children}</div>\r\n);\r\n","import { type ClassValue, clsx } from \"clsx\";\r\nimport { twMerge } from \"tailwind-merge\";\r\n\r\n/**\r\n * Utility function to merge Tailwind CSS classes\r\n * Combines clsx and tailwind-merge for conflict-free class merging\r\n *\r\n * @param inputs - Class values to merge\r\n * @returns Merged class string\r\n */\r\nexport function cn(...inputs: ClassValue[]): string {\r\n return twMerge(clsx(inputs));\r\n}\r\n","\"use client\";\r\n\r\nimport { useFormContext, useWatch } from \"react-hook-form\";\r\nimport type { Control, FieldValues } from \"react-hook-form\";\r\nimport { useFieldComponent, useLayoutComponent } from \"./FormSystemContext\";\r\nimport { cn } from \"./utils\";\r\nimport type { FormGeneratorProps, Section, BaseField, Variant } from \"./types\";\r\n\r\n/**\r\n * FormGenerator - Headless Form Generator Component\r\n *\r\n * Renders a form based on a schema, using components injected via FormSystemProvider.\r\n * Supports conditional fields, dynamic layouts, and variants.\r\n *\r\n * @template TFieldValues - Form field values type\r\n *\r\n * @example\r\n * ```tsx\r\n * import { useForm } from 'react-hook-form';\r\n * import { FormGenerator } from '@classytic/formkit';\r\n *\r\n * function MyForm() {\r\n * const { control } = useForm();\r\n *\r\n * const schema = {\r\n * sections: [\r\n * {\r\n * title: \"User Details\",\r\n * fields: [\r\n * { name: \"firstName\", type: \"text\", label: \"First Name\" },\r\n * { name: \"email\", type: \"email\", label: \"Email\" }\r\n * ]\r\n * }\r\n * ]\r\n * };\r\n *\r\n * return <FormGenerator schema={schema} control={control} />;\r\n * }\r\n * ```\r\n */\r\nexport function FormGenerator<TFieldValues extends FieldValues = FieldValues>({\r\n schema,\r\n control,\r\n disabled = false,\r\n variant,\r\n}: FormGeneratorProps<TFieldValues>): JSX.Element | null {\r\n // Use provided control or fall back to context\r\n const formContext = useFormContext<TFieldValues>();\r\n const activeControl = control || formContext?.control;\r\n\r\n if (!schema?.sections) return null;\r\n\r\n return (\r\n <div className={cn(\"form-generator-root\", variant && `form-variant-${variant}`)}>\r\n {schema.sections.map((section, idx) => (\r\n <SectionRenderer\r\n key={section.id || idx}\r\n section={section}\r\n control={activeControl}\r\n disabled={disabled}\r\n variant={variant}\r\n />\r\n ))}\r\n </div>\r\n );\r\n}\r\n\r\n/**\r\n * Section Renderer Props\r\n */\r\ninterface SectionRendererProps<TFieldValues extends FieldValues = FieldValues> {\r\n section: Section;\r\n control?: Control<TFieldValues>;\r\n disabled?: boolean;\r\n variant?: Variant;\r\n}\r\n\r\n/**\r\n * Renders a single section with its fields\r\n */\r\nfunction SectionRenderer<TFieldValues extends FieldValues = FieldValues>({\r\n section,\r\n control,\r\n disabled,\r\n variant,\r\n}: SectionRendererProps<TFieldValues>): JSX.Element | null {\r\n // Allow section to override variant, or use global variant\r\n const activeVariant = section.variant || variant;\r\n const SectionLayout = useLayoutComponent(\"section\", activeVariant);\r\n\r\n // Check condition if present\r\n if (section.condition && !section.condition(control)) {\r\n return null;\r\n }\r\n\r\n return (\r\n <SectionLayout\r\n title={section.title}\r\n description={section.description}\r\n icon={section.icon}\r\n variant={activeVariant}\r\n className={section.className}\r\n >\r\n {section.render ? (\r\n section.render({ control, disabled, section })\r\n ) : (\r\n <GridRenderer\r\n fields={section.fields}\r\n cols={section.cols}\r\n control={control}\r\n disabled={disabled}\r\n variant={activeVariant}\r\n />\r\n )}\r\n </SectionLayout>\r\n );\r\n}\r\n\r\n/**\r\n * Grid Renderer Props\r\n */\r\ninterface GridRendererProps<TFieldValues extends FieldValues = FieldValues> {\r\n fields?: BaseField[];\r\n cols?: number;\r\n control?: Control<TFieldValues>;\r\n disabled?: boolean;\r\n variant?: Variant;\r\n}\r\n\r\n/**\r\n * Renders a grid of fields with specified column layout\r\n */\r\nfunction GridRenderer<TFieldValues extends FieldValues = FieldValues>({\r\n fields,\r\n cols = 1,\r\n control,\r\n disabled,\r\n variant,\r\n}: GridRendererProps<TFieldValues>): JSX.Element | null {\r\n const GridLayout = useLayoutComponent(\"grid\", variant);\r\n\r\n if (!fields || fields.length === 0) return null;\r\n\r\n return (\r\n <GridLayout cols={cols}>\r\n {fields.map((field, idx) => (\r\n <FieldWrapper\r\n key={field.name || idx}\r\n field={field}\r\n control={control}\r\n disabled={disabled}\r\n variant={variant}\r\n />\r\n ))}\r\n </GridLayout>\r\n );\r\n}\r\n\r\n/**\r\n * Field Wrapper Props\r\n */\r\ninterface FieldWrapperProps<TFieldValues extends FieldValues = FieldValues> {\r\n field: BaseField;\r\n control?: Control<TFieldValues>;\r\n disabled?: boolean;\r\n variant?: Variant;\r\n}\r\n\r\n/**\r\n * Renders a single field wrapper\r\n * Handles conditional rendering and field visibility\r\n */\r\nfunction FieldWrapper<TFieldValues extends FieldValues = FieldValues>({\r\n field,\r\n control,\r\n disabled,\r\n variant,\r\n}: FieldWrapperProps<TFieldValues>): JSX.Element | null {\r\n // Watch values for conditional rendering\r\n const formValues = useWatch({ control });\r\n\r\n if (field.condition && !field.condition(formValues)) {\r\n return null;\r\n }\r\n\r\n // Allow field to override variant\r\n const activeVariant = field.variant || variant;\r\n const FieldComponent = useFieldComponent(field.type, activeVariant);\r\n\r\n if (!FieldComponent) return null;\r\n\r\n return (\r\n <div className={field.fullWidth ? \"col-span-full\" : \"\"}>\r\n <FieldComponent\r\n field={field}\r\n control={control as any}\r\n disabled={disabled || field.disabled}\r\n variant={activeVariant}\r\n // Pass all field props to the component\r\n {...field}\r\n />\r\n </div>\r\n );\r\n}\r\n"]}
package/package.json ADDED
@@ -0,0 +1,87 @@
1
+ {
2
+ "name": "@classytic/formkit",
3
+ "version": "1.0.0",
4
+ "description": "Headless, type-safe form generation engine for React. Schema-driven with full TypeScript support.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.mjs",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.mjs",
13
+ "require": "./dist/index.js"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "README.md",
19
+ "LICENSE"
20
+ ],
21
+ "scripts": {
22
+ "build": "tsup",
23
+ "dev": "tsup --watch",
24
+ "lint": "tsc --noEmit",
25
+ "test": "node --test tests/*.test.js",
26
+ "test:watch": "node --test --watch tests/*.test.js",
27
+ "prepublishOnly": "npm run lint && npm run build",
28
+ "version:patch": "npm version patch",
29
+ "version:minor": "npm version minor",
30
+ "version:major": "npm version major",
31
+ "publish:npm": "npm publish --access public",
32
+ "release:patch": "npm run version:patch && npm run publish:npm",
33
+ "release:minor": "npm run version:minor && npm run publish:npm",
34
+ "release:major": "npm run version:major && npm run publish:npm"
35
+ },
36
+ "keywords": [
37
+ "react",
38
+ "react19",
39
+ "forms",
40
+ "form-generator",
41
+ "headless",
42
+ "schema-driven",
43
+ "typescript",
44
+ "type-safe",
45
+ "react-hook-form",
46
+ "ui-agnostic",
47
+ "tailwind",
48
+ "shadcn",
49
+ "dynamic-forms",
50
+ "conditional-fields"
51
+ ],
52
+ "author": "Classytic",
53
+ "license": "MIT",
54
+ "repository": {
55
+ "type": "git",
56
+ "url": "git+https://github.com/classytic/formkit.git"
57
+ },
58
+ "bugs": {
59
+ "url": "https://github.com/classytic/formkit/issues"
60
+ },
61
+ "homepage": "https://github.com/classytic/formkit#readme",
62
+ "peerDependencies": {
63
+ "react": ">=18.3.0",
64
+ "react-dom": ">=18.3.0",
65
+ "react-hook-form": ">=7.50.0"
66
+ },
67
+ "dependencies": {
68
+ "clsx": "^2.1.1",
69
+ "tailwind-merge": "^2.5.5"
70
+ },
71
+ "devDependencies": {
72
+ "@types/node": "^22.8.7",
73
+ "@types/react": "^18.3.12",
74
+ "@types/react-dom": "^18.3.1",
75
+ "react": "^19.0.0",
76
+ "react-dom": "^19.0.0",
77
+ "react-hook-form": "^7.53.2",
78
+ "tsup": "^8.3.5",
79
+ "typescript": "^5.6.3"
80
+ },
81
+ "engines": {
82
+ "node": ">=18.0.0"
83
+ },
84
+ "publishConfig": {
85
+ "access": "public"
86
+ }
87
+ }