diff --git a/projects/packages/publicize/_inc/components/media-section-v2/icons/sparkle.tsx b/projects/packages/publicize/_inc/components/media-section-v2/icons/sparkle.tsx new file mode 100644 index 000000000000..6b30eaa54a84 --- /dev/null +++ b/projects/packages/publicize/_inc/components/media-section-v2/icons/sparkle.tsx @@ -0,0 +1,10 @@ +const sparkle = ( + + + +); + +export default sparkle; diff --git a/projects/packages/publicize/_inc/components/media-section-v2/index.tsx b/projects/packages/publicize/_inc/components/media-section-v2/index.tsx index cc59e85db26b..505b32ead962 100644 --- a/projects/packages/publicize/_inc/components/media-section-v2/index.tsx +++ b/projects/packages/publicize/_inc/components/media-section-v2/index.tsx @@ -11,7 +11,6 @@ import { BaseControl, Button, ExternalLink } from '@wordpress/components'; import { useCallback, useMemo, useReducer, useRef } from '@wordpress/element'; import { applyFilters } from '@wordpress/hooks'; import { __ } from '@wordpress/i18n'; -import clsx from 'clsx'; import useFeaturedImage from '../../hooks/use-featured-image'; import useImageGeneratorConfig from '../../hooks/use-image-generator-config'; import useMediaDetails from '../../hooks/use-media-details'; @@ -395,38 +394,37 @@ export default function MediaSectionV2( { render={ renderMediaUpload } /> - { /* Show dropdown + preview when there's media */ } + { /* Show preview + dropdown when there's media */ } { previewData && ( <> - - { ( { open } ) => ( - - ) } - - { currentSource === 'sig' && ( - - ) } + featuredImageId={ featuredImageId } + onRemove={ handleRemove } + /> + { currentSource === 'sig' && ( +
+ +
+ ) } + ) } { currentSource === 'media-library' && ( diff --git a/projects/packages/publicize/_inc/components/media-section-v2/media-preview.tsx b/projects/packages/publicize/_inc/components/media-section-v2/media-preview.tsx index 3566abb752ef..92494db994f7 100644 --- a/projects/packages/publicize/_inc/components/media-section-v2/media-preview.tsx +++ b/projects/packages/publicize/_inc/components/media-section-v2/media-preview.tsx @@ -3,73 +3,38 @@ * Displays media preview */ -import { - Button, - Spinner, - __experimentalHStack as HStack, // eslint-disable-line @wordpress/no-unsafe-wp-apis -} from '@wordpress/components'; +import { Spinner } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import styles from './styles.module.scss'; import { MediaPreviewProps } from './types'; /** - * MediaPreview component matching WordPress core featured image pattern + * MediaPreview component * * @param {MediaPreviewProps} props - Component props * @return {JSX.Element|null} MediaPreview component */ -export default function MediaPreview( { - media, - isLoading = false, - onReplace, - onRemove, - disabled = false, - showRemove = true, -}: MediaPreviewProps ) { +export default function MediaPreview( { media, isLoading = false }: MediaPreviewProps ) { if ( ! media && ! isLoading ) { return null; } return ( -
-
- { media && - ! isLoading && - ( media.type === 'video' ? ( - - ) : ( - { - ) ) } - { isLoading && } -
- { ( media || isLoading ) && ( - - - { showRemove && ( - - ) } - - ) } +
+ { media && + ! isLoading && + ( media.type === 'video' ? ( + + ) : ( + { + ) ) } + { isLoading && }
); } diff --git a/projects/packages/publicize/_inc/components/media-section-v2/media-source-menu.tsx b/projects/packages/publicize/_inc/components/media-section-v2/media-source-menu.tsx index e60cb973146e..50b6088c1af8 100644 --- a/projects/packages/publicize/_inc/components/media-section-v2/media-source-menu.tsx +++ b/projects/packages/publicize/_inc/components/media-section-v2/media-source-menu.tsx @@ -3,7 +3,7 @@ * Displays a dropdown menu with grouped media source options */ -import { Button, Dropdown, MenuGroup } from '@wordpress/components'; +import { Button, Dropdown, MenuGroup, MenuItem } from '@wordpress/components'; import { useCallback, useMemo } from '@wordpress/element'; import { __, _x } from '@wordpress/i18n'; import MediaSourceMenuItem from './media-source-menu-item'; @@ -11,6 +11,38 @@ import styles from './styles.module.scss'; import { MediaSourceMenuProps } from './types'; import { getMediaSourceOptions } from './utils/media-source-options'; +/** + * Menu item for clearing media selection. + * + * @param {object} root0 - Component props. + * @param {boolean} root0.isSelected - Whether this option is currently active. + * @param {Function} root0.onRemove - Callback to clear media. + * @param {Function} root0.onClose - Callback to close the dropdown. + * @return {JSX.Element} NoMediaMenuItem component. + */ +function NoMediaMenuItem( { + isSelected, + onRemove, + onClose, +}: { + isSelected: boolean; + onRemove?: () => void; + onClose: () => void; +} ) { + const handleClick = useCallback( () => { + onRemove?.(); + onClose(); + }, [ onRemove, onClose ] ); + + return ( + + + { __( 'No media', 'jetpack-publicize-pkg' ) } + + + ); +} + /** * MediaSourceMenu component * @@ -24,15 +56,19 @@ export default function MediaSourceMenu( { onAiImageClick, disabled = false, featuredImageId, + onRemove, children, }: MediaSourceMenuProps ) { // Get options from function to ensure translations are loaded const options = useMemo( () => getMediaSourceOptions(), [] ); - // Group options by category + // Group options by category, hiding "Featured image" when no featured image exists const linkPreviewOptions = useMemo( - () => options.filter( opt => opt.group === 'link-preview' ), - [ options ] + () => + options.filter( + opt => opt.group === 'link-preview' && ( opt.id !== 'featured-image' || featuredImageId ) + ), + [ options, featuredImageId ] ); const attachmentOptions = useMemo( () => options.filter( opt => opt.group === 'attachment' ), @@ -44,6 +80,7 @@ export default function MediaSourceMenu( { <> { ! children && ( ), [] ); - // Render toggle for preview dropdown (wraps MediaPreview) - const renderPreviewToggle = useCallback( - ( { onToggle }: { onToggle: () => void } ) => ( - - ), - [ previewData, isLoading, handleRemove ] - ); - return (
{ /* Hidden MediaUpload component */ } @@ -299,14 +330,8 @@ export function BackgroundImagePicker( { { /* Source label */ }

{ getImageSourceLabel( imageType ) }

- { /* Image preview - reuses MediaPreview from media-section-v2 */ } - { previewData && ( - - ) } + { /* Image preview */ } + { previewData && } { /* Warning notice for missing featured image */ } { showFeaturedImageNotice && ( @@ -315,15 +340,13 @@ export function BackgroundImagePicker( { ) } - { /* No image state - show select button */ } - { ! previewData && ! isLoading && ( - - ) } + { /* Select image dropdown */ } +
); } diff --git a/projects/packages/publicize/_inc/components/unified-modal/edit-template/test/sidebar.test.tsx b/projects/packages/publicize/_inc/components/unified-modal/edit-template/test/sidebar.test.tsx index d1027e99f9d6..e7d45afbf78c 100644 --- a/projects/packages/publicize/_inc/components/unified-modal/edit-template/test/sidebar.test.tsx +++ b/projects/packages/publicize/_inc/components/unified-modal/edit-template/test/sidebar.test.tsx @@ -137,7 +137,7 @@ describe( 'Sidebar', () => { expect( screen.getByText( 'You are using your post featured image' ) ).toBeInTheDocument(); } ); - it( 'should show Replace and Remove buttons for image preview', () => { + it( 'should show Select image button for image preview', () => { render( { /> ); - expect( screen.getByText( 'Replace' ) ).toBeInTheDocument(); - expect( screen.getByText( 'Remove' ) ).toBeInTheDocument(); + expect( screen.getByRole( 'img', { name: 'Media preview' } ) ).toBeInTheDocument(); + expect( screen.getByRole( 'button', { name: 'Select image' } ) ).toBeInTheDocument(); } ); - it( 'should show image options when Replace button is clicked', async () => { + it( 'should show image options when Select image button is clicked', async () => { const user = userEvent.setup(); render( { /> ); - const replaceButton = screen.getByText( 'Replace' ); - await user.click( replaceButton ); + await user.click( screen.getByRole( 'button', { name: 'Select image' } ) ); await waitFor( () => { expect( screen.getByText( 'Featured Image' ) ).toBeInTheDocument(); @@ -182,14 +181,41 @@ describe( 'Sidebar', () => { /> ); - const replaceButton = screen.getByText( 'Replace' ); - await user.click( replaceButton ); + await user.click( screen.getByRole( 'button', { name: 'Select image' } ) ); await waitFor( () => { expect( screen.getByText( 'Default Image' ) ).toBeInTheDocument(); } ); } ); + it( 'should clear local image state when No image option is selected', async () => { + const user = userEvent.setup(); + render( + + ); + + await user.click( screen.getByRole( 'button', { name: 'Select image' } ) ); + await user.click( screen.getByRole( 'menuitem', { name: 'No image' } ) ); + + await waitFor( () => { + expect( mockSetLocalState ).toHaveBeenCalled(); + const updaterFunctions = mockSetLocalState.mock.calls.map( call => call[ 0 ] ); + const updatedStates = updaterFunctions.map( updater => updater( defaultLocalState ) ); + + expect( updatedStates ).toEqual( + expect.arrayContaining( [ + expect.objectContaining( { imageType: 'none' } ), + expect.objectContaining( { imageId: null } ), + ] ) + ); + } ); + } ); + it( 'should switch imageType from default to none when default image is deleted', async () => { ( useMediaDetails as jest.Mock ).mockReturnValue( [ {}, true ] ); render( diff --git a/projects/packages/publicize/changelog/update-social-media-picker-ux-improvements b/projects/packages/publicize/changelog/update-social-media-picker-ux-improvements new file mode 100644 index 000000000000..e97438a82bfe --- /dev/null +++ b/projects/packages/publicize/changelog/update-social-media-picker-ux-improvements @@ -0,0 +1,4 @@ +Significance: minor +Type: changed + +Social: Move media section buttons below preview to be more apparent.