@sonata-innovations/fiber-fbre 3.2.1 → 3.4.0

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
- # @sonata-innovations/fiber-fbre v2
1
+ # @sonata-innovations/fiber-fbre
2
2
 
3
- Fiber Render Engine — consumes Flow JSON and renders data collection forms. Handles conditional logic, validation, screen transitions, and outputs collected FlowData back to the parent application.
3
+ Fiber Render Engine — consumes Flow JSON and renders data collection forms. Handles conditional logic, validation, calculations, screen transitions, theming, and outputs collected FlowData back to the parent application. Renders 30 component types across standard and conversational modes; zero UI-framework dependency (custom CSS only).
4
4
 
5
5
  ## Quick Start
6
6
 
@@ -68,7 +68,7 @@ function App() {
68
68
  npm install @sonata-innovations/fiber-fbre
69
69
  ```
70
70
 
71
- Then import the CSS in your app entry point:
71
+ Then import the CSS **once** in your app entry point — without it the form renders unstyled:
72
72
 
73
73
  ```ts
74
74
  import "@sonata-innovations/fiber-fbre/styles";
@@ -78,6 +78,22 @@ import "@sonata-innovations/fiber-fbre/styles";
78
78
 
79
79
  **Runtime dependencies:** `zustand`, `@sonata-innovations/fiber-types`, `@sonata-innovations/fiber-shared`
80
80
 
81
+ ## Documentation
82
+
83
+ This README covers evaluation and day-to-day API lookup. Deeper guides ship **inside the package** under `docs/` (and an `AGENTS.md` routing guide at the package root), and are browsable in the public docs mirror at [github.com/sonata-innovations/fiber-docs](https://github.com/sonata-innovations/fiber-docs).
84
+
85
+ | Topic | In package | Mirror |
86
+ |-------|-----------|--------|
87
+ | Concepts overview | `docs/fiber-concepts.md` | [fiber-concepts.md](https://github.com/sonata-innovations/fiber-docs/blob/main/fiber-concepts.md) |
88
+ | FBRE integration guide | `docs/integration/fbre.md` | [integration/fbre.md](https://github.com/sonata-innovations/fiber-docs/blob/main/integration/fbre.md) |
89
+ | Theming reference | `docs/features/fbre-theming.md` | [features/fbre-theming.md](https://github.com/sonata-innovations/fiber-docs/blob/main/features/fbre-theming.md) |
90
+ | Confirmation screen | `docs/features/confirmation-screen.md` | [features/confirmation-screen.md](https://github.com/sonata-innovations/fiber-docs/blob/main/features/confirmation-screen.md) |
91
+
92
+ Schema references (Flow input and FlowData output) ship in the **`@sonata-innovations/fiber-types`** package under `docs/schema/`, and are mirrored at:
93
+
94
+ - [schema/flow-schema.md](https://github.com/sonata-innovations/fiber-docs/blob/main/schema/flow-schema.md) · [schema/flow-quick-reference.md](https://github.com/sonata-innovations/fiber-docs/blob/main/schema/flow-quick-reference.md)
95
+ - [schema/flow-data-schema.md](https://github.com/sonata-innovations/fiber-docs/blob/main/schema/flow-data-schema.md) · [schema/flow-schema.json](https://github.com/sonata-innovations/fiber-docs/blob/main/schema/flow-schema.json)
96
+
81
97
  ## API
82
98
 
83
99
  ### `<FBRE />` Props
@@ -91,7 +107,7 @@ Render a Flow object you already have in memory.
91
107
  | Prop | Type | Required | Description |
92
108
  |------|------|----------|-------------|
93
109
  | `flow` | `Flow` | Yes | Flow JSON object |
94
- | `data` | `FlowData` | No | Pre-populated form data |
110
+ | `data` | `FlowData` | No | Pre-populated form data (not reactive — read once at mount) |
95
111
  | `mode` | `FlowModeType` | No | Override form mode (`"standard"` \| `"conversational"`) |
96
112
  | `screenIndex` | `number` | No | Initial screen index (default `0`) |
97
113
  | `theme` | `ThemeConfig` | No | Override theme settings (merged over `flow.config.theme`) |
@@ -99,9 +115,8 @@ Render a Flow object you already have in memory.
99
115
  | `controls` | `ControlsConfig` | No | Override controls settings (merged over `flow.config.controls`) |
100
116
  | `context` | `Record<string, string \| boolean \| number>` | No | External context values for condition evaluation and calculations |
101
117
  | `storeRef` | `MutableRefObject<StoreApi<FBREStoreState> \| null>` | No | Ref to access the Zustand store |
102
- | `onFlowComplete` | `(data: FlowData) => void` | Yes | Called when the user completes the flow |
118
+ | `onFlowComplete` | `(data: FlowData) => void \| ConfirmationResult \| Promise<void \| ConfirmationResult>` | Yes | Called when the user completes the flow. Return/resolve a `ConfirmationResult` to override the configured confirmation message; a rejected Promise surfaces a completion error |
103
119
  | `onScreenChange` | `(index: number, data: FlowData) => void` | No | Called on screen navigation |
104
- | `onScreenValidationChange` | `(index: number, data: any) => void` | No | Called when screen validity changes |
105
120
 
106
121
  #### Remote Mode
107
122
 
@@ -112,7 +127,7 @@ Fetch a published flow from a Fiber API by ID.
112
127
  | `flowId` | `string` | Yes | ID of the published flow |
113
128
  | `apiEndpoint` | `string` | Yes | Base URL of the Fiber API |
114
129
  | `apiKey` | `string` | No | API key for authentication |
115
- | `data` | `FlowData` | No | Pre-populated form data |
130
+ | `data` | `FlowData` | No | Pre-populated form data (not reactive) |
116
131
  | `mode` | `FlowModeType` | No | Override form mode (`"standard"` \| `"conversational"`) |
117
132
  | `screenIndex` | `number` | No | Initial screen index (default `0`) |
118
133
  | `theme` | `ThemeConfig` | No | Override theme settings |
@@ -120,13 +135,12 @@ Fetch a published flow from a Fiber API by ID.
120
135
  | `controls` | `ControlsConfig` | No | Override controls settings |
121
136
  | `context` | `Record<string, string \| boolean \| number>` | No | External context values for condition evaluation and calculations |
122
137
  | `storeRef` | `MutableRefObject<StoreApi<FBREStoreState> \| null>` | No | Ref to access the Zustand store |
123
- | `onFlowComplete` | `(data: FlowData) => void` | Yes | Called when the user completes the flow |
138
+ | `onFlowComplete` | `(data: FlowData) => void \| ConfirmationResult \| Promise<void \| ConfirmationResult>` | Yes | Called when the user completes the flow (see Local Mode) |
124
139
  | `onScreenChange` | `(index: number, data: FlowData) => void` | No | Called on screen navigation |
125
- | `onScreenValidationChange` | `(index: number, data: any) => void` | No | Called when screen validity changes |
126
140
 
127
141
  #### Server-Driven Mode
128
142
 
129
- Session-based rendering. The server evaluates conditions and validation; the client renders one screen at a time.
143
+ Session-based rendering. The server evaluates conditions and validation; the client renders one screen at a time. On completion it resets the submit button and shows a terminal confirmation (from `config.confirmation`, else a generic "Thank you"); `onFlowComplete` still fires with the server result if you want to drive your own post-completion UI.
130
144
 
131
145
  | Prop | Type | Required | Description |
132
146
  |------|------|----------|-------------|
@@ -137,7 +151,7 @@ Session-based rendering. The server evaluates conditions and validation; the cli
137
151
  | `theme` | `ThemeConfig` | No | Override theme settings |
138
152
  | `context` | `Record<string, string \| boolean \| number>` | No | External context values for condition evaluation and calculations |
139
153
  | `onFlowComplete` | `(data: FlowData) => void` | Yes | Called when the user completes the flow |
140
- | `onScreenChange` | `(screenNumber: number) => void` | No | Called on screen navigation (note: receives screen number, not index + data) |
154
+ | `onScreenChange` | `(screenNumber: number) => void` | No | Called on screen navigation (receives screen number, not index + data) |
141
155
 
142
156
  ### Imperative Access
143
157
 
@@ -155,6 +169,8 @@ const data = storeApi.getState().getFlowData();
155
169
  const apiContext = useFBREApi();
156
170
  ```
157
171
 
172
+ `getMaxScreenIndex()` on the store returns the maximum screen **index** (screen count − 1). (Renamed from `getMaxScreenCount`, whose name implied a count.)
173
+
158
174
  ### Events
159
175
 
160
176
  ```tsx
@@ -165,10 +181,12 @@ addFBREEventListener("file-upload", handler);
165
181
  removeFBREEventListener("file-upload", handler);
166
182
  ```
167
183
 
184
+ Note: the `file-upload` event bus is **module-global** — a listener fires for uploads from every `<FBRE />` instance on the page, not just one. Filter by the emitted component id if you run multiple instances.
185
+
168
186
  ### Exports
169
187
 
170
188
  ```ts
171
- // Components
189
+ // Component
172
190
  import { FBRE } from "@sonata-innovations/fiber-fbre";
173
191
 
174
192
  // Store hooks
@@ -180,7 +198,7 @@ import { addFBREEventListener, removeFBREEventListener } from "@sonata-innovatio
180
198
  // Error classes
181
199
  import { ApiError, TimeoutError } from "@sonata-innovations/fiber-fbre";
182
200
 
183
- // Types
201
+ // Types — props, store, and the full Flow/FlowData/Component type surface
184
202
  import type {
185
203
  FBREProps,
186
204
  FBRELocalProps,
@@ -194,335 +212,64 @@ import type {
194
212
  ThemeConfig,
195
213
  NavigationConfig,
196
214
  ControlsConfig,
197
- FlowMetadata,
215
+ ConfirmationConfig,
216
+ ConfirmationResult,
198
217
  Component,
199
218
  ComponentProperties,
200
- ComponentDisplayProperties,
201
- ComponentDividerProperties,
202
- ComponentInputProperties,
203
- ComponentOptionProperties,
204
- ComponentSliderProperties,
205
- ComponentSwitchProperties,
206
- ComponentRatingProperties,
207
- ComponentFileUploadProperties,
208
- ComponentGroupProperties,
209
219
  FlowData,
210
220
  ScreenData,
211
221
  ComponentData,
212
222
  FileUploadData,
213
- FileUploadBase64Data,
214
- FileUploadS3Data,
215
223
  FlowConditionConfig,
216
224
  ConditionOperator,
217
- ConditionRule,
218
- ConditionGroup,
219
- ConditionDependency,
225
+ // …plus per-component property types and condition/rule types
220
226
  } from "@sonata-innovations/fiber-fbre";
221
227
  ```
222
228
 
223
- ## Flow JSON Schema
224
-
225
- ```
226
- Flow
227
- ├── uuid: string
228
- ├── metadata: { name?, description?, ...}
229
- ├── config?: { mode?, theme?: { color?, colorScheme?, style?, background?, surface?, text?, border?, radius?, fontFamily?, error?, success?, warning? }, navigation?: { transition?, allowInvalidTransition? }, controls?: { show?, layout?, showStepper? }, summary? }
230
- └── screens: FlowScreen[]
231
- ├── uuid: string
232
- ├── label?: string
233
- ├── conditions?: FlowConditionConfig
234
- └── components: Component[]
235
- ├── uuid: string
236
- ├── type: string
237
- ├── properties: { ... }
238
- ├── conditions?: FlowConditionConfig
239
- └── components?: Component[] (groups and repeaters)
240
- ```
241
-
242
- ### Component Types
243
-
244
- | Type | Category | Description |
245
- |------|----------|-------------|
246
- | `header` | Display | Heading text |
247
- | `text` | Display | Rich text with markup support |
248
- | `divider` | Display | Horizontal rule with optional label |
249
- | `callout` | Display | Styled alert/info box with variant coloring (info, success, warning, neutral) |
250
- | `table` | Display | Static comparison/data table with optional column highlighting |
251
- | `inputText` | Input | Single-line text field |
252
- | `inputTextArea` | Input | Multi-line text field |
253
- | `inputNumber` | Input | Numeric input (supports decimal restriction and start adornment) |
254
- | `dropDown` | Selection | Native select dropdown |
255
- | `dropDownMulti` | Selection | Multi-select with tag chips |
256
- | `checkbox` | Selection | Checkbox group |
257
- | `radio` | Selection | Radio button group |
258
- | `toggleSwitch` | Selection | On/off toggle |
259
- | `yesNo` | Selection | Two large tappable buttons for binary yes/no |
260
- | `cardSelect` | Selection | Card-based visual selection with image/icon support |
261
- | `date` | Date & Time | Calendar popup date picker |
262
- | `time` | Date & Time | Hour/minute time selector |
263
- | `dateTime` | Date & Time | Combined date + time picker |
264
- | `dateRange` | Date & Time | Two date pickers for start/end |
265
- | `timeRange` | Date & Time | Two time pickers for start/end |
266
- | `dateTimeRange` | Date & Time | Two datetime pickers for start/end |
267
- | `fileUpload` | Interactive | File picker with size validation |
268
- | `rating` | Interactive | Star rating with half-star precision |
269
- | `slider` | Interactive | Range slider with value label |
270
- | `colorPicker` | Interactive | Saturation/hue picker with hex input |
271
- | `computed` | Display | Calculated field with formula-based computed values |
272
- | `signature` | Interactive | Signature pad — draw on canvas, type name, or both |
273
- | `group` | Container | Component container with layout grid, collapsible |
274
- | `repeater` | Container | Iterable component container (add/remove iterations, pre-populated rows) |
275
-
276
- ### Conditions
277
-
278
- Components and screens can be conditionally shown/hidden using `FlowConditionConfig`:
279
-
280
- ```json
281
- {
282
- "action": "show",
283
- "when": {
284
- "logic": "and",
285
- "rules": [
286
- {
287
- "source": "other-component-uuid",
288
- "operator": "equals",
289
- "value": "yes"
290
- }
291
- ]
292
- }
293
- }
294
- ```
295
-
296
- **Logic:** `"and"` (all rules must match) or `"or"` (any rule matches).
297
-
298
- **Context rules:** Set `sourceType: "context"` on a rule to reference an external context value instead of a component. In this case, `source` is a context key (not a component UUID).
299
-
300
- **Operators:**
301
-
302
- | Category | Operators |
303
- |----------|-----------|
304
- | Equality | `equals`, `notEquals` |
305
- | String | `contains`, `notContains`, `startsWith`, `endsWith` |
306
- | Presence | `isEmpty`, `isNotEmpty` |
307
- | Numeric | `greaterThan`, `greaterThanOrEqual`, `lessThan`, `lessThanOrEqual` |
308
- | Set (single) | `isOneOf`, `isNotOneOf` |
309
- | Set (multi) | `includesAny`, `includesAll`, `includesNone` |
310
- | Boolean | `isTrue`, `isFalse` |
311
-
312
- Condition results are stored separately from the Flow JSON in a `conditionResults` map (keyed by target UUID). Hidden components are excluded from FlowData output.
313
-
314
- ### Validation
315
-
316
- Components support a rules-based validation system via `properties.validation`:
317
-
318
- ```json
319
- {
320
- "validation": {
321
- "rules": [
322
- { "type": "required" },
323
- { "type": "email" },
324
- { "type": "minLength", "params": { "min": 5 } }
325
- ]
326
- }
327
- }
328
- ```
329
-
330
- **17 built-in validators:** `required`, `email`, `phone`, `url`, `minLength`, `maxLength`, `exactLength`, `minValue`, `maxValue`, `pattern`, `minSelected`, `maxSelected`, `fileType`, `fileSize`, `contains`, `excludes`, `matchesField`
229
+ ## Flow schema, conditions, validation, calculations
331
230
 
332
- Each validator has a default error message. Custom messages can be set per rule via the `message` field.
231
+ The Flow JSON hierarchy (Flow Screen Component), all **30 component types**, the **19 condition operators**, the **17 validators**, calculation formulas, and inline markup are documented in the schema references — `docs/schema/` in `@sonata-innovations/fiber-types`, mirrored at [schema/flow-schema.md](https://github.com/sonata-innovations/fiber-docs/blob/main/schema/flow-schema.md) (with a [quick reference](https://github.com/sonata-innovations/fiber-docs/blob/main/schema/flow-quick-reference.md)).
333
232
 
334
- **Error display:** The first failing message is shown below the field with an "(and N more)" count when multiple rules fail.
233
+ A few runtime facts worth knowing up front:
335
234
 
336
- **Cross-field validation:** `matchesField` enables bidirectional re-evaluation changing either field re-validates the other.
235
+ - **No auto-migration.** Legacy flat `required` / `regex` properties are **not** converted at load. Pre-migrate flows to `validation.rules` (and `conditions`) before passing them to FBRE.
236
+ - **Calculation formulas use single braces** to reference component values: `{uuid}`. They support arithmetic, comparisons, `IF()`, aggregation (`SUM`/`COUNT`/`AVG`/`MIN`/`MAX` over repeaters), and update reactively.
237
+ - **Condition and validation results live on the store** (`conditionResults`, `validationErrors`), not in the Flow JSON. Components hidden by a condition are excluded from FlowData and don't block screen validity.
337
238
 
338
- **Migration:** Legacy `required` and `regex` flat properties are automatically converted to validation rules on load.
239
+ ## Confirmation Screen
339
240
 
340
- Validation errors are stored in `validationErrors: Record<string, string[]>` on the store (keyed by component UUID). Hidden components (by conditions) do not block screen validity.
341
-
342
- ### Calculations
343
-
344
- Flows can include formula-based computed values via `FlowCalculation`:
345
-
346
- ```json
347
- {
348
- "calculations": [
349
- {
350
- "uuid": "calc-uuid",
351
- "formula": "{{comp-a-uuid}} + {{comp-b-uuid}} * 0.1",
352
- "label": "Total"
353
- }
354
- ]
355
- }
356
- ```
357
-
358
- - Formulas reference component values via `{uuid}` tokens and support standard arithmetic (`+`, `-`, `*`, `/`)
359
- - Aggregation functions: `SUM()`, `COUNT()`, `AVG()`, `MIN()`, `MAX()` over repeater iterations
360
- - Scalar `MIN(expr, ...)` / `MAX(expr, ...)` for capping values (e.g. `MIN({discount}, 25)`)
361
- - `IF(condition, then, else)` for conditional formulas (e.g. tiered pricing)
362
- - Comparison operators: `>`, `<`, `>=`, `<=`, `==`, `!=` (return 1/0)
363
- - Option metadata is accessible for option-based components
364
- - Results update reactively when source values change
365
- - Access results via `getCalculationResult(uuid)` on the store
366
-
367
- ### Markup
368
-
369
- Text components and `detail` fields support inline markup:
370
-
371
- - `[b]bold[/b]` → **bold**
372
- - `[i]italic[/i]` → *italic*
373
- - `[l href="url"]link[/l]` → [link](url)
241
+ A terminal "thank you" screen shown after submission, configured on the flow (`config.confirmation`) rather than as a `Screen`. It replaces the final screen and hides the controls/stepper; `title`/`body` support reference markup (collected fields, calculations, and `context` values by name). `onFlowComplete` decides *when* it shows — return `void` for immediate, a `Promise` to keep the submit button in-flight until it settles, or a `ConfirmationResult` (`{ title?, body? }`) to override the configured message (e.g. with a server reference number). Full guide: [features/confirmation-screen.md](https://github.com/sonata-innovations/fiber-docs/blob/main/features/confirmation-screen.md).
374
242
 
375
243
  ## Conversational Mode
376
244
 
377
- Set `mode: "conversational"` to transform FBRE into a one-question-per-screen experience:
245
+ Set `mode: "conversational"` (via the JSX prop or `flow.config.mode`) to turn FBRE into a one-question-per-screen experience: content is centered, single-select components auto-advance ~500ms after selection, and Enter advances text/number fields. Auto-advance only fires when a screen renders **exactly one** interactive component (display-only and condition-hidden components don't count); multi-input screens behave like a normal form. Components fade/scale/stagger in on transitions (respecting `prefers-reduced-motion`), and controls are enlarged for tapping.
378
246
 
379
- ```tsx
380
- // Via JSX prop
381
- <FBRE flow={myFlow} mode="conversational" onFlowComplete={handleComplete} />
382
-
383
- // Via flow config
384
- const flow = {
385
- ...myFlow,
386
- config: { ...myFlow.config, mode: "conversational" }
387
- };
388
- <FBRE flow={flow} onFlowComplete={handleComplete} />
389
- ```
390
-
391
- **What changes:**
392
-
393
- - Content is vertically and horizontally centered
394
- - Single-select components (`radio`, `yesNo`, `cardSelect`, `dropDown`) auto-advance ~500ms after selection
395
- - Pressing Enter on `inputText` / `inputNumber` advances to the next screen
396
- - Auto-advance (both Enter and on-change) only fires when the screen renders **exactly one interactive component**. Multi-input screens — including those produced by FBTL's per-card page-break toggle — behave like a normal form: the user fills every field and clicks Continue. Display-only components (text, header, callout, divider, table, computed) and components hidden by conditions don't count toward the input total.
397
- - Components animate in with fade + scale + stagger on screen transitions (respects `prefers-reduced-motion`)
398
- - Option items, Yes/No buttons, card-select cards, and input fields are enlarged for easier tapping
399
- - Each of the 6 existing styles has a tailored conversational presentation (e.g. clean = bordered cards, soft-outlined = pill-shaped options, defined-outlined = inverted selection)
400
-
401
- **What doesn't change:**
402
-
403
- - FlowData output is identical
404
- - Conditions, validation, calculations all work the same
405
- - Navigation buttons still work alongside auto-advance
406
- - Works with all 6 transition types and dark mode
247
+ FlowData output, conditions, validation, calculations, transitions, and dark mode all behave identically to standard mode. Styles partition by mode: **6 standard** (`clean`, `outlined`, `refined-clean`, `airy-clean`, `soft-outlined`, `defined-outlined`) and **4 conversational** (`centered-minimal`, `stacked-cards`, `soft-float`, `bold-statement`).
407
248
 
408
249
  ## Theming
409
250
 
410
- FBRE's appearance is driven entirely by CSS custom properties (the `--fbre-*` tokens). There are two independent axes **`style`** controls the *shape* (borders vs. underlines, fills, spacing; set via `theme.style`), and the **palette** controls the *colors*. This section is a complete reference for the palette.
411
-
412
- Palette values resolve through four layers, each overriding the one above, so you only specify what you want to change:
251
+ FBRE's appearance is driven entirely by `--fbre-*` CSS custom properties. Two independent axes: **`theme.style`** controls shape (borders vs. underlines, fills, spacing), and the **palette** controls colors. There are three ways to set the palette, and they **layer** each overrides the one before, so you specify only what you want to change:
413
252
 
414
- ```
415
- 1. Light defaults base.css (lowest)
416
- 2. Preset: colorScheme: "dark" base.css [data-mode="dark"]
417
- 3. Knobs: theme.{surface,text…} inline custom properties
418
- 4. Raw CSS: your own --fbre-* rules your stylesheet (highest)
419
- ```
420
-
421
- ### Color scheme (presets)
422
-
423
- `theme.colorScheme` (`"light"` default | `"dark"`) seeds the **entire** token set with a coherent light or dark palette and sets `data-mode` on the container. Set it on the `theme` prop or in the flow config (the prop takes precedence):
424
-
425
- ```tsx
426
- <FBRE flow={myFlow} theme={{ colorScheme: "dark" }} onFlowComplete={handleComplete} />
427
- ```
428
-
429
- > Replaces the former `darkMode: boolean`: `darkMode: true` → `colorScheme: "dark"`, `darkMode: false`/absent → `colorScheme: "light"`. There is no runtime fallback — migrate stored flows before upgrading.
430
-
431
- ### Theme knobs
432
-
433
- Layer brand colors on top of the preset with the `theme` palette knobs. Each knob sets its primary `--fbre-*` token **plus the tokens derived from it** (e.g. `surface` also drives the input fills and hover/alt surfaces, via mode-aware `color-mix`). Any subset may be set; unset knobs fall through to the preset.
253
+ 1. **`colorScheme` preset** — `theme.colorScheme` (`"light"` default | `"dark"`) seeds the entire token set with a coherent palette and sets `data-mode` on the container. (Replaces the former `darkMode` boolean; there is no runtime fallback — migrate stored flows.)
254
+ 2. **Palette knobs** — `theme.{color, background, surface, text, border, radius, fontFamily, error, success, warning}` set their primary token plus the tokens derived from it (e.g. `surface` also drives input fills and hover/alt surfaces). Any subset; unset knobs fall through to the preset.
255
+ 3. **Raw `--fbre-*` CSS** — override any token directly (including ones with no knob, like `--fbre-star-filled`) by scoping rules to your wrapper.
434
256
 
435
257
  ```tsx
436
258
  <FBRE
437
259
  flow={myFlow}
438
- theme={{
439
- colorScheme: "dark",
440
- color: "#bcd3cd", // accent
441
- background: "#1a2a3a", // form ground
442
- surface: "#243244", // input fills / cards
443
- text: "#e8ede9",
444
- border: "#2e4158",
445
- radius: "3px",
446
- fontFamily: '"Inter", system-ui, sans-serif',
447
- }}
260
+ theme={{ colorScheme: "dark", color: "#bcd3cd", surface: "#243244", radius: "3px" }}
448
261
  onFlowComplete={handleComplete}
449
262
  />
450
263
  ```
451
264
 
452
- | Knob | Primary token | Also derives |
453
- | --- | --- | --- |
454
- | `color` | `--fbre-theme-color` | light/dark accent tints |
455
- | `background` | `--fbre-bg` | — |
456
- | `surface` | `--fbre-surface` | input fills, hover/alt/subtle surfaces, control surface |
457
- | `text` | `--fbre-text` | secondary/placeholder/disabled, label |
458
- | `border` | `--fbre-border` | hover/light/subtle |
459
- | `radius` | `--fbre-radius` | — |
460
- | `fontFamily` | `--fbre-font` | — |
461
- | `error` / `success` / `warning` | `--fbre-{error,success,warning}` | their `-bg` / `-border` (and `error-light`) |
462
-
463
- ### Raw CSS (escape hatch)
464
-
465
- For tokens not exposed as a knob (e.g. `--fbre-star-filled`, `--fbre-shadow-color`) — or for surgical control — override the `--fbre-*` variables directly. Scope to your wrapper so you out-specify the engine defaults regardless of load order:
466
-
467
- ```css
468
- .my-form-wrapper .fbre-container {
469
- --fbre-surface: #243244;
470
- --fbre-star-filled: #bcd3cd;
471
- }
472
- ```
473
-
474
- Knobs are inline (highest specificity among engine-supplied values), so a knob and a raw rule on the *same* token: the knob wins unless your selector is also high-specificity. Use knobs for the common palette, raw CSS for the long tail.
475
-
476
- ### Token catalog
477
-
478
- Every themeable value, with light/dark preset defaults. Tokens marked **(derived)** are produced from a knob when it's set; you can also set any of them raw.
479
-
480
- **Accent** — `--fbre-theme-color` (`#1976d2` / `#42a5f5`, knob `color`), `--fbre-theme-light` (`#e3f2fd` / `#1a2332`, derived), `--fbre-theme-dark` (`#1565c0` / `#1e88e5`, derived), `--fbre-on-primary` (`#fff` / `#fff`).
481
-
482
- **Ground & surfaces**
483
-
484
- | Token | Light | Dark | Knob |
485
- | --- | --- | --- | --- |
486
- | `--fbre-bg` | `#fff` | `#121212` | `background` |
487
- | `--fbre-surface` | `#fff` | `#1e1e1e` | `surface` |
488
- | `--fbre-surface-hover` | `#f5f5f5` | `#2a2a2a` | (derived) |
489
- | `--fbre-surface-alt` | `#e0e0e0` | `#333` | (derived) |
490
- | `--fbre-surface-subtle` | `#e8e8e8` | `#2a2a2a` | (derived) |
491
- | `--fbre-control-surface` | `#fff` | `#e0e0e0` | (derived) |
492
- | `--fbre-input-bg` | `var(--fbre-surface)` | `var(--fbre-surface)` | (derived) |
493
- | `--fbre-input-bg-focus` | `var(--fbre-bg)` | `var(--fbre-bg)` | (derived) |
494
-
495
- **Text** — `--fbre-text` (`#212121` / `#e0e0e0`, knob `text`), `--fbre-text-secondary` (`#666` / `#9e9e9e`, derived), `--fbre-text-placeholder` (`#aaa` / `#666`, derived), `--fbre-text-disabled` (`#bbb` / `#555`, derived), `--fbre-label` (`var(--fbre-text-secondary)`, derived).
496
-
497
- **Borders** — `--fbre-border` (`#ccc` / `#444`, knob `border`), `--fbre-border-hover` (`#999` / `#666`, derived), `--fbre-border-light` (`#ddd` / `#444`, derived), `--fbre-border-subtle` (`#eee` / `#333`, derived).
498
-
499
- **State colors**
500
-
501
- | Token | Light | Dark | Knob |
502
- | --- | --- | --- | --- |
503
- | `--fbre-error` | `#d32f2f` | `#ef5350` | `error` |
504
- | `--fbre-error-light` | `#fdecea` | `#2c1b1b` | (derived) |
505
- | `--fbre-success` | `#2d6a28` | `#a8e6a0` | `success` |
506
- | `--fbre-success-bg` | `#ddffda` | `#1a3318` | (derived) |
507
- | `--fbre-success-border` | `#b8e6b4` | `#2d5a28` | (derived) |
508
- | `--fbre-warning` | `#7a5900` | `#ffe082` | `warning` |
509
- | `--fbre-warning-bg` | `#fff8e1` | `#332b00` | (derived) |
510
- | `--fbre-warning-border` | `#ffe082` | `#665500` | (derived) |
511
-
512
- **Controls (no knob — set raw)** — `--fbre-toggle-track` (`#ccc` / `#555`), `--fbre-star-empty` (`#ddd` / `#444`), `--fbre-star-filled` (`#ffc107` / `#ffca28`), `--fbre-slider-track` (`#ddd` / `#444`), `--fbre-shadow-color` (`rgba(0,0,0,.12)` / `rgba(0,0,0,.4)`).
265
+ Defaults worth noting: `--fbre-radius` is `4px` and `--fbre-font` is `"Segoe UI", system-ui, -apple-system, sans-serif`. The complete token catalog with light/dark defaults is in [features/fbre-theming.md](https://github.com/sonata-innovations/fiber-docs/blob/main/features/fbre-theming.md).
513
266
 
514
- **Shape & motion** — `--fbre-radius` (`4px`, knob `radius`), `--fbre-font` (`"Segoe UI", system-ui, sans-serif`, knob `fontFamily`), `--fbre-layout-gap` (`4px`, varies by style), `--fbre-transition-duration` (`250ms`), `--fbre-transition-easing` (`cubic-bezier(0.4, 0, 0.2, 1)`).
515
-
516
- ### Screen Transitions
267
+ ## Screen Transitions
517
268
 
518
269
  Animate screen changes by setting `navigation.transition` in the flow config:
519
270
 
520
271
  ```json
521
- {
522
- "config": {
523
- "navigation": { "transition": "slideFade" }
524
- }
525
- }
272
+ { "config": { "navigation": { "transition": "slideFade" } } }
526
273
  ```
527
274
 
528
275
  | Type | Effect | Direction-aware? |
@@ -534,76 +281,25 @@ Animate screen changes by setting `navigation.transition` in the flow config:
534
281
  | `"rise"` | Vertical rise/sink | Yes |
535
282
  | `"scaleFade"` | Scale + opacity | No |
536
283
 
537
- Direction-aware transitions reverse when navigating backward. Buttons are disabled during animation to prevent overlapping transitions.
538
-
539
- Customize timing via CSS custom properties:
540
-
541
- ```css
542
- .fbre-container {
543
- --fbre-transition-duration: 250ms;
544
- --fbre-transition-easing: cubic-bezier(0.4, 0, 0.2, 1);
545
- }
546
- ```
547
-
548
- `@media (prefers-reduced-motion: reduce)` sets duration to `0ms` automatically.
549
-
550
- ## Architecture
551
-
552
- | Concern | Implementation |
553
- |---------|---------------|
554
- | UI framework | Zero-dependency custom CSS |
555
- | Styling | CSS custom properties + BEM (`fbre-` prefix) |
556
- | Display detection | `isDisplayComponent()` checks type against constant set (`header`, `text`, `divider`, `callout`, `table`) |
557
- | State | Zustand store factory, one store per `<FBRE />` instance via React Context |
558
- | Re-renders | Zustand selectors — granular, automatic |
559
- | Build | Vite library mode |
560
- | Components | Registry pattern (`Map<string, FC>`), one file per type |
561
- | Dependencies | react, zustand, @sonata-innovations/fiber-types, @sonata-innovations/fiber-shared |
284
+ Direction-aware transitions reverse when navigating backward; buttons are disabled mid-animation to prevent overlap. Customize timing via `--fbre-transition-duration` / `--fbre-transition-easing`. `@media (prefers-reduced-motion: reduce)` sets the duration to `0ms` automatically.
562
285
 
563
286
  ## FlowData Output
564
287
 
565
- `getFlowData()` returns collected form data. Display-only components (`header`, `text`, `divider`, `callout`, `table`) are excluded automatically based on their type.
566
-
567
- ```
568
- FlowData
569
- ├── uuid: string
570
- ├── metadata: Record<string, string>
571
- └── screens: ScreenData[]
572
- ├── uuid: string
573
- ├── label?: string
574
- └── components: ComponentData[]
575
- ├── uuid: string
576
- ├── type: string
577
- ├── label?: string (omitted when empty)
578
- ├── value: string | string[] | number | number[] | boolean | null | FileUploadData
579
- └── components?: ComponentData[][] (groups and repeaters)
580
- ```
581
-
582
- ## Build
583
-
584
- ```bash
585
- npm run build
586
- ```
587
-
588
- Output:
589
-
590
- ```
591
- dist/
592
- ├── index.d.ts # Bundled type declarations
593
- ├── fiber-fbre.js # ES module
594
- ├── fiber-fbre.cjs # CommonJS
595
- └── fiber-fbre.css # All styles
596
- ```
288
+ `getFlowData()` returns the collected form data, mirroring the Flow structure (Flow → ScreenData → ComponentData, with `components?: ComponentData[][]` for groups and repeaters). Display-only components `header`, `text`, `divider`, `callout`, `table` are excluded automatically; `computed` fields **are** included. Full shape and field semantics: [schema/flow-data-schema.md](https://github.com/sonata-innovations/fiber-docs/blob/main/schema/flow-data-schema.md).
597
289
 
598
290
  ## Responsive Sizing
599
291
 
600
- FBRE uses CSS container queries to automatically scale controls in narrow containers (under 400px). Buttons, stepper dots, and toggle switches shrink proportionally. No configuration needed just embed in a smaller container and the compact sizing kicks in.
292
+ FBRE uses CSS container queries to automatically scale controls in narrow containers (under 400px). Buttons, stepper dots, and toggle switches shrink proportionally no configuration needed; just embed in a smaller container and the compact sizing kicks in.
601
293
 
602
294
  ## Multiple Instances
603
295
 
604
- Each `<FBRE />` creates its own isolated Zustand store. Multiple instances can run simultaneously on the same page without state conflicts.
296
+ Each `<FBRE />` creates its own isolated Zustand store, so multiple instances run simultaneously on the same page without state conflicts. (The one exception is the module-global `file-upload` event bus noted under Events.)
605
297
 
606
298
  ```tsx
607
299
  <FBRE flow={flowA} onFlowComplete={handleA} />
608
300
  <FBRE flow={flowB} onFlowComplete={handleB} />
609
- ```
301
+ ```
302
+
303
+ ## License
304
+
305
+ MIT