@bacano/sdk 1.1.15 → 1.1.17
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 +6 -0
- package/STOREFRONT_FORMS.md +147 -0
- package/client.d.ts +8 -1
- package/index.cjs +172 -2
- package/index.d.ts +1 -1
- package/index.js +172 -2
- package/modules/forms.d.ts +9 -0
- package/package.json +6 -1
- package/react/form-field-control.d.ts +10 -0
- package/react/forms.cjs +450 -0
- package/react/forms.d.ts +49 -0
- package/react/forms.js +435 -0
- package/react.cjs +172 -2
- package/react.js +172 -2
- package/skills/bacano-web/SKILL.md +3 -0
- package/skills/bacano-web/references/forms.md +50 -0
- package/skills/bacano-web/references/informational.md +3 -0
- package/types.d.ts +83 -0
package/react/forms.cjs
ADDED
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// libs/sdk/src/react/forms.tsx
|
|
21
|
+
var forms_exports = {};
|
|
22
|
+
__export(forms_exports, {
|
|
23
|
+
BacanoForm: () => BacanoForm,
|
|
24
|
+
useBacanoForm: () => useBacanoForm
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(forms_exports);
|
|
27
|
+
var import_react3 = require("react");
|
|
28
|
+
|
|
29
|
+
// libs/sdk/src/react/form-field-control.tsx
|
|
30
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
31
|
+
function BacanoFormFieldControl(props) {
|
|
32
|
+
const { field, className, value, onChange } = props;
|
|
33
|
+
const common = {
|
|
34
|
+
id: field.key,
|
|
35
|
+
name: field.key,
|
|
36
|
+
required: field.required,
|
|
37
|
+
className
|
|
38
|
+
};
|
|
39
|
+
if (field.type === "textarea") {
|
|
40
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
41
|
+
"textarea",
|
|
42
|
+
{
|
|
43
|
+
...common,
|
|
44
|
+
value: typeof value === "string" ? value : "",
|
|
45
|
+
placeholder: field.placeholder ?? void 0,
|
|
46
|
+
minLength: field.validation?.minLength ?? void 0,
|
|
47
|
+
maxLength: field.validation?.maxLength ?? void 0,
|
|
48
|
+
onChange: (event) => onChange(event.target.value)
|
|
49
|
+
}
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
if (field.type === "select" || field.type === "multi_select") {
|
|
53
|
+
const isMultiple = field.type === "multi_select";
|
|
54
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
55
|
+
"select",
|
|
56
|
+
{
|
|
57
|
+
...common,
|
|
58
|
+
multiple: isMultiple,
|
|
59
|
+
value: isMultiple && Array.isArray(value) ? value : String(value ?? ""),
|
|
60
|
+
onChange: (event) => onChange(
|
|
61
|
+
isMultiple ? Array.from(
|
|
62
|
+
event.target.selectedOptions,
|
|
63
|
+
(option) => option.value
|
|
64
|
+
) : event.target.value
|
|
65
|
+
),
|
|
66
|
+
children: [
|
|
67
|
+
!isMultiple ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "", children: field.placeholder ?? "Selecciona una opci\xF3n" }) : null,
|
|
68
|
+
(field.options ?? []).map((option) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: option.value, children: option.label }, option.value))
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
if (field.type === "radio") {
|
|
74
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { role: "radiogroup", "aria-labelledby": `${field.key}-label`, children: (field.options ?? []).map((option) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
|
|
75
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
76
|
+
"input",
|
|
77
|
+
{
|
|
78
|
+
type: "radio",
|
|
79
|
+
name: field.key,
|
|
80
|
+
value: option.value,
|
|
81
|
+
checked: value === option.value,
|
|
82
|
+
required: field.required,
|
|
83
|
+
onChange: (event) => onChange(event.target.value)
|
|
84
|
+
}
|
|
85
|
+
),
|
|
86
|
+
option.label
|
|
87
|
+
] }, option.value)) });
|
|
88
|
+
}
|
|
89
|
+
if (field.type === "checkbox") {
|
|
90
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
91
|
+
"input",
|
|
92
|
+
{
|
|
93
|
+
...common,
|
|
94
|
+
type: "checkbox",
|
|
95
|
+
checked: value === true,
|
|
96
|
+
onChange: (event) => onChange(event.target.checked)
|
|
97
|
+
}
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
101
|
+
"input",
|
|
102
|
+
{
|
|
103
|
+
...common,
|
|
104
|
+
type: field.type === "phone" ? "tel" : field.type,
|
|
105
|
+
value: typeof value === "string" ? value : "",
|
|
106
|
+
placeholder: field.placeholder ?? void 0,
|
|
107
|
+
min: field.validation?.min ?? void 0,
|
|
108
|
+
max: field.validation?.max ?? void 0,
|
|
109
|
+
minLength: field.validation?.minLength ?? void 0,
|
|
110
|
+
maxLength: field.validation?.maxLength ?? void 0,
|
|
111
|
+
onChange: (event) => onChange(event.target.value)
|
|
112
|
+
}
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// libs/sdk/src/react/hooks.ts
|
|
117
|
+
var import_react2 = require("react");
|
|
118
|
+
|
|
119
|
+
// libs/sdk/src/react/context-registry.ts
|
|
120
|
+
var import_react = require("react");
|
|
121
|
+
var REACT_CONTEXT_REGISTRY = /* @__PURE__ */ Symbol.for(
|
|
122
|
+
"@bacano/sdk/react-context-registry/v1"
|
|
123
|
+
);
|
|
124
|
+
function getReactContextRegistry() {
|
|
125
|
+
const globalRegistry = globalThis;
|
|
126
|
+
const existing = globalRegistry[REACT_CONTEXT_REGISTRY];
|
|
127
|
+
if (existing instanceof Map) {
|
|
128
|
+
return existing;
|
|
129
|
+
}
|
|
130
|
+
const registry = /* @__PURE__ */ new Map();
|
|
131
|
+
globalRegistry[REACT_CONTEXT_REGISTRY] = registry;
|
|
132
|
+
return registry;
|
|
133
|
+
}
|
|
134
|
+
function getOrCreateReactContext(key, defaultValue) {
|
|
135
|
+
const registry = getReactContextRegistry();
|
|
136
|
+
const existing = registry.get(key);
|
|
137
|
+
if (existing) {
|
|
138
|
+
return existing;
|
|
139
|
+
}
|
|
140
|
+
const context = (0, import_react.createContext)(defaultValue);
|
|
141
|
+
registry.set(key, context);
|
|
142
|
+
return context;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// libs/sdk/src/react/context.ts
|
|
146
|
+
var BacanoReactContext = getOrCreateReactContext("bacano-client-v1", {
|
|
147
|
+
client: null,
|
|
148
|
+
loading: true,
|
|
149
|
+
error: null
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// libs/sdk/src/react/hooks.ts
|
|
153
|
+
function useBacanoState() {
|
|
154
|
+
return (0, import_react2.useContext)(BacanoReactContext);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// libs/sdk/src/react/forms.tsx
|
|
158
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
159
|
+
var EMPTY_FORM_FIELDS = [];
|
|
160
|
+
var EMPTY_FORM_SECTIONS = [];
|
|
161
|
+
var EMPTY_FORM_STEPS = [];
|
|
162
|
+
function useBacanoForm(formKey, options) {
|
|
163
|
+
const {
|
|
164
|
+
client,
|
|
165
|
+
loading: clientLoading,
|
|
166
|
+
error: clientError
|
|
167
|
+
} = useBacanoState();
|
|
168
|
+
const enabled = options?.enabled ?? true;
|
|
169
|
+
const initialForm = options?.initialForm?.key === formKey ? options.initialForm : null;
|
|
170
|
+
const [form, setForm] = (0, import_react3.useState)(initialForm);
|
|
171
|
+
const formRef = (0, import_react3.useRef)(initialForm);
|
|
172
|
+
const [loading, setLoading] = (0, import_react3.useState)(enabled && !initialForm);
|
|
173
|
+
const [error, setError] = (0, import_react3.useState)(null);
|
|
174
|
+
const load = (0, import_react3.useCallback)(
|
|
175
|
+
async (force = false) => {
|
|
176
|
+
if (!enabled || !client) return;
|
|
177
|
+
setLoading(formRef.current === null);
|
|
178
|
+
setError(null);
|
|
179
|
+
try {
|
|
180
|
+
const nextForm = await client.forms.getDefinition(formKey, { force });
|
|
181
|
+
formRef.current = nextForm;
|
|
182
|
+
setForm(nextForm);
|
|
183
|
+
} catch (loadError) {
|
|
184
|
+
if (!formRef.current) setForm(null);
|
|
185
|
+
setError(
|
|
186
|
+
loadError instanceof Error ? loadError : new Error(String(loadError))
|
|
187
|
+
);
|
|
188
|
+
} finally {
|
|
189
|
+
setLoading(false);
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
[client, enabled, formKey]
|
|
193
|
+
);
|
|
194
|
+
(0, import_react3.useEffect)(() => {
|
|
195
|
+
if (!enabled) {
|
|
196
|
+
formRef.current = null;
|
|
197
|
+
setForm(null);
|
|
198
|
+
setLoading(false);
|
|
199
|
+
setError(null);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
if (client) void load();
|
|
203
|
+
}, [client, enabled, load]);
|
|
204
|
+
return {
|
|
205
|
+
form,
|
|
206
|
+
loading: enabled && form === null && (clientLoading || loading),
|
|
207
|
+
error: clientError ?? error,
|
|
208
|
+
refresh: async () => load(true)
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
function FormFields(props) {
|
|
212
|
+
return props.fields.map((field) => {
|
|
213
|
+
const value = props.values[field.key] ?? (field.type === "checkbox" ? false : field.type === "multi_select" ? [] : "");
|
|
214
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: props.classNames?.field, children: [
|
|
215
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
216
|
+
"label",
|
|
217
|
+
{
|
|
218
|
+
id: `${field.key}-label`,
|
|
219
|
+
htmlFor: field.type === "radio" ? void 0 : field.key,
|
|
220
|
+
className: props.classNames?.label,
|
|
221
|
+
children: field.label
|
|
222
|
+
}
|
|
223
|
+
),
|
|
224
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
225
|
+
BacanoFormFieldControl,
|
|
226
|
+
{
|
|
227
|
+
field,
|
|
228
|
+
value,
|
|
229
|
+
className: props.classNames?.input,
|
|
230
|
+
onChange: (nextValue) => props.onChange(field.key, nextValue)
|
|
231
|
+
}
|
|
232
|
+
),
|
|
233
|
+
field.helpText ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: props.classNames?.helpText, children: field.helpText }) : null
|
|
234
|
+
] }, field.key);
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
function FormSections(props) {
|
|
238
|
+
return props.sections.map((section) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("section", { className: props.classNames?.section, children: [
|
|
239
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { className: props.classNames?.sectionTitle, children: section.title }),
|
|
240
|
+
section.description ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: props.classNames?.sectionDescription, children: section.description }) : null,
|
|
241
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
242
|
+
FormFields,
|
|
243
|
+
{
|
|
244
|
+
fields: section.fields,
|
|
245
|
+
values: props.values,
|
|
246
|
+
classNames: props.classNames,
|
|
247
|
+
onChange: props.onChange
|
|
248
|
+
}
|
|
249
|
+
)
|
|
250
|
+
] }, section.key));
|
|
251
|
+
}
|
|
252
|
+
function BacanoForm(props) {
|
|
253
|
+
const { client } = useBacanoState();
|
|
254
|
+
const { form, loading, error } = useBacanoForm(props.formKey, {
|
|
255
|
+
initialForm: props.initialForm
|
|
256
|
+
});
|
|
257
|
+
const [values, setValues] = (0, import_react3.useState)({});
|
|
258
|
+
const [step, setStep] = (0, import_react3.useState)(0);
|
|
259
|
+
const [submitting, setSubmitting] = (0, import_react3.useState)(false);
|
|
260
|
+
const [submitted, setSubmitted] = (0, import_react3.useState)(false);
|
|
261
|
+
const [submissionError, setSubmissionError] = (0, import_react3.useState)(null);
|
|
262
|
+
const [honeypot, setHoneypot] = (0, import_react3.useState)("");
|
|
263
|
+
const idempotencyKey = (0, import_react3.useRef)(crypto.randomUUID());
|
|
264
|
+
const fields = form?.definition.fields ?? EMPTY_FORM_FIELDS;
|
|
265
|
+
const sections = form?.definition.sections ?? EMPTY_FORM_SECTIONS;
|
|
266
|
+
const steps = form?.definition.steps ?? EMPTY_FORM_STEPS;
|
|
267
|
+
const fieldKeys = (0, import_react3.useMemo)(
|
|
268
|
+
() => new Set(
|
|
269
|
+
[
|
|
270
|
+
...fields,
|
|
271
|
+
...sections.flatMap((section) => section.fields),
|
|
272
|
+
...steps.flatMap((formStep) => [
|
|
273
|
+
...formStep.fields,
|
|
274
|
+
...formStep.sections.flatMap((section) => section.fields)
|
|
275
|
+
])
|
|
276
|
+
].map((field) => field.key)
|
|
277
|
+
),
|
|
278
|
+
[fields, sections, steps]
|
|
279
|
+
);
|
|
280
|
+
(0, import_react3.useEffect)(() => {
|
|
281
|
+
setStep(0);
|
|
282
|
+
setValues(
|
|
283
|
+
(current) => Object.fromEntries(
|
|
284
|
+
Object.entries(current).filter(([key]) => fieldKeys.has(key))
|
|
285
|
+
)
|
|
286
|
+
);
|
|
287
|
+
setSubmitted(false);
|
|
288
|
+
setSubmissionError(null);
|
|
289
|
+
setHoneypot("");
|
|
290
|
+
idempotencyKey.current = crypto.randomUUID();
|
|
291
|
+
}, [fieldKeys, form?.version]);
|
|
292
|
+
if (loading) return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: props.loadingFallback ?? null });
|
|
293
|
+
if (error && !form)
|
|
294
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: props.errorFallback?.(error) ?? props.unavailableFallback ?? null });
|
|
295
|
+
if (!form) return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: props.unavailableFallback ?? null });
|
|
296
|
+
if (form.status === "PAUSED") {
|
|
297
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: props.classNames?.message, "data-bacano-form-unavailable": true, children: props.unavailableFallback ?? form.definition.unavailableMessage });
|
|
298
|
+
}
|
|
299
|
+
if (submitted) {
|
|
300
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: props.classNames?.message, "data-bacano-form-success": true, children: form.definition.successMessage });
|
|
301
|
+
}
|
|
302
|
+
const isMultiStep = steps.length > 0;
|
|
303
|
+
const activeStep = isMultiStep ? steps[step] : null;
|
|
304
|
+
const isLastStep = !isMultiStep || step === steps.length - 1;
|
|
305
|
+
const setFieldValue = (key, value) => setValues((current) => ({ ...current, [key]: value }));
|
|
306
|
+
const submit = async (event) => {
|
|
307
|
+
event.preventDefault();
|
|
308
|
+
if (!isLastStep) {
|
|
309
|
+
setStep((current) => current + 1);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
setSubmitting(true);
|
|
313
|
+
setSubmissionError(null);
|
|
314
|
+
try {
|
|
315
|
+
let responseId = null;
|
|
316
|
+
if (props.onSubmit) {
|
|
317
|
+
await props.onSubmit(values, form);
|
|
318
|
+
} else {
|
|
319
|
+
if (!client) throw new Error("Bacano client is not ready");
|
|
320
|
+
const result = await client.forms.submitResponse({
|
|
321
|
+
formKey: form.key,
|
|
322
|
+
formVersion: form.version,
|
|
323
|
+
definitionHash: form.definitionHash,
|
|
324
|
+
idempotencyKey: idempotencyKey.current,
|
|
325
|
+
values,
|
|
326
|
+
honeypot
|
|
327
|
+
});
|
|
328
|
+
if (!result.accepted) throw new Error(result.message);
|
|
329
|
+
responseId = result.responseId;
|
|
330
|
+
}
|
|
331
|
+
setSubmitted(true);
|
|
332
|
+
props.onSubmitted?.(responseId);
|
|
333
|
+
} catch (submitError) {
|
|
334
|
+
setSubmissionError(
|
|
335
|
+
submitError instanceof Error ? submitError : new Error(String(submitError))
|
|
336
|
+
);
|
|
337
|
+
} finally {
|
|
338
|
+
setSubmitting(false);
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
342
|
+
"form",
|
|
343
|
+
{
|
|
344
|
+
className: props.classNames?.root,
|
|
345
|
+
"data-bacano-form": form.key,
|
|
346
|
+
"data-bacano-form-version": form.version,
|
|
347
|
+
onSubmit: (event) => void submit(event),
|
|
348
|
+
children: [
|
|
349
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
350
|
+
"div",
|
|
351
|
+
{
|
|
352
|
+
"aria-hidden": "true",
|
|
353
|
+
style: {
|
|
354
|
+
position: "absolute",
|
|
355
|
+
width: 1,
|
|
356
|
+
height: 1,
|
|
357
|
+
overflow: "hidden",
|
|
358
|
+
clip: "rect(0 0 0 0)"
|
|
359
|
+
},
|
|
360
|
+
children: [
|
|
361
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { htmlFor: `${form.key}-company-website`, children: "Empresa" }),
|
|
362
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
363
|
+
"input",
|
|
364
|
+
{
|
|
365
|
+
id: `${form.key}-company-website`,
|
|
366
|
+
name: "company_website",
|
|
367
|
+
tabIndex: -1,
|
|
368
|
+
autoComplete: "off",
|
|
369
|
+
value: honeypot,
|
|
370
|
+
onChange: (event) => setHoneypot(event.target.value)
|
|
371
|
+
}
|
|
372
|
+
)
|
|
373
|
+
]
|
|
374
|
+
}
|
|
375
|
+
),
|
|
376
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h2", { className: props.classNames?.title, children: form.name }),
|
|
377
|
+
form.description ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: props.classNames?.description, children: form.description }) : null,
|
|
378
|
+
activeStep ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("section", { className: props.classNames?.step, "data-bacano-form-step": true, children: [
|
|
379
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { className: props.classNames?.stepTitle, children: activeStep.title }),
|
|
380
|
+
activeStep.description ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: props.classNames?.stepDescription, children: activeStep.description }) : null,
|
|
381
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
382
|
+
FormFields,
|
|
383
|
+
{
|
|
384
|
+
fields: activeStep.fields,
|
|
385
|
+
values,
|
|
386
|
+
classNames: props.classNames,
|
|
387
|
+
onChange: setFieldValue
|
|
388
|
+
}
|
|
389
|
+
),
|
|
390
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
391
|
+
FormSections,
|
|
392
|
+
{
|
|
393
|
+
sections: activeStep.sections,
|
|
394
|
+
values,
|
|
395
|
+
classNames: props.classNames,
|
|
396
|
+
onChange: setFieldValue
|
|
397
|
+
}
|
|
398
|
+
)
|
|
399
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
400
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
401
|
+
FormFields,
|
|
402
|
+
{
|
|
403
|
+
fields,
|
|
404
|
+
values,
|
|
405
|
+
classNames: props.classNames,
|
|
406
|
+
onChange: setFieldValue
|
|
407
|
+
}
|
|
408
|
+
),
|
|
409
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
410
|
+
FormSections,
|
|
411
|
+
{
|
|
412
|
+
sections,
|
|
413
|
+
values,
|
|
414
|
+
classNames: props.classNames,
|
|
415
|
+
onChange: setFieldValue
|
|
416
|
+
}
|
|
417
|
+
)
|
|
418
|
+
] }),
|
|
419
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: props.classNames?.actions, children: [
|
|
420
|
+
isMultiStep && step > 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
421
|
+
"button",
|
|
422
|
+
{
|
|
423
|
+
type: "button",
|
|
424
|
+
onClick: () => setStep((current) => current - 1),
|
|
425
|
+
children: "Anterior"
|
|
426
|
+
}
|
|
427
|
+
) : null,
|
|
428
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
429
|
+
"button",
|
|
430
|
+
{
|
|
431
|
+
type: "submit",
|
|
432
|
+
className: props.classNames?.button,
|
|
433
|
+
disabled: submitting,
|
|
434
|
+
children: isLastStep ? form.definition.submitLabel : "Continuar"
|
|
435
|
+
}
|
|
436
|
+
)
|
|
437
|
+
] }),
|
|
438
|
+
submissionError ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
439
|
+
"div",
|
|
440
|
+
{
|
|
441
|
+
role: "alert",
|
|
442
|
+
className: props.classNames?.message,
|
|
443
|
+
"data-bacano-form-error": true,
|
|
444
|
+
children: submissionError.message
|
|
445
|
+
}
|
|
446
|
+
) : null
|
|
447
|
+
]
|
|
448
|
+
}
|
|
449
|
+
);
|
|
450
|
+
}
|
package/react/forms.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import type { WebsitePublishedForm } from "../types";
|
|
3
|
+
export type BacanoFormValues = Record<string, string | string[] | boolean>;
|
|
4
|
+
export interface UseBacanoFormOptions {
|
|
5
|
+
enabled?: boolean;
|
|
6
|
+
/** Definition embedded by a server/build to preserve Static First rendering. */
|
|
7
|
+
initialForm?: WebsitePublishedForm | null;
|
|
8
|
+
}
|
|
9
|
+
export interface UseBacanoFormResult {
|
|
10
|
+
form: WebsitePublishedForm | null;
|
|
11
|
+
loading: boolean;
|
|
12
|
+
error: Error | null;
|
|
13
|
+
refresh(): Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
export declare function useBacanoForm(formKey: string, options?: UseBacanoFormOptions): UseBacanoFormResult;
|
|
16
|
+
export interface BacanoFormClassNames {
|
|
17
|
+
root?: string;
|
|
18
|
+
title?: string;
|
|
19
|
+
description?: string;
|
|
20
|
+
section?: string;
|
|
21
|
+
sectionTitle?: string;
|
|
22
|
+
sectionDescription?: string;
|
|
23
|
+
step?: string;
|
|
24
|
+
stepTitle?: string;
|
|
25
|
+
stepDescription?: string;
|
|
26
|
+
field?: string;
|
|
27
|
+
label?: string;
|
|
28
|
+
input?: string;
|
|
29
|
+
helpText?: string;
|
|
30
|
+
actions?: string;
|
|
31
|
+
button?: string;
|
|
32
|
+
message?: string;
|
|
33
|
+
}
|
|
34
|
+
export interface BacanoFormProps {
|
|
35
|
+
formKey: string;
|
|
36
|
+
/** Definition embedded by a server/build; it is revalidated in runtime. */
|
|
37
|
+
initialForm?: WebsitePublishedForm | null;
|
|
38
|
+
/**
|
|
39
|
+
* Optional custom submission. When omitted, the official Bacano response
|
|
40
|
+
* endpoint is used. Existing storefronts may keep a custom handler.
|
|
41
|
+
*/
|
|
42
|
+
onSubmit?(values: BacanoFormValues, form: WebsitePublishedForm): void | Promise<void>;
|
|
43
|
+
onSubmitted?(responseId: string | null): void;
|
|
44
|
+
classNames?: BacanoFormClassNames;
|
|
45
|
+
loadingFallback?: ReactNode;
|
|
46
|
+
unavailableFallback?: ReactNode;
|
|
47
|
+
errorFallback?: (error: Error) => ReactNode;
|
|
48
|
+
}
|
|
49
|
+
export declare function BacanoForm(props: BacanoFormProps): import("react").JSX.Element;
|