@usefillo/react 0.5.4 → 0.6.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @usefillo/react
2
2
 
3
- Headless React components and hooks for embedding [Fillo](https://fillo.so) forms **natively inside your product** — rendered in your own DOM, with your styles, on your route. No iframe.
3
+ React components and hooks for embedding [Fillo](https://fillo.so) forms **natively inside your product** — rendered in your own DOM, with your styles, on your route. No iframe.
4
4
 
5
5
  ### 📚 Full documentation → **[fillo.so/docs](https://fillo.so/docs)**
6
6
 
@@ -10,34 +10,88 @@ npm i @usefillo/react
10
10
 
11
11
  `react` and `react-dom` (18 or 19) are peer dependencies.
12
12
 
13
+ ## Write forms as components
14
+
13
15
  ```tsx
14
- import { FilloForm } from "@usefillo/react";
16
+ "use client";
17
+ import { Fillo, when, createClient } from "@usefillo/react";
15
18
  import "@usefillo/react/styles.css"; // optional default theme — or bring your own
16
19
 
17
- export function Feedback() {
18
- return <FilloForm formId="cust-feedback" onSubmitted={(r) => confetti()} />;
20
+ const client = createClient({ key: process.env.NEXT_PUBLIC_FILLO_KEY });
21
+
22
+ export function ContactForm() {
23
+ return (
24
+ <Fillo.Form id="contact" title="Talk to us" client={client}>
25
+ <Fillo.Text id="name" label="Your name" required />
26
+ <Fillo.Email id="email" label="Work email" required />
27
+ <Fillo.Select id="topic" label="Topic" required>
28
+ <Fillo.Option id="sales" label="Sales" />
29
+ <Fillo.Option id="support" label="Support" />
30
+ </Fillo.Select>
31
+ <Fillo.LongText id="message" label="How can we help?"
32
+ visibleIf={when("topic").eq("support")} />
33
+ </Fillo.Form>
34
+ );
19
35
  }
20
36
  ```
21
37
 
22
- Every part is replaceable. Pass your own field components, theme the form via the `theme` prop, or drop down to the hooks and own the entire render:
38
+ The first time this runs, the form appears in your Fillo workspace as a draft —
39
+ publish it there and responses, logic, exports, webhooks, and integrations all
40
+ work. Field ids are permanent (they key your responses); conditional questions
41
+ are `visibleIf`, never conditional JSX. The equivalent config style,
42
+ `defineForm({ id, pages })`, remains first-class — JSX compiles to it exactly.
43
+
44
+ ## Or embed a form built in the dashboard
45
+
46
+ ```tsx
47
+ import { FilloForm } from "@usefillo/react";
48
+
49
+ <FilloForm formId="cust-feedback" onSubmitted={(r) => confetti()} />
50
+ ```
51
+
52
+ ## Style it with your own classes
53
+
54
+ ```tsx
55
+ <Fillo.Form
56
+ id="contact"
57
+ client={client}
58
+ appearance={{
59
+ theme: { primary: "#4f46e5", radius: "12px" }, // also themes your hosted page
60
+ classNames: {
61
+ control: "rounded-xl border-zinc-200 data-[invalid]:border-red-400",
62
+ option: "rounded-lg border p-3 data-[selected]:border-indigo-600",
63
+ button: (s) => (s.variant === "primary" ? "bg-indigo-600 text-white" : ""),
64
+ },
65
+ fields: { nps: { control: "grid grid-cols-11 gap-1" } },
66
+ }}
67
+ >
68
+ ```
23
69
 
24
- - `<FilloForm>` / `<FilloProvider>` — render a form, or wrap your own layout
25
- - `useFillo()` / `useField()` — build a fully custom UI against form state
26
- - `useFilloController()` — headless controller for total control
27
- - `FormField` / `BlockRenderer` — render individual blocks
28
- - `defineForm()` — author a form in code and sync it to your workspace on first run
70
+ Every rendered part carries a named slot (`data-fillo`) and state attributes
71
+ (`data-invalid`, `data-selected`, `data-checked`, …), and the default
72
+ stylesheet is cascade-layered so your utilities always win. On Tailwind v3 or
73
+ reset-heavy sites import `@usefillo/react/styles.unlayered.css` instead.
74
+ Localize every built-in string with the `strings` prop. Styling contract:
75
+ [fillo.so/docs/styling](https://fillo.so/docs/styling).
29
76
 
30
- Published `formId` embeds can fetch and submit without a publishable key. Use `createClient({ key })` when syncing `defineForm()` schemas from code, or when you need to point the SDK at a custom API origin.
77
+ ## Go fully headless
31
78
 
32
- For tiny feedback widgets, set `settings.submitMode: "auto"` on a select/rating/checkbox/dropdown/linear scale form. The default renderer hides the first submit button, submits after a complete discrete answer, and brings the submit button back if that answer opens a text or upload follow-up. Add `submissionLimit: "once_per_visitor"` for browser-scoped one-response feedback.
79
+ Every part is replaceable — and every embed method is free:
33
80
 
34
- The default stylesheet follows system dark mode for unthemed embeds. Pass `theme={{ colorScheme: "dark" }}` or `"light"` when the host surface is known.
81
+ - `components` / `customComponents` — swap any field kind for your own
82
+ - `<FilloProvider>` + `<FormField>` / `useField()` — your layout, Fillo's engine
83
+ - `useFilloController()` — the bare engine for total control
35
84
 
36
- This package re-exports the embedding surface from [`@usefillo/core`](https://www.npmjs.com/package/@usefillo/core) (`createClient`, `FormSchema`, `FormTheme`, …) so a single import is usually enough.
85
+ URL prefill works in embeds (`?field=value`, hidden-field `paramName`),
86
+ submissions retry safely, and failed submits show a visible, answer-preserving
87
+ error. This package re-exports the embedding surface from
88
+ [`@usefillo/core`](https://www.npmjs.com/package/@usefillo/core) so a single
89
+ import is usually enough.
37
90
 
38
91
  ## Links
39
92
 
40
93
  - **Docs:** [fillo.so/docs](https://fillo.so/docs)
94
+ - **Authoring guide:** [fillo.so/docs/authoring](https://fillo.so/docs/authoring)
41
95
  - **Website:** [fillo.so](https://fillo.so)
42
96
 
43
97
  MIT licensed.
package/dist/index.d.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import * as react from 'react';
2
- import { ComponentType, ReactNode } from 'react';
3
- import { FormSchema, FilloClient, ResponseData, FieldValue, FormPage, Block, FormStatus, FieldKind, Field, CustomField, FormTheme, FilloError, CodeForm } from '@usefillo/core';
4
- export { CodeForm, Field, FieldValue, FileValue, FilloClient, FilloError, FormSchema, FormStatus, FormTheme, ProvisionWorkspaceResult, PublishedForm, ResponseData, createClient, defineForm, provisionWorkspace } from '@usefillo/core';
2
+ import { ComponentType, ReactNode, ReactElement } from 'react';
3
+ import * as _usefillo_core from '@usefillo/core';
4
+ import { FormSchema, FilloClient, ResponseData, FieldValue, FormPage, Block, FormStatus, FieldKind, Field, CustomField, FormTheme, FilloAppearance, FilloStrings, FilloError, CodeForm, FormSettings, Condition, TextField, PhoneField, NumberField, ChoiceField, SelectOption, CheckboxField, RatingField, LinearScaleField, RankingField, SignatureField, DateField, FileUploadField as FileUploadField$1, HiddenField } from '@usefillo/core';
5
+ export { CodeForm, Field, FieldValue, FileValue, FilloAppearance, FilloClient, FilloError, FilloJsxError, FilloSlot, FilloStrings, FormSchema, FormStatus, FormTheme, ProvisionWorkspaceResult, PublishedForm, ResponseData, SlotState, createClient, defineForm, provisionWorkspace, when } from '@usefillo/core';
5
6
 
6
7
  /**
7
8
  * Everything a custom renderer needs. Returned by useFillo() and provided
@@ -27,6 +28,8 @@ interface FilloApi {
27
28
  status: FormStatus;
28
29
  /** True while any file upload is in flight — submit is blocked. */
29
30
  uploading: boolean;
31
+ /** Human-readable message for the last failed submit; cleared on edit/retry. */
32
+ submitError?: string;
30
33
  /** Used by upload fields to gate submission. */
31
34
  setUploading: (fieldId: string, busy: boolean) => void;
32
35
  }
@@ -69,6 +72,14 @@ type CustomComponents = Record<string, ComponentType<FieldComponentProps<CustomF
69
72
 
70
73
  interface FilloFormBaseProps {
71
74
  theme?: FormTheme;
75
+ /**
76
+ * The styling contract: theme tokens plus per-slot class strings (Tailwind
77
+ * or your own), appended after the built-in fillo-* classes so they win by
78
+ * cascade order. `appearance.theme` outranks every other theme source.
79
+ */
80
+ appearance?: FilloAppearance;
81
+ /** Override any visitor-facing renderer string (for localized sites). */
82
+ strings?: Partial<FilloStrings>;
72
83
  /** Swap any built-in field kind for your own component. */
73
84
  components?: FieldComponents;
74
85
  /** Renderers for your own `custom` field kinds, keyed by `component`. */
@@ -118,6 +129,85 @@ type PreviewFormProps = FilloFormBaseProps & {
118
129
  type FilloFormProps = HostedFormProps | CodeBackedFormProps | PreviewFormProps;
119
130
  declare function FilloForm(props: FilloFormProps): react.JSX.Element;
120
131
 
132
+ /**
133
+ * The authoring namespace: `<Fillo.Form id="contact"><Fillo.Email id="email"
134
+ * label="Work email"/></Fillo.Form>`. Field elements are inert descriptors
135
+ * compiled (never rendered) into the exact CodeForm defineForm() emits, then
136
+ * fed to the existing framed <FilloForm> — same sync, same draft-by-default,
137
+ * same badge, same responses. Define forms in a client module ("use client");
138
+ * pass the compiled VALUE across server/client boundaries, never the JSX.
139
+ */
140
+ type WithVisible<T> = Omit<T, "kind" | "visibleIf"> & {
141
+ visibleIf?: Condition | Condition[];
142
+ };
143
+ type ChoiceProps = Omit<WithVisible<ChoiceField>, "options"> & {
144
+ options?: SelectOption[];
145
+ children?: ReactNode;
146
+ };
147
+ type ContentProps = {
148
+ id: string;
149
+ children?: string;
150
+ text?: string;
151
+ };
152
+ /** Inert: rendering one throws; they exist to be read by the compiler. */
153
+ type Inert<P> = (props: P) => never;
154
+ interface FilloJsxFormProps extends Omit<FilloFormProps, "form" | "formId" | "client"> {
155
+ /** Workspace handle — the form's identity across syncs. */
156
+ id: string;
157
+ title?: string;
158
+ description?: string;
159
+ settings?: FormSettings;
160
+ theme?: FormTheme;
161
+ client?: FilloClient;
162
+ children?: ReactNode;
163
+ }
164
+ declare function JsxForm(props: FilloJsxFormProps): react.JSX.Element;
165
+ declare function defineFormFromJsx(element: ReactElement<FilloJsxFormProps>): CodeForm;
166
+ declare const Fillo: {
167
+ readonly Form: typeof JsxForm;
168
+ /** Compile a <Fillo.Form> element to a CodeForm at module scope — the same
169
+ * value FilloProvider (headless) and the CLI consume. */
170
+ readonly defineForm: typeof defineFormFromJsx;
171
+ readonly Text: Inert<WithVisible<TextField>>;
172
+ readonly LongText: Inert<WithVisible<TextField>>;
173
+ readonly Email: Inert<WithVisible<TextField>>;
174
+ readonly Url: Inert<WithVisible<TextField>>;
175
+ readonly Phone: Inert<WithVisible<PhoneField>>;
176
+ readonly Number: Inert<WithVisible<NumberField>>;
177
+ readonly Select: Inert<ChoiceProps>;
178
+ readonly MultiSelect: Inert<ChoiceProps>;
179
+ readonly Dropdown: Inert<ChoiceProps>;
180
+ readonly Checkbox: Inert<WithVisible<CheckboxField>>;
181
+ readonly Rating: Inert<WithVisible<RatingField>>;
182
+ readonly Scale: Inert<WithVisible<LinearScaleField>>;
183
+ readonly Ranking: Inert<Omit<WithVisible<RankingField>, "options"> & {
184
+ options?: SelectOption[];
185
+ children?: ReactNode;
186
+ }>;
187
+ readonly Matrix: Inert<WithVisible<_usefillo_core.MatrixField>>;
188
+ readonly Signature: Inert<WithVisible<SignatureField>>;
189
+ readonly Date: Inert<WithVisible<DateField>>;
190
+ readonly FileUpload: Inert<WithVisible<FileUploadField$1>>;
191
+ readonly Hidden: Inert<WithVisible<HiddenField>>;
192
+ readonly Custom: Inert<WithVisible<CustomField>>;
193
+ readonly Heading: Inert<ContentProps>;
194
+ readonly Paragraph: Inert<ContentProps>;
195
+ readonly Divider: Inert<{
196
+ id: string;
197
+ }>;
198
+ readonly Page: Inert<{
199
+ id: string;
200
+ title?: string;
201
+ children?: ReactNode;
202
+ }>;
203
+ readonly Option: Inert<Omit<SelectOption, "id"> & {
204
+ id: string;
205
+ }>;
206
+ };
207
+
208
+ /** The raw appearance object — for renderers that resolve slots inside loops. */
209
+ declare function useFilloAppearance(): FilloAppearance | undefined;
210
+
121
211
  interface ControllerOptions {
122
212
  form: FormSchema;
123
213
  formId?: string;
@@ -129,8 +219,14 @@ interface ControllerOptions {
129
219
  getHoneypot?: () => string;
130
220
  /** @internal Preview-only page navigation escape hatch. Submission still validates. */
131
221
  skipValidation?: boolean;
132
- /** Embedding surface; "headless" (FilloProvider) is server-enforced as paid. */
222
+ /**
223
+ * Embedding surface, recorded per response for measurement. Defaults to
224
+ * "headless" (your own markup), matching @usefillo/core — the framed
225
+ * renderers pass "default" explicitly.
226
+ */
133
227
  surface?: "default" | "headless";
228
+ /** Resolve the submission target at submit time when formId is still unset. */
229
+ resolveFormId?: () => Promise<string>;
134
230
  }
135
231
  /**
136
232
  * React binding for the framework-agnostic engine in @usefillo/core
@@ -145,6 +241,10 @@ declare function useFilloController(options: ControllerOptions): FilloApi;
145
241
  interface FilloProviderProps extends Omit<ControllerOptions, "form"> {
146
242
  /** A schema, or a code-defined form from defineForm() — which also syncs. */
147
243
  form: FormSchema | CodeForm;
244
+ /** Slot classes for FormField-rendered fields in your composed layout. */
245
+ appearance?: FilloAppearance;
246
+ /** Override any visitor-facing renderer string (for localized sites). */
247
+ strings?: Partial<FilloStrings>;
148
248
  children: ReactNode;
149
249
  }
150
250
  /**
@@ -166,7 +266,7 @@ interface FilloProviderProps extends Omit<ControllerOptions, "form"> {
166
266
  * defineForm() form and a keyed client, the structure also syncs into your
167
267
  * workspace, exactly like <FilloForm>.
168
268
  */
169
- declare function FilloProvider({ children, form, formId, client, ...options }: FilloProviderProps): react.JSX.Element;
269
+ declare function FilloProvider({ children, form, formId, client, appearance, strings, ...options }: FilloProviderProps): react.JSX.Element;
170
270
 
171
271
  /**
172
272
  * The full form engine — data, errors, pages, submit, status. Use inside a
@@ -217,4 +317,4 @@ declare function FormField({ id, components, customComponents, }: {
217
317
  */
218
318
  declare function FileUploadField({ field, value, error, setValue, api, ids: providedIds }: FieldComponentProps): react.JSX.Element;
219
319
 
220
- export { BlockRenderer, type ControllerOptions, type CustomComponents, type FieldComponentProps, type FieldComponents, type FieldHandle, type FilloApi, type FilloFieldIds, FileUploadField as FilloFileUpload, FilloForm, type FilloFormProps, FilloProvider, type FilloProviderProps, FormField, useField, useFillo, useFilloController };
320
+ export { BlockRenderer, type ControllerOptions, type CustomComponents, type FieldComponentProps, type FieldComponents, type FieldHandle, Fillo, type FilloApi, type FilloFieldIds, FileUploadField as FilloFileUpload, FilloForm, type FilloFormProps, type FilloJsxFormProps, FilloProvider, type FilloProviderProps, FormField, useField, useFillo, useFilloAppearance, useFilloController };