@cxtms/cx-schema 1.0.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/.claude/skills/cx-core/SKILL.md +93 -0
  2. package/.claude/skills/cx-core/ref-entity-accounting.md +173 -0
  3. package/.claude/skills/cx-core/ref-entity-commodity.md +205 -0
  4. package/.claude/skills/cx-core/ref-entity-contact.md +153 -0
  5. package/.claude/skills/cx-core/ref-entity-geography.md +119 -0
  6. package/.claude/skills/cx-core/ref-entity-job.md +77 -0
  7. package/.claude/skills/cx-core/ref-entity-order-sub.md +140 -0
  8. package/.claude/skills/cx-core/ref-entity-order.md +168 -0
  9. package/.claude/skills/cx-core/ref-entity-rate.md +174 -0
  10. package/.claude/skills/cx-core/ref-entity-shared.md +147 -0
  11. package/.claude/skills/cx-core/ref-entity-warehouse.md +110 -0
  12. package/.claude/skills/cx-module/SKILL.md +402 -0
  13. package/.claude/skills/cx-module/ref-components-data.md +286 -0
  14. package/.claude/skills/cx-module/ref-components-display.md +394 -0
  15. package/.claude/skills/cx-module/ref-components-forms.md +362 -0
  16. package/.claude/skills/cx-module/ref-components-interactive.md +306 -0
  17. package/.claude/skills/cx-module/ref-components-layout.md +295 -0
  18. package/.claude/skills/cx-module/ref-components-specialized.md +427 -0
  19. package/.claude/skills/cx-workflow/SKILL.md +330 -0
  20. package/.claude/skills/cx-workflow/ref-accounting.md +66 -0
  21. package/.claude/skills/cx-workflow/ref-communication.md +161 -0
  22. package/.claude/skills/cx-workflow/ref-entity.md +162 -0
  23. package/.claude/skills/cx-workflow/ref-expressions.md +239 -0
  24. package/.claude/skills/cx-workflow/ref-filetransfer.md +80 -0
  25. package/.claude/skills/cx-workflow/ref-flow.md +180 -0
  26. package/.claude/skills/cx-workflow/ref-other.md +120 -0
  27. package/.claude/skills/cx-workflow/ref-query.md +85 -0
  28. package/.claude/skills/cx-workflow/ref-utilities.md +171 -0
  29. package/README.md +34 -34
  30. package/dist/cli.js +545 -35
  31. package/dist/cli.js.map +1 -1
  32. package/dist/types.d.ts +17 -1
  33. package/dist/types.d.ts.map +1 -1
  34. package/dist/workflowValidator.d.ts +31 -0
  35. package/dist/workflowValidator.d.ts.map +1 -1
  36. package/dist/workflowValidator.js +299 -9
  37. package/dist/workflowValidator.js.map +1 -1
  38. package/package.json +4 -3
  39. package/schemas/schema.graphql +2077 -321
  40. package/schemas/workflows/flow/aggregation.json +44 -0
  41. package/schemas/workflows/flow/entity.json +110 -0
  42. package/schemas/workflows/flow/state.json +105 -0
  43. package/schemas/workflows/flow/transition.json +143 -0
  44. package/schemas/workflows/input.json +53 -7
  45. package/schemas/workflows/output.json +20 -0
  46. package/schemas/workflows/trigger.json +4 -0
  47. package/schemas/workflows/variable.json +2 -6
  48. package/schemas/workflows/workflow.json +179 -21
  49. package/scripts/postinstall.js +30 -1
  50. package/templates/module-configuration.yaml +110 -0
  51. package/templates/module-form.yaml +152 -0
  52. package/templates/module-grid.yaml +229 -0
  53. package/templates/module-select.yaml +139 -0
  54. package/templates/module.yaml +3 -3
  55. package/templates/workflow-api-tracking.yaml +189 -0
  56. package/templates/workflow-basic.yaml +76 -0
  57. package/templates/workflow-document.yaml +155 -0
  58. package/templates/workflow-entity-trigger.yaml +90 -0
  59. package/templates/workflow-ftp-edi.yaml +158 -0
  60. package/templates/workflow-ftp-tracking.yaml +161 -0
  61. package/templates/workflow-mcp-tool.yaml +112 -0
  62. package/templates/workflow-public-api.yaml +135 -0
  63. package/templates/workflow-scheduled.yaml +125 -0
  64. package/templates/workflow-utility.yaml +96 -0
  65. package/templates/workflow-webhook.yaml +128 -0
  66. package/templates/workflow.yaml +52 -12
@@ -0,0 +1,362 @@
1
+ # Form & Input Components
2
+
3
+ ## form
4
+
5
+ Data entry form with validation, queries, and submission. Wraps React Hook Form's FormProvider.
6
+
7
+ **Props:**
8
+ | Prop | Type | Description |
9
+ |------|------|-------------|
10
+ | `title` | `ILocalizeString` | Form title |
11
+ | `initialValues` | `object \| query config` | Initial data — static object, template, or query config |
12
+ | `initialValues.fromQuery` | `{name, path}` | Load initial values from a named query |
13
+ | `initialValues.append` | `Record<string, any>` | Merge additional defaults after query load |
14
+ | `validationSchema` | `Record<string, {type, required?, ...}>` | Yup-based validation rules |
15
+ | `queries` | `QueryDef[]` | GraphQL queries for data loading |
16
+ | `prefix` | `string` | Field name namespace prefix |
17
+ | `refreshHandler` | `string` | Remount on refresh event |
18
+ | `dirtyGuard` | `DirtyGuardProps` | Unsaved changes protection |
19
+ | `dirtyGuard.enabled` | `boolean` | Enable guard |
20
+ | `dirtyGuard.title` | `ILocalizeString` | Dialog title |
21
+ | `dirtyGuard.message` | `ILocalizeString` | Dialog message |
22
+ | `dirtyGuard.confirmLabel` | `ILocalizeString` | Confirm button text |
23
+ | `dirtyGuard.cancelLabel` | `ILocalizeString` | Cancel button text |
24
+ | `autoSave` | `boolean` | Auto-save on field change |
25
+ | `resetOnSubmit` | `boolean` | Reset form after submit |
26
+ | `preventDefault` | `boolean` | Prevent default form submit |
27
+ | `toolbar` | `component[]` | Toolbar components |
28
+ | `cols` | `number` | Column layout for children |
29
+ | `orientation` | `string` | Layout orientation |
30
+
31
+ **Events:**
32
+ | Event | Description |
33
+ |-------|-------------|
34
+ | `onSubmit` | Action chain on form submission |
35
+ | `onChange` | Action chain on any field change |
36
+ | `onLoad` | Action chain when form data loads |
37
+ | `onReset` | Action chain on form reset |
38
+ | `onValidate` | Action chain on validation |
39
+ | `onError` | Action chain on error |
40
+
41
+ **Children:** Yes — typically `field` components. Provides `formName` and `createMode` in variables.
42
+
43
+ ```yaml
44
+ component: form
45
+ name: orderForm
46
+ props:
47
+ title: { en-US: "Order Details" }
48
+ toolbar:
49
+ - component: button
50
+ name: saveBtn
51
+ props:
52
+ label: { en-US: "Save" }
53
+ icon: check
54
+ options: { type: submit, variant: primary }
55
+ queries:
56
+ - name: getOrder
57
+ query:
58
+ command: |
59
+ query GetOrder($id: Int!) {
60
+ order(id: $id) { id orderNumber status }
61
+ }
62
+ variables:
63
+ id: "{{ number id }}"
64
+ initialValues:
65
+ fromQuery:
66
+ name: getOrder
67
+ path: order
68
+ append:
69
+ status: "Draft"
70
+ validationSchema:
71
+ orderNumber:
72
+ type: string
73
+ required: true
74
+ status:
75
+ type: string
76
+ required: true
77
+ dirtyGuard:
78
+ enabled: true
79
+ title: { en-US: "Unsaved Changes" }
80
+ message: { en-US: "You have unsaved changes. Leave?" }
81
+ confirmLabel: { en-US: "Leave" }
82
+ cancelLabel: { en-US: "Stay" }
83
+ onSubmit:
84
+ - mutation:
85
+ command: |
86
+ mutation SaveOrder($input: OrderInput!) {
87
+ saveOrder(input: $input) { id }
88
+ }
89
+ variables:
90
+ input: "{{ form }}"
91
+ onSuccess:
92
+ - notification: { message: { en-US: "Saved!" }, type: success }
93
+ - navigateBack: { fallback: "/orders" }
94
+ children:
95
+ - component: field
96
+ name: orderNumber
97
+ props: { type: text, label: { en-US: "Order Number" }, required: true }
98
+ - component: field
99
+ name: status
100
+ props:
101
+ type: select
102
+ label: { en-US: "Status" }
103
+ items:
104
+ - { label: "Draft", value: "Draft" }
105
+ - { label: "Active", value: "Active" }
106
+ ```
107
+
108
+ ---
109
+
110
+ ## field
111
+
112
+ Polymorphic form field — renders different input types based on `type` prop.
113
+
114
+ **Props:**
115
+ | Prop | Type | Description |
116
+ |------|------|-------------|
117
+ | `type` | `string` | **Required.** Field type (see table below) |
118
+ | `label` | `ILocalizeString` | Localized label |
119
+ | `placeholder` | `string` | Placeholder text (template-parsed) |
120
+ | `disabled` | `string \| boolean` | Disable state (template expression) |
121
+ | `readonly` | `boolean` | Read-only mode |
122
+ | `required` | `boolean` | Required field |
123
+ | `prefix` | `string` | Field name prefix for nested paths |
124
+ | `transformValue` | `{onEdit?, onLoad?}` | Template expressions to transform values |
125
+ | `multiple` | `boolean` | Multi-value mode (for `text`) |
126
+ | `autoComplete` | `string` | HTML autocomplete attribute |
127
+ | `rows` | `number` | Rows for textarea (default 2) |
128
+ | `defaultCountry` | `string` | Default country for phone type |
129
+ | `isClearable` | `boolean` | Allow clearing the value |
130
+ | `InputProps` | `object` | Passed to underlying MUI TextField |
131
+
132
+ **Field Types:**
133
+ | Type | Description |
134
+ |------|-------------|
135
+ | `text` | Standard text input. `multiple: true` → multi-value tags |
136
+ | `number` | Numeric input |
137
+ | `email` | Email input |
138
+ | `password` | Password input |
139
+ | `tel` / `phone` | Phone number with country selector |
140
+ | `textarea` | Multi-line text with `rows` prop |
141
+ | `checkbox` | Boolean checkbox |
142
+ | `radio` | Radio button (use `value` prop) |
143
+ | `date` | Date picker |
144
+ | `datetime` | Date + time picker |
145
+ | `rangedatetime` | Date range picker |
146
+ | `enhanced-rangedatetime` | Enhanced date range picker |
147
+ | `time` | Time picker |
148
+ | `select` | Dropdown select from `items[]` |
149
+ | `select-async` | Async search select (GraphQL-backed) |
150
+ | `autocomplete` | Autocomplete from `items[]` |
151
+ | `autocomplete-async` | Async autocomplete (GraphQL-backed) |
152
+ | `autocomplete-googleplaces` | Google Places autocomplete |
153
+ | `file` | File upload (converts to byte array) |
154
+ | `attachment` | File attachment |
155
+ | `hidden` | Hidden input |
156
+ | `quill` | Rich text editor (Quill) |
157
+ | `object` | JSON object editor |
158
+ | `yaml` | YAML editor |
159
+
160
+ **Select/Async options (under `options`):**
161
+ | Prop | Type | Description |
162
+ |------|------|-------------|
163
+ | `items` | `{label, value}[]` | Static select items |
164
+ | `allowMultiple` | `boolean` | Multi-select mode |
165
+ | `allowClear` | `boolean` | Show clear button |
166
+ | `allowSearch` | `boolean` | Searchable dropdown |
167
+ | `valueFieldName` | `string` | Result field holding the value |
168
+ | `itemLabelTemplate` | `string` | Handlebars template for labels |
169
+ | `itemValueTemplate` | `string` | Handlebars template for values |
170
+ | `navigateActionPermission` | `string` | Permission for edit icon |
171
+ | `searchQuery` | `{name, path, params}` | Paginated search query ref |
172
+ | `valueQuery` | `{name, path, params}` | Single-value lookup query ref |
173
+ | `variant` | `string` | Select variant |
174
+
175
+ **Events:**
176
+ | Event | Description |
177
+ |-------|-------------|
178
+ | `onClick` | Fires on click |
179
+ | `onChange` | Fires on value change (data: `changedValues`, `checked`) |
180
+ | `onBlur` | Fires on blur |
181
+ | `onFocus` | Fires on focus |
182
+ | `onKeyPress` | Fires on keypress (data: `key`, `keyCode`) |
183
+ | `onSelectValue` | Fires on select-async value selection |
184
+ | `onEditClick` | Fires when edit icon clicked (select-async, permission-gated) |
185
+
186
+ ```yaml
187
+ # Text field
188
+ - component: field
189
+ name: companyName
190
+ props:
191
+ type: text
192
+ label: { en-US: "Company Name" }
193
+ required: true
194
+ placeholder: "Enter company name"
195
+
196
+ # Select field
197
+ - component: field
198
+ name: status
199
+ props:
200
+ type: select
201
+ label: { en-US: "Status" }
202
+ items:
203
+ - { label: "Active", value: "active" }
204
+ - { label: "Inactive", value: "inactive" }
205
+
206
+ # Async select with search
207
+ - component: field
208
+ name: customerId
209
+ props:
210
+ type: select-async
211
+ label: { en-US: "Customer" }
212
+ options:
213
+ valueFieldName: contactId
214
+ itemLabelTemplate: "{{contactName}}"
215
+ itemValueTemplate: "{{contactId}}"
216
+ allowSearch: true
217
+ allowClear: true
218
+ searchQuery:
219
+ name: getContacts
220
+ path: contacts.items
221
+ params:
222
+ search: "{{ string search }}"
223
+ take: "{{ number pageSize }}"
224
+ skip: "{{ number skip }}"
225
+ valueQuery:
226
+ name: getContact
227
+ path: contact
228
+ params:
229
+ contactId: "{{ contactId }}"
230
+ queries:
231
+ - name: getContacts
232
+ query:
233
+ command: >-
234
+ query($search: String!, $take: Int!, $skip: Int!) {
235
+ contacts(search: $search, take: $take, skip: $skip) {
236
+ items { contactId contactName } totalCount
237
+ }
238
+ }
239
+ - name: getContact
240
+ query:
241
+ command: >-
242
+ query($contactId: Int!) {
243
+ contact(contactId: $contactId) { contactId contactName }
244
+ }
245
+ variables:
246
+ contactId: "{{ number contactId }}"
247
+
248
+ # Date field with transform
249
+ - component: field
250
+ name: effectiveDate
251
+ props:
252
+ type: date
253
+ label: { en-US: "Effective Date" }
254
+ transformValue:
255
+ onLoad: "{{ format effectiveDate YYYY-MM-DD }}"
256
+
257
+ # Conditional visibility
258
+ - component: field
259
+ name: notes
260
+ props:
261
+ type: textarea
262
+ label: { en-US: "Notes" }
263
+ rows: 4
264
+ disabled: "{{ eval !canEdit }}"
265
+ ```
266
+
267
+ ---
268
+
269
+ ## field-collection
270
+
271
+ Dynamic array/list editor for repeating field groups. Supports add/remove/reorder with drag-and-drop.
272
+
273
+ **Props:**
274
+ | Prop | Type | Default | Description |
275
+ |------|------|---------|-------------|
276
+ | `fieldName` | `string` | — | **Required.** Form array field binding |
277
+ | `itemTemplate` | `ComponentProps` | — | Template for each item |
278
+ | `itemType` | `object \| string \| number \| boolean` | `object` | Item type |
279
+ | `options.allowAdd` | `boolean` | `true` | Show add button |
280
+ | `options.allowRemove` | `boolean` | `true` | Show remove buttons |
281
+ | `options.allowRemoveAll` | `boolean` | `false` | Show remove all button |
282
+ | `options.allowReorder` | `boolean` | `true` | Enable drag-and-drop |
283
+ | `options.minItems` | `number` | `0` | Minimum items (auto-created) |
284
+ | `options.maxItems` | `number` | `∞` | Maximum items |
285
+ | `addButton.label` | `ILocalizeString` | `Add Item` | Add button label |
286
+ | `addButton.icon` | `string` | `plus` | Add button icon |
287
+ | `addButton.position` | `top \| bottom \| both` | `bottom` | Add button placement |
288
+ | `defaultItem` | `any` | — | Template for new items |
289
+ | `layout` | `list \| grid \| accordion` | `list` | Layout mode |
290
+ | `cols` | `number` | `1` | Grid columns |
291
+ | `groupMode` | `boolean` | `false` | Enable grouping |
292
+ | `groupBy` | `string` | — | Field path to group by |
293
+ | `groups` | `{key, label, icon?}[]` | — | Group definitions |
294
+ | `showIndex` | `boolean` | `false` | Show item index |
295
+ | `showDragHandle` | `boolean` | `false` | Show drag handles |
296
+
297
+ **Children:** Uses `itemTemplate` (not `children`). Each item gets `item`, `index`, `collection` variables.
298
+
299
+ ```yaml
300
+ component: field-collection
301
+ name: lineItems
302
+ props:
303
+ fieldName: lineItems
304
+ options:
305
+ allowAdd: true
306
+ allowRemove: true
307
+ allowReorder: true
308
+ minItems: 1
309
+ maxItems: 50
310
+ addButton:
311
+ label: { en-US: "Add Line Item" }
312
+ icon: plus
313
+ position: bottom
314
+ defaultItem:
315
+ description: ""
316
+ quantity: 1
317
+ unitPrice: 0
318
+ layout: list
319
+ itemTemplate:
320
+ component: row
321
+ name: lineItemRow
322
+ props: { spacing: 2 }
323
+ children:
324
+ - component: field
325
+ name: description
326
+ props: { type: text, label: { en-US: "Description" } }
327
+ - component: field
328
+ name: quantity
329
+ props: { type: number, label: { en-US: "Qty" } }
330
+ - component: field
331
+ name: unitPrice
332
+ props: { type: number, label: { en-US: "Unit Price" } }
333
+ ```
334
+
335
+ ---
336
+
337
+ ## barcodeScanner
338
+
339
+ Headless barcode/keyboard scanner listener. Captures rapid keystrokes and fires on scan.
340
+
341
+ **Props:**
342
+ | Prop | Type | Default | Description |
343
+ |------|------|---------|-------------|
344
+ | `minBarcodeLength` | `number` | `4` | Min chars to qualify as scan |
345
+ | `onScan` | `action[]` | — | **Required.** Action on scan detection |
346
+
347
+ **Renders:** Nothing (empty fragment). Listens on `document` keypress.
348
+
349
+ **Scan data:** `result: { data: string, format: 'input' }`
350
+
351
+ ```yaml
352
+ component: barcodeScanner
353
+ name: scanner
354
+ props:
355
+ minBarcodeLength: 6
356
+ onScan:
357
+ - setFields:
358
+ barcode: "{{ result.data }}"
359
+ - notification:
360
+ message: { en-US: "Scanned: {{ result.data }}" }
361
+ type: info
362
+ ```
@@ -0,0 +1,306 @@
1
+ # Interactive & Navigation Components
2
+
3
+ ## button
4
+
5
+ MUI Button with icon, label, loading state, and action dispatch.
6
+
7
+ **Props:**
8
+ | Prop | Type | Description |
9
+ |------|------|-------------|
10
+ | `label` | `ILocalizeString` | Button text (localized, template-parsed) |
11
+ | `icon` | `string` | Icon name (template-parsed) |
12
+ | `stopPropagation` | `boolean` | Stop click event propagation |
13
+ | `options.variant` | `string` | Visual variant (see mapping below) |
14
+ | `options.disabled` | `boolean \| string` | Disable state (template expression) |
15
+ | `options.type` | `submit \| reset \| button` | Button type |
16
+ | `options.sx` | `SxProps` | MUI sx styles |
17
+ | `options.className` | `string` | CSS class |
18
+
19
+ **Variant mapping:**
20
+ | YAML Value | MUI Rendered |
21
+ |------------|-------------|
22
+ | `primary` | `contained` + primary color |
23
+ | `secondary` | `outlined` + secondary color |
24
+ | `outline-primary` | `outlined` + primary color |
25
+ | `outline-secondary` | `outlined` + secondary color |
26
+ | `danger` | `contained` + error color |
27
+ | `success` | `contained` + success color |
28
+ | `warning` | `contained` + warning color |
29
+ | `info` | `contained` + info color |
30
+ | `link` / `text` | `text` variant |
31
+ | `contained-primary` | compound format supported |
32
+
33
+ **Events:** `onClick` — dispatches action chain with loading spinner while executing.
34
+
35
+ ```yaml
36
+ # Primary submit button
37
+ - component: button
38
+ name: saveBtn
39
+ props:
40
+ label: { en-US: "Save" }
41
+ icon: check
42
+ options:
43
+ type: submit
44
+ variant: primary
45
+
46
+ # Danger button with confirmation
47
+ - component: button
48
+ name: deleteBtn
49
+ props:
50
+ label: { en-US: "Delete" }
51
+ icon: trash
52
+ options:
53
+ variant: danger
54
+ onClick:
55
+ - confirm:
56
+ title: { en-US: "Delete Item?" }
57
+ message: { en-US: "This action cannot be undone." }
58
+ - mutation:
59
+ command: "mutation($id: Int!) { deleteItem(id: $id) { success } }"
60
+ variables: { id: "{{ number id }}" }
61
+ onSuccess:
62
+ - notification: { message: { en-US: "Deleted" }, type: success }
63
+ - navigateBack: { fallback: "/items" }
64
+
65
+ # Conditional disabled state
66
+ - component: button
67
+ name: approveBtn
68
+ props:
69
+ label: { en-US: "Approve" }
70
+ icon: check-circle
71
+ options:
72
+ variant: success
73
+ disabled: "{{ eval status !== 'Pending' }}"
74
+ onClick:
75
+ - mutation:
76
+ command: "mutation($id: Int!) { approveOrder(id: $id) { success } }"
77
+ variables: { id: "{{ number id }}" }
78
+ ```
79
+
80
+ ---
81
+
82
+ ## dropdown
83
+
84
+ Action dropdown menu (not a form select). MUI Button + Menu with permission-gated items.
85
+
86
+ **Props:**
87
+ | Prop | Type | Description |
88
+ |------|------|-------------|
89
+ | `label` | `ILocalizeString` | Trigger button label |
90
+ | `icon` | `string` | Trigger button icon |
91
+ | `name` | `string` | Component name |
92
+ | `items` | `MenuItem[]` | Menu items |
93
+ | `items[].label` | `ILocalizeString` | Item label |
94
+ | `items[].value` | `any` | Item key |
95
+ | `items[].disabled` | `string \| boolean` | Disable (template expression) |
96
+ | `items[].permission` | `string` | Permission gate — hidden if not granted |
97
+ | `items[].onClick` | `action[]` | Per-item action |
98
+ | `options.size` | `string` | Button size (default: `medium`) |
99
+ | `options.variant` | `string` | Button variant (default: `outlined`) |
100
+
101
+ **Events:** `onClick` (button level), `items[].onClick` (per item)
102
+
103
+ ```yaml
104
+ component: dropdown
105
+ name: actionsDropdown
106
+ props:
107
+ label: { en-US: "Actions" }
108
+ icon: activity
109
+ options:
110
+ variant: secondary
111
+ items:
112
+ - label: { en-US: "Export CSV" }
113
+ onClick:
114
+ - notification: { message: { en-US: "Exporting..." }, type: info }
115
+ - label: { en-US: "Import" }
116
+ permission: "Module/Import"
117
+ onClick:
118
+ - dialog:
119
+ component: Module/ImportDialog
120
+ - label: { en-US: "Archive All" }
121
+ disabled: "{{ eval selectedItems.length === 0 }}"
122
+ onClick:
123
+ - confirm: { title: { en-US: "Archive?" }, message: { en-US: "Archive selected items?" } }
124
+ ```
125
+
126
+ ---
127
+
128
+ ## menuButton
129
+
130
+ Collapsible menu with custom component children. Paper container with click toggle.
131
+
132
+ **Props:**
133
+ | Prop | Type | Description |
134
+ |------|------|-------------|
135
+ | `label` | `ILocalizeString` | Trigger element text |
136
+ | `options` | `object` | HTML attributes on trigger div |
137
+ | `options.allowCreate` | `boolean` | Show children with `create` in name |
138
+
139
+ **Children:** Yes — each child rendered as a MenuItem via ComponentRender. Children with `create` in name are gated by `allowCreate`.
140
+
141
+ ```yaml
142
+ component: menuButton
143
+ name: quickActions
144
+ props:
145
+ label: { en-US: "Quick Actions" }
146
+ options:
147
+ allowCreate: true
148
+ children:
149
+ - component: button
150
+ name: createOrder
151
+ props: { label: { en-US: "Create Order" }, icon: plus }
152
+ - component: button
153
+ name: viewReports
154
+ props: { label: { en-US: "View Reports" }, icon: bar-chart }
155
+ ```
156
+
157
+ ---
158
+
159
+ ## link
160
+
161
+ HTML anchor link with template-parsed URL and label.
162
+
163
+ **Props:**
164
+ | Prop | Type | Description |
165
+ |------|------|-------------|
166
+ | `label` | `string` | Display text (template-parsed, localized) |
167
+ | `to` | `string` | URL href (template-parsed) |
168
+ | `options` | `object` | Additional `<a>` attributes (target, rel, etc.) |
169
+
170
+ ```yaml
171
+ component: link
172
+ name: externalLink
173
+ props:
174
+ label: "View on External System"
175
+ to: "https://external.com/entity/{{ entityId }}"
176
+ options:
177
+ target: _blank
178
+ rel: noopener noreferrer
179
+ ```
180
+
181
+ ---
182
+
183
+ ## redirect
184
+
185
+ Programmatic navigation — renders nothing (or spinner with delay).
186
+
187
+ **Props:**
188
+ | Prop | Type | Default | Description |
189
+ |------|------|---------|-------------|
190
+ | `path` | `string` | — | **Required.** Destination (template-parsed) |
191
+ | `params` | `Record<string, any>` | — | Query parameters |
192
+ | `delay` | `number` | `0` | Delay in ms before redirect |
193
+ | `replace` | `boolean` | `false` | Use replace instead of push |
194
+ | `condition` | `any` | — | Template expression; skip redirect if falsy |
195
+
196
+ **Path types:**
197
+ - `http://...` / `https://...` — External redirect
198
+ - `~path` — System path (bypasses org prefix)
199
+ - `/path` — Org-relative path (prepends `/org/{orgId}/v2/`)
200
+
201
+ ```yaml
202
+ # Conditional redirect
203
+ component: redirect
204
+ name: createRedirect
205
+ props:
206
+ condition: "{{ eval !id }}"
207
+ path: "/orders/create"
208
+
209
+ # External redirect with delay
210
+ component: redirect
211
+ name: externalRedirect
212
+ props:
213
+ path: "https://external.com/order/{{ externalId }}"
214
+ delay: 2000
215
+ ```
216
+
217
+ ---
218
+
219
+ ## navbar
220
+
221
+ Vertical navigation menu with accordion submenus. Uses `@menu/vertical-menu`.
222
+
223
+ **Props:**
224
+ | Prop | Type | Description |
225
+ |------|------|-------------|
226
+ | `items` | `component[]` | Primary nav items (navbarItem, navbarLink, navDropdown) |
227
+ | `contextItems` | `component[]` | Additional nav items (bottom section) |
228
+
229
+ **Children:** No — uses `items` and `contextItems` props.
230
+
231
+ ---
232
+
233
+ ## navbarItem
234
+
235
+ Nav section with optional label header. Child of `navbar`.
236
+
237
+ **Props:**
238
+ | Prop | Type | Description |
239
+ |------|------|-------------|
240
+ | `label` | `ILocalizeString` | Section header label |
241
+
242
+ **Children:** Yes — rendered as section content.
243
+
244
+ ---
245
+
246
+ ## navbarLink
247
+
248
+ Single navigation link. Child of `navbarItem` or `navDropdown`.
249
+
250
+ **Props:**
251
+ | Prop | Type | Description |
252
+ |------|------|-------------|
253
+ | `to` | `string` | Target path (template-parsed, locale-prefixed) |
254
+ | `label` | `ILocalizeString` | Link display text |
255
+ | `icon` | `string` | Menu item icon |
256
+
257
+ **Events:** `onClick`
258
+
259
+ ---
260
+
261
+ ## navDropdown
262
+
263
+ Collapsible submenu. Child of `navbarItem`. Auto-expands if child path matches current URL.
264
+
265
+ **Props:**
266
+ | Prop | Type | Description |
267
+ |------|------|-------------|
268
+ | `label` | `ILocalizeString` | Submenu label |
269
+ | `icon` | `string` | Submenu icon |
270
+
271
+ **Children:** Yes — typically `navbarLink` items.
272
+
273
+ ```yaml
274
+ # Full navbar example
275
+ component: navbar
276
+ name: mainNav
277
+ props:
278
+ items:
279
+ - component: navbarItem
280
+ name: mainSection
281
+ props:
282
+ label: { en-US: "Main" }
283
+ children:
284
+ - component: navbarLink
285
+ name: dashboard
286
+ props:
287
+ to: "/dashboard"
288
+ label: { en-US: "Dashboard" }
289
+ icon: home
290
+ - component: navDropdown
291
+ name: ordersMenu
292
+ props:
293
+ label: { en-US: "Orders" }
294
+ icon: package
295
+ children:
296
+ - component: navbarLink
297
+ name: allOrders
298
+ props:
299
+ to: "/orders"
300
+ label: { en-US: "All Orders" }
301
+ - component: navbarLink
302
+ name: createOrder
303
+ props:
304
+ to: "/orders/create"
305
+ label: { en-US: "Create Order" }
306
+ ```