Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .changeset/formview-presentation-props.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"@objectstack/spec": minor
---

Complete the FormView protocol with the form-presentation options the ObjectForm
component already accepts (conformance follow-up). FormViewSchema gains optional
`layout`, `columns`, `title`, `description`, `defaultTab`, `tabPosition`,
`allowSkip`, `showStepIndicator`, `splitDirection`, `splitSize`, `splitResizable`,
`drawerSide`, `drawerWidth`, `modalSize` — the per-`type` (tabbed/wizard/split/
drawer/modal) presentation config. The spec↔frontend conformance check went from
14 frontend-only → 0 for object-form; the react-tier contract now sources these
from the spec (with descriptions) instead of a hand-authored overlay.
2 changes: 1 addition & 1 deletion packages/spec/src/ui/react-blocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export const REACT_BLOCKS: ReactBlockDef[] = [
schemaType: 'object-form',
summary: "Server-connected create/edit/view form for one object. Config props come from the spec FormView schema; bind + wire it with the React props below.",
schema: FormViewSchema,
dataProps: ['sections', 'subforms', 'submitBehavior', 'defaultSort'],
dataProps: ['layout', 'columns', 'tabPosition', 'drawerSide', 'drawerWidth', 'modalSize', 'splitDirection', 'sections', 'subforms', 'submitBehavior'],
interactions: [
OBJECT_NAME,
{ name: 'mode', type: "'create' | 'edit' | 'view'", kind: 'controlled', description: 'Create a new record, or edit/view an existing one — drive from React state.' },
Expand Down
27 changes: 26 additions & 1 deletion packages/spec/src/ui/view.zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -801,7 +801,32 @@ export const FormViewSchema = lazySchema(() => z.object({
'drawer', // Side panel
'modal' // Dialog
]).default('simple'),


// --- Presentation options (per `type` variant). These mirror what the
// ObjectForm component accepts so the protocol declares them; all optional. ---
/** Field layout within the form body. */
layout: z.enum(['vertical', 'horizontal', 'inline', 'grid']).optional().describe('Field layout direction'),
/** Number of columns for the form body (grid/multi-column layouts). */
columns: z.number().int().min(1).optional().describe('Number of columns for the form body'),
/** Optional form title / description (for embedded or standalone forms). */
title: z.string().optional().describe('Form title'),
description: z.string().optional().describe('Form description'),
/** Tabbed (`type: 'tabbed'`). */
defaultTab: z.string().optional().describe('Initially active tab (tabbed forms)'),
tabPosition: z.enum(['top', 'bottom', 'left', 'right']).optional().describe('Tab strip position (tabbed forms)'),
/** Wizard (`type: 'wizard'`). */
allowSkip: z.boolean().optional().describe('Allow skipping steps (wizard forms)'),
showStepIndicator: z.boolean().optional().describe('Show the step indicator (wizard forms)'),
/** Split (`type: 'split'`). */
splitDirection: z.enum(['horizontal', 'vertical']).optional().describe('Split orientation (split forms)'),
splitSize: z.number().optional().describe('Primary split panel size, % (split forms)'),
splitResizable: z.boolean().optional().describe('Whether the split is resizable (split forms)'),
/** Drawer (`type: 'drawer'`). */
drawerSide: z.enum(['top', 'bottom', 'left', 'right']).optional().describe('Drawer side (drawer forms)'),
drawerWidth: z.string().optional().describe('Drawer width, e.g. "480px" (drawer forms)'),
/** Modal (`type: 'modal'`). */
modalSize: z.enum(['sm', 'default', 'lg', 'xl', 'full']).optional().describe('Modal size (modal forms)'),

/** Data Source Configuration */
data: ViewDataSchema.optional().describe('Data source configuration (defaults to "object" provider)'),

Expand Down
56 changes: 49 additions & 7 deletions skills/objectstack-ui/contracts/react-blocks.contract.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,55 @@
"required": false,
"description": "Custom persistence instead of the default create/update."
},
{
"name": "layout",
"type": "'vertical' | 'horizontal' | 'inline' | 'grid'",
"kind": "data",
"required": false,
"description": "Field layout direction"
},
{
"name": "columns",
"type": "integer",
"kind": "data",
"required": false,
"description": "Number of columns for the form body"
},
{
"name": "tabPosition",
"type": "'top' | 'bottom' | 'left' | 'right'",
"kind": "data",
"required": false,
"description": "Tab strip position (tabbed forms)"
},
{
"name": "drawerSide",
"type": "'top' | 'bottom' | 'left' | 'right'",
"kind": "data",
"required": false,
"description": "Drawer side (drawer forms)"
},
{
"name": "drawerWidth",
"type": "string",
"kind": "data",
"required": false,
"description": "Drawer width, e.g. \"480px\" (drawer forms)"
},
{
"name": "modalSize",
"type": "'sm' | 'default' | 'lg' | 'xl' | 'full'",
"kind": "data",
"required": false,
"description": "Modal size (modal forms)"
},
{
"name": "splitDirection",
"type": "'horizontal' | 'vertical'",
"kind": "data",
"required": false,
"description": "Split orientation (split forms)"
},
{
"name": "sections",
"type": "object[]",
Expand All @@ -100,13 +149,6 @@
"kind": "data",
"required": false,
"description": "Post-submit behavior"
},
{
"name": "defaultSort",
"type": "object[]",
"kind": "data",
"required": false,
"description": "Default sort order for related list views within this form"
}
]
},
Expand Down
8 changes: 7 additions & 1 deletion skills/objectstack-ui/references/react-blocks.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,16 @@ Server-connected create/edit/view form for one object. Config props come from th
| `onError` | `(error: Error) => void` | callback | | Called when the save fails. |
| `onCancel` | `() => void` | callback | | Called when the user cancels. |
| `submitHandler` | `(values) => any \| Promise<any>` | callback | | Custom persistence instead of the default create/update. |
| `layout` | `'vertical' \| 'horizontal' \| 'inline' \| 'grid'` | data | | Field layout direction |
| `columns` | `integer` | data | | Number of columns for the form body |
| `tabPosition` | `'top' \| 'bottom' \| 'left' \| 'right'` | data | | Tab strip position (tabbed forms) |
| `drawerSide` | `'top' \| 'bottom' \| 'left' \| 'right'` | data | | Drawer side (drawer forms) |
| `drawerWidth` | `string` | data | | Drawer width, e.g. "480px" (drawer forms) |
| `modalSize` | `'sm' \| 'default' \| 'lg' \| 'xl' \| 'full'` | data | | Modal size (modal forms) |
| `splitDirection` | `'horizontal' \| 'vertical'` | data | | Split orientation (split forms) |
| `sections` | `object[]` | data | | |
| `subforms` | `object[]` | data | | Inline master-detail child collections |
| `submitBehavior` | `object` | data | | Post-submit behavior |
| `defaultSort` | `object[]` | data | | Default sort order for related list views within this form |

## `<ListView>` — `list-view`

Expand Down