@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 +21 -0
- package/README.md +273 -0
- package/dist/index.cjs +169 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +265 -0
- package/dist/index.d.ts +265 -0
- package/dist/index.js +164 -0
- package/dist/index.js.map +1 -0
- package/package.json +87 -0
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
|
+
[](https://www.npmjs.com/package/@classytic/formkit)
|
|
6
|
+
[](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"]}
|
package/dist/index.d.cts
ADDED
|
@@ -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.d.ts
ADDED
|
@@ -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
|
+
}
|