Skip to content

feat(card): add gallery layout and media overlay slot to swc-card - #6536

Merged
5t3ph merged 6 commits into
seckles/swc-cardfrom
seckles/swc-card-gallery
Jul 23, 2026
Merged

feat(card): add gallery layout and media overlay slot to swc-card#6536
5t3ph merged 6 commits into
seckles/swc-cardfrom
seckles/swc-card-gallery

Conversation

@5t3ph

@5t3ph 5t3ph commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Description

Adds the gallery layout to swc-card — the last of the three swc-card layouts (regular/collection/gallery) called out in the card family plan — plus a new media overlay slot that lets consumers layer additional content (ex. badge, avatar) over the preview/collection region.

  • Gallery layout: triggered by the absence of all of title/description/actions/footer/default-slot content, regardless of whether preview or collection is populated — the image/asset becomes the card's only content and fills the available space. Implemented via a --_swc-card-media-layout: gallery custom property (set by a :host(:not(:has(...))) selector) read through @container style(--_swc-card-media-layout: gallery), with a companion --_swc-card-media-contains property gating the preview-only aspect-ratio override so it doesn't apply when collection slots are also filled and visible.
  • media slot: opt-in per component via a new renderMedia callback on renderCardTemplate() (mirroring the existing renderCollection/renderGlyph pattern), wired up in swc-card's Card.ts. The slotted content shares grid-area: media with .swc-CardBase-media in the shared card-template.css, so it visually overlays the preview/collection region via normal DOM stacking order — no z-index needed. This resolves the card family plan's previously-open Q2 (gallery badge/avatar decoration) with a purpose-built slot instead of the originally-sketched actions-slot reuse.

TEMP: ObserveSlotText mixin fix (2nd-gen/packages/core/mixins/observe-slot-text.ts)

This mixin-level fix is a temporary patch, not the long-term shape. #6522 replaces ObserveSlotText/ObserveSlotPresence with reactive controllers (SlotTextController/SlotPresenceController) and independently avoids both bugs by design (no @queryAssignedNodes decorator at all; add/remove detection moves to native @slotchange rather than MutationObserver childList). Once it merges, Card/Button/Badge should migrate to the controller and this patch can be dropped rather than carried forward.

Building the gallery trigger condition requires CardBase to correctly detect default-slot content, which surfaced two real, previously-uncaught bugs in this shared mixin (also used by ButtonBase for hasLabel and Badge.ts for its no-label class):

  1. Its MutationController config only watched characterData, not childList — adding/removing an entire slotted node went undetected.
  2. Its @queryAssignedNodes-decorated field didn't resolve against the mixin's computed (Symbol-keyed) private field, always returning undefined — silently no-oping the entire reactive re-check path for every consumer of this mixin, not just Card.

Both are fixed here (config now includes childList: true; the decorated field is replaced with a plain method that queries renderRoot directly). Regression tests added for both Card (DefaultSlotClassUpdatesDynamicallyTest) and Button (LabelSlotUpdatesDynamicallyTest), since no existing test exercised the dynamic add/remove path before now.

Motivation and context

Gallery was deliberately deferred to its own follow-up ticket so it didn't block the collection-slot work (SWC-2362) landing separately — see the card family plan's Scope and Component checklist. The media slot wasn't originally scoped as its own deliverable, but fell out naturally while implementing gallery: the plan's Q2 ("gallery badge/avatar via actions-slot reuse") needed resolving, and a dedicated overlay slot proved simpler and more flexible than the originally-sketched actions-slot reuse.

Related issue(s)

  • SWC-2363

Screenshots (if appropriate)

See the Gallery story in Storybook: preview-only, collection-only, and a badge/avatar media-overlay variant)

image

Manual review test cases

  • Gallery layout renders correctly with only preview populated

    1. Go to Storybook → Card → Gallery
    2. View the first example (preview-only, no title/description/actions/footer)
    3. Expect the image to fill the card with no content region, rounded corners intact
  • Gallery layout renders correctly with both preview and collection populated

    1. Go to Storybook → Card → Gallery
    2. View the second example (collection-only, no title/description/actions/footer)
    3. Expect the preview and collection grid to fill the card with no content region, rounded corners intact
  • media overlay slot positions independently of the underlying image

    1. Go to Storybook → Card → Gallery
    2. View the third example (badge + avatar overlaid on a preview image)
    3. Expect the badge to sit in the top-right corner and the avatar in the bottom-left, both layered on top of the image with no layout shift to the image itself
  • ObserveSlotText dynamic add/remove fix doesn't regress Button/Badge

    1. Go to Storybook → Button, render an icon+label button
    2. Remove the label's text node via devtools
    3. Expect the icon-only-missing-label dev warning to fire (previously it did not, after first render)

Device review

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

Accessibility testing checklist

  • Keyboard (required — document steps below)

    1. Go to Storybook → Card → Gallery
    2. Tab to a selectable gallery-layout card (combine with the Selectable story's args)
    3. Expect the existing focus-visible outline (applied to .swc-CardBase-media in gallery mode) to render correctly around the image, with no change to tab order versus non-gallery cards
  • Screen reader (required — document steps below)

    1. With VoiceOver/NVDA, navigate to a gallery-layout card with a media-slotted swc-badge/swc-avatar
    2. Verify the badge/avatar announce per their own existing accessible-name contracts (Card does not alter or bridge their labeling — see the card family plan's A11y-1)
    3. Expect no duplicate or missing announcements introduced by the media slot itself; a gallery card with no title/description is expected to have no accessible name from Card (consumer-owned via aria-label if needed, out of scope for this PR)

@5t3ph
5t3ph requested a review from a team as a code owner July 21, 2026 19:18
@5t3ph 5t3ph added Component:Card Status:Ready for review PR ready for review or re-review. gen2 These issues or PRs map to our 2nd generation work to modernizing infrastructure. labels Jul 21, 2026
@changeset-bot

changeset-bot Bot commented Jul 21, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 5bc73d9

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

@@ -10,11 +10,9 @@
* governing permissions and limitations under the License.
*/

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.

See the PR description for the explanation on this temp fix until #6522 merges


:host {
display: inline-block;
display: inline-flex;

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.

Correctly collapse space around the avatar

@github-actions

Copy link
Copy Markdown
Contributor

📚 Branch Preview Links

🔍 Gen1 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-6536

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.

@cdransf cdransf left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This looks awesome! Just a tiny test question ✨

Comment thread 2nd-gen/packages/swc/components/card/card.css Outdated
Base automatically changed from seckles/swc-card-collection-slot to seckles/swc-card July 23, 2026 14:40
@5t3ph 5t3ph added Status:Ready for re-review PR has had its feedback addressed and is once again ready for review. and removed Status:Ready for review PR ready for review or re-review. labels Jul 23, 2026
@5t3ph
5t3ph requested a review from rise-erpelding July 23, 2026 15:06
grid-template-rows: repeat(2, auto);
}

::slotted([slot="media"]) {

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.

Should this be shared in card-template? User card could also this

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.

user card and product card's preview area default aspect-ratio is very short so I was hesitant to have this be shared quite yet

--_swc-card-collection-columns: repeat(3, 1fr);
}

.swc-Card-media {

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.

When selectable state swc-Card-media has a visible focus outline. Does it need this?

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.

Yes, that is expected per the Figma and it receives the outline since tabindex="0" is also added to the card to make it a tab stop.

@rise-erpelding rise-erpelding left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Amazing work!

@5t3ph
5t3ph merged commit 27e7c73 into seckles/swc-card Jul 23, 2026
30 checks passed
@5t3ph
5t3ph deleted the seckles/swc-card-gallery branch July 23, 2026 19:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Component:Card gen2 These issues or PRs map to our 2nd generation work to modernizing infrastructure. Status:Ready for re-review PR has had its feedback addressed and is once again ready for review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants