Skip to content

feat(ep-commerce): per-variation input rendering via EPVariationCase (#336)#337

Merged
field123 merged 1 commit into
masterfrom
feat/336-variation-axis-slot-pattern
Jun 2, 2026
Merged

feat(ep-commerce): per-variation input rendering via EPVariationCase (#336)#337
field123 merged 1 commit into
masterfrom
feat/336-variation-axis-slot-pattern

Conversation

@field123
Copy link
Copy Markdown
Collaborator

@field123 field123 commented Jun 1, 2026

Summary

Closes #336.

Adds a per-variation "input pattern" capability to EPVariationPicker so each variation on a product can render with its own input style — primarily so a storefront's PDP can render Language as a native <select> and Format as native radios, while other variations continue to render as the existing card/chip pattern.

The shape is the Discriminated Slot Children pattern, modelled after HTML's <picture>/<source>. Consumers compose:

<EPVariationPicker>
  <EPVariationCase forVariation="Language">
    <EPVariationField field="name" />
    <EPVariationOptionSelect />
  </EPVariationCase>

  <EPVariationCase forVariation="Format">
    <EPVariationField field="name" />
    <EPVariationOptionRadioGroup />
  </EPVariationCase>

  <EPVariationCase>{/* un-keyed = fallback for any other variation */}
    <EPVariationField field="name" />
    <EPVariationOptionList />
  </EPVariationCase>
</EPVariationPicker>

What's new

  • EPVariationCase — slot host with optional forVariation prop. Keyed cases render only when iterating their named variation; un-keyed cases act as the fallback for any variation no keyed sibling has claimed.
  • EPVariationOptionSelect — atomic native <select> renderer for one variation.
  • EPVariationOptionRadioGroup — atomic native radio-group renderer for one variation.
  • VariationPickerContext now exposes claimedVariations: ReadonlySet<string> and registerClaim(variationName) => unregister. Keyed EPVariationCase instances self-register on mount; the fallback consults the registry to decide whether to render. No React.Children scanning, so Plasmic's runtime div wrapping doesn't break the filter.
  • Picker default children slot now contains a single un-keyed EPVariationCase wrapping the existing Field + EPVariationOptionList. Zero-config N-variation usage is unchanged.

Picker hydration fix

While wiring this I noticed the picker's init useEffect wasn't populating selectedValues when no defaultVariantId resolved, leaving option triggers un-highlighted on first load even though a variant was effectively in use. Fixed in the same effect: falls back to variants[0] and populates selectedValues accordingly. Bundled here because the new EPVariationCase registration would re-trigger the bug in any consumer that adopts the new pattern.

Naming

The component is named EPVariationCase (switch/case analogy: "this is the case for variation X"), matching the existing Variation namespace in this package. Earlier drafts of this PR used EPVariationAxis — that's been renamed to align with the rest of the surface (EPVariationField, EPVariationOptionList, etc.) and to keep "variation" as the single domain term consumers see.

Ordering

Preserved from EP. The picker still iterates product.options[] in EP order; forVariation only decides which subtree renders during each iteration. Reorder in the EP catalog to change display order.

Backwards compatibility

Existing projects using <EPVariationOptionList> directly under the picker continue to work — the new default children content is semantically identical to the prior default content. EPVariationOptionList's selectionMode="dropdown" branch is not removed; new use is steered to EPVariationOptionSelect.

Alternatives considered

Rejected during design grilling:

  • selectionModeByVariation: Record<variationName, mode> config prop. Awkward Plasmic Studio authoring (no first-class UI for Record<string, enum>), config split between picker prop and child components, silent failure on typo.
  • Per-variation explicit instances with no fallback. Forces explicit declaration of every variation even when overriding one.
  • Render-prop children (Shopify Hydrogen VariantSelector shape). Unauthorable in Plasmic Studio.

Test plan

  • Build the package: yarn build in plasmicpkgs/commerce-providers/elastic-path — verify no TS errors.
  • Drop EPVariationPicker on a Plasmic page with a 2-variation product (e.g. ISO 5495:2005 with Language × Format). Default children renders chip pattern for both variations.
  • Add <EPVariationCase forVariation="Language"><EPVariationOptionSelect/></EPVariationCase> and <EPVariationCase forVariation="Format"><EPVariationOptionRadioGroup/></EPVariationCase> inside the picker. Confirm Language renders as <select>, Format as native radios.
  • Confirm the picker still emits the same useFormContext field names (variation_<axisId>, ProductVariant).
  • Confirm ?variant= URL round-trip works through the new renderers.
  • Confirm no infinite-render loops by checking the dev-tools console after selecting an option.
  • Confirm zero-config drop-in (no EPVariationCase instances authored) renders the picker with the default fallback chip pattern across N variations.

@field123 field123 force-pushed the feat/336-variation-axis-slot-pattern branch from 752b695 to b77ff98 Compare June 1, 2026 16:13
@field123 field123 changed the title feat(ep-commerce): per-axis input rendering via EPVariationAxis (#336) feat(ep-commerce): per-variation input rendering via EPVariationCase (#336) Jun 1, 2026
…336)

Allow each variation on a product to render with its own input
pattern (e.g. Language as a native <select>, Format as native radios)
while keeping the existing single-drop-in ergonomic for the uniform
N-variation case.

Implements the "Discriminated Slot Children" pattern (modelled after
HTML's <picture>/<source>):

- New EPVariationCase: slot host with an optional `forVariation` prop.
  A keyed case renders only when iterating its named variation; an
  un-keyed case acts as the fallback for any variation no keyed
  sibling has claimed.
- New EPVariationOptionSelect: atomic native <select> renderer for
  one variation.
- New EPVariationOptionRadioGroup: atomic native radio-group renderer
  for one variation.
- VariationPickerContext exposes `claimedVariations` + `registerClaim`.
  Each keyed EPVariationCase self-registers on mount (Primer-style
  context registration); the fallback consults the registry to decide
  whether to render. No React.Children scanning, so wrapping by
  Plasmic's runtime divs doesn't break filtering.
- Picker default `children` slot now contains one un-keyed
  EPVariationCase wrapping the existing Field + EPVariationOptionList
  default, so zero-config N-variation usage is unchanged.

Also fixes a picker hydration bug uncovered while wiring this: the
init effect now falls back to `variants[0]` when no defaultVariantId
resolves and populates `selectedValues` so trigger highlights match
the auto-selected variant exposed via the URL writer.

Ordering is preserved from EP — the picker iterates `product.options[]`
in EP order; `forVariation` only chooses which slot subtree renders
during each iteration.
@field123 field123 force-pushed the feat/336-variation-axis-slot-pattern branch from b77ff98 to 7bcc9ce Compare June 1, 2026 17:06
@field123 field123 merged commit 741af15 into master Jun 2, 2026
9 checks passed
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.

EPVariationPicker: support per-axis input type (selectionModeByAxis)

1 participant