Skip to content

feat(advertising): replace placements UI with inline expandable cards#4625

Merged
thomasguillot merged 8 commits intotrunkfrom
feat/advertising-placements-ui
Apr 16, 2026
Merged

feat(advertising): replace placements UI with inline expandable cards#4625
thomasguillot merged 8 commits intotrunkfrom
feat/advertising-placements-ui

Conversation

@thomasguillot
Copy link
Copy Markdown
Contributor

@thomasguillot thomasguillot commented Apr 2, 2026

All Submissions:

Changes proposed in this Pull Request:

Replaces the action card toggle + modal pattern on the Advertising Placements screen with an inline expandable card UI.

Each placement card now supports:

  • An Enable button (secondary) when the placement is disabled
  • An Enabled badge + Edit/Cancel button (tertiary) when enabled
  • An inline settings form that expands within the card on Edit or Enable, with Update/Enable (primary) and Disable (destructive tertiary) buttons
  • ESC key to cancel editing (handled inside the card, no external event wiring needed)
  • Other cards' buttons are disabled while a placement is open for editing
  • Snackbar notifications (via portal to #wpbody) on Enable, Update, and Disable
  • Update and Enable buttons disabled until changes/ad unit selection are made
  • Enable, Update, and Disable all guard the panel close + snackbar behind a successful API response — no false success feedback on error

Also:

  • Adds a reusable CardForm component to packages/components/src with title/description/badge/actions/children slots, built-in ESC handling via onRequestClose, README, and examples in the components demo
  • Improves PlacementControl to derive the effective provider when only one is available, fixing empty ad unit options and missing GAM bidder fields in single-provider setups
  • Adds hasHeaderBorder prop to CoreCard for seamless inline form expansion
  • Adds 12-column support to the Grid component (responsive: applies at 1054px+)
  • Fixes stick_to_top toggle to use an immutable state update
  • Updates font-weight for card headings to 600 at the base __header-content level to align with WordPress Core's card header weight. Applies globally to every CoreCard (CardFeature, CardForm, etc.) — this is an intentional design-system alignment.

How to test the changes in this Pull Request:

  1. Go to Newspack → Advertising → Placements.
  2. Verify all placements are listed as cards with an Enable button when disabled.
  3. Click Enable on a placement — the card should expand with a settings form. Confirm the Enable button in the footer is disabled until an ad unit is selected. Confirm all other placements' Enable buttons are disabled.
  4. Select a provider and ad unit, then click Enable — a snackbar should confirm the placement was enabled.
  5. Verify the Enabled badge appears in the card header.
  6. Click Edit — the form expands again. Confirm Update is disabled until a change is made. Confirm other placements' Edit buttons are disabled.
  7. Make a change (e.g. select a different ad unit) and click Update — a snackbar should confirm the update.
  8. Click Edit again, then press ESC — the form should close without saving.
  9. Click Edit, then click Cancel — the form should close and changes should be reverted.
  10. Click EditDisable — the placement should be disabled and a snackbar should confirm.
  11. Click Enable then Cancel before saving — the placement should revert to disabled.
  12. Verify snackbars appear centered at the bottom of the screen.
  13. For placements with multiple hooks (e.g. before/after content), verify each hook has its own ad unit selector.
  14. If only one ad provider is configured, verify the provider select is hidden and the ad unit selector still populates correctly — including GAM bidder ID fields if applicable.
  15. Go to the components demo page and verify the CardForm section shows the enable/edit flow working interactively and all four badge levels.

Other information:

  • Have you added an explanation of what your changes do and why you'd like us to include them?
  • Have you written new tests for your changes, as applicable?
  • Have you successfully ran tests with your changes locally?

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR updates the Advertising Placements screen UI by replacing the toggle + modal workflow with inline, expandable placement cards, adding snackbar confirmations, and enhancing shared UI components to support the new layout.

Changes:

  • Replaces modal-based placement settings with inline expandable cards, including enable/edit/cancel flows and snackbar notifications.
  • Updates PlacementControl to hide the provider selector when only one provider is available.
  • Extends shared UI components: adds 12-column support to Grid, adds hasHeaderBorder to CoreCard, and aligns style-core.scss with WordPress base-style variables.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/wizards/advertising/views/placements/index.js Implements the new inline card UI for placements, including enable/edit flows and snackbar notifications.
src/wizards/advertising/style.scss Adds fixed-position snackbar container styling for placement action confirmations.
src/wizards/advertising/components/placement-control/index.js Hides provider select when only one provider exists; refactors layout to a vertical stack.
packages/components/src/grid/style.scss Adds a 12-column grid variant used by the updated placements layout.
packages/components/src/card/style-core.scss Switches to WP base-style variables and adds styles to support borderless headers during inline expansion.
packages/components/src/card/core-card.js Adds hasHeaderBorder prop to allow removing header border/padding for seamless inline expansion.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/wizards/advertising/views/placements/index.js Outdated
Comment thread src/wizards/advertising/views/placements/index.js Outdated
Comment thread src/wizards/advertising/views/placements/index.js Outdated
Comment thread src/wizards/advertising/components/placement-control/index.js Outdated
Comment thread packages/components/src/grid/style.scss Outdated
@thomasguillot thomasguillot force-pushed the feat/advertising-placements-ui branch from ea6b68b to 9179c61 Compare April 2, 2026 19:22
@thomasguillot thomasguillot changed the title feat(advertising): replace placement toggles with inline expandable cards feat(advertising): replace placements UI with inline expandable cards Apr 2, 2026
- Replace action card toggles with inline expandable CardForm cards
- Add CardForm component to packages/components/src with ESC support
- Add hasHeaderBorder prop to CoreCard for seamless form expansion
- Add 12-column responsive grid support (applies at 1054px+)
- Improve PlacementControl to auto-derive provider when only one exists
- Fix stick_to_top toggle to use immutable state update
- Only close edit panel and show snackbar when update succeeds
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 5 comments.

Comments suppressed due to low confidence (1)

src/wizards/advertising/views/placements/index.js:48

  • placementsApiFetch catches fetch errors but never rethrows/returns a status. Callers can’t reliably tell whether a request failed, which leads to follow-up UI actions (open panel, close panel, snackbars) running even when the API call errored. Consider returning a boolean (or rethrowing) so callers can gate subsequent state changes on success.
	const placementsApiFetch = async options => {
		try {
			const data = await apiFetch( options );
			setPlacements( data );
			setError( null );
		} catch ( err ) {
			setError( err );
		}
	};

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/wizards/advertising/views/placements/index.js
Comment thread src/wizards/advertising/views/placements/index.js Outdated
Comment thread src/wizards/advertising/views/placements/index.js
Comment thread src/wizards/advertising/components/placement-control/index.js Outdated
Comment thread packages/components/src/card/style-core.scss
- Guard handlePlacementToggle with inFlight and return success flag
- Gate disable close/snackbar on successful response
- Disable other cards' actions while a placement is being edited
- Derive placementAdUnit from effectiveProvider in PlacementControl
- Move font-weight: 600 to base header-content rule, remove from is-small
@thomasguillot thomasguillot marked this pull request as ready for review April 2, 2026 19:48
@thomasguillot thomasguillot requested a review from a team as a code owner April 2, 2026 19:48
@thomasguillot thomasguillot added the [Status] Needs Review The issue or pull request needs to be reviewed label Apr 2, 2026
Copy link
Copy Markdown
Member

@adekbadek adekbadek left a comment

Choose a reason for hiding this comment

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

A handful of inline notes – nothing strictly blocking, apart from the Cancel/ESC path: the PR description promises "guard panel close behind a successful API response" and that guarantee doesn't hold for the cancel path today.

Comment thread src/wizards/advertising/views/placements/index.js Outdated
Comment thread src/wizards/advertising/views/placements/index.js Outdated
Comment thread src/wizards/advertising/views/placements/index.js Outdated
Comment thread src/wizards/advertising/components/placement-control/index.js
Comment thread packages/components/src/card-form/index.tsx Outdated
Comment thread packages/components/src/card-form/index.tsx Outdated
Comment thread packages/components/src/card-form/index.tsx Outdated
Comment thread packages/components/src/card/style-core.scss Outdated
Comment thread packages/components/src/card/style-core.scss
@github-actions github-actions bot added the [Status] Needs Changes or Feedback The issue or pull request needs action from the original creator label Apr 15, 2026
…tices

- cancelEditing now only closes the panel when the disable request
  succeeds on the Enabling path, and reverts placements[key].data to
  originalData on the edit path so other cards do not see dirty values
- hasChanges is only computed for the editing card and uses
  lodash.isEqual to avoid key-order footguns with JSON.stringify
- notices state collapses to a single notice with a timestamp key so
  rapid-fire actions no longer collide on React keys
- add bidders to the placement-control useEffect dep array so
  biddersErrors does not go stale once bidders resolve async
- move focus into the body on open and restore it on close
- render the body as a labelled region and link it to the title
- scope the Escape listener to the open body so multiple open cards
  do not all close on a single keypress and callers can preventDefault
  from inner controls without tripping the close
- add a titleLevel prop so consumers can pick the heading that fits
  their document outline
- export BadgeLevel from Badge and drop the local duplicate in CardForm
- boost the CardForm title weight selector so it beats the CoreCard
  heading rule without !important, keeping the 600 scoped to CardForm
- drop !important on header--no-border and body--no-header-border in
  favour of two-class selectors, so consumers can still override
- revert the global __header-content heading font-weight back to 500
  so CoreCard callers outside placements are not silently restyled;
  the 600 weight is now scoped to CardForm titles only
Drops !important on header--no-border and body--no-header-border in
favour of two-class selectors, so consumers can still override without
escalating further. The global h1-h6 font-weight: 600 in __header-content
stays as-is — that is intentional alignment with WP Core card headers.
@thomasguillot thomasguillot requested a review from adekbadek April 16, 2026 08:07
@thomasguillot thomasguillot removed the [Status] Needs Changes or Feedback The issue or pull request needs action from the original creator label Apr 16, 2026
Copy link
Copy Markdown
Member

@adekbadek adekbadek left a comment

Choose a reason for hiding this comment

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

All feedback addressed. Re-tested the cancel/ESC paths on the updated code – Enable → Cancel now correctly guards on DELETE success before closing, and Edit → ESC reverts dirty data to originalData. ARIA, focus management, scoped ESC, !important removal, hasChanges scoping, single-notice state, and bidders dep fix all verified in code and browser.

@github-actions github-actions bot added [Status] Approved The pull request has been reviewed and is ready to merge and removed [Status] Needs Review The issue or pull request needs to be reviewed labels Apr 16, 2026
@thomasguillot thomasguillot merged commit e5de003 into trunk Apr 16, 2026
11 checks passed
@thomasguillot thomasguillot deleted the feat/advertising-placements-ui branch April 16, 2026 09:35
@github-actions
Copy link
Copy Markdown

Hey @thomasguillot, good job getting this PR merged! 🎉

Now, the needs-changelog label has been added to it.

Please check if this PR needs to be included in the "Upcoming Changes" and "Release Notes" doc. If it doesn't, simply remove the label.

If it does, please add an entry to our shared document, with screenshots and testing instructions if applicable, then remove the label.

Thank you! ❤️

matticbot pushed a commit that referenced this pull request Apr 16, 2026
# [6.38.0-alpha.1](v6.37.0...v6.38.0-alpha.1) (2026-04-16)

### Bug Fixes

* `is_donor` read-only only on WooCommerce-backed donations ([1dde930](1dde930))
* **access-control:** fix incorrect class name ([#4638](#4638)) ([a7a7908](a7a7908))
* **access-control:** hide My Account group features if Memberships is still active ([#4650](#4650)) ([214fa0d](214fa0d))
* add object-level authorization to corrections REST endpoint ([#4643](#4643)) ([a39a62d](a39a62d))
* address false positives in subscription status alerts on My Account ([f7d6bb8](f7d6bb8))
* address false positives in subscription status alerts on My Account ([31c8313](31c8313))
* clear stored referrer data where referrer is none or local ([c7ef6f3](c7ef6f3))
* count `pending-cancel` by testing against our canonical constant ([2a1f85b](2a1f85b))
* count `pending-cancel` by testing against our canonical constant ([8906b64](8906b64))
* include products without children when reviewing active subscriptions ([d714f3c](d714f3c))
* include products without children when reviewing active subscriptions ([aae3da2](aae3da2))
* limit success and notice snackbar treatments to My Account page ([3b40bec](3b40bec))
* **my-account:** limit notice/success snackbar overrides to My Account ([49517c1](49517c1))
* normalize referrer data when comparing ([d377afb](d377afb))
* **reader-activation:** clear referrer data when none provided ([696012b](696012b))
* **reader-data:** make is_donor read-only only when platform has server-side tracking ([92b0d34](92b0d34))
* update docblock to reflect simple product support ([ea1f03e](ea1f03e))
* update docblock to reflect simple product support ([ba14f85](ba14f85))
* **woocommerce:** image handling in paginated block ([#4149](#4149)) ([1c91f6c](1c91f6c))

### Features

* **access-control:** advanced settings for RSS content restriction ([#4613](#4613)) ([85aa560](85aa560))
* **access-control:** update default patterns ([#4569](#4569)) ([7f9a6c9](7f9a6c9))
* add filter for future integrations to self-declare server-side (secure?) donor tracking ([76e40fc](76e40fc))
* add README.md for the Overlay Block ([#4651](#4651)) ([6d7de6e](6d7de6e))
* **advertising:** replace placements UI with inline expandable cards ([#4625](#4625)) ([e5de003](e5de003))
* **block-theme:** add overlay block for the block theme ([#4578](#4578)) ([af1e4b9](af1e4b9))
* **components:** update CardFeature button size and variant ([#4609](#4609)) ([1d03d4c](1d03d4c))
* **content-gate:** add per-block access control for Group, Stack, and Row blocks ([#4646](#4646)) ([5bdf458](5bdf458))
* **content-gate:** add POST method for external IP access checks ([#4598](#4598)) ([36bfeea](36bfeea))
* **google-site-kit:** enable custom GA frontend params by default ([#4664](#4664)) ([19830ce](19830ce))
* **handoff:** add URL-based handoff with customizable banner text ([#4603](#4603)) ([be59c68](be59c68))
* **integrations:** add My Account menu hook to integration abstraction ([#4640](#4640)) ([4ef2c91](4ef2c91))
* **integrations:** add subscription and donation metadata ([#4597](#4597)) ([ca928f8](ca928f8))
* **integrations:** implement profile metadata ([#4624](#4624)) ([b1daf85](b1daf85))
* make it easier to do "Block until consent given" setups in Complianz and improve blocking ([#4549](#4549)) ([44dda72](44dda72))
* reader activation segments ([#4604](#4604)) ([3821fed](3821fed))
* **reader-data:** add engagement fields ([#4594](#4594)) ([1cba4ef](1cba4ef))
* **reader-data:** store sync reconciliation ([#4633](#4633)) ([69bdda4](69bdda4))
* require reader to set name when commenting ([#4647](#4647)) ([db5ad73](db5ad73))
* session hydration ([#4618](#4618)) ([665b152](665b152))

### Reverts

* address false positives in subscription status alerts on My Account ([9d203b0](9d203b0))
@github-actions
Copy link
Copy Markdown

🎉 This PR is included in version 6.38.0-alpha.1 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

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

Labels

[Status] Approved The pull request has been reviewed and is ready to merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants