@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 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