@usefillo/react 0.6.0 → 0.6.2

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
@@ -148,6 +148,7 @@ type ContentProps = {
148
148
  id: string;
149
149
  children?: string;
150
150
  text?: string;
151
+ visibleIf?: Condition | Condition[];
151
152
  };
152
153
  /** Inert: rendering one throws; they exist to be read by the compiler. */
153
154
  type Inert<P> = (props: P) => never;
@@ -194,6 +195,7 @@ declare const Fillo: {
194
195
  readonly Paragraph: Inert<ContentProps>;
195
196
  readonly Divider: Inert<{
196
197
  id: string;
198
+ visibleIf?: Condition | Condition[];
197
199
  }>;
198
200
  readonly Page: Inert<{
199
201
  id: string;
package/dist/index.js CHANGED
@@ -706,10 +706,11 @@ function FileUploadField({ field, value, error, setValue, api, ids: providedIds
706
706
  setInFlight((prev) => prev.filter((f) => f.key !== key));
707
707
  const current = Array.isArray(api.data[field.id]) ? api.data[field.id] : [];
708
708
  setValue([...current, uploaded]);
709
- } catch {
709
+ } catch (err) {
710
710
  if (mountedRef.current) {
711
+ const message = err instanceof Error && err.name === "FilloError" && err.message ? err.message : "Upload failed \u2014 try again";
711
712
  setInFlight(
712
- (prev) => prev.map((f) => f.key === key ? { ...f, error: "Upload failed \u2014 try again" } : f)
713
+ (prev) => prev.map((f) => f.key === key ? { ...f, error: message } : f)
713
714
  );
714
715
  }
715
716
  } finally {
@@ -773,7 +774,7 @@ function FileUploadField({ field, value, error, setValue, api, ids: providedIds
773
774
  hidden: true,
774
775
  multiple: maxFiles > 1,
775
776
  accept: schema.accept?.join(","),
776
- onChange: (e) => handleFiles(e.target.files)
777
+ onChange: (e) => canUpload && handleFiles(e.target.files)
777
778
  }
778
779
  ),
779
780
  canUpload ? /* @__PURE__ */ jsxs3(Fragment, { children: [
@@ -2149,8 +2150,8 @@ function FilloProvider({ children, form, formId, client, appearance, strings, ..
2149
2150
  client,
2150
2151
  form: schema,
2151
2152
  formId: syncedFormId ?? formId,
2152
- // Headless embedding renders no Fillo layout (so it carries no branding) —
2153
- // a paid capability the server enforces when these responses are submitted.
2153
+ // Headless = your markup, no Fillo-rendered layout. Free like every embed
2154
+ // method; recorded per response so usage stays measurable.
2154
2155
  surface: "headless"
2155
2156
  });
2156
2157
  return /* @__PURE__ */ jsx7(FilloContext.Provider, { value: api, children: /* @__PURE__ */ jsx7(FilloStringsContext.Provider, { value: resolveStrings2(strings), children: /* @__PURE__ */ jsx7(FilloAppearanceContext.Provider, { value: appearance, children: /* @__PURE__ */ jsx7(FilloInstanceIdContext.Provider, { value: instanceId, children }) }) }) });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usefillo/react",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
4
4
  "description": "Headless React components for embedding Fillo forms natively in your product.",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -33,7 +33,7 @@
33
33
  "access": "public"
34
34
  },
35
35
  "dependencies": {
36
- "@usefillo/core": "^0.6.0"
36
+ "@usefillo/core": "^0.6.2"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "react": "^18.0.0 || ^19.0.0",