Skip to content

feat(thumbnail): add showRemoveOn prop#4300

Merged
kentonquatman merged 1 commit into
mainfrom
feat/thumbnail-show-remove-on
Jul 26, 2026
Merged

feat(thumbnail): add showRemoveOn prop#4300
kentonquatman merged 1 commit into
mainfrom
feat/thumbnail-show-remove-on

Conversation

@kentonquatman

@kentonquatman kentonquatman commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a showRemoveOn prop to Thumbnail to control when the overlaid remove button is visible. This PR is scoped to only that prop — no changes to the remove-button colors, hover styling, or image hairline.

showRemoveOn prop — defaults to 'hover'

Adds showRemoveOn?: 'always' | 'hover' (default 'hover'). Only has an effect when onRemove is set. The naming follows the existing Overlay showOn vocabulary for consistency.

  • 'hover'(default) the button is hidden at rest and revealed on hover or keyboard focus, and stays visible on any touch-capable device.
  • 'always' — the button is always shown at rest.

Accessibility

The reveal is CSS-only (opacity) — the button stays mounted, so it remains keyboard- and screen-reader-reachable. Tabbing into the thumbnail triggers a :focus-within reveal. A scoped defineMarker + stylex.when.ancestor(':hover' / ':focus-within') keeps the reveal responsive to this thumbnail rather than any outer hoverable container. An @media (any-pointer: coarse) branch keeps the button visible on any touch-capable device — including hybrid touchscreen laptops (which report hover: hover + pointer: fine and would otherwise get no reveal on touch). Motion respects prefers-reduced-motion.

Examples audit: because the default is hover-reveal, every example whose purpose is to show the remove button at rest passes showRemoveOn="always" explicitly (so it renders correctly in static docs previews/screenshots): the Storybook WithRemove, WithCaption, Placeholder, MediaModeTest, and Gallery stories; the ThumbnailGallery, ThumbnailRemovable, and ThumbnailDisabled (enabled row) showcase blocks. A new Remove on hover Storybook story demonstrates the default, and showRemoveOn is added as a Storybook control.

Implementation

The remove button is wrapped in a positioned "slot" <div> that carries the opacity reveal and the scope marker (ancestor-marker styles can't ride on a child component's xstyle). The existing MediaTheme/useImageMode contrast handling and the secondary-Button treatment are unchanged — the slot simply fades the whole button in/out.

Testing

  • pnpm -F @astryxdesign/core build ✅ (verified in dist/astryx.css: the @media (any-pointer: coarse) reveal branch; no changes to remove-button colors or the --color-border hairline)
  • pnpm -F @astryxdesign/core typecheck
  • Thumbnail unit tests: 22 passing (adds 4 for showRemoveOn)
  • Docsite: generate + 282 data-extraction tests passing
  • lint (strict/CI mode) ✅, sync:exports ✅, token-docs ✅

@vercel

vercel Bot commented Jul 24, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
astryx Ready Ready Preview, Comment Jul 26, 2026 3:45am

Request Review

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Jul 24, 2026
@github-actions github-actions Bot added the needs:code-review High-risk change (new package/component/API) — needs human code review before merge label Jul 24, 2026
github-actions Bot added a commit that referenced this pull request Jul 24, 2026
@github-actions

github-actions Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

PR Analysis Report

📚 Storybook Preview

View Storybook for this PR
GitHub Pages may take up to a minute to hydrate after deploy.

🧪 Sandbox Preview

View Sandbox for this PR
GitHub Pages may take up to a minute to hydrate after deploy.

Modified Components

Thumbnail · View in Storybook
Metric Before After Delta
Bundle Size (ESM) N/A N/A N/A
Lines of Code N/A 308 -
Complexity N/A Very High (34) -

Bundle Size Summary

Package Size (ESM) Size (CJS) Gzipped
@astryxdesign/core N/A 4.7KB 0B

Accessibility Audit

Status: 1 accessibility violation(s) found — 1 serious.

Thumbnail - 1 issue(s)
  • 🟠 serious: Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds
    • Rule: color-contrast · Affects 2/12 stories · Learn more
    • WCAG: 1.4.3 (Level AA)

Generated by PR Enrichment workflow | Storybook | Sandbox | View full report

@kentonquatman
kentonquatman marked this pull request as draft July 25, 2026 04:47
github-actions Bot added a commit that referenced this pull request Jul 25, 2026
@kentonquatman
kentonquatman force-pushed the feat/thumbnail-show-remove-on branch from dcfa9ac to 98171d2 Compare July 25, 2026 05:30
github-actions Bot added a commit that referenced this pull request Jul 25, 2026
github-actions Bot added a commit that referenced this pull request Jul 25, 2026
github-actions Bot added a commit that referenced this pull request Jul 25, 2026
github-actions Bot added a commit that referenced this pull request Jul 25, 2026
github-actions Bot added a commit that referenced this pull request Jul 25, 2026
github-actions Bot added a commit that referenced this pull request Jul 25, 2026
github-actions Bot added a commit that referenced this pull request Jul 25, 2026
github-actions Bot added a commit that referenced this pull request Jul 25, 2026
@kentonquatman
kentonquatman marked this pull request as ready for review July 25, 2026 21:59
@kentonquatman kentonquatman changed the title feat(thumbnail): add showRemoveOn prop; drop hover shadow and elevation feat(thumbnail): add showRemoveOn prop + styling fixes Jul 25, 2026
@kentonquatman
kentonquatman enabled auto-merge (squash) July 25, 2026 22:04

@cixzhang cixzhang left a comment

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.

Thanks, Kenton — dropping APCA image-sampling for a fixed treatment is the right direction, and reusing Overlay's showOn vocabulary for showRemoveOn keeps the API coherent. The ::after tint matching ClickableCard (the currentColor derivation + @media (hover: hover) guard) is spot on, and the z-index layering checks out. Three blocking items before this lands.

🔴 1. showRemoveOn="hover" breaks the remove path on touch (accessibility). The reveal fires only on :hover (hover-capable), :focus-within, or @media (hover: none) and (pointer: coarse). Keyboard, screen reader, mouse, and phone/tablet are all fine. But a hybrid device (touchscreen laptop, Surface — reports hover: hover + pointer: fine) matches none of the three, so touch gets no reveal:

  • Interactive thumbnails degrade (tap focuses the inner button → :focus-within reveals remove — removal needs a second aimed tap).
  • Non-interactive thumbnails (onRemove, no onClick) are the sharp edge: the remove button is the only focusable element, opacity: 0 but still mounted and clickable. A touch user must tap an invisible destructive control — and that same tap both reveals and fires onRemove. Silent delete with no visual affordance.

Since touch is an operable/accessibility modality, this is blocking. A clean signal is @media (any-pointer: coarse) — if the device can be touched at all (including hybrid laptops with both a mouse and a touchscreen), just keep the button visible rather than gating it behind hover. Alternatively, treat "no onClick" as always-visible. Your call on the exact approach.

🔴 2. Remove-button backing — use --color-overlay. backgroundColor: '#00000066' is raw hex on the button's xstyle (top of the cascade → un-themeable), and the eslint-disable documents the bypass rather than fixing it. The semantic scrim token is --color-overlay, already used for this file's uploadOverlay — a one-token swap that keeps the darkening intent and stays themeable. (The --color-on-dark X and the secondary-variant hover/press overlay are already tokenized — no change there.)

🔴 3. Inset hairline — --color-border (or derive from a token). light-dark(#00000010, #FFFFFF40) is raw hex inside light-dark(), slipping past no-hardcoded-styles via the const indirection. Colors must be a var or derived from vars — no raw hex, including as light-dark() / color-mix() inputs. Please revert to --color-border; if a neutral-over-media edge is truly required, derive it from --color-on-light / --color-on-dark rather than raw hex.

Confirmed clean: the elevation removal is safe — it's not in the published CHANGELOG, and you've correctly edited the still-pending elevation changeset to drop Thumbnail.

Recommendation: request changes. All three have concrete targets — happy to pair on the touch-reveal approach.

@kentonquatman

Copy link
Copy Markdown
Contributor Author

@cixzhang to use --border-color for the hairline, I'll need to convert the light theme's hex value #ebebeb into the comparable 8-digit #00000014 (for some reason the dark theme already uses an 8-digit hex). This is a bigger change, but possibly better because it will help maintain border contrast over different background colors.

@kentonquatman
kentonquatman disabled auto-merge July 26, 2026 00:45
@cixzhang

Copy link
Copy Markdown
Contributor

The rule is no hardcoded colors to keep components themeable. Perhaps apply a color mix over one of the other tokens but we should avoid hardcoded hex

Comment thread packages/cli/templates/themes/neutral/neutralTheme.ts Outdated
Comment thread packages/core/src/Thumbnail/Thumbnail.tsx Outdated
github-actions Bot added a commit that referenced this pull request Jul 26, 2026
@kentonquatman
kentonquatman force-pushed the feat/thumbnail-show-remove-on branch from 5e0fd27 to 325a2a9 Compare July 26, 2026 02:26
@github-actions
github-actions Bot requested a review from cixzhang July 26, 2026 02:27
@kentonquatman kentonquatman changed the title feat(thumbnail): add showRemoveOn prop + styling fixes feat(thumbnail): add showRemoveOn prop Jul 26, 2026
github-actions Bot added a commit that referenced this pull request Jul 26, 2026
@kentonquatman
kentonquatman force-pushed the feat/thumbnail-show-remove-on branch from 325a2a9 to b41f77c Compare July 26, 2026 02:44
github-actions Bot added a commit that referenced this pull request Jul 26, 2026
Comment thread packages/core/src/Thumbnail/Thumbnail.tsx Outdated
@github-actions github-actions Bot removed the needs:code-review High-risk change (new package/component/API) — needs human code review before merge label Jul 26, 2026
Adds `showRemoveOn?: 'always' | 'hover'` (default 'hover').

- 'hover' — the remove button is hidden at rest and revealed on hover or
  keyboard focus, and stays visible on any touch-capable device. The
  reveal is CSS-only (opacity) so the button stays mounted and remains
  reachable by keyboard and assistive tech; a scoped marker keeps the
  reveal responsive to this thumbnail only.
- 'always' — the button is always shown at rest.

Only applies when onRemove is set.
@kentonquatman
kentonquatman force-pushed the feat/thumbnail-show-remove-on branch from b41f77c to e7bf76c Compare July 26, 2026 03:42
github-actions Bot added a commit that referenced this pull request Jul 26, 2026
@kentonquatman
kentonquatman merged commit 07362a9 into main Jul 26, 2026
19 checks passed
@github-actions
github-actions Bot deleted the feat/thumbnail-show-remove-on branch July 26, 2026 06:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants