From 641f6d13d16d341f6476fab8ab0c2cafa00f3ca3 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 16 Oct 2026 14:50:49 +0000 Subject: [PATCH 1/3] feat(components): page regions lay blocks on a 12-column grid with kind-derived spans; the page description sits under its title A region's blocks take a span derived from their kind (metric tiles 3, charts 6, everything else 12), folded by breakpoint. When a titled page:header owns the page title, it draws the page description under the title instead of the page drawing it above. The Studio page designer's block card shows the derived span, read from the same table. Co-authored-by: Claude Claude-Session: https://claude.ai/code/session_01NvsorV5JuG3fpRuUjWRHir --- .../src/views/metadata-admin/i18n.ts | 2 + .../previews/PageBlockCanvas.tsx | 36 +++++++- packages/components/src/index.ts | 6 ++ .../src/renderers/layout/containers.tsx | 11 +++ .../src/renderers/layout/page-block-span.ts | 88 +++++++++++++++++++ .../layout/page-description-context.ts | 23 +++++ .../components/src/renderers/layout/page.tsx | 56 +++++++++--- 7 files changed, 206 insertions(+), 16 deletions(-) create mode 100644 packages/components/src/renderers/layout/page-block-span.ts create mode 100644 packages/components/src/renderers/layout/page-description-context.ts diff --git a/packages/app-shell/src/views/metadata-admin/i18n.ts b/packages/app-shell/src/views/metadata-admin/i18n.ts index fa6889ace7..d5590dfe33 100644 --- a/packages/app-shell/src/views/metadata-admin/i18n.ts +++ b/packages/app-shell/src/views/metadata-admin/i18n.ts @@ -1789,6 +1789,7 @@ const ENGINE_STRINGS_EN: Record = { 'Inherited from the default page — add a block to override this slot.', 'engine.pageBlockCanvas.regionEmpty': 'Empty region — drop a block here or use the add button below.', 'engine.pageBlockCanvas.select': 'Select {label}', + 'engine.pageBlockCanvas.span': 'Spans {span} of 12 columns on a wide screen', 'engine.pageBlockCanvas.groupEmpty': 'Empty — add a block.', 'engine.pageBlockCanvas.searchTypes': 'Search block type…', 'engine.pageBlockCanvas.noMatch': 'No matching types.', @@ -5220,6 +5221,7 @@ const ENGINE_STRINGS_ZH: Record = { 'engine.pageBlockCanvas.slotInherited': '继承自默认页面 —— 添加一个区块即可覆盖此插槽。', 'engine.pageBlockCanvas.regionEmpty': '空区域 —— 将区块拖放到这里,或使用下方的添加按钮。', 'engine.pageBlockCanvas.select': '选择 {label}', + 'engine.pageBlockCanvas.span': '宽屏下占 12 列中的 {span} 列', 'engine.pageBlockCanvas.groupEmpty': '空 —— 添加一个区块。', 'engine.pageBlockCanvas.searchTypes': '搜索区块类型…', 'engine.pageBlockCanvas.noMatch': '没有匹配的类型。', diff --git a/packages/app-shell/src/views/metadata-admin/previews/PageBlockCanvas.tsx b/packages/app-shell/src/views/metadata-admin/previews/PageBlockCanvas.tsx index b4c81421ca..e436a841ca 100644 --- a/packages/app-shell/src/views/metadata-admin/previews/PageBlockCanvas.tsx +++ b/packages/app-shell/src/views/metadata-admin/previews/PageBlockCanvas.tsx @@ -20,6 +20,9 @@ import { Badge, Button, cn, + isPageRailRegion, + pageBlockSpan, + type PageBlockSpan, Popover, PopoverContent, PopoverTrigger, @@ -491,6 +494,7 @@ export function PageBlockCanvas({ baseIdOf={(compIdx) => selectionId(shape, regionIdx, compIdx, region.name)} onSelectId={(sid, lbl) => onSelectionChange?.({ kind: 'block', id: sid, label: lbl })} onAddNested={addNestedBlock} + template={(draft as { template?: unknown }).template} locale={locale} /> ))} @@ -521,6 +525,7 @@ function RegionSection({ onRenameLabel, onSelectId, onAddNested, + template, locale, }: { region: Region; @@ -540,9 +545,16 @@ function RegionSection({ baseIdOf: (compIdx: number) => string; onSelectId: (id: string, label: string) => void; onAddNested: (baseId: string, pathSuffix: string, type: BlockTypeId) => void; + /** The page's `template`, which decides whether this region is a rail. */ + template?: unknown; locale?: string; }) { const comps = Array.isArray(region.components) ? region.components : []; + // The span each block takes in the page's region grid (objectui#12624), read + // from the page renderer's own table so the card cannot disagree with the + // page. Only a `regions` page lays its blocks on that grid: a `children` page + // stacks them, so its cards show no span. + const rail = isPageRailRegion(region.name, template); const [active, setActive] = React.useState(false); const handleDragOver = (e: React.DragEvent) => { @@ -606,6 +618,7 @@ function RegionSection({ onClick={() => onSelectBlock(compIdx, blk)} onMoveBlock={onMoveBlock} onRenameLabel={(v) => onRenameLabel(regionIdx, compIdx, v)} + span={shape === 'regions' ? pageBlockSpan(blk.type, rail) : undefined} locale={locale} /> void; onRenameLabel: (nextLabel: string) => void; + /** The block's derived span in the region grid; read-only, nothing edits it. */ + span?: PageBlockSpan; locale?: string; }) { const typeStr = String(block.type ?? ''); + const spanBadge = span !== undefined && ( + + {span}/12 + + ); // DISPLAY meta, not the palette catalogue. `BLOCK_TYPE_META` answers "what may // an author drag in"; a node already in the page may be a spelling the palette // does not offer yet the app renders fine — the `record:discussion` / @@ -792,9 +818,12 @@ function BlockRow({ #{block.id} )} - - {typeStr} - +
+ {spanBadge} + + {typeStr} + +
) : ( <> @@ -819,6 +848,7 @@ function BlockRow({ )} > {draggable &&