@saastro/forms 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +347 -0
- package/cli.js +732 -0
- package/dist/DateRenderers-3JUQNLKJ.js +139 -0
- package/dist/DateRenderers-3JUQNLKJ.js.map +1 -0
- package/dist/chunk-GHDCNAWC.js +247 -0
- package/dist/chunk-GHDCNAWC.js.map +1 -0
- package/dist/chunk-YXKDN3PU.js +687 -0
- package/dist/chunk-YXKDN3PU.js.map +1 -0
- package/dist/index.d.ts +3754 -0
- package/dist/index.js +7115 -0
- package/dist/index.js.map +1 -0
- package/dist/submitOrchestrator-RF76MOWG.js +13 -0
- package/dist/submitOrchestrator-RF76MOWG.js.map +1 -0
- package/package.json +85 -0
package/README.md
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
# @saastro/forms
|
|
2
|
+
|
|
3
|
+
Dynamic form system for React with Zod validation, React Hook Form, and a plugin system.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 30+ field types (text, select, date, slider, OTP, button-card, etc.)
|
|
8
|
+
- **Zero-config mode**: Just pass your shadcn components inline
|
|
9
|
+
- Multi-step forms with conditional navigation
|
|
10
|
+
- Zod schema validation per field
|
|
11
|
+
- Plugin system (localStorage, analytics, autosave)
|
|
12
|
+
- Modular submit actions (HTTP, webhook, email)
|
|
13
|
+
- Dependency injection for UI components
|
|
14
|
+
- Builder pattern API
|
|
15
|
+
- Responsive 12-column grid layout
|
|
16
|
+
- TypeScript strict mode
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @saastro/forms
|
|
22
|
+
# or
|
|
23
|
+
bun add @saastro/forms
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Peer Dependencies
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install react react-dom react-hook-form zod date-fns react-day-picker
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### shadcn/ui Components
|
|
33
|
+
|
|
34
|
+
Install only the components you need. For a basic text form:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npx shadcn@latest add input button label field form
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
For all available field types:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npx shadcn@latest add input button label textarea select checkbox radio-group popover calendar tooltip slider switch separator dialog command input-otp field form
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Tailwind CSS 4
|
|
47
|
+
|
|
48
|
+
If you're using Tailwind CSS 4, add the following `@source` directive to your main CSS file so Tailwind can detect the classes used by this package:
|
|
49
|
+
|
|
50
|
+
```css
|
|
51
|
+
@source "node_modules/@saastro/forms/dist/**/*.js";
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Quick Start (Zero-Config with Auto-Discovery)
|
|
55
|
+
|
|
56
|
+
The simplest way to use `@saastro/forms` — auto-discover all your shadcn components with Vite's glob:
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
import { Form, FormBuilder } from '@saastro/forms';
|
|
60
|
+
|
|
61
|
+
// Auto-discover all shadcn components at build time
|
|
62
|
+
const uiComponents = import.meta.glob('@/components/ui/*.tsx', { eager: true });
|
|
63
|
+
|
|
64
|
+
const config = FormBuilder.create('contact')
|
|
65
|
+
.addField('name', (f) => f.type('text').label('Name').required().minLength(2))
|
|
66
|
+
.addField('email', (f) => f.type('email').label('Email').required().email())
|
|
67
|
+
.addStep('main', ['name', 'email'])
|
|
68
|
+
.buttons({ submit: { type: 'submit', label: 'Send' } })
|
|
69
|
+
.build();
|
|
70
|
+
|
|
71
|
+
export function ContactForm() {
|
|
72
|
+
return (
|
|
73
|
+
<Form
|
|
74
|
+
config={config}
|
|
75
|
+
components={uiComponents}
|
|
76
|
+
onSubmit={(values) => console.log('Submitted:', values)}
|
|
77
|
+
/>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
That's it! No FormProvider, no barrel file, no manual component registration. The Form auto-discovers components by parsing the glob result.
|
|
83
|
+
|
|
84
|
+
### Alternative: Explicit Components
|
|
85
|
+
|
|
86
|
+
If you prefer explicit imports or need to override specific components:
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
import { Form, FormBuilder } from '@saastro/forms';
|
|
90
|
+
import { Input, Button, Label } from '@/components/ui';
|
|
91
|
+
import { Field, FieldLabel, FieldDescription, FieldError } from '@/components/ui/field';
|
|
92
|
+
import { FormField, FormControl } from '@/components/ui/form';
|
|
93
|
+
|
|
94
|
+
<Form
|
|
95
|
+
config={config}
|
|
96
|
+
components={{
|
|
97
|
+
Input,
|
|
98
|
+
Button,
|
|
99
|
+
Label,
|
|
100
|
+
Field,
|
|
101
|
+
FieldLabel,
|
|
102
|
+
FieldDescription,
|
|
103
|
+
FieldError,
|
|
104
|
+
FormField,
|
|
105
|
+
FormControl,
|
|
106
|
+
}}
|
|
107
|
+
/>;
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Benefits of Zero-Config
|
|
111
|
+
|
|
112
|
+
1. **One line of setup** — just `import.meta.glob` and you're done
|
|
113
|
+
2. **Auto-discovers components** — no manual registration needed
|
|
114
|
+
3. **Missing component fallback** — shows helpful warnings with install commands
|
|
115
|
+
4. **No Provider boilerplate** — just render `<Form />`
|
|
116
|
+
5. **Override per-form** — mix glob with explicit overrides
|
|
117
|
+
|
|
118
|
+
## Quick Start (Provider Mode)
|
|
119
|
+
|
|
120
|
+
For apps with many forms, you can still use the provider pattern:
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
import { Form, FormBuilder, ComponentProvider, createComponentRegistry } from '@saastro/forms';
|
|
124
|
+
import * as shadcn from '@/lib/form-components'; // Your barrel file
|
|
125
|
+
|
|
126
|
+
const registry = createComponentRegistry(shadcn);
|
|
127
|
+
|
|
128
|
+
export function App() {
|
|
129
|
+
return (
|
|
130
|
+
<ComponentProvider components={registry}>
|
|
131
|
+
<ContactForm />
|
|
132
|
+
<SignupForm />
|
|
133
|
+
<CheckoutForm />
|
|
134
|
+
</ComponentProvider>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function ContactForm() {
|
|
139
|
+
const config = FormBuilder.create('contact')
|
|
140
|
+
.addField('name', (f) => f.type('text').label('Name').schema(z.string().min(2)))
|
|
141
|
+
.addStep('main', ['name'])
|
|
142
|
+
.build();
|
|
143
|
+
|
|
144
|
+
return <Form config={config} />;
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Which Components Do I Need?
|
|
149
|
+
|
|
150
|
+
Each field type requires certain components. When a component is missing, you'll see a helpful warning with install instructions.
|
|
151
|
+
|
|
152
|
+
| Field Type | Required Components |
|
|
153
|
+
| ---------------------- | ------------------------------------------------------------------------- |
|
|
154
|
+
| `text`, `email`, `tel` | Input, Label, Field, FieldLabel, FieldError, FormField, FormControl |
|
|
155
|
+
| `textarea` | Textarea, Label, Field, FieldLabel, FieldError, FormField, FormControl |
|
|
156
|
+
| `select` | Select, SelectTrigger, SelectContent, SelectItem, SelectValue, Field, ... |
|
|
157
|
+
| `checkbox`, `switch` | Checkbox/Switch, Label, Field, ... |
|
|
158
|
+
| `date` | Calendar, Popover, PopoverTrigger, PopoverContent, Button, Field, ... |
|
|
159
|
+
| `combobox` | Command, CommandInput, CommandList, Popover, Button, Field, ... |
|
|
160
|
+
|
|
161
|
+
Use `getRequiredComponents(config)` to programmatically check:
|
|
162
|
+
|
|
163
|
+
```tsx
|
|
164
|
+
import { getRequiredComponents, getInstallCommand } from '@saastro/forms';
|
|
165
|
+
|
|
166
|
+
const required = getRequiredComponents(config);
|
|
167
|
+
// ['Input', 'Button', 'Label', 'Field', ...]
|
|
168
|
+
|
|
169
|
+
const installCmd = getInstallCommand(required);
|
|
170
|
+
// 'npx shadcn@latest add input button label field form'
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Multi-Step Form
|
|
174
|
+
|
|
175
|
+
```tsx
|
|
176
|
+
const config = FormBuilder.create('signup')
|
|
177
|
+
.layout('manual')
|
|
178
|
+
.columns(2)
|
|
179
|
+
.addField('email', (f) => f.type('email').label('Email').schema(z.string().email()))
|
|
180
|
+
.addField('password', (f) => f.type('text').label('Password').schema(z.string().min(8)))
|
|
181
|
+
.addField('name', (f) => f.type('text').label('Full Name').schema(z.string().min(2)))
|
|
182
|
+
.addField('plan', (f) =>
|
|
183
|
+
f
|
|
184
|
+
.type('button-radio')
|
|
185
|
+
.label('Choose Plan')
|
|
186
|
+
.schema(z.string())
|
|
187
|
+
.options([
|
|
188
|
+
{ label: 'Free', value: 'free' },
|
|
189
|
+
{ label: 'Pro', value: 'pro' },
|
|
190
|
+
]),
|
|
191
|
+
)
|
|
192
|
+
.addStep('account', ['email', 'password'])
|
|
193
|
+
.addStep('profile', ['name', 'plan'])
|
|
194
|
+
.initialStep('account')
|
|
195
|
+
.buttons({
|
|
196
|
+
submit: { type: 'submit', label: 'Create Account' },
|
|
197
|
+
next: { type: 'next', label: 'Next' },
|
|
198
|
+
back: { type: 'back', label: 'Back' },
|
|
199
|
+
})
|
|
200
|
+
.build();
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Plugins
|
|
204
|
+
|
|
205
|
+
```tsx
|
|
206
|
+
import { FormBuilder, PluginManager, localStoragePlugin, analyticsPlugin } from '@saastro/forms';
|
|
207
|
+
|
|
208
|
+
const plugins = new PluginManager();
|
|
209
|
+
plugins.register(localStoragePlugin({ key: 'my-form' }));
|
|
210
|
+
plugins.register(analyticsPlugin());
|
|
211
|
+
|
|
212
|
+
const config = FormBuilder.create('my-form')
|
|
213
|
+
.usePlugins(plugins)
|
|
214
|
+
// ... fields and steps
|
|
215
|
+
.build();
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Submit Actions
|
|
219
|
+
|
|
220
|
+
```tsx
|
|
221
|
+
const config = FormBuilder.create('form')
|
|
222
|
+
// ... fields and steps
|
|
223
|
+
.build();
|
|
224
|
+
|
|
225
|
+
// Add submit actions to config
|
|
226
|
+
config.submitActions = {
|
|
227
|
+
sendToAPI: {
|
|
228
|
+
id: 'sendToAPI',
|
|
229
|
+
action: {
|
|
230
|
+
name: 'Send to API',
|
|
231
|
+
type: 'http',
|
|
232
|
+
config: {
|
|
233
|
+
url: 'https://api.example.com/submit',
|
|
234
|
+
method: 'POST',
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
trigger: { type: 'onSubmit' },
|
|
238
|
+
order: 1,
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## Conditional Fields
|
|
244
|
+
|
|
245
|
+
```tsx
|
|
246
|
+
// Hide field based on another field's value
|
|
247
|
+
.addField("company", (f) =>
|
|
248
|
+
f.type("text")
|
|
249
|
+
.label("Company")
|
|
250
|
+
.schema(z.string().optional())
|
|
251
|
+
.hidden((values) => values.type !== "business")
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
// Responsive visibility
|
|
255
|
+
.addField("sidebar", (f) =>
|
|
256
|
+
f.type("html")
|
|
257
|
+
.hidden({ default: "hidden", lg: "visible" })
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
// Declarative conditions
|
|
261
|
+
.addField("discount", (f) =>
|
|
262
|
+
f.type("text")
|
|
263
|
+
.label("Discount Code")
|
|
264
|
+
.schema(z.string().optional())
|
|
265
|
+
.hidden({
|
|
266
|
+
operator: "and",
|
|
267
|
+
conditions: [
|
|
268
|
+
{ field: "plan", operator: "equals", value: "free" },
|
|
269
|
+
],
|
|
270
|
+
})
|
|
271
|
+
)
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
## Component Override
|
|
275
|
+
|
|
276
|
+
Override specific components per-form, even when using a provider:
|
|
277
|
+
|
|
278
|
+
```tsx
|
|
279
|
+
import { MyCustomDatePicker } from './components/MyCustomDatePicker';
|
|
280
|
+
|
|
281
|
+
<Form
|
|
282
|
+
config={config}
|
|
283
|
+
components={{
|
|
284
|
+
Calendar: MyCustomDatePicker, // Override just the Calendar
|
|
285
|
+
}}
|
|
286
|
+
/>;
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
## Field Types
|
|
290
|
+
|
|
291
|
+
| Type | Description |
|
|
292
|
+
| ---------------------- | ------------------------------------- |
|
|
293
|
+
| `text`, `email`, `tel` | Text inputs |
|
|
294
|
+
| `textarea` | Multi-line text |
|
|
295
|
+
| `select` | Dropdown select |
|
|
296
|
+
| `native-select` | Native HTML select |
|
|
297
|
+
| `combobox` | Searchable select (Popover + Command) |
|
|
298
|
+
| `command` | Command palette select |
|
|
299
|
+
| `checkbox`, `switch` | Boolean toggles |
|
|
300
|
+
| `radio` | Radio group |
|
|
301
|
+
| `button-radio` | Button-style radio |
|
|
302
|
+
| `button-checkbox` | Button-style multi-select |
|
|
303
|
+
| `button-card` | Card-style selection |
|
|
304
|
+
| `checkbox-group` | Multiple checkboxes |
|
|
305
|
+
| `switch-group` | Multiple switches |
|
|
306
|
+
| `date` | Date picker (simple or popover) |
|
|
307
|
+
| `daterange` | Date range picker |
|
|
308
|
+
| `slider` | Range slider |
|
|
309
|
+
| `otp` | OTP input |
|
|
310
|
+
| `input-group` | Input with prefix/suffix |
|
|
311
|
+
| `html` | Raw HTML content |
|
|
312
|
+
|
|
313
|
+
## API Reference
|
|
314
|
+
|
|
315
|
+
### Form Props
|
|
316
|
+
|
|
317
|
+
```tsx
|
|
318
|
+
interface FormProps {
|
|
319
|
+
/** Form configuration */
|
|
320
|
+
config: FormConfig;
|
|
321
|
+
/** Component overrides (zero-config mode) */
|
|
322
|
+
components?: PartialComponentRegistry;
|
|
323
|
+
/** Called on successful submit */
|
|
324
|
+
onSubmit?: (values: Record<string, unknown>) => void | Promise<void>;
|
|
325
|
+
/** Called on submit error */
|
|
326
|
+
onError?: (error: Error) => void;
|
|
327
|
+
/** CSS class for form element */
|
|
328
|
+
className?: string;
|
|
329
|
+
}
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### Utility Functions
|
|
333
|
+
|
|
334
|
+
```tsx
|
|
335
|
+
// Get components needed for a form
|
|
336
|
+
getRequiredComponents(config: FormConfig): ComponentName[]
|
|
337
|
+
|
|
338
|
+
// Get missing components
|
|
339
|
+
getMissingComponents(required: ComponentName[], provided: Partial<...>): ComponentName[]
|
|
340
|
+
|
|
341
|
+
// Generate install command
|
|
342
|
+
getInstallCommand(missing: ComponentName[]): string
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
## License
|
|
346
|
+
|
|
347
|
+
MIT
|