diff --git a/.changeset/page-element-blocks-inputs.md b/.changeset/page-element-blocks-inputs.md new file mode 100644 index 000000000..dd1bf39c2 --- /dev/null +++ b/.changeset/page-element-blocks-inputs.md @@ -0,0 +1,34 @@ +--- +"@object-ui/components": patch +--- + +feat(page,element): declare inputs for the eight configurable page:\* / element:\* blocks + +Same gap objectui#3027 closed for `record:*`, in the two namespaces next door: +renderers that read real props while every authoring surface reported "takes no +configuration". Declarations mirror what each renderer reads — + +| block | inputs | +|---|---| +| `page:tabs` | items, tabStyle (line/card/pill), position (top/left) | +| `page:card` | title, bordered, body (slot), footer (slot) | +| `page:accordion` | items, allowMultiple, variant (flush/card) | +| `element:text` | content, variant (heading/subheading/body/caption), align | +| `element:number` | object, aggregate (count/sum/avg/min/max), field, filter, format, prefix, suffix | +| `element:button` | label, action (inline ActionDef), variant, size, icon, iconPosition, disabled | +| `element:definition-list` | items, columns, inline | +| `element:repeater` | object, titleField, fields, filter, sort, limit, emptyText, divided | + +`page:section`, `page:footer` and `page:sidebar` are left at zero inputs on +purpose: their renderers render `children`/`body` and nothing else — like +`element:divider`, they are genuinely prop-less containers, and inventing +inputs for them would be the opposite falsehood. + +`aria` and `className` stay undeclared throughout, per the convention on +`record:details`: escape hatches and styling pass-throughs, not authoring +choices. `element:button`'s registration also documents the split against +`action:button` — inline ActionDef vs a declared action referenced by name — +so the two stop reading as duplicates. + +Declaration only; no renderer behavior changes. Curation (which of these join +`PUBLIC_BLOCKS`) is a separate change. diff --git a/packages/components/src/renderers/basic/data-list.tsx b/packages/components/src/renderers/basic/data-list.tsx index 02d6a608c..d6c56c49f 100644 --- a/packages/components/src/renderers/basic/data-list.tsx +++ b/packages/components/src/renderers/basic/data-list.tsx @@ -82,6 +82,11 @@ ComponentRegistry.register('definition-list', DefinitionListRenderer, { skipFallback: true, label: 'Definition List', category: 'content', + inputs: [ + { name: 'items', type: 'array', label: 'Items', required: true, description: 'Term/description pairs [{ term, description }]' }, + { name: 'columns', type: 'enum', label: 'Columns', enum: ['1', '2'], defaultValue: '1' }, + { name: 'inline', type: 'boolean', label: 'Inline', description: 'Term and description on one baseline-aligned row' }, + ], }); // --------------------------------------------------------------------------- @@ -178,4 +183,14 @@ ComponentRegistry.register('repeater', RepeaterRenderer, { skipFallback: true, label: 'Repeater', category: 'content', + inputs: [ + { name: 'object', type: 'string', label: 'Object', required: true, description: 'Object whose records the list repeats over' }, + { name: 'titleField', type: 'string', label: 'Title Field' }, + { name: 'fields', type: 'array', label: 'Fields', description: 'Columns per row — bare names or { field, label? }' }, + { name: 'filter', type: 'array', label: 'Filter' }, + { name: 'sort', type: 'array', label: 'Sort' }, + { name: 'limit', type: 'number', label: 'Limit' }, + { name: 'emptyText', type: 'string', label: 'Empty Text' }, + { name: 'divided', type: 'boolean', label: 'Divided', description: 'Separator between rows' }, + ], }); diff --git a/packages/components/src/renderers/basic/elements.tsx b/packages/components/src/renderers/basic/elements.tsx index a0d40f14a..7ee38ebd0 100644 --- a/packages/components/src/renderers/basic/elements.tsx +++ b/packages/components/src/renderers/basic/elements.tsx @@ -97,6 +97,11 @@ ComponentRegistry.register('text', ElementTextRenderer, { skipFallback: true, label: 'Text', category: 'content', + inputs: [ + { name: 'content', type: 'string', label: 'Content', required: true, description: 'Accepts an inline translation map ({ en, "zh-CN", … })' }, + { name: 'variant', type: 'enum', label: 'Variant', enum: ['heading', 'subheading', 'body', 'caption'], defaultValue: 'body' }, + { name: 'align', type: 'enum', label: 'Align', enum: ['left', 'center', 'right'], defaultValue: 'left' }, + ], }); // --------------------------------------------------------------------------- @@ -268,6 +273,18 @@ ComponentRegistry.register('button', ElementButtonRenderer, { skipFallback: true, label: 'Button', category: 'action', + // Distinct from `action:button`, which references a DECLARED action by name + // and gates on visible/enabled predicates. This one carries an inline + // ActionDef — the standalone-page button. + inputs: [ + { name: 'label', type: 'string', label: 'Label', required: true, description: 'Accepts an inline translation map ({ en, "zh-CN", … })' }, + { name: 'action', type: 'object', label: 'Action', description: 'Inline ActionDef executed on click (url / navigation / api / script / modal / flow); omitted → renders inert' }, + { name: 'variant', type: 'enum', label: 'Variant', enum: ['primary', 'secondary', 'danger', 'ghost', 'link'], defaultValue: 'primary' }, + { name: 'size', type: 'enum', label: 'Size', enum: ['small', 'medium', 'large'], defaultValue: 'medium' }, + { name: 'icon', type: 'string', label: 'Icon', description: 'Lucide icon name' }, + { name: 'iconPosition', type: 'enum', label: 'Icon Position', enum: ['left', 'right'], defaultValue: 'left' }, + { name: 'disabled', type: 'boolean', label: 'Disabled' }, + ], }); // --------------------------------------------------------------------------- @@ -390,4 +407,13 @@ ComponentRegistry.register('number', ElementNumberRenderer, { skipFallback: true, label: 'Number', category: 'content', + inputs: [ + { name: 'object', type: 'string', label: 'Object', required: true, description: 'Object the aggregate runs over' }, + { name: 'aggregate', type: 'enum', label: 'Aggregate', enum: ['count', 'sum', 'avg', 'min', 'max'], required: true }, + { name: 'field', type: 'string', label: 'Field', description: 'Measure field (required for every aggregate except count)' }, + { name: 'filter', type: 'array', label: 'Filter' }, + { name: 'format', type: 'enum', label: 'Format', enum: ['number', 'currency', 'percent'], defaultValue: 'number' }, + { name: 'prefix', type: 'string', label: 'Prefix' }, + { name: 'suffix', type: 'string', label: 'Suffix' }, + ], }); diff --git a/packages/components/src/renderers/layout/containers.tsx b/packages/components/src/renderers/layout/containers.tsx index 8f154a051..b1c033b5b 100644 --- a/packages/components/src/renderers/layout/containers.tsx +++ b/packages/components/src/renderers/layout/containers.tsx @@ -559,6 +559,11 @@ ComponentRegistry.register('tabs', PageTabsRenderer, { label: 'Page Tabs', category: 'layout', isContainer: true, + inputs: [ + { name: 'items', type: 'array', label: 'Tabs', required: true, description: 'Tab definitions [{ label, value?, icon?, count?, visibleWhen?, children }] — value is the stable ?tab= URL token, count auto-derives from record:related_list descendants when omitted' }, + { name: 'tabStyle', type: 'enum', label: 'Style', enum: ['line', 'card', 'pill'], defaultValue: 'line' }, + { name: 'position', type: 'enum', label: 'Position', enum: ['top', 'left'], defaultValue: 'top' }, + ], }); // --------------------------------------------------------------------------- @@ -602,6 +607,12 @@ ComponentRegistry.register('card', PageCardRenderer, { label: 'Page Card', category: 'layout', isContainer: true, + inputs: [ + { name: 'title', type: 'string', label: 'Title', description: 'Accepts an inline translation map ({ en, "zh-CN", … })' }, + { name: 'bordered', type: 'boolean', label: 'Bordered', defaultValue: true }, + { name: 'body', type: 'slot', label: 'Body', description: 'Card content; plain `children` also render here' }, + { name: 'footer', type: 'slot', label: 'Footer' }, + ], }); // --------------------------------------------------------------------------- @@ -683,6 +694,11 @@ ComponentRegistry.register('accordion', PageAccordionRenderer, { label: 'Page Accordion', category: 'layout', isContainer: true, + inputs: [ + { name: 'items', type: 'array', label: 'Panels', required: true, description: 'Panel definitions [{ label, collapsed?, children }] — collapsed: false opens a panel by default' }, + { name: 'allowMultiple', type: 'boolean', label: 'Allow Multiple Open', defaultValue: false }, + { name: 'variant', type: 'enum', label: 'Variant', enum: ['flush', 'card'], defaultValue: 'flush' }, + ], }); // ---------------------------------------------------------------------------