feat: card primitives web app gaps#2769
Merged
Merged
Conversation
Two related additions to the Card.Banner family, addressing gaps hit while
rebuilding web-app's library cards on the Card.* primitives:
- Card.BannerImage gains a `padding` prop ('none' | 'small' | 'medium' |
'large'; none by default). Padding is applied inside the banner and pairs
with `fit="contain"`, giving logo/icon previews 12/24/32px of breathing room
without dropping out of the component. Tokens are mapped by value, not by
same-named spacing tokens (--spacing-medium is 16px, not 24px).
- Card.Banner gains a `tone` prop ('dim' | 'active' | 'inverted'). 'inverted'
renders the dark drop-target state (near-black background + white icon);
'dim'/'active' pin the background and opt out of the implicit hover/active
shift applied when a Card.BannerIcon is nested. Leaving `tone` unset preserves
the previous behavior. This supersedes a separate BannerIcon dark variant:
background is a banner concern, so one tone prop covers both the dark state
and the "don't auto-change background" request.
Includes Storybook stories (padded image, inverted/dim tones), unit tests, and
JSDoc. Backward compatible.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Card.Root now accepts a `className`, merged after the internal styles onto its root element (same pattern as Button/Notice/Divider). This lets consumers apply layout hooks such as Tailwind's `group` — e.g. to drive `group-hover:` styles on descendants — without wrapping the card in an extra element that could break the surrounding grid. Backward compatible. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Card.Action now spreads unknown props onto its rendered <div> (className and the internal data-test-id default are still owned by the component). This lets consumers attach attributes such as an analytics/onboarding-tour selector (e.g. data-intercom-tour-selector) directly on Card.Action instead of wrapping its contents in an extra element. Backward compatible. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The unit tests assert the data-* plumbing; these Playwright component tests verify the actual computed CSS, which jsdom can't resolve: - BannerImage `padding="medium"` applies --spacing-large (24px) inner padding and keeps object-fit: contain. - Banner `tone="inverted"` paints --color-primary-default and flips the nested icon to --color-primary-on-primary. - Banner `tone="dim"` stays dim on hover (opts out of the implicit shift), while an un-toned banner still shifts to --color-surface-hover (backward-compat). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Narrow CardBannerTone to 'dim' | 'inverted'. The 'active' value had no consuming use case (the dark drop-target state is 'inverted'; 'dim' covers pinning the resting background), so removing it keeps the public API focused. Updates the type, JSDoc, SCSS, and changeset. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- BannerImageWithPadding: add live `padding` and `fit` controls and use an inline SVG logo as the image, so the breathing room is adjustable and clear. - BannerToneInverted: use a white arrow-align-down icon (the drop-target glyph), which inherits the inverted tone's on-primary color automatically. - BannerToneDim: show a default vs tone="dim" pair, since the difference only appears on hover (default brightens, dim stays). Hover to compare. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: 41d779d The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
✅ Deploy Preview for fondue-components ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
SonarCloud flagged the `useState` calls in the inline `render` arrows of the new banner stories (a lowercase `render` isn't recognized as a React component or a hook). Move each stateful story body into an uppercase-named component so the hooks run inside a real component, and drop the now-redundant inline comments. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
syeo66
reviewed
Jun 19, 2026
|
|
||
| type BannerImagePaddingControls = { padding: CardBannerImagePadding; fit: CardBannerFit }; | ||
|
|
||
| // Story bodies are extracted into named components so `useState` runs inside a React component. |
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.
Summary
Closes API gaps in the
Card.*primitives surfaced while web-app rebuilds its library asset/folder cards on Fondue. Five backward-compatible additions acrossCard.Banner,Card.BannerImage,Card.Root, andCard.Action— no breaking changes; every new prop defaults to today's behavior.🎨 Design
Figma
What changed
Card.BannerImagepaddingprop (none|small|medium|large;noneby default). Applies inner padding so logo/icon previews get breathing room; pairs withfit="contain".Card.Bannertoneprop (dim|inverted).inverted= dark drop-target state (near-black bg + white icon);dimpinssurface-dimand opts out of the implicit hover/active shift. Unset = previous behavior.Card.RootclassName(merged after internal styles) — enables Tailwindgroup/group-hover:without an extra wrapper.Card.Actiondata-*(and other unknown) props onto the element — e.g. an Intercom/analytics selector — no wrapper needed.Design decisions & trade-offs
Card.Banner, notCard.BannerIcon. Background is a banner concern, so a singletone="inverted"sets the dark background and auto-flips the nested icon toon-primary(white). This also resolves the "don't auto-change background" friction: setting anytonepins the background and disables the implicit:has(> .bannerIcon)hover shift.--spacing-mediumis 16px, but the spec wantsmedium = 24px, sosmall/medium/large→--spacing-small/--spacing-large/--spacing-x-large(12/24/32px). Documented in the SCSS.