robobyte-front-builder 1.0.26 → 1.0.28

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.
Files changed (88) hide show
  1. package/INTEGRATION.md +6 -0
  2. package/LICENSE +65 -0
  3. package/README.md +166 -21
  4. package/docs/ReportViewer.md +581 -0
  5. package/docs/fetchReportData.md +379 -0
  6. package/docs/printLayout.md +405 -0
  7. package/package.json +29 -1
  8. package/src/lib/index.js +14 -0
  9. package/src/lib/muiTheme.js +655 -0
  10. package/src/lib/providers/RoboByteFrontBuilderProvider.jsx +45 -1
  11. package/src/pages/_app.js +1 -0
  12. package/src/pages/printBuilder/index.jsx +26 -19
  13. package/src/pages/viewBuilder/index.jsx +29 -19
  14. package/training/00-index.md +168 -0
  15. package/training/01-input.md +144 -0
  16. package/training/02-checkbox.md +107 -0
  17. package/training/03-dropdown.md +135 -0
  18. package/training/04-datepicker.md +139 -0
  19. package/training/05-radio.md +123 -0
  20. package/training/06-number.md +133 -0
  21. package/training/07-textarea.md +114 -0
  22. package/training/08-richtext.md +112 -0
  23. package/training/09-tag.md +110 -0
  24. package/training/10-time.md +107 -0
  25. package/training/11-toggle.md +108 -0
  26. package/training/12-signature.md +107 -0
  27. package/training/13-autocomplete.md +134 -0
  28. package/training/14-button.md +168 -0
  29. package/training/15-label.md +138 -0
  30. package/training/16-header.md +128 -0
  31. package/training/17-divider.md +96 -0
  32. package/training/18-image.md +105 -0
  33. package/training/19-link.md +108 -0
  34. package/training/20-banner.md +122 -0
  35. package/training/21-progress-circle.md +101 -0
  36. package/training/22-progress-line.md +93 -0
  37. package/training/23-menu.md +139 -0
  38. package/training/24-popover.md +114 -0
  39. package/training/25-layout.md +116 -0
  40. package/training/26-layout-cell.md +143 -0
  41. package/training/27-card.md +87 -0
  42. package/training/28-wizard.md +126 -0
  43. package/training/29-wizard-step.md +92 -0
  44. package/training/30-repeater.md +123 -0
  45. package/training/31-dialog.md +131 -0
  46. package/training/32-breadcrumb.md +121 -0
  47. package/training/33-dataGrid.md +129 -0
  48. package/training/34-dataTableViewer.md +115 -0
  49. package/training/35-reportViewer.md +673 -0
  50. package/training/36-viewRenderer.md +110 -0
  51. package/training/37-treeView.md +170 -0
  52. package/training/38-kpi-metric.md +148 -0
  53. package/training/39-kpi-trend.md +105 -0
  54. package/training/40-kpi-badge.md +112 -0
  55. package/training/41-kpi-statusDot.md +118 -0
  56. package/training/42-kpi-iconBox.md +114 -0
  57. package/training/43-kpi-gauge.md +143 -0
  58. package/training/44-kpi-bulletChart.md +126 -0
  59. package/training/45-kpi-colorScale.md +143 -0
  60. package/training/46-kpi-rating.md +125 -0
  61. package/training/47-kpi-countdown.md +151 -0
  62. package/training/48-fetchReportData.md +276 -0
  63. package/training/49-printLayout.md +215 -0
  64. package/training/examples/01-login-form.json +176 -0
  65. package/training/examples/02-contact-form.json +141 -0
  66. package/training/examples/03-kpi-cards-row.json +123 -0
  67. package/training/examples/04-settings-toggles.json +153 -0
  68. package/training/examples/05-user-profile-card.json +136 -0
  69. package/training/examples/06-date-range-filter.json +108 -0
  70. package/training/examples/07-search-bar-results.json +130 -0
  71. package/training/examples/08-notification-settings.json +131 -0
  72. package/training/examples/09-employee-profile-form.json +259 -0
  73. package/training/examples/10-invoice-form.json +241 -0
  74. package/training/examples/11-dashboard-overview.json +251 -0
  75. package/training/examples/12-registration-wizard.json +154 -0
  76. package/training/examples/13-product-catalog.json +168 -0
  77. package/training/examples/14-data-table-with-filters.json +180 -0
  78. package/training/examples/15-tabbed-profile.json +92 -0
  79. package/training/examples/16-kpi-full-row.json +203 -0
  80. package/training/examples/17-tree-detail-view.json +139 -0
  81. package/training/examples/18-employee-management.json +233 -0
  82. package/training/examples/19-sales-dashboard.json +272 -0
  83. package/training/examples/20-checkout-wizard.json +225 -0
  84. package/training/examples/21-analytics-page.json +222 -0
  85. package/training/examples/22-hr-onboarding.json +222 -0
  86. package/training/examples/23-document-browser.json +241 -0
  87. package/training/examples/24-order-management.json +290 -0
  88. package/training/examples/25-crm-contact-page.json +272 -0
@@ -0,0 +1,215 @@
1
+ # Helper: `openPrintLayout` / `closePrintLayout` — Calculation-scope Reference Manual
2
+
3
+ > **Audience.** AI sessions emitting builder Calculation code (`actionsConfig.code`, `viewerActions.code`, button `onClick`, `onSubmit`, `onLoad`, column functions, etc.). The two helpers below are **auto-injected** into every Calculation scope — you never `import` them in a builder schema.
4
+
5
+ Triggers a saved print layout from inside any Calculation. The layout itself is designed in the **Print Layout Builder** at `/printBuilder` and persisted on the backend; this helper fetches it by id, renders the multi-zone preview, and lets the user fire `window.print()` with the data you pass.
6
+
7
+ > ⚠️ **Different from `<reportViewer>` PDF export.** The toolbar's PDF icon prints the current grid view as-is. `openPrintLayout` renders a **designed template** (multi-page, header/footer, page numbers, mixed components) populated by the data you pass.
8
+
9
+ ---
10
+
11
+ ## 1. Quick start
12
+
13
+ ```js
14
+ openPrintLayout('invoice-layout-id', {
15
+ invoiceNumber: data.invoiceNumber,
16
+ customerName: data.customer.name,
17
+ lines: data.lines,
18
+ })
19
+ ```
20
+
21
+ That's it. The dialog opens, fetches the layout, renders preview, user clicks Print, OS print dialog appears. No `await` — fire-and-forget.
22
+
23
+ ---
24
+
25
+ ## 2. Signature
26
+
27
+ Both helpers are auto-injected into the Calculation scope. No import.
28
+
29
+ ```ts
30
+ openPrintLayout(layoutId: string, data?: object): void
31
+ closePrintLayout(): void
32
+ ```
33
+
34
+ - `layoutId` — string id from the saved layout (the value the designer copies from `/printBuilder/layouts`).
35
+ - `data` — plain JS object made available inside the layout as `data` (see section 3).
36
+
37
+ Returns nothing. Errors are logged to the console and surfaced via `react-hot-toast` if the layout id is unknown — no try/catch needed in your Calculation.
38
+
39
+ ---
40
+
41
+ ## 3. The `data` you pass
42
+
43
+ The second argument becomes the layout's `data` scope. Inside the layout's expressions and Calculations, the values you pass read back as `data.x`.
44
+
45
+ ```js
46
+ // Pass:
47
+ openPrintLayout('invoice', {
48
+ invoiceNumber: 'INV-1042',
49
+ customer: { name: 'Acme Corp', address: '1 Main St' },
50
+ lines: [{ sku: 'A', qty: 2 }, { sku: 'B', qty: 5 }],
51
+ })
52
+
53
+ // Inside the layout's expressions / Calculations:
54
+ data.invoiceNumber // → 'INV-1042'
55
+ data.customer.name // → 'Acme Corp'
56
+ data.lines.reduce((sum, l) => sum + l.qty, 0) // → 7
57
+ ```
58
+
59
+ The contract is whatever the layout's designer designed against. In the Print Layout Builder's *Settings* tab, the **Test Data** JSON field shows the shape the designer used for preview — your call site should pass the same shape.
60
+
61
+ ---
62
+
63
+ ## 4. When to call it
64
+
65
+ | Trigger | Where it lives in the schema |
66
+ |---|---|
67
+ | Toolbar button on a `reportViewer` | `viewerActions[N].code` |
68
+ | Row action button | `actionsConfig[N].code` |
69
+ | Form submit (`onSubmit`) | `actions[N].code` with `event: 'onSubmit'` |
70
+ | After a `*Service` call succeeds | inside any action `code` |
71
+ | On load (rare — prints automatically on page open) | `actions[N].code` with `event: 'onLoad'` |
72
+ | Page-level button click | `button` component's `onClick.code` |
73
+
74
+ There's no rule preventing you from firing it from any Calculation, but the typical pattern is "user action → fetch / mutate → print".
75
+
76
+ ---
77
+
78
+ ## 5. Recipes
79
+
80
+ ### 5.1 Print a single record from a row action
81
+
82
+ ```json
83
+ {
84
+ "type": "reportViewer",
85
+ "props": {
86
+ "key": "invoices",
87
+ "pageId": "INVOICE_LIST",
88
+ "actionsConfig": [
89
+ {
90
+ "label": "Print Invoice",
91
+ "icon": "PrintOutlined",
92
+ "color": "primary",
93
+ "variant": "text",
94
+ "code": "openPrintLayout('invoice-layout', { invoice: rowData })"
95
+ }
96
+ ]
97
+ }
98
+ }
99
+ ```
100
+
101
+ Inside `actionsConfig.code`, `rowData` is the current AG Grid row — pass it straight to the layout.
102
+
103
+ ### 5.2 Print after saving a form
104
+
105
+ ```js
106
+ async function Calculation(form, data, setData) {
107
+ const r = await PostService(Endpoints.Orders.Post.Create, true, form)
108
+ if (!r) return
109
+ // r.data is the new order id
110
+ showToast('Order created', 'success')
111
+ openPrintLayout('order-layout', { ...form, id: r.data })
112
+ }
113
+ ```
114
+
115
+ ### 5.3 Print all selected rows as one document
116
+
117
+ If your layout uses a `repeater` over `data.rows`, you can hand the whole array in:
118
+
119
+ ```js
120
+ async function Calculation(form, data, setData, dataRef, reportRefs) {
121
+ // Pull the selected rows from the report's grid API.
122
+ // The report's nodeName is its `key` ("invoices" below).
123
+ const api = reportRefs['invoices']?.current?.api
124
+ const selected = api?.getSelectedRows?.() ?? []
125
+ if (selected.length === 0) { showToast('Select at least one row', 'warning'); return }
126
+ openPrintLayout('invoices-batch-layout', { rows: selected })
127
+ }
128
+ ```
129
+
130
+ ### 5.4 Print with filters applied
131
+
132
+ When the layout includes a `reportViewer` zone (e.g. an itemized list inside a header), pass the filter object so the print render fetches only the right rows:
133
+
134
+ ```js
135
+ function Calculation(form, data) {
136
+ openPrintLayout('sales-report', {
137
+ title: `Sales — ${data.year}`,
138
+ filter: {
139
+ Tfilter: [
140
+ { path: 'Year', value: data.year, method: 'Equal' },
141
+ ],
142
+ },
143
+ })
144
+ }
145
+ ```
146
+
147
+ Inside the layout, the `reportViewer` zone's `filter` prop binds to `data.filter`.
148
+
149
+ ### 5.5 Conditional layout — pick one of several at runtime
150
+
151
+ ```js
152
+ function Calculation(form, data) {
153
+ const layoutId = data.invoiceType === 'B2B'
154
+ ? 'invoice-b2b-layout'
155
+ : 'invoice-b2c-layout'
156
+ openPrintLayout(layoutId, { invoice: data.invoice })
157
+ }
158
+ ```
159
+
160
+ ### 5.6 Close the dialog programmatically (rare)
161
+
162
+ ```js
163
+ // e.g. when an external event invalidates the open print preview
164
+ closePrintLayout()
165
+ ```
166
+
167
+ Usually the user closes it themselves via the dialog's Close button or by completing the print.
168
+
169
+ ### 5.7 Open print from a viewer-action button
170
+
171
+ ```json
172
+ {
173
+ "type": "reportViewer",
174
+ "props": {
175
+ "key": "orders",
176
+ "pageId": "ORDER_LIST",
177
+ "viewerActions": [
178
+ {
179
+ "label": "Print Today's Orders",
180
+ "icon": "PrintOutlined",
181
+ "variant": "contained",
182
+ "code": "openPrintLayout('daily-orders', { date: new Date().toISOString().slice(0,10) })"
183
+ }
184
+ ]
185
+ }
186
+ }
187
+ ```
188
+
189
+ The action runs **without** a row context (`rowData` is not in scope for viewer actions).
190
+
191
+ ---
192
+
193
+ ## 6. Pitfalls
194
+
195
+ - **Wrong `layoutId`.** The id must match a saved layout. A typo silently fails — the dialog opens, the fetch errors, the dialog stays in the loading state. Test in `/printBuilder/layouts` first; copy the id from there.
196
+ - **Wrong `data` shape.** The layout reads `data.x` for whatever fields the designer used. If the designer wrote `data.invoiceNumber` and you pass `{ number: ... }`, you'll see blank cells. Mirror the **Test Data** JSON in the layout's Settings tab.
197
+ - **No `await`.** `openPrintLayout` is fire-and-forget. Adding `await` is a no-op and may confuse a reader — the dialog handles its own lifecycle.
198
+ - **Trying to gate on print completion.** There's no callback. The print outcome lives in the OS print dialog. If you need post-print behavior (e.g. mark as printed), handle it in your Save / Approve action right *before* calling `openPrintLayout`, not after.
199
+ - **`reportViewer` zones inside a layout fetch ALL rows.** AG Grid virtualization breaks under `window.print()`, so the print path uses `isPagination: false` and renders a static HTML table. For huge datasets the preview may take seconds to populate — pass a filter that scopes the rows tightly.
200
+ - **Calling it during `onLoad`.** Triggering print as soon as a view opens is jarring UX. Use it sparingly (e.g. a dedicated "/print/[id]" page that auto-prints and closes).
201
+ - **Multiple opens at once.** Calling `openPrintLayout` again while the dialog is already open replaces the content. To print several layouts back-to-back, concat them into a single layout with a `repeater` instead.
202
+ - **Calling outside Calculation scope.** `openPrintLayout` is only auto-injected into Calculation code. If you write a non-Calculation context (e.g. a raw `valueGetter` string on a colDef), it won't be defined — use a row-action `code` instead.
203
+
204
+ ---
205
+
206
+ ## 7. Cheat sheet
207
+
208
+ 1. `openPrintLayout(layoutId, data)` — fires the print dialog. No `await`. No try/catch.
209
+ 2. `closePrintLayout()` — closes it programmatically. Rarely needed.
210
+ 3. `data` becomes the layout's runtime `data` scope — match the shape from the layout's Test Data.
211
+ 4. From a **row action**, `rowData` is in scope — pass it as-is or shape it: `{ invoice: rowData }`.
212
+ 5. From a **viewer action**, `rowData` is **not** in scope — use `data`, `form`, `reportRefs`, or selected rows via `reportRefs[key]?.current?.api?.getSelectedRows()`.
213
+ 6. To pick a layout at runtime: build `layoutId` from a conditional and pass it.
214
+ 7. The dialog's render path fetches **all** rows of any embedded `reportViewer` zone — scope filters tightly for large datasets.
215
+ 8. Companion host-app docs (for non-Calculation callers): [`docs/printLayout.md`](../docs/printLayout.md).
@@ -0,0 +1,176 @@
1
+ {
2
+ "description": "Simple login form with email, password, remember me checkbox, and submit button",
3
+ "components_used": ["layout", "layout-cell", "header", "label", "input", "checkbox", "button", "link", "divider"],
4
+ "root": {
5
+ "type": "layout",
6
+ "props": { "cols": 1, "gap": 0, "rowGap": 0 },
7
+ "style": {},
8
+ "children": [
9
+ {
10
+ "type": "layout-cell",
11
+ "props": {},
12
+ "style": {
13
+ "backgroundColor": "#ffffff",
14
+ "borderRadius": "12px",
15
+ "boxShadow": "0 4px 24px rgba(0,0,0,0.12)",
16
+ "padding": "40px",
17
+ "maxWidth": "420px",
18
+ "width": "100%",
19
+ "alignItems": "center"
20
+ },
21
+ "children": [
22
+ {
23
+ "type": "layout",
24
+ "props": { "cols": 1, "rowGap": 20 },
25
+ "children": [
26
+ {
27
+ "type": "layout-cell",
28
+ "props": {},
29
+ "style": { "alignItems": "center", "padding": "0 0 8px 0" },
30
+ "children": [
31
+ {
32
+ "type": "header",
33
+ "props": {
34
+ "text": "Welcome Back",
35
+ "variant": "h4",
36
+ "component": "h1",
37
+ "align": "center",
38
+ "color": "textPrimary"
39
+ }
40
+ }
41
+ ]
42
+ },
43
+ {
44
+ "type": "layout-cell",
45
+ "props": {},
46
+ "style": {},
47
+ "children": [
48
+ {
49
+ "type": "input",
50
+ "props": {
51
+ "key": "email",
52
+ "label": "Email Address",
53
+ "labelPosition": "top",
54
+ "required": true,
55
+ "placeholder": "you@example.com",
56
+ "prefix": "EmailOutlined",
57
+ "size": "medium"
58
+ },
59
+ "style": { "width": "100%", "color": "primary" }
60
+ }
61
+ ]
62
+ },
63
+ {
64
+ "type": "layout-cell",
65
+ "props": {},
66
+ "style": {},
67
+ "children": [
68
+ {
69
+ "type": "input",
70
+ "props": {
71
+ "key": "password",
72
+ "label": "Password",
73
+ "labelPosition": "top",
74
+ "required": true,
75
+ "placeholder": "Enter your password",
76
+ "prefix": "LockOutlined",
77
+ "suffix": "VisibilityOutlined",
78
+ "size": "medium"
79
+ },
80
+ "style": { "width": "100%", "color": "primary" }
81
+ }
82
+ ]
83
+ },
84
+ {
85
+ "type": "layout-cell",
86
+ "props": {},
87
+ "style": { "justifyContent": "space-between", "alignItems": "center" },
88
+ "children": [
89
+ {
90
+ "type": "checkbox",
91
+ "props": {
92
+ "key": "rememberMe",
93
+ "label": "Remember me",
94
+ "labelPosition": "right",
95
+ "size": "small"
96
+ }
97
+ },
98
+ {
99
+ "type": "link",
100
+ "props": {
101
+ "text": "Forgot password?",
102
+ "href": "/forgot-password",
103
+ "color": "primary",
104
+ "variant": "body2",
105
+ "underline": "hover"
106
+ }
107
+ }
108
+ ]
109
+ },
110
+ {
111
+ "type": "layout-cell",
112
+ "props": {},
113
+ "style": {},
114
+ "children": [
115
+ {
116
+ "type": "button",
117
+ "props": {
118
+ "label": "Sign In",
119
+ "variant": "contained",
120
+ "color": "primary",
121
+ "fullWidth": true,
122
+ "size": "large"
123
+ },
124
+ "actions": {
125
+ "onClick": { "type": "submit" }
126
+ }
127
+ }
128
+ ]
129
+ },
130
+ {
131
+ "type": "layout-cell",
132
+ "props": {},
133
+ "style": {},
134
+ "children": [
135
+ {
136
+ "type": "divider",
137
+ "props": { "text": "OR", "orientation": "horizontal", "variant": "middle", "textAlign": "center" }
138
+ }
139
+ ]
140
+ },
141
+ {
142
+ "type": "layout-cell",
143
+ "props": {},
144
+ "style": { "justifyContent": "center", "alignItems": "center" },
145
+ "children": [
146
+ {
147
+ "type": "label",
148
+ "props": {
149
+ "text": "Don't have an account?",
150
+ "variant": "body2",
151
+ "color": "textSecondary",
152
+ "align": "center"
153
+ }
154
+ },
155
+ {
156
+ "type": "link",
157
+ "props": {
158
+ "text": "Sign up",
159
+ "href": "/register",
160
+ "color": "primary",
161
+ "variant": "body2",
162
+ "underline": "hover"
163
+ }
164
+ }
165
+ ]
166
+ }
167
+ ]
168
+ }
169
+ ]
170
+ }
171
+ ]
172
+ },
173
+ "dialogs": [],
174
+ "actions": [],
175
+ "timers": []
176
+ }
@@ -0,0 +1,141 @@
1
+ {
2
+ "description": "Simple contact us form with name, email, subject dropdown, message textarea, and submit",
3
+ "components_used": ["layout", "layout-cell", "header", "label", "input", "dropdown", "textarea", "button", "banner"],
4
+ "root": {
5
+ "type": "layout",
6
+ "props": { "cols": 1, "rowGap": 0 },
7
+ "children": [
8
+ {
9
+ "type": "layout-cell",
10
+ "props": {},
11
+ "style": { "padding": "32px", "backgroundColor": "#ffffff", "borderRadius": "8px", "boxShadow": "0 2px 12px rgba(0,0,0,0.08)" },
12
+ "children": [
13
+ {
14
+ "type": "layout",
15
+ "props": { "cols": 1, "rowGap": 24 },
16
+ "children": [
17
+ {
18
+ "type": "layout-cell",
19
+ "props": {},
20
+ "style": {},
21
+ "children": [
22
+ { "type": "header", "props": { "text": "Contact Us", "variant": "h5", "component": "h1", "color": "textPrimary", "gutterBottom": false } }
23
+ ]
24
+ },
25
+ {
26
+ "type": "layout-cell",
27
+ "props": {},
28
+ "style": {},
29
+ "children": [
30
+ {
31
+ "type": "banner",
32
+ "props": {
33
+ "key": "successMsg",
34
+ "message": "Your message was sent! We'll get back to you within 24 hours.",
35
+ "severity": "success",
36
+ "variant": "outlined",
37
+ "open": "data.submitted",
38
+ "dismissible": true,
39
+ "icon": true
40
+ },
41
+ "actions": {
42
+ "onClose": { "type": "custom", "code": "setData({ submitted: false })" }
43
+ }
44
+ }
45
+ ]
46
+ },
47
+ {
48
+ "type": "layout-cell",
49
+ "props": {},
50
+ "style": {},
51
+ "children": [
52
+ {
53
+ "type": "layout",
54
+ "props": { "cols": 2, "gap": 16, "rowGap": 16 },
55
+ "children": [
56
+ {
57
+ "type": "layout-cell",
58
+ "props": {},
59
+ "style": {},
60
+ "children": [
61
+ { "type": "input", "props": { "key": "firstName", "label": "First Name", "required": true, "size": "medium" }, "style": { "width": "100%" } }
62
+ ]
63
+ },
64
+ {
65
+ "type": "layout-cell",
66
+ "props": {},
67
+ "style": {},
68
+ "children": [
69
+ { "type": "input", "props": { "key": "lastName", "label": "Last Name", "required": true, "size": "medium" }, "style": { "width": "100%" } }
70
+ ]
71
+ },
72
+ {
73
+ "type": "layout-cell",
74
+ "props": { "colSpan": 2 },
75
+ "style": {},
76
+ "children": [
77
+ { "type": "input", "props": { "key": "email", "label": "Email", "required": true, "prefix": "EmailOutlined", "size": "medium" }, "style": { "width": "100%" } }
78
+ ]
79
+ },
80
+ {
81
+ "type": "layout-cell",
82
+ "props": { "colSpan": 2 },
83
+ "style": {},
84
+ "children": [
85
+ {
86
+ "type": "dropdown",
87
+ "props": {
88
+ "key": "subject",
89
+ "label": "Subject",
90
+ "required": true,
91
+ "fullWidth": true,
92
+ "placeholder": "Select a topic",
93
+ "options": [
94
+ { "label": "General Inquiry", "value": "general" },
95
+ { "label": "Technical Support", "value": "support" },
96
+ { "label": "Billing", "value": "billing" },
97
+ { "label": "Partnership", "value": "partnership" }
98
+ ]
99
+ }
100
+ }
101
+ ]
102
+ },
103
+ {
104
+ "type": "layout-cell",
105
+ "props": { "colSpan": 2 },
106
+ "style": {},
107
+ "children": [
108
+ { "type": "textarea", "props": { "key": "message", "label": "Message", "required": true, "rows": 5, "placeholder": "Tell us how we can help you..." }, "style": { "width": "100%" } }
109
+ ]
110
+ },
111
+ {
112
+ "type": "layout-cell",
113
+ "props": { "colSpan": 2 },
114
+ "style": { "justifyContent": "flex-end" },
115
+ "children": [
116
+ {
117
+ "type": "button",
118
+ "props": { "label": "Send Message", "variant": "contained", "color": "primary", "prefix": "SendOutlined" },
119
+ "actions": {
120
+ "onClick": {
121
+ "type": "custom",
122
+ "code": "sendContactForm({ firstName: form.firstName, lastName: form.lastName, email: form.email, subject: form.subject, message: form.message }); setData({ submitted: true })"
123
+ }
124
+ }
125
+ }
126
+ ]
127
+ }
128
+ ]
129
+ }
130
+ ]
131
+ }
132
+ ]
133
+ }
134
+ ]
135
+ }
136
+ ]
137
+ },
138
+ "dialogs": [],
139
+ "actions": [],
140
+ "timers": []
141
+ }
@@ -0,0 +1,123 @@
1
+ {
2
+ "description": "Three KPI metric cards in a row with trend indicators — typical dashboard top section",
3
+ "components_used": ["layout", "layout-cell", "kpi-metric", "kpi-trend", "kpi-badge", "kpi-statusDot", "divider"],
4
+ "root": {
5
+ "type": "layout",
6
+ "props": { "cols": 1, "rowGap": 24 },
7
+ "children": [
8
+ {
9
+ "type": "layout-cell",
10
+ "props": {},
11
+ "style": { "padding": "0 0 8px 0" },
12
+ "children": [
13
+ { "type": "header", "props": { "text": "Today's Overview", "variant": "h5", "color": "textPrimary" } }
14
+ ]
15
+ },
16
+ {
17
+ "type": "layout-cell",
18
+ "props": {},
19
+ "style": {},
20
+ "children": [
21
+ {
22
+ "type": "layout",
23
+ "props": { "cols": 3, "gap": 16 },
24
+ "children": [
25
+ {
26
+ "type": "layout-cell",
27
+ "props": {},
28
+ "style": { "backgroundColor": "#ffffff", "borderRadius": "12px", "boxShadow": "0 2px 8px rgba(0,0,0,0.08)", "padding": "24px" },
29
+ "children": [
30
+ {
31
+ "type": "layout",
32
+ "props": { "cols": 1, "rowGap": 8 },
33
+ "children": [
34
+ {
35
+ "type": "layout-cell",
36
+ "props": {},
37
+ "style": { "justifyContent": "space-between", "alignItems": "flex-start" },
38
+ "children": [
39
+ { "type": "kpi-metric", "props": { "valueKey": "data.totalRevenue", "label": "Total Revenue", "format": "currency", "currency": "USD", "decimals": 0, "fontSize": "28px", "fontWeight": "700", "align": "left", "labelPosition": "below", "labelColor": "text.secondary", "labelFontSize": "13px" } },
40
+ { "type": "kpi-statusDot", "props": { "valueKey": "data.revenueStatus", "size": "10", "pulse": true, "showLabel": false, "thresholds": [{ "operator": "==", "value": "healthy", "color": "success.main" }] } }
41
+ ]
42
+ },
43
+ {
44
+ "type": "layout-cell",
45
+ "props": {},
46
+ "style": {},
47
+ "children": [
48
+ { "type": "kpi-trend", "props": { "valueKey": "data.revenueGrowth", "label": "vs last month", "isPercent": true, "decimals": 1, "variant": "chip", "size": "small", "showIcon": true, "align": "left" } }
49
+ ]
50
+ }
51
+ ]
52
+ }
53
+ ]
54
+ },
55
+ {
56
+ "type": "layout-cell",
57
+ "props": {},
58
+ "style": { "backgroundColor": "#ffffff", "borderRadius": "12px", "boxShadow": "0 2px 8px rgba(0,0,0,0.08)", "padding": "24px" },
59
+ "children": [
60
+ {
61
+ "type": "layout",
62
+ "props": { "cols": 1, "rowGap": 8 },
63
+ "children": [
64
+ {
65
+ "type": "layout-cell",
66
+ "props": {},
67
+ "style": {},
68
+ "children": [
69
+ { "type": "kpi-metric", "props": { "valueKey": "data.totalOrders", "label": "Total Orders", "format": "number", "fontSize": "28px", "fontWeight": "700", "align": "left", "labelPosition": "below", "labelColor": "text.secondary", "labelFontSize": "13px" } }
70
+ ]
71
+ },
72
+ {
73
+ "type": "layout-cell",
74
+ "props": {},
75
+ "style": {},
76
+ "children": [
77
+ { "type": "kpi-trend", "props": { "valueKey": "data.ordersGrowth", "label": "vs last week", "isPercent": true, "decimals": 0, "variant": "chip", "size": "small", "showIcon": true, "align": "left" } }
78
+ ]
79
+ }
80
+ ]
81
+ }
82
+ ]
83
+ },
84
+ {
85
+ "type": "layout-cell",
86
+ "props": {},
87
+ "style": { "backgroundColor": "#ffffff", "borderRadius": "12px", "boxShadow": "0 2px 8px rgba(0,0,0,0.08)", "padding": "24px" },
88
+ "children": [
89
+ {
90
+ "type": "layout",
91
+ "props": { "cols": 1, "rowGap": 8 },
92
+ "children": [
93
+ {
94
+ "type": "layout-cell",
95
+ "props": {},
96
+ "style": { "justifyContent": "space-between", "alignItems": "flex-start" },
97
+ "children": [
98
+ { "type": "kpi-metric", "props": { "valueKey": "data.satisfactionScore", "label": "Satisfaction", "format": "number", "decimals": 1, "suffix": "/5", "fontSize": "28px", "fontWeight": "700", "align": "left", "labelPosition": "below", "labelColor": "text.secondary", "labelFontSize": "13px" } },
99
+ { "type": "kpi-badge", "props": { "valueKey": "data.satisfactionLevel", "variant": "soft", "size": "small", "thresholds": [{ "operator": "==", "value": "excellent", "color": "success.main", "label": "Excellent" }, { "operator": "==", "value": "good", "color": "info.main", "label": "Good" }] } }
100
+ ]
101
+ },
102
+ {
103
+ "type": "layout-cell",
104
+ "props": {},
105
+ "style": {},
106
+ "children": [
107
+ { "type": "kpi-trend", "props": { "valueKey": "data.satisfactionDelta", "label": "vs last quarter", "isPercent": false, "decimals": 1, "variant": "chip", "size": "small", "showIcon": true, "align": "left" } }
108
+ ]
109
+ }
110
+ ]
111
+ }
112
+ ]
113
+ }
114
+ ]
115
+ }
116
+ ]
117
+ }
118
+ ]
119
+ },
120
+ "dialogs": [],
121
+ "actions": [],
122
+ "timers": []
123
+ }