@sonata-innovations/fiber-fbre 3.3.0 → 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/AGENTS.md +58 -0
- package/CHANGELOG.md +129 -0
- package/README.md +58 -385
- package/dist/fiber-fbre.cjs +1 -1
- package/dist/fiber-fbre.js +951 -939
- package/dist/index.d.ts +1 -2
- package/docs/features/confirmation-screen.md +125 -0
- package/docs/features/fbre-theming.md +264 -0
- package/docs/fiber-concepts.md +461 -0
- package/docs/integration/fbre.md +387 -0
- package/package.json +19 -10
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# @sonata-innovations/fiber-fbre
|
|
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`) |
|
|
@@ -101,7 +117,6 @@ Render a Flow object you already have in memory.
|
|
|
101
117
|
| `storeRef` | `MutableRefObject<StoreApi<FBREStoreState> \| null>` | No | Ref to access the Zustand store |
|
|
102
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 \| ConfirmationResult \| Promise<void \| ConfirmationResult>` | 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 (
|
|
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
|
-
//
|
|
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,
|
|
@@ -196,356 +214,62 @@ import type {
|
|
|
196
214
|
ControlsConfig,
|
|
197
215
|
ConfirmationConfig,
|
|
198
216
|
ConfirmationResult,
|
|
199
|
-
FlowMetadata,
|
|
200
217
|
Component,
|
|
201
218
|
ComponentProperties,
|
|
202
|
-
ComponentDisplayProperties,
|
|
203
|
-
ComponentDividerProperties,
|
|
204
|
-
ComponentInputProperties,
|
|
205
|
-
ComponentOptionProperties,
|
|
206
|
-
ComponentSliderProperties,
|
|
207
|
-
ComponentSwitchProperties,
|
|
208
|
-
ComponentRatingProperties,
|
|
209
|
-
ComponentFileUploadProperties,
|
|
210
|
-
ComponentGroupProperties,
|
|
211
219
|
FlowData,
|
|
212
220
|
ScreenData,
|
|
213
221
|
ComponentData,
|
|
214
222
|
FileUploadData,
|
|
215
|
-
FileUploadBase64Data,
|
|
216
|
-
FileUploadS3Data,
|
|
217
223
|
FlowConditionConfig,
|
|
218
224
|
ConditionOperator,
|
|
219
|
-
|
|
220
|
-
ConditionGroup,
|
|
221
|
-
ConditionDependency,
|
|
225
|
+
// …plus per-component property types and condition/rule types
|
|
222
226
|
} from "@sonata-innovations/fiber-fbre";
|
|
223
227
|
```
|
|
224
228
|
|
|
225
|
-
##
|
|
226
|
-
|
|
227
|
-
A terminal "thank you" screen shown after submission, configured on the flow (`config.confirmation`) — not a `Screen`. Renders only when `show` is not `false` and `title`/`body` has content, replacing the final screen and hiding the controls/stepper. `title`/`body` support `${...}` reference markup (collected fields, calculations, and `context` values by name).
|
|
228
|
-
|
|
229
|
-
```tsx
|
|
230
|
-
const flow = {
|
|
231
|
-
...myFlow,
|
|
232
|
-
config: { ...myFlow.config, confirmation: { show: true, title: "Thank you!", body: "Submitted, ${email}." } },
|
|
233
|
-
};
|
|
234
|
-
<FBRE flow={flow} onFlowComplete={handleComplete} />
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
`onFlowComplete` decides *when* it shows: return `void` → shows immediately; return a `Promise` → the submit button stays in-flight until it settles (resolve shows it, **reject** shows a completion error instead); return/resolve a `ConfirmationResult` (`{ title?, body? }`) → overrides the configured message, e.g. with a server reference number.
|
|
238
|
-
|
|
239
|
-
```tsx
|
|
240
|
-
const handleComplete = async (data: FlowData): Promise<ConfirmationResult> => {
|
|
241
|
-
const { referenceId } = await saveToBackend(data);
|
|
242
|
-
return { title: "All done!", body: `Reference: ${referenceId}.` };
|
|
243
|
-
};
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
## Flow JSON Schema
|
|
247
|
-
|
|
248
|
-
```
|
|
249
|
-
Flow
|
|
250
|
-
├── uuid: string
|
|
251
|
-
├── metadata: { name?, description?, ...}
|
|
252
|
-
├── config?: { mode?, theme?: { color?, colorScheme?, style?, background?, surface?, text?, border?, radius?, fontFamily?, error?, success?, warning? }, navigation?: { transition?, allowInvalidTransition? }, controls?: { show?, layout?, showStepper? }, summary?, confirmation?: { show?, title?, body? } }
|
|
253
|
-
└── screens: FlowScreen[]
|
|
254
|
-
├── uuid: string
|
|
255
|
-
├── label?: string
|
|
256
|
-
├── conditions?: FlowConditionConfig
|
|
257
|
-
└── components: Component[]
|
|
258
|
-
├── uuid: string
|
|
259
|
-
├── type: string
|
|
260
|
-
├── properties: { ... }
|
|
261
|
-
├── conditions?: FlowConditionConfig
|
|
262
|
-
└── components?: Component[] (groups and repeaters)
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
### Component Types
|
|
266
|
-
|
|
267
|
-
| Type | Category | Description |
|
|
268
|
-
|------|----------|-------------|
|
|
269
|
-
| `header` | Display | Heading text |
|
|
270
|
-
| `text` | Display | Rich text with markup support |
|
|
271
|
-
| `divider` | Display | Horizontal rule with optional label |
|
|
272
|
-
| `callout` | Display | Styled alert/info box with variant coloring (info, success, warning, neutral) |
|
|
273
|
-
| `table` | Display | Static comparison/data table with optional column highlighting |
|
|
274
|
-
| `inputText` | Input | Single-line text field |
|
|
275
|
-
| `inputTextArea` | Input | Multi-line text field |
|
|
276
|
-
| `inputNumber` | Input | Numeric input (supports decimal restriction and start adornment) |
|
|
277
|
-
| `dropDown` | Selection | Native select dropdown |
|
|
278
|
-
| `dropDownMulti` | Selection | Multi-select with tag chips |
|
|
279
|
-
| `checkbox` | Selection | Checkbox group |
|
|
280
|
-
| `radio` | Selection | Radio button group |
|
|
281
|
-
| `toggleSwitch` | Selection | On/off toggle |
|
|
282
|
-
| `yesNo` | Selection | Two large tappable buttons for binary yes/no |
|
|
283
|
-
| `cardSelect` | Selection | Card-based visual selection with image/icon support |
|
|
284
|
-
| `date` | Date & Time | Calendar popup date picker |
|
|
285
|
-
| `time` | Date & Time | Hour/minute time selector |
|
|
286
|
-
| `dateTime` | Date & Time | Combined date + time picker |
|
|
287
|
-
| `dateRange` | Date & Time | Two date pickers for start/end |
|
|
288
|
-
| `timeRange` | Date & Time | Two time pickers for start/end |
|
|
289
|
-
| `dateTimeRange` | Date & Time | Two datetime pickers for start/end |
|
|
290
|
-
| `fileUpload` | Interactive | File picker with size validation |
|
|
291
|
-
| `rating` | Interactive | Star rating with half-star precision |
|
|
292
|
-
| `slider` | Interactive | Range slider with value label |
|
|
293
|
-
| `colorPicker` | Interactive | Saturation/hue picker with hex input |
|
|
294
|
-
| `computed` | Display | Calculated field with formula-based computed values |
|
|
295
|
-
| `signature` | Interactive | Signature pad — draw on canvas, type name, or both |
|
|
296
|
-
| `group` | Container | Component container with layout grid, collapsible |
|
|
297
|
-
| `repeater` | Container | Iterable component container (add/remove iterations, pre-populated rows) |
|
|
298
|
-
|
|
299
|
-
### Conditions
|
|
300
|
-
|
|
301
|
-
Components and screens can be conditionally shown/hidden using `FlowConditionConfig`:
|
|
302
|
-
|
|
303
|
-
```json
|
|
304
|
-
{
|
|
305
|
-
"action": "show",
|
|
306
|
-
"when": {
|
|
307
|
-
"logic": "and",
|
|
308
|
-
"rules": [
|
|
309
|
-
{
|
|
310
|
-
"source": "other-component-uuid",
|
|
311
|
-
"operator": "equals",
|
|
312
|
-
"value": "yes"
|
|
313
|
-
}
|
|
314
|
-
]
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
```
|
|
229
|
+
## Flow schema, conditions, validation, calculations
|
|
318
230
|
|
|
319
|
-
|
|
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)).
|
|
320
232
|
|
|
321
|
-
|
|
233
|
+
A few runtime facts worth knowing up front:
|
|
322
234
|
|
|
323
|
-
**
|
|
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.
|
|
324
238
|
|
|
325
|
-
|
|
326
|
-
|----------|-----------|
|
|
327
|
-
| Equality | `equals`, `notEquals` |
|
|
328
|
-
| String | `contains`, `notContains`, `startsWith`, `endsWith` |
|
|
329
|
-
| Presence | `isEmpty`, `isNotEmpty` |
|
|
330
|
-
| Numeric | `greaterThan`, `greaterThanOrEqual`, `lessThan`, `lessThanOrEqual` |
|
|
331
|
-
| Set (single) | `isOneOf`, `isNotOneOf` |
|
|
332
|
-
| Set (multi) | `includesAny`, `includesAll`, `includesNone` |
|
|
333
|
-
| Boolean | `isTrue`, `isFalse` |
|
|
334
|
-
|
|
335
|
-
Condition results are stored separately from the Flow JSON in a `conditionResults` map (keyed by target UUID). Hidden components are excluded from FlowData output.
|
|
336
|
-
|
|
337
|
-
### Validation
|
|
338
|
-
|
|
339
|
-
Components support a rules-based validation system via `properties.validation`:
|
|
340
|
-
|
|
341
|
-
```json
|
|
342
|
-
{
|
|
343
|
-
"validation": {
|
|
344
|
-
"rules": [
|
|
345
|
-
{ "type": "required" },
|
|
346
|
-
{ "type": "email" },
|
|
347
|
-
{ "type": "minLength", "params": { "min": 5 } }
|
|
348
|
-
]
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
```
|
|
352
|
-
|
|
353
|
-
**17 built-in validators:** `required`, `email`, `phone`, `url`, `minLength`, `maxLength`, `exactLength`, `minValue`, `maxValue`, `pattern`, `minSelected`, `maxSelected`, `fileType`, `fileSize`, `contains`, `excludes`, `matchesField`
|
|
354
|
-
|
|
355
|
-
Each validator has a default error message. Custom messages can be set per rule via the `message` field.
|
|
356
|
-
|
|
357
|
-
**Error display:** The first failing message is shown below the field with an "(and N more)" count when multiple rules fail.
|
|
358
|
-
|
|
359
|
-
**Cross-field validation:** `matchesField` enables bidirectional re-evaluation — changing either field re-validates the other.
|
|
360
|
-
|
|
361
|
-
**Migration:** Legacy `required` and `regex` flat properties are automatically converted to validation rules on load.
|
|
362
|
-
|
|
363
|
-
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.
|
|
364
|
-
|
|
365
|
-
### Calculations
|
|
366
|
-
|
|
367
|
-
Flows can include formula-based computed values via `FlowCalculation`:
|
|
368
|
-
|
|
369
|
-
```json
|
|
370
|
-
{
|
|
371
|
-
"calculations": [
|
|
372
|
-
{
|
|
373
|
-
"uuid": "calc-uuid",
|
|
374
|
-
"formula": "{{comp-a-uuid}} + {{comp-b-uuid}} * 0.1",
|
|
375
|
-
"label": "Total"
|
|
376
|
-
}
|
|
377
|
-
]
|
|
378
|
-
}
|
|
379
|
-
```
|
|
380
|
-
|
|
381
|
-
- Formulas reference component values via `{uuid}` tokens and support standard arithmetic (`+`, `-`, `*`, `/`)
|
|
382
|
-
- Aggregation functions: `SUM()`, `COUNT()`, `AVG()`, `MIN()`, `MAX()` over repeater iterations
|
|
383
|
-
- Scalar `MIN(expr, ...)` / `MAX(expr, ...)` for capping values (e.g. `MIN({discount}, 25)`)
|
|
384
|
-
- `IF(condition, then, else)` for conditional formulas (e.g. tiered pricing)
|
|
385
|
-
- Comparison operators: `>`, `<`, `>=`, `<=`, `==`, `!=` (return 1/0)
|
|
386
|
-
- Option metadata is accessible for option-based components
|
|
387
|
-
- Results update reactively when source values change
|
|
388
|
-
- Access results via `getCalculationResult(uuid)` on the store
|
|
389
|
-
|
|
390
|
-
### Markup
|
|
391
|
-
|
|
392
|
-
Text components and `detail` fields support inline markup:
|
|
239
|
+
## Confirmation Screen
|
|
393
240
|
|
|
394
|
-
- `[
|
|
395
|
-
- `[i]italic[/i]` → *italic*
|
|
396
|
-
- `[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).
|
|
397
242
|
|
|
398
243
|
## Conversational Mode
|
|
399
244
|
|
|
400
|
-
Set `mode: "conversational"` to
|
|
401
|
-
|
|
402
|
-
```tsx
|
|
403
|
-
// Via JSX prop
|
|
404
|
-
<FBRE flow={myFlow} mode="conversational" onFlowComplete={handleComplete} />
|
|
405
|
-
|
|
406
|
-
// Via flow config
|
|
407
|
-
const flow = {
|
|
408
|
-
...myFlow,
|
|
409
|
-
config: { ...myFlow.config, mode: "conversational" }
|
|
410
|
-
};
|
|
411
|
-
<FBRE flow={flow} onFlowComplete={handleComplete} />
|
|
412
|
-
```
|
|
413
|
-
|
|
414
|
-
**What changes:**
|
|
415
|
-
|
|
416
|
-
- Content is vertically and horizontally centered
|
|
417
|
-
- Single-select components (`radio`, `yesNo`, `cardSelect`, `dropDown`) auto-advance ~500ms after selection
|
|
418
|
-
- Pressing Enter on `inputText` / `inputNumber` advances to the next screen
|
|
419
|
-
- 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.
|
|
420
|
-
- Components animate in with fade + scale + stagger on screen transitions (respects `prefers-reduced-motion`)
|
|
421
|
-
- Option items, Yes/No buttons, card-select cards, and input fields are enlarged for easier tapping
|
|
422
|
-
- 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)
|
|
423
|
-
|
|
424
|
-
**What doesn't change:**
|
|
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.
|
|
425
246
|
|
|
426
|
-
|
|
427
|
-
- Conditions, validation, calculations all work the same
|
|
428
|
-
- Navigation buttons still work alongside auto-advance
|
|
429
|
-
- 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`).
|
|
430
248
|
|
|
431
249
|
## Theming
|
|
432
250
|
|
|
433
|
-
FBRE's appearance is driven entirely by CSS custom properties
|
|
434
|
-
|
|
435
|
-
Palette values resolve through four layers, each overriding the one above, so you only specify what you want to change:
|
|
436
|
-
|
|
437
|
-
```
|
|
438
|
-
1. Light defaults base.css (lowest)
|
|
439
|
-
2. Preset: colorScheme: "dark" base.css [data-mode="dark"]
|
|
440
|
-
3. Knobs: theme.{surface,text…} inline custom properties
|
|
441
|
-
4. Raw CSS: your own --fbre-* rules your stylesheet (highest)
|
|
442
|
-
```
|
|
443
|
-
|
|
444
|
-
### Color scheme (presets)
|
|
445
|
-
|
|
446
|
-
`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):
|
|
447
|
-
|
|
448
|
-
```tsx
|
|
449
|
-
<FBRE flow={myFlow} theme={{ colorScheme: "dark" }} onFlowComplete={handleComplete} />
|
|
450
|
-
```
|
|
451
|
-
|
|
452
|
-
> Replaces the former `darkMode: boolean`: `darkMode: true` → `colorScheme: "dark"`, `darkMode: false`/absent → `colorScheme: "light"`. There is no runtime fallback — migrate stored flows before upgrading.
|
|
453
|
-
|
|
454
|
-
### Theme knobs
|
|
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:
|
|
455
252
|
|
|
456
|
-
|
|
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.
|
|
457
256
|
|
|
458
257
|
```tsx
|
|
459
258
|
<FBRE
|
|
460
259
|
flow={myFlow}
|
|
461
|
-
theme={{
|
|
462
|
-
colorScheme: "dark",
|
|
463
|
-
color: "#bcd3cd", // accent
|
|
464
|
-
background: "#1a2a3a", // form ground
|
|
465
|
-
surface: "#243244", // input fills / cards
|
|
466
|
-
text: "#e8ede9",
|
|
467
|
-
border: "#2e4158",
|
|
468
|
-
radius: "3px",
|
|
469
|
-
fontFamily: '"Inter", system-ui, sans-serif',
|
|
470
|
-
}}
|
|
260
|
+
theme={{ colorScheme: "dark", color: "#bcd3cd", surface: "#243244", radius: "3px" }}
|
|
471
261
|
onFlowComplete={handleComplete}
|
|
472
262
|
/>
|
|
473
263
|
```
|
|
474
264
|
|
|
475
|
-
|
|
476
|
-
| --- | --- | --- |
|
|
477
|
-
| `color` | `--fbre-theme-color` | light/dark accent tints |
|
|
478
|
-
| `background` | `--fbre-bg` | — |
|
|
479
|
-
| `surface` | `--fbre-surface` | input fills, hover/alt/subtle surfaces, control surface |
|
|
480
|
-
| `text` | `--fbre-text` | secondary/placeholder/disabled, label |
|
|
481
|
-
| `border` | `--fbre-border` | hover/light/subtle |
|
|
482
|
-
| `radius` | `--fbre-radius` | — |
|
|
483
|
-
| `fontFamily` | `--fbre-font` | — |
|
|
484
|
-
| `error` / `success` / `warning` | `--fbre-{error,success,warning}` | their `-bg` / `-border` (and `error-light`) |
|
|
485
|
-
|
|
486
|
-
### Raw CSS (escape hatch)
|
|
487
|
-
|
|
488
|
-
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:
|
|
489
|
-
|
|
490
|
-
```css
|
|
491
|
-
.my-form-wrapper .fbre-container {
|
|
492
|
-
--fbre-surface: #243244;
|
|
493
|
-
--fbre-star-filled: #bcd3cd;
|
|
494
|
-
}
|
|
495
|
-
```
|
|
496
|
-
|
|
497
|
-
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.
|
|
498
|
-
|
|
499
|
-
### Token catalog
|
|
500
|
-
|
|
501
|
-
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.
|
|
502
|
-
|
|
503
|
-
**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`).
|
|
504
|
-
|
|
505
|
-
**Ground & surfaces**
|
|
506
|
-
|
|
507
|
-
| Token | Light | Dark | Knob |
|
|
508
|
-
| --- | --- | --- | --- |
|
|
509
|
-
| `--fbre-bg` | `#fff` | `#121212` | `background` |
|
|
510
|
-
| `--fbre-surface` | `#fff` | `#1e1e1e` | `surface` |
|
|
511
|
-
| `--fbre-surface-hover` | `#f5f5f5` | `#2a2a2a` | (derived) |
|
|
512
|
-
| `--fbre-surface-alt` | `#e0e0e0` | `#333` | (derived) |
|
|
513
|
-
| `--fbre-surface-subtle` | `#e8e8e8` | `#2a2a2a` | (derived) |
|
|
514
|
-
| `--fbre-control-surface` | `#fff` | `#e0e0e0` | (derived) |
|
|
515
|
-
| `--fbre-input-bg` | `var(--fbre-surface)` | `var(--fbre-surface)` | (derived) |
|
|
516
|
-
| `--fbre-input-bg-focus` | `var(--fbre-bg)` | `var(--fbre-bg)` | (derived) |
|
|
517
|
-
|
|
518
|
-
**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).
|
|
519
|
-
|
|
520
|
-
**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).
|
|
521
|
-
|
|
522
|
-
**State colors**
|
|
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).
|
|
523
266
|
|
|
524
|
-
|
|
525
|
-
| --- | --- | --- | --- |
|
|
526
|
-
| `--fbre-error` | `#d32f2f` | `#ef5350` | `error` |
|
|
527
|
-
| `--fbre-error-light` | `#fdecea` | `#2c1b1b` | (derived) |
|
|
528
|
-
| `--fbre-success` | `#2d6a28` | `#a8e6a0` | `success` |
|
|
529
|
-
| `--fbre-success-bg` | `#ddffda` | `#1a3318` | (derived) |
|
|
530
|
-
| `--fbre-success-border` | `#b8e6b4` | `#2d5a28` | (derived) |
|
|
531
|
-
| `--fbre-warning` | `#7a5900` | `#ffe082` | `warning` |
|
|
532
|
-
| `--fbre-warning-bg` | `#fff8e1` | `#332b00` | (derived) |
|
|
533
|
-
| `--fbre-warning-border` | `#ffe082` | `#665500` | (derived) |
|
|
534
|
-
|
|
535
|
-
**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)`).
|
|
536
|
-
|
|
537
|
-
**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)`).
|
|
538
|
-
|
|
539
|
-
### Screen Transitions
|
|
267
|
+
## Screen Transitions
|
|
540
268
|
|
|
541
269
|
Animate screen changes by setting `navigation.transition` in the flow config:
|
|
542
270
|
|
|
543
271
|
```json
|
|
544
|
-
{
|
|
545
|
-
"config": {
|
|
546
|
-
"navigation": { "transition": "slideFade" }
|
|
547
|
-
}
|
|
548
|
-
}
|
|
272
|
+
{ "config": { "navigation": { "transition": "slideFade" } } }
|
|
549
273
|
```
|
|
550
274
|
|
|
551
275
|
| Type | Effect | Direction-aware? |
|
|
@@ -557,76 +281,25 @@ Animate screen changes by setting `navigation.transition` in the flow config:
|
|
|
557
281
|
| `"rise"` | Vertical rise/sink | Yes |
|
|
558
282
|
| `"scaleFade"` | Scale + opacity | No |
|
|
559
283
|
|
|
560
|
-
Direction-aware transitions reverse when navigating backward
|
|
561
|
-
|
|
562
|
-
Customize timing via CSS custom properties:
|
|
563
|
-
|
|
564
|
-
```css
|
|
565
|
-
.fbre-container {
|
|
566
|
-
--fbre-transition-duration: 250ms;
|
|
567
|
-
--fbre-transition-easing: cubic-bezier(0.4, 0, 0.2, 1);
|
|
568
|
-
}
|
|
569
|
-
```
|
|
570
|
-
|
|
571
|
-
`@media (prefers-reduced-motion: reduce)` sets duration to `0ms` automatically.
|
|
572
|
-
|
|
573
|
-
## Architecture
|
|
574
|
-
|
|
575
|
-
| Concern | Implementation |
|
|
576
|
-
|---------|---------------|
|
|
577
|
-
| UI framework | Zero-dependency custom CSS |
|
|
578
|
-
| Styling | CSS custom properties + BEM (`fbre-` prefix) |
|
|
579
|
-
| Display detection | `isDisplayComponent()` checks type against constant set (`header`, `text`, `divider`, `callout`, `table`) |
|
|
580
|
-
| State | Zustand store factory, one store per `<FBRE />` instance via React Context |
|
|
581
|
-
| Re-renders | Zustand selectors — granular, automatic |
|
|
582
|
-
| Build | Vite library mode |
|
|
583
|
-
| Components | Registry pattern (`Map<string, FC>`), one file per type |
|
|
584
|
-
| 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.
|
|
585
285
|
|
|
586
286
|
## FlowData Output
|
|
587
287
|
|
|
588
|
-
`getFlowData()` returns collected form data. Display-only components
|
|
589
|
-
|
|
590
|
-
```
|
|
591
|
-
FlowData
|
|
592
|
-
├── uuid: string
|
|
593
|
-
├── metadata: Record<string, string>
|
|
594
|
-
└── screens: ScreenData[]
|
|
595
|
-
├── uuid: string
|
|
596
|
-
├── label?: string
|
|
597
|
-
└── components: ComponentData[]
|
|
598
|
-
├── uuid: string
|
|
599
|
-
├── type: string
|
|
600
|
-
├── label?: string (omitted when empty)
|
|
601
|
-
├── value: string | string[] | number | number[] | boolean | null | FileUploadData
|
|
602
|
-
└── components?: ComponentData[][] (groups and repeaters)
|
|
603
|
-
```
|
|
604
|
-
|
|
605
|
-
## Build
|
|
606
|
-
|
|
607
|
-
```bash
|
|
608
|
-
npm run build
|
|
609
|
-
```
|
|
610
|
-
|
|
611
|
-
Output:
|
|
612
|
-
|
|
613
|
-
```
|
|
614
|
-
dist/
|
|
615
|
-
├── index.d.ts # Bundled type declarations
|
|
616
|
-
├── fiber-fbre.js # ES module
|
|
617
|
-
├── fiber-fbre.cjs # CommonJS
|
|
618
|
-
└── fiber-fbre.css # All styles
|
|
619
|
-
```
|
|
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).
|
|
620
289
|
|
|
621
290
|
## Responsive Sizing
|
|
622
291
|
|
|
623
|
-
FBRE uses CSS container queries to automatically scale controls in narrow containers (under 400px). Buttons, stepper dots, and toggle switches shrink proportionally
|
|
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.
|
|
624
293
|
|
|
625
294
|
## Multiple Instances
|
|
626
295
|
|
|
627
|
-
Each `<FBRE />` creates its own isolated Zustand store
|
|
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.)
|
|
628
297
|
|
|
629
298
|
```tsx
|
|
630
299
|
<FBRE flow={flowA} onFlowComplete={handleA} />
|
|
631
300
|
<FBRE flow={flowB} onFlowComplete={handleB} />
|
|
632
|
-
```
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
## License
|
|
304
|
+
|
|
305
|
+
MIT
|