@page-speed/forms 0.7.9 → 0.8.1
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 +217 -116
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
# @page-speed/forms
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## Type-safe, high-performance React form state and input components for OpenSite/DashTrack workloads
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
<br />
|
|
6
8
|
|
|
7
9
|
[](https://www.npmjs.com/package/@page-speed/forms)
|
|
8
10
|
[](https://www.npmjs.com/package/@page-speed/forms)
|
|
@@ -10,8 +12,8 @@ Type-safe, high-performance React form state and input components for OpenSite/D
|
|
|
10
12
|
|
|
11
13
|
## Highlights
|
|
12
14
|
|
|
15
|
+
- **`FormEngine`** — declarative form component with built-in API integration
|
|
13
16
|
- Field-level reactivity via `@legendapp/state/react`
|
|
14
|
-
- Typed `useForm` and `useField` APIs
|
|
15
17
|
- Built-in input library (text, select, date, time, upload, rich text)
|
|
16
18
|
- Tree-shakable subpath exports (`/core`, `/inputs`, `/validation`, `/upload`, `/integration`)
|
|
17
19
|
- Validation rules and utilities (sync + async)
|
|
@@ -30,112 +32,151 @@ Peer dependencies:
|
|
|
30
32
|
- `react >= 16.8.0`
|
|
31
33
|
- `react-dom >= 16.8.0`
|
|
32
34
|
|
|
33
|
-
## Quick Start
|
|
35
|
+
## Quick Start with FormEngine
|
|
36
|
+
|
|
37
|
+
`FormEngine` is the recommended entry point for most use cases. It provides a declarative API for rendering forms with built-in API integration, validation, file uploads, and styling.
|
|
34
38
|
|
|
35
39
|
```tsx
|
|
36
40
|
import * as React from "react";
|
|
37
|
-
import {
|
|
38
|
-
|
|
39
|
-
|
|
41
|
+
import {
|
|
42
|
+
FormEngine,
|
|
43
|
+
type FormFieldConfig,
|
|
44
|
+
} from "@page-speed/forms/integration";
|
|
40
45
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
46
|
+
const fields: FormFieldConfig[] = [
|
|
47
|
+
{
|
|
48
|
+
name: "full_name",
|
|
49
|
+
type: "text",
|
|
50
|
+
label: "Full Name",
|
|
51
|
+
required: true,
|
|
52
|
+
placeholder: "Your name",
|
|
53
|
+
columnSpan: 12,
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: "email",
|
|
57
|
+
type: "email",
|
|
58
|
+
label: "Email",
|
|
59
|
+
required: true,
|
|
60
|
+
placeholder: "you@example.com",
|
|
61
|
+
columnSpan: 6,
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: "phone",
|
|
65
|
+
type: "tel",
|
|
66
|
+
label: "Phone",
|
|
67
|
+
columnSpan: 6,
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: "content",
|
|
71
|
+
type: "textarea",
|
|
72
|
+
label: "Message",
|
|
73
|
+
required: true,
|
|
74
|
+
columnSpan: 12,
|
|
75
|
+
},
|
|
76
|
+
];
|
|
57
77
|
|
|
78
|
+
export function ContactForm() {
|
|
58
79
|
return (
|
|
59
|
-
<
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
80
|
+
<FormEngine
|
|
81
|
+
api={{
|
|
82
|
+
endpoint: "/api/contact",
|
|
83
|
+
method: "post",
|
|
84
|
+
submissionConfig: { behavior: "showConfirmation" },
|
|
85
|
+
}}
|
|
86
|
+
fields={fields}
|
|
87
|
+
successMessage="Thanks for reaching out!"
|
|
88
|
+
formLayoutSettings={{
|
|
89
|
+
submitButtonSetup: {
|
|
90
|
+
submitLabel: "Send Message",
|
|
91
|
+
},
|
|
92
|
+
}}
|
|
93
|
+
/>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
```
|
|
69
97
|
|
|
70
|
-
|
|
71
|
-
{({ field, meta }) => (
|
|
72
|
-
<TextInput
|
|
73
|
-
{...field}
|
|
74
|
-
type="email"
|
|
75
|
-
placeholder="you@example.com"
|
|
76
|
-
error={Boolean(meta.touched && meta.error)}
|
|
77
|
-
/>
|
|
78
|
-
)}
|
|
79
|
-
</Field>
|
|
98
|
+
### FormEngine Props
|
|
80
99
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
/>
|
|
92
|
-
)}
|
|
93
|
-
</Field>
|
|
100
|
+
| Prop | Type | Description |
|
|
101
|
+
|------|------|-------------|
|
|
102
|
+
| `api` | `PageSpeedFormConfig` | API endpoint and submission configuration |
|
|
103
|
+
| `fields` | `FormFieldConfig[]` | Array of field definitions |
|
|
104
|
+
| `formLayoutSettings` | `FormEngineLayoutSettings` | Layout, style, and submit button settings |
|
|
105
|
+
| `successMessage` | `ReactNode` | Message shown after successful submission |
|
|
106
|
+
| `onSubmit` | `(values) => void \| Promise<void>` | Custom submit handler |
|
|
107
|
+
| `onSuccess` | `(data) => void` | Called after successful submission |
|
|
108
|
+
| `onError` | `(error) => void` | Called when submission fails |
|
|
109
|
+
| `resetOnSuccess` | `boolean` | Reset form after success (default: `true`) |
|
|
94
110
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
111
|
+
### Field Configuration
|
|
112
|
+
|
|
113
|
+
Each field in the `fields` array supports:
|
|
114
|
+
|
|
115
|
+
```tsx
|
|
116
|
+
interface FormFieldConfig {
|
|
117
|
+
name: string;
|
|
118
|
+
type: "text" | "email" | "tel" | "textarea" | "select" | "multiselect" |
|
|
119
|
+
"date" | "daterange" | "time" | "file" | "checkbox" | "radio";
|
|
120
|
+
label?: string;
|
|
121
|
+
placeholder?: string;
|
|
122
|
+
required?: boolean;
|
|
123
|
+
columnSpan?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
|
|
124
|
+
className?: string;
|
|
125
|
+
options?: { label: string; value: string }[]; // For select/multiselect/radio
|
|
126
|
+
// File-specific props
|
|
127
|
+
accept?: string;
|
|
128
|
+
maxFiles?: number;
|
|
129
|
+
maxFileSize?: number;
|
|
100
130
|
}
|
|
101
131
|
```
|
|
102
132
|
|
|
103
|
-
###
|
|
133
|
+
### Layout Options
|
|
104
134
|
|
|
105
|
-
|
|
135
|
+
#### Standard Layout (default)
|
|
106
136
|
|
|
107
|
-
|
|
108
|
-
import * as React from "react";
|
|
109
|
-
import { Form } from "@page-speed/forms";
|
|
137
|
+
Multi-column grid with a submit button below the fields:
|
|
110
138
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
139
|
+
```tsx
|
|
140
|
+
<FormEngine
|
|
141
|
+
fields={fields}
|
|
142
|
+
formLayoutSettings={{
|
|
143
|
+
formLayout: "standard",
|
|
144
|
+
submitButtonSetup: {
|
|
145
|
+
submitLabel: "Submit",
|
|
146
|
+
submitVariant: "default", // | "destructive" | "outline" | "secondary" | "ghost" | "link"
|
|
147
|
+
},
|
|
148
|
+
styleRules: {
|
|
149
|
+
formContainer: "max-w-2xl mx-auto",
|
|
150
|
+
fieldsContainer: "gap-6",
|
|
151
|
+
formClassName: "space-y-4",
|
|
152
|
+
},
|
|
121
153
|
}}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
154
|
+
/>
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
#### Button-Group Layout
|
|
158
|
+
|
|
159
|
+
Inline input with submit button (e.g., newsletter signup):
|
|
160
|
+
|
|
161
|
+
```tsx
|
|
162
|
+
<FormEngine
|
|
163
|
+
fields={[{ name: "email", type: "email", label: "Email", required: true }]}
|
|
164
|
+
formLayoutSettings={{
|
|
165
|
+
formLayout: "button-group",
|
|
166
|
+
buttonGroupSetup: {
|
|
167
|
+
size: "lg", // | "xs" | "sm" | "default"
|
|
168
|
+
submitLabel: "Subscribe",
|
|
169
|
+
submitVariant: "default",
|
|
127
170
|
},
|
|
128
171
|
}}
|
|
129
|
-
|
|
172
|
+
/>
|
|
130
173
|
```
|
|
131
174
|
|
|
132
|
-
###
|
|
175
|
+
### Using formEngineSetup Wrapper
|
|
133
176
|
|
|
134
|
-
|
|
135
|
-
for block/component libraries that want to provide local default fields and styles.
|
|
177
|
+
For block/component libraries that provide default configurations:
|
|
136
178
|
|
|
137
179
|
```tsx
|
|
138
|
-
import * as React from "react";
|
|
139
180
|
import {
|
|
140
181
|
FormEngine,
|
|
141
182
|
type FormEngineSetup,
|
|
@@ -151,68 +192,78 @@ const defaultStyleRules: FormEngineStyleRules = {
|
|
|
151
192
|
formClassName: "space-y-6",
|
|
152
193
|
};
|
|
153
194
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
195
|
+
// Consumer passes setup, component provides defaults
|
|
196
|
+
function ContactBlock({ formEngineSetup }: { formEngineSetup?: FormEngineSetup }) {
|
|
197
|
+
return (
|
|
198
|
+
<FormEngine
|
|
199
|
+
formEngineSetup={formEngineSetup}
|
|
200
|
+
defaultFields={defaultFields}
|
|
201
|
+
defaultStyleRules={defaultStyleRules}
|
|
202
|
+
/>
|
|
203
|
+
);
|
|
204
|
+
}
|
|
164
205
|
```
|
|
165
206
|
|
|
166
207
|
## Package Entry Points
|
|
167
208
|
|
|
168
209
|
### Main
|
|
210
|
+
|
|
169
211
|
- `@page-speed/forms`
|
|
170
212
|
|
|
171
|
-
Exports:
|
|
213
|
+
#### Exports:
|
|
214
|
+
|
|
172
215
|
- `useForm`, `useField`, `Form`, `Field`, `FormContext`
|
|
173
|
-
-
|
|
216
|
+
- Core form/types interfaces
|
|
217
|
+
|
|
218
|
+
### Integration (Recommended)
|
|
219
|
+
|
|
220
|
+
- `@page-speed/forms/integration`
|
|
221
|
+
|
|
222
|
+
#### Exports:
|
|
223
|
+
|
|
224
|
+
- `FormEngine`, `FormEngineSetup`, `FormEngineProps`
|
|
225
|
+
- `FormFieldConfig`, `FormEngineStyleRules`, `FormEngineLayoutSettings`
|
|
226
|
+
- `DynamicFormField`, `useContactForm`, `useFileUpload`
|
|
174
227
|
|
|
175
228
|
### Inputs
|
|
229
|
+
|
|
176
230
|
- `@page-speed/forms/inputs`
|
|
177
231
|
|
|
178
|
-
Exports:
|
|
179
|
-
|
|
180
|
-
- `TextArea`
|
|
181
|
-
- `
|
|
182
|
-
- `CheckboxGroup`
|
|
183
|
-
- `Radio`
|
|
184
|
-
- `Select`
|
|
185
|
-
- `MultiSelect`
|
|
186
|
-
- `DatePicker`
|
|
187
|
-
- `DateRangePicker`
|
|
188
|
-
- `TimePicker`
|
|
232
|
+
#### Exports:
|
|
233
|
+
|
|
234
|
+
- `TextInput`, `TextArea`, `Checkbox`, `CheckboxGroup`, `Radio`
|
|
235
|
+
- `Select`, `MultiSelect`, `DatePicker`, `DateRangePicker`, `TimePicker`
|
|
189
236
|
- `FileInput`
|
|
190
237
|
|
|
191
238
|
### Validation
|
|
239
|
+
|
|
192
240
|
- `@page-speed/forms/validation`
|
|
193
241
|
- `@page-speed/forms/validation/rules`
|
|
194
242
|
- `@page-speed/forms/validation/utils`
|
|
195
243
|
- `@page-speed/forms/validation/valibot`
|
|
196
244
|
|
|
197
|
-
### Upload
|
|
245
|
+
### Upload
|
|
246
|
+
|
|
198
247
|
- `@page-speed/forms/upload`
|
|
199
|
-
- `@page-speed/forms/integration`
|
|
200
248
|
|
|
201
249
|
## Input Notes
|
|
202
250
|
|
|
203
251
|
### `TimePicker`
|
|
204
|
-
|
|
252
|
+
|
|
253
|
+
`TimePicker` uses a native `input[type="time"]` UX internally.
|
|
205
254
|
|
|
206
255
|
- Accepts controlled values in `HH:mm` (24-hour) or `h:mm AM/PM` (12-hour)
|
|
207
256
|
- Emits `HH:mm` when `use24Hour` is `true`
|
|
208
257
|
- Emits `h:mm AM/PM` when `use24Hour` is `false`
|
|
209
258
|
|
|
210
259
|
### `DatePicker` and `DateRangePicker`
|
|
260
|
+
|
|
211
261
|
- Calendar popovers close on outside click
|
|
212
262
|
- Compact month/day layout using tokenized Tailwind classes
|
|
213
263
|
- `DateRangePicker` renders two months and highlights endpoints + in-range dates
|
|
214
264
|
|
|
215
265
|
### `Select` and `MultiSelect`
|
|
266
|
+
|
|
216
267
|
- Close on outside click
|
|
217
268
|
- Search support
|
|
218
269
|
- Option groups
|
|
@@ -222,8 +273,6 @@ Exports:
|
|
|
222
273
|
|
|
223
274
|
This library ships with Tailwind utility classes and semantic token class names.
|
|
224
275
|
|
|
225
|
-
It is **not** a BEM-only unstyled package anymore.
|
|
226
|
-
|
|
227
276
|
### Base conventions
|
|
228
277
|
|
|
229
278
|
- Inputs/triggers are transparent shells with semantic borders/rings
|
|
@@ -231,6 +280,19 @@ It is **not** a BEM-only unstyled package anymore.
|
|
|
231
280
|
- Error states use destructive border/ring
|
|
232
281
|
- Dropdown selected rows use muted backgrounds
|
|
233
282
|
|
|
283
|
+
### FormEngine Style Rules
|
|
284
|
+
|
|
285
|
+
```tsx
|
|
286
|
+
interface FormEngineStyleRules {
|
|
287
|
+
formContainer?: string; // Wrapper around <form>
|
|
288
|
+
fieldsContainer?: string; // Grid wrapper for fields
|
|
289
|
+
fieldClassName?: string; // Fallback className for fields
|
|
290
|
+
formClassName?: string; // Applied to <form> element
|
|
291
|
+
successMessageClassName?: string;
|
|
292
|
+
errorMessageClassName?: string;
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
234
296
|
### Autofill normalization
|
|
235
297
|
|
|
236
298
|
Text-like controls apply autofill reset classes to avoid browser-injected background/text colors breaking your theme contrast.
|
|
@@ -240,6 +302,7 @@ See `INPUT_AUTOFILL_RESET_CLASSES` in `src/utils.ts`.
|
|
|
240
302
|
### Token requirements
|
|
241
303
|
|
|
242
304
|
Ensure your app defines semantic tokens used in classes such as:
|
|
305
|
+
|
|
243
306
|
- `background`, `foreground`, `border`, `input`, `ring`
|
|
244
307
|
- `primary`, `primary-foreground`
|
|
245
308
|
- `muted`, `muted-foreground`
|
|
@@ -249,9 +312,45 @@ Ensure your app defines semantic tokens used in classes such as:
|
|
|
249
312
|
|
|
250
313
|
For complete styling guidance, see [`docs/STYLES.md`](./docs/STYLES.md).
|
|
251
314
|
|
|
315
|
+
## Advanced: Low-Level APIs
|
|
316
|
+
|
|
317
|
+
For custom form implementations, the lower-level `useForm`, `Form`, and `Field` APIs are available:
|
|
318
|
+
|
|
319
|
+
```tsx
|
|
320
|
+
import { Form, Field, useForm } from "@page-speed/forms";
|
|
321
|
+
import { TextInput } from "@page-speed/forms/inputs";
|
|
322
|
+
|
|
323
|
+
function CustomForm() {
|
|
324
|
+
const form = useForm({
|
|
325
|
+
initialValues: { email: "" },
|
|
326
|
+
validationSchema: {
|
|
327
|
+
email: (value) => (!value ? "Required" : undefined),
|
|
328
|
+
},
|
|
329
|
+
onSubmit: async (values) => {
|
|
330
|
+
console.log(values);
|
|
331
|
+
},
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
return (
|
|
335
|
+
<Form form={form}>
|
|
336
|
+
<Field name="email" label="Email">
|
|
337
|
+
{({ field, meta }) => (
|
|
338
|
+
<TextInput
|
|
339
|
+
{...field}
|
|
340
|
+
error={Boolean(meta.touched && meta.error)}
|
|
341
|
+
/>
|
|
342
|
+
)}
|
|
343
|
+
</Field>
|
|
344
|
+
<button type="submit">Submit</button>
|
|
345
|
+
</Form>
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
252
350
|
## Validation Utilities
|
|
253
351
|
|
|
254
352
|
Use built-in rules:
|
|
353
|
+
|
|
255
354
|
- `required`, `email`, `url`, `phone`
|
|
256
355
|
- `minLength`, `maxLength`, `min`, `max`
|
|
257
356
|
- `pattern`, `matches`, `oneOf`
|
|
@@ -259,14 +358,16 @@ Use built-in rules:
|
|
|
259
358
|
- `compose`
|
|
260
359
|
|
|
261
360
|
Use utilities from `/validation/utils`:
|
|
361
|
+
|
|
262
362
|
- `debounce`, `asyncValidator`, `crossFieldValidator`, `when`
|
|
263
363
|
- `setErrorMessages`, `getErrorMessage`, `resetErrorMessages`
|
|
264
364
|
|
|
265
365
|
## File Uploads
|
|
266
366
|
|
|
267
|
-
`FileInput`
|
|
367
|
+
`FileInput` and `FormEngine` support validation, drag/drop, preview, and crop workflows.
|
|
268
368
|
|
|
269
369
|
For full two-phase upload patterns and serializer usage, see:
|
|
370
|
+
|
|
270
371
|
- [`docs/FILE_UPLOADS.md`](./docs/FILE_UPLOADS.md)
|
|
271
372
|
- `@page-speed/forms/integration`
|
|
272
373
|
|