scaffolder: merge description and type into a single value column

Combine the description and type columns into a single "Value" column
that shows the type, description, and nested content inline. This
avoids the broken layout where expanded rows were rendered as siblings
below the table, decoupled from their row context.

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
Made-with: Cursor
This commit is contained in:
Patrik Oldsberg
2026-03-28 19:20:55 +01:00
parent 7936e5c917
commit 7daa71fdc6
5 changed files with 125 additions and 147 deletions
+1
View File
@@ -806,6 +806,7 @@ export const scaffolderTranslationRef: TranslationRef<
readonly 'renderSchema.undefined': 'No schema defined';
readonly 'renderSchema.tableCell.name': 'Name';
readonly 'renderSchema.tableCell.type': 'Type';
readonly 'renderSchema.tableCell.value': 'Value';
readonly 'renderSchema.tableCell.title': 'Title';
readonly 'renderSchema.tableCell.description': 'Description';
readonly 'templatingExtensions.content.values.title': 'Values';
+1
View File
@@ -708,6 +708,7 @@ export const scaffolderTranslationRef: TranslationRef<
readonly 'renderSchema.undefined': 'No schema defined';
readonly 'renderSchema.tableCell.name': 'Name';
readonly 'renderSchema.tableCell.type': 'Type';
readonly 'renderSchema.tableCell.value': 'Value';
readonly 'renderSchema.tableCell.title': 'Title';
readonly 'renderSchema.tableCell.description': 'Description';
readonly 'templatingExtensions.content.values.title': 'Values';
@@ -146,13 +146,10 @@ describe('JSON schema UI rendering', () => {
const allCells = [...headerCells, ...dataCells];
const cellTexts = allCells.map(c => c.textContent);
expect(cellTexts).toEqual(
expect.arrayContaining([
expect.stringContaining(p),
expect.stringContaining(`the ${pt}`),
expect.stringContaining(String(pt)),
]),
);
expect(cellTexts[0]).toContain(p);
const valueCell = cellTexts[1];
expect(valueCell).toContain(String(pt));
expect(valueCell).toContain(`the ${pt}`);
if (basic.required?.includes(p)) {
expect(cellTexts[0]).toContain('*');
@@ -257,19 +257,12 @@ export const RenderSchema = ({
elements = [{ schema }];
columns = [
{
key: 'type',
title: t('renderSchema.tableCell.type'),
render: renderTypeCell,
key: 'value',
title: t('renderSchema.tableCell.value'),
render: renderValueCell,
width: '3fr',
},
];
if (schema.description) {
columns.unshift({
key: 'description',
title: t('renderSchema.tableCell.description'),
render: renderDescriptionCell,
width: '3fr',
});
}
}
} else if (schema.properties) {
columns = [
@@ -279,16 +272,11 @@ export const RenderSchema = ({
render: renderNameCell,
},
{
key: 'description',
title: t('renderSchema.tableCell.description'),
render: renderDescriptionCell,
key: 'value',
title: t('renderSchema.tableCell.value'),
render: renderValueCell,
width: '3fr',
},
{
key: 'type',
title: t('renderSchema.tableCell.type'),
render: renderTypeCell,
},
];
elements = Object.entries(schema.properties!).map(([key, v]) => ({
schema: v,
@@ -303,89 +291,35 @@ export const RenderSchema = ({
return (
<Flex direction="column" gap="2">
{columns && elements && (
<>
<TableRoot
data-testid={`${strategy}_${context.parentId}`}
aria-label={`${strategy} schema for ${context.parentId}`}
>
<TableHeader>
{columns.map((col, index) => (
<Column
key={col.key}
id={col.key}
isRowHeader={index === 0}
defaultWidth={col.width ?? undefined}
>
{col.title}
</Column>
))}
</TableHeader>
<TableBody>
{elements.map(el => (
<Row
key={generateId(el, context)}
id={`${strategy}-row_${generateId(el, context)}`}
data-testid={`${strategy}-row_${generateId(el, context)}`}
columns={columns}
>
{col => col.render(el, context)}
</Row>
))}
</TableBody>
</TableRoot>
{elements.map(el => {
const id = generateId(el, context);
const info = inspectSchema(el.schema);
const hasDetails =
typeof el.schema !== 'boolean' &&
(info.canSubschema || info.hasEnum);
const s =
typeof el.schema !== 'boolean'
? (el.schema as JSONSchema7)
: undefined;
const isOpen =
hasDetails && s && (!getTypes(s) || isExpanded[id]);
if (!isOpen) {
return null;
}
return (
<div
key={id}
data-testid={`expansion_${id}`}
style={{ paddingLeft: 16 }}
<TableRoot
data-testid={`${strategy}_${context.parentId}`}
aria-label={`${strategy} schema for ${context.parentId}`}
>
<TableHeader>
{columns.map((col, index) => (
<Column
key={col.key}
id={col.key}
isRowHeader={index === 0}
defaultWidth={col.width ?? undefined}
>
{info.canSubschema && (
<RenderSchema
strategy="properties"
context={{
...context,
parentId: id,
parent: context,
}}
schema={
s!.type === 'array'
? (s!.items as JSONSchema7 | undefined)
: s
}
/>
)}
{info.hasEnum && (
<>
<Text as="h4" variant="title-small" weight="bold">
Valid values:
</Text>
<RenderEnum
data-testid={`enum_${id}`}
e={enumFrom(s!)!}
/>
</>
)}
</div>
);
})}
</>
{col.title}
</Column>
))}
</TableHeader>
<TableBody>
{elements.map(el => (
<Row
key={generateId(el, context)}
id={`${strategy}-row_${generateId(el, context)}`}
data-testid={`${strategy}-row_${generateId(el, context)}`}
columns={columns}
>
{col => col.render(el, context)}
</Row>
))}
</TableBody>
</TableRoot>
)}
{(Object.keys(subschemas) as Array<keyof subSchemasType>).map(sk => {
const subId = `${context.parentId}_${sk}`;
@@ -433,6 +367,55 @@ export const RenderSchema = ({
return result ?? <Text as="p">No schema defined</Text>;
};
function RenderExpansion({
element,
context,
}: {
element: SchemaRenderElement;
context: SchemaRenderContext;
}) {
const id = generateId(element, context);
const info = inspectSchema(element.schema);
const hasDetails =
typeof element.schema !== 'boolean' && (info.canSubschema || info.hasEnum);
const s =
typeof element.schema !== 'boolean'
? (element.schema as JSONSchema7)
: undefined;
const [isExpanded] = context.expanded;
const isOpen = hasDetails && s && (!getTypes(s) || isExpanded[id]);
if (!isOpen) {
return null;
}
return (
<div data-testid={`expansion_${id}`} style={{ paddingLeft: 16 }}>
{info.canSubschema && (
<RenderSchema
strategy="properties"
context={{
...context,
parentId: id,
parent: context,
}}
schema={
s!.type === 'array' ? (s!.items as JSONSchema7 | undefined) : s
}
/>
)}
{info.hasEnum && (
<>
<Text as="h4" variant="title-small" weight="bold">
Valid values:
</Text>
<RenderEnum data-testid={`enum_${id}`} e={enumFrom(s!)!} />
</>
)}
</div>
);
}
function renderNameCell(
element: SchemaRenderElement,
_context: SchemaRenderContext,
@@ -441,17 +424,7 @@ function renderNameCell(
return <CellText title={element.required ? `${name} *` : name} />;
}
function renderDescriptionCell(element: SchemaRenderElement) {
return (
<Cell>
<MarkdownContent
content={(element.schema as JSONSchema7).description ?? ''}
/>
</Cell>
);
}
function renderTypeCell(
function renderValueCell(
element: SchemaRenderElement,
context: SchemaRenderContext,
) {
@@ -462,36 +435,41 @@ function renderTypeCell(
const [isExpanded, setIsExpanded] = context.expanded;
const id = generateId(element, context);
const info = inspectSchema(element.schema);
const description = element.schema.description;
return (
<Cell>
<Flex gap="1" align="center">
{types?.map((type, index) =>
(info.canSubschema || info.hasEnum) && index === 0 ? (
<Button
key={type}
data-testid={`expand_${id}`}
variant="tertiary"
size="small"
onPress={() =>
setIsExpanded(prevState => ({
...prevState,
[id]: !prevState[id],
}))
}
>
{isExpanded[id] ? '▴' : '▾'} {type}
</Button>
) : (
<Text
key={type}
as="span"
variant="body-small"
style={{ fontFamily: 'monospace' }}
>
{type}
</Text>
),
)}
<Flex direction="column" gap="1">
<Flex gap="1" align="center">
{types?.map((type, index) =>
(info.canSubschema || info.hasEnum) && index === 0 ? (
<Button
key={type}
data-testid={`expand_${id}`}
variant="tertiary"
size="small"
onPress={() =>
setIsExpanded(prevState => ({
...prevState,
[id]: !prevState[id],
}))
}
>
{isExpanded[id] ? '▴' : '▾'} {type}
</Button>
) : (
<Text
key={type}
as="span"
variant="body-small"
style={{ fontFamily: 'monospace' }}
>
{type}
</Text>
),
)}
</Flex>
{description && <MarkdownContent content={description} />}
<RenderExpansion element={element} context={context} />
</Flex>
</Cell>
);
+1
View File
@@ -203,6 +203,7 @@ export const scaffolderTranslationRef = createTranslationRef({
title: 'Title',
description: 'Description',
type: 'Type',
value: 'Value',
},
undefined: 'No schema defined',
},