Skip to content

Shapes in the palette, and a radio kind - #176

Merged
lero62 merged 4 commits into
mainfrom
feat/json-form-shapes-and-radio
Oct 9, 2026
Merged

lero62 merged 4 commits into
mainfrom
feat/json-form-shapes-and-radio

Conversation

@lero62

@lero62 lero62 commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

Both come from one need: a consumer wanted its own link — a page, an anchor and an address, with the right field showing at the right moment — and could only get it by typing json in Code mode at every use.

shapes

A consumer registers ready-made subtrees by name:

jsonFormPlugin({
  shapes: { link: [radio('linkType', ['page', 'external']), select('page', PAGES), text('anchor')] },
})

They appear in the palette in a Custom group above the plain kinds. A pick drops the fields flat into the level you are standing in, under the names the schema declares — nothing to answer, no wrapper, and the keys the site reads are the same wherever an editor used it.

Each node carries shape: '<name>', which is the whole mechanism:

  • the builder draws a run of them inside one bordered block, with the shape's name and one delete for the set;
  • inside it nothing can be edited, reordered, removed or dragged — the schema owns the composition, and the block moves as one;
  • on read, the run is rebuilt from the schema in the config: the code stays the source of truth. Reorder a field there and it reorders everywhere it was used, with no migration; add one and it appears; remove one and it goes. Values follow their field by name.
  • a shape the config no longer offers takes its fields with it.

The rebuild runs where the shared-section one already runs, and works at any depth — inside groups, tabs and array rows.

/shared gains builders — text, select, radio, group, array and the rest — so a schema in a consumer's config reads as a schema rather than as literal nodes.

radio

A leaf kind holding value and options exactly as select does, drawn as buttons in a row. Two-option choices lose something real when flattened into a dropdown, and that is the shape most of these links take.

Worth knowing: @payloadcms/ui exports a standalone input for checkbox, select, text, textarea, relationship and upload — but not for radio. Only RadioGroupField is public, and it reads useField/useForm, so it cannot be driven by a value the builder holds. Its internal Radio is exactly the presentational component we need and is not exported. So the markup is reproduced here and rides on Payload's own classes — which means a rename on their side would move it silently. Worth a glance after a Payload upgrade.

Also

JsonNode.tsx had grown to 342 lines with the radio, the container branches and the whole input chain inlined. Split along the package's existing pattern — one component per file:

JsonNode    181  dispatcher
JsonLeaf    133  the input chain
JsonHolder   91  groups, tabs, collapsibles
JsonRadio    52  the new kind

Scope

No migration. A stored value only gains a radio node or a shape mark once someone adds one, and an unknown kind already keeps a field in Code mode rather than breaking it.

@vercel

vercel Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
ideal-cms Ready Ready Preview Oct 9, 2026 2:38am UTC

Request Review

@lero62
lero62 merged commit dbfc925 into main Oct 9, 2026
2 checks passed
@github-actions

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown

🎉 This PR is included in version 1.5.0 🎉

The release is available on npm package (@latest dist-tag)

Your semantic-release bot 📦🚀

This branch was successfully deployed

1 active deployment
Preview — a01a8770 Deployed Oct 9, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant