Feature: React and Web Components Transformers - #358
Open
nathanacurtis wants to merge 7 commits into
Open
Conversation
…to their own package Rebuilds the transform work onto the current release. Three adaptations the original branch predates: clipsContent (ADR-069), configuration read from settings.data.directory (ADR-071), and outputFormat now required on TransformerContext. - cssvars emits library-level CSS custom properties from the fetched library JSON - css expresses inline shadows, blurs and gradient fills that were dropped before - react and stories are consumed from @directededges/react-from-specs Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…vars docs Cherry-picks the webcomponents wiring onto the current release rather than merging feat/webcomponents-from-specs, whose base predates ADR-069 and ADR-071 and would have reinstated clipContent and the removed Config type. - webcomponents and webcomponents-stories come from @directededges/webcomponents-from-specs, consumed like react-from-specs - both are marked experimental in the docs and changelog; the output shape is not yet stable - the imported webcomponents page referred to config.processing.states, which ADR-071 replaced with the figma.states convention - cssvars had a docs page but no nav entry or index row, so it was unreachable Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`--components` narrows the per-component pass, but cssvars produces all of its output in finalize() over the whole data directory, so a single-component run still paid for the full library stylesheet. TransformerContext now carries `scoped`, set when --components is given, and the cssvars transform returns early rather than rebuilding output a scoped run cannot have invalidated. Re-running it unscoped stays the way to pick up token changes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Figma frame dimensions include padding; the CSS default excludes it, so every element carrying both a fixed dimension and padding rendered larger than the spec by exactly its padding — a 24px frame with 4px padding measured 32px. Generated stylesheets now set border-box on the block and its descendants. False variant values emitted `[data-x="false"]`, but scaffolds write booleans as presence: the attribute is set to "" when true and omitted when false, never written as "false". Those rules matched nothing, so every false-valued variant's styling was dead. They now emit `:not([data-x])`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…versals Cursor is an affordance Figma has no concept of. It is now inferred from what the stylesheet actually styles: a component that styles a pressed state is a press target and gets `pointer`; one that styles a disabled state gets `not-allowed`. Keying off emitted rules rather than declared states matters — a spec can declare a state whose variant produces no styling at all, which is not evidence of interactivity, and that gave text inputs a pointer. An opacity VARIABLE is authored on a percentage scale (36) while an unbound opacity arrives as the ratio Figma stores (0.36). Emitting the variable bare produced `opacity: 36`, which clamps to 1 and silently discarded the state. Token references are now multiplied into a CSS percentage; raw values are untouched, so the two authoring paths both round-trip. A classified boolean's FALSE value has no concept of its own — it is the negation of the true concept. Those variants were dropped entirely as base/rest state, so an unselected variant and every hover/pressed pairing with it emitted no rule. They now emit `:not([aria-selected="true"])`, with multi-part concepts negated as an AND of nots. A variant layout that exactly reverses a flex parent's children now emits `flex-direction: row-reverse`/`column-reverse` under that variant's selector. This is a visual swap, so DOM order — and reading and tab order — stays as authored. Partial reorders are not expressible this way and are relocated by the emitters instead. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A negative itemSpacing means children OVERLAP, which `gap` cannot express. The branch that recognised it returned early with a note that child margins were "deferred" — so overlap was never emitted, and every layout declaration after that point was silently dropped for the element too, including FILL translation. Stylesheets now emit a negative margin on each child after the first, along the parent's main axis, and the negative case only skips `gap`. `--get-images` picked the file to query by a hard-coded preference for the `library` alias, so generating from a different source asked the wrong file for its image URLs. A hash present only in the source file came back missing and was reported as Figma failing to return it. The alias now comes from the manifest being generated, which already names its source. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Brings in ADR-080: PropConfigurationValue and InstanceExample.propConfigurations admit null, so a configuration can state that a nullable prop is unset.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Rebuilds the transform work onto the current release branch. The previous
feat/react-from-specsbranched from the 0.28.0/0.25.0 release and missed threecycles, so its schema-side changes had become regressions — it still carried
clipContent,originalName, and a resurrectedConfig.ts. Those are discarded;only the transform payload is carried forward. The webcomponents work is
cherry-picked for the same reason rather than merged.
Added
cssvarstransformer — emits library-level CSS custom properties, resolvedfrom the fetched library JSON in the workspace data directory, so generated
stylesheets stand on their own.
csstransformer — shadows, blurs, andgradient fills declared directly on an element now become real
box-shadow,filter, andbackground-imagedeclarations. They were previously dropped.webcomponentsandwebcomponents-storiestransformers — experimental.A Lit element and a web-components Storybook page, the same shape
reactandstoriesproduce for React. The output shape may change without abreaking-change note.
Changed
reactandstoriesare consumed from@directededges/react-from-specs;webcomponentsandwebcomponents-storiesfrom@directededges/webcomponents-from-specs. Names, options, and output unchanged.Adaptations this port required
styleToCSS.tsandCss.mapping.mdusedclipContent, which theengine no longer emits. Renamed to
clipsContentso theoverflowrule fires.config.dataDirectory; nowconfig.settings.data?.directory. The importedwebcomponents.mdreferred toconfig.processing.states, now thefigma.statesconvention.outputFormatis now required onTransformerContext, so every ported testcontext failed to compile. Supplied it, and replaced two
as anycasts inCssvars.test.tswith real contexts rather than letting them hide the gap.Css.mapping.mdcarried a client library namespace in three places; replacedwith the neutral
dsforms used on the release branch.cssvarshad a docs page but no nav entry or index row — it was unreachable.Verification
tsc --noEmit: zero new errors. Two remain (GenerateCommand.ts:589, anrfc/storybook import) — both confirmed pre-existing on the release branch.
vitest packages/cli/tests: 688 passed, 2 skipped."unknown transformer" warning; a control run with a bogus name does warn,
confirming the external packages load at runtime.
Dependencies
Neither
@directededges/react-from-specsnor@directededges/webcomponents-from-specsis published yet; both resolve throughworkspace symlinks for development. Both need a first publish before a release
carrying this can ship.
The matching engine work is on
feat/react-from-specsin specs-from-figma, whichcarries the ADR-071 engine refactor this CLI calls against.
🤖 Generated with Claude Code