Skip to content

feature/design-tokens-module - #997

Draft
d4mation wants to merge 1864 commits into
masterfrom
feature/design-tokens-module
Draft

feature/design-tokens-module#997
d4mation wants to merge 1864 commits into
masterfrom
feature/design-tokens-module

Conversation

@d4mation

@d4mation d4mation commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

🎫 https://linear.app/nexcess/project/design-system-7186b33eeebd/overview

...

Also relies on stellarwp/kadence-helpers#7 and stellarwp/kadence-components#20

Checklist

  • I have performed a self-review.
  • No unrelated files are modified.
  • No debugging statements exist (Ex: console.log, error_log).
  • There are no warnings or notices in the wordpress error log.
  • Passes all tests (linting, acceptance, & unit)

Block specific checklist (where relevant)

  • Tested with an existing instance of this block .
  • Tested creating a new instance of this block.
  • Tested with Dynamic content & Elements.

@github-actions

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

🎉 Zip build complete

Build timestamp

@coderabbitai

coderabbitai Bot commented Aug 14, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Comment @coderabbitai help to get the list of available commands.

d4mation and others added 27 commits August 26, 2026 17:30
SOFT-4195 [4/4]: mount the token picker on Icon Size
…-update

[DTM] SOFT-4212: compare metadata saves against the client's version
The stack's fifteen PRs merged bottom-up into each other rather than into this branch: the
bottom one was based on SOFT-4212/metadata-write-lost-update, which had already merged here,
so the chain accumulated on that branch instead of arriving. This brings the whole chain
across in one commit.
…tensions-sections

[DTM] SOFT-4211: reject a non-array $extensions metadata section
isBackedToken() failed open whenever the active library's resolved value map
had no keys, which conflated "nothing localized" with "the library resolved
and backs nothing". The PHP renderer's token_alias_is_backed() only fails open
when resolution itself fails, so an empty-but-successful id map would have had
PHP treat every alias as unbacked while JS treated every alias as backed.

resolvedValues() now returns null when there is no map to consult -- the
pickable pool is absent, or the active library is missing from it (the catalog
skips a library whose stored document cannot be resolved, the same corruption
PHP catches as a `\Throwable`) -- and isBackedToken() fails open only on null.
A localized library that resolves to an empty map is an answer, so every alias
reads as unbacked there, in both languages alike.
… blocks

Adds `supports.kbPreset` to Advanced Image, Row Layout, Section, Single Icon and Advanced Text,
which is what makes `early-filters.js` inject the `kbPreset` attribute, append the
`kb-preset--<slug>` class to saved markup and mirror it in the canvas.

Declared as a bare support with no matching block.json attribute, exactly as `kbPalette` already is
on these same five blocks — the attribute comes from the registerBlockType filter.

Existing content stays valid: the save filter appends nothing while `kbPreset` is empty, so saved
markup is unchanged for every block already on a page. The inspector panel stays hidden too — it
renders only for a block with at least one preset option, and these have none until their
declarations gain a picker label.
…set in pixels

A control that stores a raw number with no companion unit attribute — `kadence/single-icon`'s
`size`, written straight into the SVG's geometry attributes — has its default seeded from the token
by PHP's `Converts_Number_To_Px`. So a never-touched icon holds `24` while its preset resolves to
`1.5rem`. Compared as strings those disagree, and the control reported as overridden before anyone
had touched it.

Fall back to a pixel comparison when the stored value carries NO unit and the preset literal does,
converting through the same table the seeding used. A control that does carry a unit attribute keeps
the strict same-unit compare, so a `rem` value against a `px` preset still reads as overridden
rather than being converted into agreement.

The conversion moves to `token-controls/helpers/px-from-length`, beside `parse-css-length`: the
editor's `token-px` wraps it with alias resolution against the active library, and this compare
needs the bare conversion with no library lookup. One table, and `kinds/dimension` does not have to
import the picker's dependencies to reach it.

Both scalar and per-corner compares now route through one `lengthMatches()`.
The first preset screen after the Button, and the pattern for the three that follow. Per the Style
Library README's recipe it is one config plus a binding: `presets/single-icon-preset.js` describes
the block (surface, preview, schema) and `SingleIconScreen`/`SingleIconSettings` bind it to the
generic `PresetScreen`/`PresetSidebar`. Nothing generic changed.

Declaring `label` and `style_library` on the icon's bindings is what turns it on, adding the Style
Library nav entry and the editor's preset picker now that both have somewhere to go.

Two shape notes. The icon binds no hover property, so the config declares no tabs and the sidebar
renders its field area bare. Size uses `token-select` so a preset stores a token id and keeps
following the Icon Sizes scale, which also means it carries no breakpoint switcher — one size for
every device — while the binding keeps its `responsive_attrs` so a per-breakpoint override stays
expressible once a responsive token picker exists.

The row preview draws a neutral glyph rather than a real Kadence icon: a preset skins whichever icon
a block uses, so previewing a specific one would imply the preset selects it.
…ze and color

Three separate faults, all only reachable once the icon had presets to select.

`resetAttrPatch` cleared every dimension to a measure control's 4-side `['', '', '', '']` array. The
icon's `size` is a single number written into the SVG's geometry attributes, so selecting a preset
stored an array in a scalar attribute and the control read it back as a custom value. The shape now
comes from the block's own attribute schema, passed in by the one caller that has it, and the unit
and per-device companions are written only where the block declares them.

The editor preview resolved a cleared size to the BINDING's token rather than the selected preset's
value, so every preset showed the same size in the canvas while the front end showed each preset's
own — the front end reads `--kb-icon-size`, which the preset's scoped rule sets, ahead of the token.
The preview and the size control's inherited-value display now resolve the preset first, then the
token.

Color never reached the canvas at all. The front end paints a cleared color through the
block-default rule's `var(--kb-icon-color, ...)` chain on the `.kb-svg-icon-wrap` span PHP hydrates;
the editor renders `GenIcon`'s own div, which carries no such class, so that rule matches nothing
there. The preview resolves the preset's color and paints it inline, the same split the size has
always used.
The icon's size field used `token-select`, a bare picker with nowhere to put a breakpoint switcher, so
a preset could only ever give one size to every device. The block's own control is per-device — it
stores `size`/`tabletSize`/`mobileSize`, all three declared on the binding — so a preset could not
reproduce a look a site owner had already built with that control.

Add `token-scalar`: the scalar sibling of `BoxTokenField`, wrapping `ScalarControl` (which carries the
switcher) with the same responsive envelope, the same breakpoint inheritance, the same picker
narrowing, and the same stored/control conversion reused rather than restated. What it drops is
everything that only makes sense for four slots — the link toggle, the per-slot mapping, the slot-list
unit sniffing.

Nothing else was needed for it to work end to end: the responsive envelope, the per-breakpoint
projection, and the editor's device-aware preset read are all generic, so a scalar dimension on a
block other than the Button travels the whole path already. Covered by a wpunit case asserting an icon
preset's mobile size lands in its own media block.

`token-select` stays as the non-responsive single picker; the two now sit side by side in the demo
schema, which is contracted to exercise every registered type.
"default" is the literal the block's `/default` sub-route uses, so the reserved-slug guard refused any
write naming it. That was meant to stop someone MINTING a preset the per-preset item route could never
address — but it ran over every write, and "default" is not hypothetical: every block but the Button
ships its baseline look under exactly that slug. A site owner editing it got a 422, which on the Icon
screen meant the one preset the block has was permanently read-only.

Scope the guard to creation: a slug already present in the block's effective presets is writable, a new
one under a reserved slug is still refused. The only route this forecloses for the shipped preset is
DELETE, which it should refuse anyway — it is the block's built-in look, and the editor offers deletion
only for user-created presets.
…paints a preset value

The editor preset catalog carried only flattened literals, which is right for deciding
bound-vs-overridden — a control cannot compare itself against a `var()` chain. But an editor render
path that has to APPLY a preset value itself, rather than let a stylesheet do it, needs the opposite:
the Icon's preview paints its color inline, and a literal had already been flattened against the
default palette upstream, so an icon on an alternate per-block palette rendered the default palette's
color.

Carry the CSS references alongside the literals — the same `var()` chains the projected CSS uses — and
paint with those. The chain resolves through the projector's `[data-kb-palette]` layer, and the editor
already mirrors the block's selected palette onto its wrapper, so the icon now follows whichever
palette the block is on, exactly as the front end does.

Both maps stay: they answer different questions, and neither substitutes for the other.
…ttr so usePresetBinding can track the border indicator
The Padding and Margin sidebar fields had no "Default" hint to show, because
the shipped Primary/Secondary presets never set button-padding/button-margin
at all (unlike button-radius). The field rendered blank and visually shrank.

The button's own rendered CSS was never affected by this - a separate,
pre-existing mechanism (semantic.spacing.button-padding-top/right/bottom/left
and the button-margin equivalents) already governs the real default padding
and margin. This fix only wires the preset-property system (what the sidebar
control reads) to the same values.

Preset_Resolver already fully supports a per-corner (4-value array) preset
property value - flatten_slots()/project() are already built and unit-tested
for it, just never exercised by a real preset until now. No JS or resolver
code changed; only the baseline preset data and two test fixtures that
enumerated the old (padding/margin-less) property set.
pauloiankoski and others added 30 commits August 31, 2026 13:16
…-shadow-binding

[DTM] SOFT-4266 [6/6]: bring the image block's shadow up to the same binding and defaults
The Button was the only block whose baseline presets were named Primary and
Secondary. Secondary is dropped, Primary becomes Default, and `$default`
follows, so all six blocks now ship a single preset slugged `default`.

The four `semantic.color.button-secondary-*` tokens existed only to feed the
Secondary map, so they go with it rather than shipping as dead entries in the
token picker. The four that remain lose their now-meaningless `primary`
segment: `semantic.color.button-primary-bg` becomes `semantic.color.button-bg`,
and likewise for text, bg-hover and text-hover, matching the sibling naming
already used by column-bg, heading-bg and image-bg.

One behavior follows from the rename. `default` is reserved against the
*creation* of a preset the per-preset item route could never address, not
against writing a slug the block already ships. The Button was the sole block
shipping no `default`, so it was the only place that write was refused; it now
merges onto the shipped preset like the other five.
Removing `testCreatingAPresetNamedDefaultIsRejected` took out everything
between it and the next reserved-slug test: the capability checks on the write
and read routes, the version-bump assertion, the malformed-shape and
empty-slug validation, and the whole preset display-order sub-route suite.
None of them have anything to do with the Button preset rename. The suite
stayed green because the tests no longer existed.

All twelve are back. Eight are byte-identical to their previous state. The
four that changed are the ones that named the removed baseline slugs: the
three set-order tests and the delete-order test now pair the shipped `default`
with a user-created `accent` preset, since the Button no longer ships two.

The only test this branch still removes is
`testCreatingAPresetNamedDefaultIsRejected`, which the rename makes
unreachable — every block now ships a `default` preset, so writing that slug
is a merge rather than a refused creation, and
`testCreatingAPresetNamedOrderIsRejected` still covers the guard's refusal
branch.
…eset-for-consistency

Ship one Default preset for the Button, matching every other block
…ry-host

[DTM] SOFT-4261 [4/4]: Open the shared color popover from a border row in the Style Library
[DTM] Give the editor's color controls the same bottom spacing as their neighbors
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants