Skip to content

feat: migrate color-loupe to gen2#6184

Open
blunteshwar wants to merge 21 commits into
mainfrom
color-loupe-migration
Open

feat: migrate color-loupe to gen2#6184
blunteshwar wants to merge 21 commits into
mainfrom
color-loupe-migration

Conversation

@blunteshwar
Copy link
Copy Markdown
Contributor

@blunteshwar blunteshwar commented Apr 16, 2026

Description

Migrates the color-loupe component from 1st-gen (sp-color-loupe) to 2nd-gen (swc-color-loupe) following the washing machine workflow. All 7 migration steps are complete and the status table has been updated.

What changed

Core package (2nd-gen/packages/core/components/color-loupe/)

  • ColorLoupe.types.ts — minimal types file with the default color constant
  • ColorLoupe.base.ts — abstract base class extending SpectrumElement; holds the two public properties (open, color)
  • index.ts — re-exports

SWC package (2nd-gen/packages/swc/components/color-loupe/)

  • ColorLoupe.ts — concrete class with render() and styles
  • color-loupe.css — token-based CSS following the S2 migration guide
  • index.tsdefineElement('swc-color-loupe', ColorLoupe) registration
  • stories/color-loupe.stories.ts — Playground, Overview, Anatomy, States, ColorDisplay (behaviors), and Accessibility stories
  • test/color-loupe.test.ts — Storybook play-function tests (defaults, open reflection, color property)
  • test/color-loupe.a11y.spec.ts — Playwright attribute test asserting aria-hidden="true" on the SVG (replaced the unusable empty toMatchAriaSnapshot which Playwright rejects even for legitimately empty trees)

Migration docs (CONTRIBUTOR-DOCS/03_project-planning/03_components/color-loupe/)

  • rendering-and-styling-migration-analysis.md — full API surface, DOM structure, factor assessment, and gen-2 delta notes (moved from 1st-gen/packages/color-loupe/MIGRATION_ANALYSIS.md)
  • accessibility-migration-analysis.md — updated
  • migration-checklist.md — all 7 steps marked complete

Status table

  • CONTRIBUTOR-DOCS/.../01_status.md — all 7 columns marked for Color Loupe

Notable changes from 1st-gen

Area Change
SVG path Fixed double-close-command bug (61.575ZZ61.575Z)
SVG mask Fixed broken mask reference (xlink:href="#path"href="#loupe-path")
xlink:href Migrated deprecated xlink:href to modern href throughout the SVG
Opacity checkerboard Replaced @spectrum-web-components/opacity-checkerboard import with an inline repeating-conic-gradient using --swc-opacity-checkerboard-* tokens and light-dark() for theme support
Drop shadow Migrated from S1 --spectrum-drop-shadow-x to S2 token("drop-shadow-elevated-*")
Token syntax All --spectrum-color-loupe-* / --mod-colorloupe-* chains replaced with token() and --swc-color-loupe-* exposed overrides
Color display Simplified render: separate div.swc-ColorLoupe-colorFill with background: ${this.color} instead of setting --spectrum-picked-color on the SVG inline style
Factor step Intentionally skipped — component is pure-presentational (~40 lines of logic), no separable state

Motivation and context

sp-color-loupe is a dependency of the color picker family. Migrating it to 2nd-gen unblocks downstream component migrations (swc-color-field, swc-color-slider, swc-color-area) and ensures it consumes Spectrum 2 design tokens.

Related issue(s)

  • Jira: SWC-1193 (WCAG 1.4.11 limitation — non-text contrast for the loupe border, documented in the accessibility analysis, not resolved in this PR)

Screenshots (if appropriate)

Visual verification via the color-loupe--overview Storybook story. No screenshot attached; teardrop shape, checkerboard, and color fill are visually unchanged from 1st-gen.

Author's checklist

  • I have read the CONTRIBUTING and PULL_REQUESTS documents.
  • I have reviewed at the Accessibility Practices for this feature, see: Aria Practices
  • I have added automated tests to cover my changes.
  • I have included a well-written changeset if my change needs to be published.
  • I have included updated documentation if my change required it.

Reviewer's checklist

  • Includes a Github Issue with appropriate flag or Jira ticket number without a link
  • Includes thoughtfully written changeset if changes suggested include patch, minor, or major features
  • Automated tests cover all use cases and follow best practices for writing
  • Validated on all supported browsers
  • All VRTs are approved before the author can update Golden Hash

Manual review test cases

  • Loupe renders correctly in Storybook

    1. Go to Color Loupe — Overview
    2. Verify the teardrop shape, checkerboard pattern, and blue color fill all render
    3. Expect pixel-accurate match against the 1st-gen sp-color-loupe
  • Open/close animation works

    1. Go to Color Loupe — States
    2. Toggle the open control in the Storybook controls panel
    3. Expect the loupe to animate in (opacity + transform) when open and animate out when closed
  • Color fill updates correctly

    1. Go to Color Loupe — Color display
    2. Change the color control to a color with alpha (e.g., rgba(255, 0, 0, 0.5))
    3. Expect the checkerboard to show through the transparent fill
  • Lint and unit tests pass

    1. Run yarn lint:2nd-gen
    2. Run yarn test:unit:2nd
    3. Expect zero errors
  • Playwright a11y tests pass

    1. Run yarn test:a11y:2nd
    2. Expect both color-loupe.a11y.spec.ts tests to pass — they assert aria-hidden="true" on the SVG element

Device review

  • Did it pass in Desktop?
  • Did it pass in (emulated) Mobile?
  • Did it pass in (emulated) iPad?

Accessibility testing checklist

Required: Complete each applicable item and document your testing steps.

  • Keyboard (non-interactive component — no focusable parts)

    1. Go to Color Loupe — Accessibility
    2. Press Tab repeatedly through the story
    3. Expect focus to skip the loupe entirely — it has no tab stop and sets no tabindex
    4. Confirm no regressions in surrounding Storybook chrome
  • Screen reader (visual-only component — SVG is aria-hidden)

    1. Open Color Loupe — Accessibility with VoiceOver (macOS) or NVDA (Windows)
    2. Navigate through the page with VO+Right / F6
    3. Expect the loupe SVG to be completely skipped — no role, name, or value should be announced
    4. Confirm the swc-color-loupe custom element itself is not announced as an interactive control

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 16, 2026

⚠️ No Changeset found

Latest commit: da87509

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 16, 2026

📚 Branch Preview Links

🔍 First Generation Visual Regression Test Results

When a visual regression test fails (or has previously failed while working on this branch), its results can be found in the following URLs:

Deployed to Azure Blob Storage: pr-6184

If the changes are expected, update the current_golden_images_cache hash in the circleci config to accept the new images. Instructions are included in that file.
If the changes are unexpected, you can investigate the cause of the differences and update the code accordingly.

blunteshwar and others added 2 commits April 22, 2026 23:36
* feat(color-loupe): implement Color Loupe component with base functionality and styles

* refactor(color-loupe): added migration planning and docs

* fix(color-loupe): fixed styling

* chore(color-loupe): add TODO for opacity-checkerboard migration

* chore(color-loupe): reverting some changes

* feat(color-loupe): enhance color loupe functionality and documentation

* refactor(color-loupe): rename States story to OpenAndClosedStates and update documentation

* refactor(color-loupe): update clip-path and SVG path for improved shape definition

* fix(color-loupe): correct story identifiers in accessibility tests

* chore(color-loupe): address PR review feedback

- ColorLoupe.ts: link TODO to SWC-2029 for opacity-checkerboard migration
- color-loupe.test.ts: add CSS regression steps asserting inner-loupe opacity flips with [open]
- package.json: revert unrelated customElements path change
- vitest.config.js: revert unrelated coverage threshold bumps
- CONTRIBUTOR-DOCS: document guidance for inline CSS strings from component properties

Made-with: Cursor

* test(color-loupe): improve opacity assertions in OpenAttributeTest

* refactor(color-loupe): refactored css according to styling guides

* refactor(color-loupe): added default color to base

* docs(color-loupe): update documentation for color loupe anatomy and inline CSS usage

* chore: minor fix

* refactor(color-loupe): improved stories

* docs(color-loupe): reference existing validateColorString in @todo

Per review, validation delegation is already handled: the parent color-picker
validates upstream via ColorController.validateColorString before passing to
the loupe. Update the @todo to reference the existing utility rather than
proposing a new one.

Made-with: Cursor

* chore: minor fix

Co-authored-by: Stephanie Eckles <seckles@adobe.com>

* chore: added a line to remove linting error

---------

Co-authored-by: Stephanie Eckles <seckles@adobe.com>
@blunteshwar blunteshwar marked this pull request as ready for review April 22, 2026 18:07
@blunteshwar blunteshwar requested a review from a team as a code owner April 22, 2026 18:07
@blunteshwar blunteshwar added Status:Ready for review PR ready for review or re-review. labels Apr 22, 2026
@blunteshwar
Copy link
Copy Markdown
Contributor Author

blunteshwar commented Apr 22, 2026

Adding the link for the PR to get context for changes
#6147

@blunteshwar blunteshwar added Status:WIP PR is a work in progress or draft and removed Status:Ready for review PR ready for review or re-review. labels Apr 22, 2026
@blunteshwar blunteshwar added Status:Ready for review PR ready for review or re-review. and removed Status:WIP PR is a work in progress or draft labels Apr 23, 2026
@rubencarvalho rubencarvalho changed the title docs: added color loupe a11y migration doc (#6108) feat: migrate color-loupe to gen2 Apr 23, 2026
Comment on lines +50 to +52
class=${classMap({
['swc-ColorLoupe']: true,
})}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
class=${classMap({
['swc-ColorLoupe']: true,
})}
class="swc-ColorLoupe"
})}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @rubencarvalho — you’re right that a plain class="swc-ColorLoupe" would be enough for a single, unconditional class.

I’m going to keep classMap on the root wrapper anyway so this line matches the same pattern we use in other 2nd-gen components (static single-class classMap on the top-level block) and stays consistent if we add conditional classes later (e.g. state/variant) without reworking the template. If we decide as a team to standardize on plain class for the non-conditional case, I’m happy to follow this up in a small cleanup PR.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disagree here. This should only be flattened to a class as @rubencarvalho suggested. Though this is not a merge blocker for now.

Copy link
Copy Markdown
Contributor

@rubencarvalho rubencarvalho left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see a direct usage of color-loupe in the metrics tool. Could we treat this as an internal component (similarly to what we're doing with icon?)
Happy to discuss as a team.

@Rajdeepc
Copy link
Copy Markdown
Contributor

Rajdeepc commented Apr 28, 2026

I don't see a direct usage of color-loupe in the metrics tool. Could we treat this as an internal component (similarly to what we're doing with icon?) Happy to discuss as a team.

Yes let's discuss and if this becomes internal, the consumer guide will go away and @status and the stories file will change accordingly. The decision must land in this PR.
@blunteshwar Once this decision is made can you kindly document this and expand your efforts on this. You might need few more points.

The analogy I'd draw is color-handle — it's also a sub-component of the color picker family, but it's shipped as a standalone package precisely because consumers building custom color UIs (like Photoshop) need it independently. color-loupe sits in the same position: while we don't currently ship gradient picker components, a loupe is a natural fit for any gradient editor — whenever a user drags a color stop, you'd want a loupe to show the picked color without the finger/cursor obscuring it. Consumers building those experiences today would need to reach for the loupe on its own.

Making it internal now would mean a breaking change later if we (or a downstream consumer) ever needs it outside the color picker context. Keeping it public is the safer default — it's a small, focused, self-contained component with a clear visual contract.

Copy link
Copy Markdown
Contributor

@miwha-adobe miwha-adobe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work! One other thing, can we add a changeset?

* Default is semi-transparent red so the opacity checkerboard is visible
* when the component is rendered without a `color` attribute.
*
* @todo Runtime validation is intentionally not performed here. The loupe
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we include a ticket number here. What ticket is responsible for making sure this todo is completed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call — I've removed the @todo. Since swc-color-loupe always receives its color value from a parent color component (e.g. swc-color-area, swc-color-slider) that already validates the color string upstream via ColorController, there's no need to add a second validation layer here or track it as future work.

opacity: 0;
filter: drop-shadow(token("drop-shadow-elevated-x") token("drop-shadow-elevated-y") token("drop-shadow-elevated-blur") token("drop-shadow-elevated-color"));

/* TODO: replace 8px with a forthcoming animation-distance token (matches Spectrum CSS S2). */
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same with this todo. Do we have a ticket #, or is it a necessary todo? When will this animation token be available?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 8px value here corresponds to --mod-colorloupe-animation-distance in Spectrum CSS S2 (colorloupe/index.css), which controls how far the loupe translates down when it transitions to the closed state. That mod-property was intentionally not carried over as a consumer-facing hook (see the CSS custom properties section), but the underlying value also doesn't map to any token in the 2nd-gen token catalog yet.

I've left the TODO so we can swap it to a token("color-loupe-animation-distance") call once it lands in spectrum tokens, rather than silently burying a magic number.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@5t3ph do you happen to have insight into when the s2 token will be carried over?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can remove the TODO, looking at the Spectrum CSS source, it seems this is in fact a magic number, and there's not a token planned. Looking at Color handle, the default gap value is 12px if we go by Figma: but Figma doesn't show a mapped token, just a static value.

Image

If we find we need to modify it from the consuming components, we could expose a custom property to override the value.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would you recommend here? Should I remove the TODO and keep the hardcoded 8px value, or expose it as a custom property instead?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's remove the TODO and keep the hardcoded value for now

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!


`<swc-color-loupe>` exposes no public CSS custom properties for consumer overrides. The component's visual appearance is fully governed by Spectrum 2 design tokens.

{/* @todo Replace the Description column with the `@cssproperty` JSDoc descriptions from `<swc-color-loupe>`'s CEM entry once they are added in a follow-up PR. */}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This todo seems like it can be removed. There are no css custom properties on this component as indicated in the Description section. Unless we anticipate this component being updated with them in which case we should have a ticket

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

* color selection controls such as `<swc-color-field>`.
*
* @element swc-color-loupe
* @status preview
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this should be changed from "preview" to "unsupported"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

@blunteshwar blunteshwar requested a review from Rajdeepc April 30, 2026 18:59
Copy link
Copy Markdown
Contributor

@5t3ph 5t3ph left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Status:Ready for review PR ready for review or re-review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants