-
Notifications
You must be signed in to change notification settings - Fork 58
feat(advertising): replace placements UI with inline expandable cards #4625
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
thomasguillot
wants to merge
4
commits into
trunk
Choose a base branch
from
feat/advertising-placements-ui
base: trunk
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 2 commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
1049d6e
feat(advertising): replace placements UI with inline expandable cards
thomasguillot 999fa19
fix: move css
thomasguillot 793d605
fix: address second round of Copilot review feedback
thomasguillot 3b94280
feat(components-demo): add CardForm examples
thomasguillot File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| # CardForm | ||
|
|
||
| A card component for presenting a named setting or feature with an expandable inline form. When open, the card body reveals children (controls, fields, action buttons) and the header border is removed for a seamless look. Intended for lists of items that can each be independently enabled, edited, or configured without leaving the page. | ||
|
|
||
| ## Layout rules | ||
|
|
||
| - Stack multiple `CardForm` cards inside a `<VStack>` — they are designed to appear as a list. | ||
| - The `actions` slot sits to the right of the badge. Keep it to one button; if you need multiple actions, use an `HStack` with `expanded={ false }`. | ||
| - The form body (`children`) is only mounted when `isOpen` is `true`. | ||
|
|
||
| ## States | ||
|
|
||
| | State | `isOpen` | `badge` | `actions` example | | ||
| |---|---|---|---| | ||
| | **Disabled** | `false` | None | "Enable" (secondary) | | ||
| | **Enabling** | `true` | None | "Cancel" (tertiary) | | ||
| | **Enabled** | `false` | Success badge | "Edit" (tertiary) | | ||
| | **Editing** | `true` | Success badge | "Cancel" (tertiary) | | ||
|
|
||
| ## Basic usage — enable/edit pattern | ||
|
|
||
| ```tsx | ||
| import { __ } from '@wordpress/i18n'; | ||
| import { useState } from '@wordpress/element'; | ||
| import { Button } from '@wordpress/components'; | ||
| import { CardForm } from '../../../../../packages/components/src'; | ||
|
|
||
| const [ isOpen, setIsOpen ] = useState( false ); | ||
| const [ isEnabled, setIsEnabled ] = useState( false ); | ||
|
|
||
| const handleClose = () => setIsOpen( false ); | ||
|
|
||
| <CardForm | ||
| title={ __( 'Above Header', 'newspack-plugin' ) } | ||
| description={ __( 'Displays an ad above the site header.', 'newspack-plugin' ) } | ||
| badge={ isEnabled ? { level: 'success', text: __( 'Enabled', 'newspack-plugin' ) } : undefined } | ||
| actions={ | ||
| isEnabled ? ( | ||
| <Button variant="tertiary" size="compact" onClick={ () => isOpen ? handleClose() : setIsOpen( true ) }> | ||
| { isOpen ? __( 'Cancel', 'newspack-plugin' ) : __( 'Edit', 'newspack-plugin' ) } | ||
| </Button> | ||
| ) : ( | ||
| <Button variant="secondary" size="compact" onClick={ () => setIsOpen( true ) }> | ||
| { __( 'Enable', 'newspack-plugin' ) } | ||
| </Button> | ||
| ) | ||
| } | ||
| isOpen={ isOpen } | ||
| onRequestClose={ handleClose } | ||
| > | ||
| { /* form controls */ } | ||
| <Button variant="primary" size="compact" onClick={ handleSave }> | ||
| { __( 'Update', 'newspack-plugin' ) } | ||
| </Button> | ||
| </CardForm> | ||
| ``` | ||
|
|
||
| ## With a custom badge level | ||
|
|
||
| The `badge` prop accepts any `BadgeLevel`. Use `warning` or `error` to communicate a degraded state. | ||
|
|
||
| ```tsx | ||
| <CardForm | ||
| title={ __( 'Above Header', 'newspack-plugin' ) } | ||
| badge={ { level: 'warning', text: __( 'Missing ad unit', 'newspack-plugin' ) } } | ||
| actions={ <Button variant="tertiary" size="compact">{ __( 'Edit', 'newspack-plugin' ) }</Button> } | ||
| isOpen={ false } | ||
| /> | ||
| ``` | ||
|
|
||
| ## Without a badge | ||
|
|
||
| Omit `badge` (or pass `undefined`) to show no badge at all. | ||
|
|
||
| ```tsx | ||
| <CardForm | ||
| title={ __( 'Sticky Footer', 'newspack-plugin' ) } | ||
| description={ __( 'Pins an ad to the bottom of the viewport.', 'newspack-plugin' ) } | ||
| actions={ | ||
| <Button variant="secondary" size="compact" onClick={ handleEnable }> | ||
| { __( 'Enable', 'newspack-plugin' ) } | ||
| </Button> | ||
| } | ||
| isOpen={ false } | ||
| /> | ||
| ``` | ||
|
|
||
| ## Props | ||
|
|
||
| | Prop | Type | Default | Description | | ||
| |---|---|---|---| | ||
| | `title` | `string` | — | Card heading (**required**) | | ||
| | `description` | `string` | — | Supporting text below the title | | ||
| | `badge` | `{ text: string; level?: BadgeLevel }` | — | Badge shown next to the actions slot. Omit or pass `undefined` to hide. | | ||
| | `actions` | `React.ReactNode` | — | JSX rendered in the header action area (buttons, dropdowns, etc.) | | ||
| | `isOpen` | `boolean` | `false` | When `true`, renders `children` in the card body and removes the header border | | ||
| | `onRequestClose` | `() => void` | — | Called when the user presses Escape while the form is open | | ||
| | `className` | `string` | — | Additional class name applied to the card element | | ||
| | `children` | `React.ReactNode` | — | Form content rendered inside the card body when `isOpen` is `true` | | ||
|
|
||
| ### `BadgeLevel` | ||
|
|
||
| ```ts | ||
| type BadgeLevel = 'default' | 'info' | 'success' | 'warning' | 'error'; | ||
| ``` |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| /** | ||
| * Card Form component. | ||
| * | ||
| * A card with an expandable inline form — title, description, optional badge, | ||
| * and an actions slot in the header. When `isOpen` is true, children are | ||
| * rendered in the card body and the header border is removed for a seamless look. | ||
| */ | ||
|
|
||
| /** | ||
| * External dependencies | ||
| */ | ||
| import classnames from 'classnames'; | ||
|
|
||
| /** | ||
| * WordPress dependencies | ||
| */ | ||
| import { useEffect } from '@wordpress/element'; | ||
| import { __experimentalHStack as HStack, __experimentalVStack as VStack } from '@wordpress/components'; // eslint-disable-line @wordpress/no-unsafe-wp-apis | ||
|
|
||
| /** | ||
| * Internal dependencies | ||
| */ | ||
| import Badge from '../badge'; | ||
| import Card from '../card'; | ||
| import './style.scss'; | ||
|
|
||
| type BadgeLevel = 'default' | 'info' | 'success' | 'warning' | 'error'; | ||
|
|
||
| type CardFormProps = { | ||
| title: string; | ||
| description?: string; | ||
| badge?: { | ||
| text: string; | ||
| level?: BadgeLevel; | ||
| }; | ||
| /** JSX rendered in the header action area (buttons, etc.). */ | ||
| actions?: React.ReactNode; | ||
| /** When true, children are shown and the header border is removed. */ | ||
| isOpen?: boolean; | ||
| /** Called when the user presses Escape while the form is open. */ | ||
| onRequestClose?: () => void; | ||
| className?: string; | ||
| children?: React.ReactNode; | ||
| }; | ||
|
|
||
| const CardForm = ( { title, description, badge, actions, isOpen = false, onRequestClose, className, children }: CardFormProps ) => { | ||
| useEffect( () => { | ||
| if ( ! isOpen || ! onRequestClose ) { | ||
| return; | ||
| } | ||
| const handleKeyDown = ( event: KeyboardEvent ) => { | ||
| if ( event.key === 'Escape' ) { | ||
| onRequestClose(); | ||
| } | ||
| }; | ||
| document.addEventListener( 'keydown', handleKeyDown ); | ||
| return () => document.removeEventListener( 'keydown', handleKeyDown ); | ||
| }, [ isOpen, onRequestClose ] ); | ||
|
|
||
| return ( | ||
| <Card | ||
| className={ classnames( 'newspack-card-form', className, { | ||
| 'newspack-card-form--open': isOpen, | ||
| } ) } | ||
| __experimentalCoreCard | ||
| isSmall | ||
| __experimentalCoreProps={ { | ||
| hasHeaderBorder: ! isOpen, | ||
| header: ( | ||
| <HStack justify="space-between" style={ { width: '100%' } }> | ||
| <VStack spacing={ 0 } style={ { flex: 1, minWidth: 0 } }> | ||
| <h3 className="newspack-card-form__title">{ title }</h3> | ||
| { description && <p className="newspack-card-form__description">{ description }</p> } | ||
| </VStack> | ||
| <HStack spacing={ 2 } expanded={ false }> | ||
| { badge && <Badge text={ badge.text } level={ badge.level ?? 'success' } /> } | ||
| { actions } | ||
| </HStack> | ||
| </HStack> | ||
| ), | ||
| } } | ||
| > | ||
| { isOpen && children } | ||
| </Card> | ||
| ); | ||
| }; | ||
|
|
||
| export default CardForm; |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| /** | ||
| * CardForm | ||
| */ | ||
|
|
||
| @use "~@wordpress/base-styles/colors" as wp-colors; | ||
| @use "~@wordpress/base-styles/variables" as wp; | ||
|
|
||
| .newspack-card-form { | ||
| &__title { | ||
| font-size: wp.$font-size-large; | ||
| font-weight: 600; | ||
| line-height: wp.$font-line-height-large; | ||
| } | ||
|
|
||
| &__description { | ||
| color: wp-colors.$gray-700; | ||
| font-size: wp.$font-size-medium; | ||
| line-height: wp.$font-line-height-medium; | ||
| } | ||
| } |
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
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
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
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
Oops, something went wrong.
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.