diff --git a/projects/js-packages/publicize-components/changelog/add-social-edit-template-modal b/projects/js-packages/publicize-components/changelog/add-social-edit-template-modal new file mode 100644 index 0000000000000..fa19f4b15cd52 --- /dev/null +++ b/projects/js-packages/publicize-components/changelog/add-social-edit-template-modal @@ -0,0 +1,4 @@ +Significance: patch +Type: added + +Add edit template modal. diff --git a/projects/js-packages/publicize-components/src/components/media-section-v2/index.tsx b/projects/js-packages/publicize-components/src/components/media-section-v2/index.tsx index c0a22db7b2757..4484030e02271 100644 --- a/projects/js-packages/publicize-components/src/components/media-section-v2/index.tsx +++ b/projects/js-packages/publicize-components/src/components/media-section-v2/index.tsx @@ -7,6 +7,7 @@ import { ThemeProvider } from '@automattic/jetpack-components'; import { useAnalytics } from '@automattic/jetpack-shared-extension-utils'; import { MediaUpload } from '@wordpress/block-editor'; import { BaseControl, Button, Notice } from '@wordpress/components'; +import { useDispatch } from '@wordpress/data'; import { useCallback, useMemo, useRef } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import useFeaturedImage from '../../hooks/use-featured-image'; @@ -15,6 +16,7 @@ import useMediaDetails from '../../hooks/use-media-details'; import { SELECTABLE_MEDIA_TYPES } from '../../hooks/use-media-restrictions/restrictions'; import { usePostMeta } from '../../hooks/use-post-meta'; import useSigPreview from '../../hooks/use-sig-preview'; +import { store as socialStore } from '../../social-store'; import CustomMediaToggle from './custom-media-toggle'; import MediaPreview from './media-preview'; import MediaSourceMenu, { getMediaSourceDescription } from './media-source-menu'; @@ -78,6 +80,7 @@ export default function MediaSectionV2( { const { isEnabled: sigEnabled } = useImageGeneratorConfig(); const { attachedMedia, imageGeneratorSettings, mediaSource, updateJetpackSocialOptions } = usePostMeta(); + const { openUnifiedModal } = useDispatch( socialStore ); // Get SIG preview URL when SIG is enabled const { url: sigPreviewUrl, isLoading: sigIsLoading } = useSigPreview( sigEnabled ); @@ -85,6 +88,11 @@ export default function MediaSectionV2( { // Ref to store the MediaUpload open function const openMediaLibraryRef = useRef< () => void >( () => {} ); + // Open edit template modal + const handleEditTemplateClick = useCallback( () => { + openUnifiedModal( { initialPath: '/edit-template', isScreenLocked: true } ); + }, [ openUnifiedModal ] ); + // Determine current media source // Priority 1: Explicit user choice (if media_source is set) // Priority 2: Detect from existing data (backward compatibility) @@ -291,7 +299,7 @@ export default function MediaSectionV2( { + ); +} diff --git a/projects/js-packages/publicize-components/src/components/social-image-generator/panel/index.js b/projects/js-packages/publicize-components/src/components/social-image-generator/panel/index.js index ecd1d321b8adb..bdb1a7302b6d3 100644 --- a/projects/js-packages/publicize-components/src/components/social-image-generator/panel/index.js +++ b/projects/js-packages/publicize-components/src/components/social-image-generator/panel/index.js @@ -1,4 +1,5 @@ import { ThemeProvider, useGlobalNotices } from '@automattic/jetpack-components'; +import { siteHasFeature } from '@automattic/jetpack-script-data'; import { ToggleControl, Button, @@ -9,7 +10,9 @@ import { useCallback, useState } from '@wordpress/element'; import { __, _x } from '@wordpress/i18n'; import useImageGeneratorConfig from '../../../hooks/use-image-generator-config'; import { useSaveImageToLibrary } from '../../../hooks/use-save-image-to-library'; +import { features } from '../../../utils'; import GeneratedImagePreview from '../../generated-image-preview'; +import { EditTemplate } from './edit-template'; import SocialImageGeneratorSettingsModal from './modal'; const SocialImageGeneratorPanel = () => { @@ -92,6 +95,10 @@ const SocialImageGeneratorPanel = () => { ) : __( 'Save to media library', 'jetpack-publicize-components' ) } + { siteHasFeature( features.UNIFIED_UI_V1 ) ? ( + // TODO: Replace EditTemplate button with full image UI controls integrated with the sidebar + + ) : null } ) } diff --git a/projects/js-packages/publicize-components/src/components/social-image-generator/template-picker/picker/index.js b/projects/js-packages/publicize-components/src/components/social-image-generator/template-picker/picker/index.js index ceafe5d3236c3..73bf6488eb776 100644 --- a/projects/js-packages/publicize-components/src/components/social-image-generator/template-picker/picker/index.js +++ b/projects/js-packages/publicize-components/src/components/social-image-generator/template-picker/picker/index.js @@ -11,12 +11,13 @@ import TEMPLATES_DATA from './templates.js'; * The pure template picker component. Does not save the template changes, just sends it back to the parent component, * with the onTemplateSelected callback. * - * @param {{value: string|null, onTemplateSelected: Function}} props - The component props: - * Value is the name of the currently selected template, onTemplateSelected is a function that - * will be called when a template is selected. Receives the name of the selected template as an argument. + * @param {{value: string|null, onTemplateSelected: Function, className: string}} props - The component props: + * Value is the name of the currently selected template, onTemplateSelected is a function that + * will be called when a template is selected. Receives the name of the selected template as an argument. + * className is an optional additional class name to apply to the container. * @return {ReactNode} - The component's rendered output. */ -const TemplatePicker = ( { value = null, onTemplateSelected = null } ) => { +const TemplatePicker = ( { value = null, onTemplateSelected = null, className = null } ) => { const onTemplateClicked = useCallback( event => { const templateName = event.target.id; @@ -26,7 +27,7 @@ const TemplatePicker = ( { value = null, onTemplateSelected = null } ) => { ); return ( -
+
{ TEMPLATES_DATA.map( template => ( + ), + [] + ); + + // Render toggle for preview dropdown (wraps MediaPreview) + const renderPreviewToggle = useCallback( + ( { onToggle }: { onToggle: () => void } ) => ( + + ), + [ previewData, isLoading, handleRemove ] + ); + + return ( +
+ { /* Hidden MediaUpload component */ } + + + { /* Source label */ } +

{ getImageSourceLabel( imageType ) }

+ + { /* Image preview - reuses MediaPreview from media-section-v2 */ } + { previewData && ( + + ) } + + { /* Warning notice for missing featured image */ } + { showFeaturedImageNotice && ( + + { __( 'Your post does not have a featured image.', 'jetpack-publicize-components' ) } + + ) } + + { /* No image state - show select button */ } + { ! previewData && ! isLoading && ( + + ) } +
+ ); +} diff --git a/projects/js-packages/publicize-components/src/components/unified-modal/edit-template/content.tsx b/projects/js-packages/publicize-components/src/components/unified-modal/edit-template/content.tsx new file mode 100644 index 0000000000000..f83d812b21af9 --- /dev/null +++ b/projects/js-packages/publicize-components/src/components/unified-modal/edit-template/content.tsx @@ -0,0 +1,51 @@ +/** + * Content component for Edit Template Modal + * + * Right side of the modal containing the live preview + */ + +import { Spinner } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import useSigPreview from '../../../hooks/use-sig-preview'; +import styles from './styles.module.scss'; +import { LocalState } from './types'; + +type ContentProps = { + localState: LocalState; +}; + +/** + * Content component with live preview + * + * @param props - Component props + * @param props.localState - Local state + * @return Content component + */ +export function Content( { localState }: ContentProps ) { + const { url, isLoading } = useSigPreview( true, { + shouldDebounce: true, + imageType: localState.imageType, + imageId: localState.imageId, + customText: localState.customText, + template: localState.template || undefined, + font: localState.font, + } ); + + return ( +
+
+ { isLoading ? ( + + ) : ( + url && ( + { + ) + ) } +
+
+ ); +} diff --git a/projects/js-packages/publicize-components/src/components/unified-modal/edit-template/sidebar.tsx b/projects/js-packages/publicize-components/src/components/unified-modal/edit-template/sidebar.tsx new file mode 100644 index 0000000000000..6d1bc672893ed --- /dev/null +++ b/projects/js-packages/publicize-components/src/components/unified-modal/edit-template/sidebar.tsx @@ -0,0 +1,136 @@ +/** + * Sidebar component for Edit Template Modal + * + * Contains all control sections: Background Image, Template, Text, Font + */ + +import { SelectControl, TextControl } from '@wordpress/components'; +import { useCallback } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import { type ImageType } from '../../../hooks/use-sig-preview/utils'; +import { useSocialImageFontOptions } from '../../../hooks/use-social-image-font-options'; +import TemplatePicker from '../../social-image-generator/template-picker/picker'; +import { BackgroundImagePicker } from './background-image-picker'; +import styles from './styles.module.scss'; +import { LocalState } from './types'; + +type SidebarProps = { + localState: LocalState; + setLocalState: React.Dispatch< React.SetStateAction< LocalState > >; + defaultImageId: number | null; + featuredImageId: number | null; +}; + +/** + * Sidebar component with all control sections + * + * @param {SidebarProps} props - Component props + * @return Sidebar component + */ +export function Sidebar( { + localState, + setLocalState, + defaultImageId, + featuredImageId, +}: SidebarProps ) { + const { isLoading: isLoadingFontOptions, fontOptions } = useSocialImageFontOptions(); + + const handleImageTypeChange = useCallback( + ( value: ImageType ) => { + setLocalState( prev => ( { ...prev, imageType: value } ) ); + }, + [ setLocalState ] + ); + + const handleImageIdChange = useCallback( + ( id: number | null ) => { + setLocalState( prev => ( { ...prev, imageId: id } ) ); + }, + [ setLocalState ] + ); + + const handleCustomTextChange = useCallback( + ( value: string ) => { + setLocalState( prev => ( { ...prev, customText: value } ) ); + }, + [ setLocalState ] + ); + + const handleTemplateChange = useCallback( + ( value: string ) => { + setLocalState( prev => ( { ...prev, template: value } ) ); + }, + [ setLocalState ] + ); + + const handleFontChange = useCallback( + ( value: string ) => { + setLocalState( prev => ( { ...prev, font: value } ) ); + }, + [ setLocalState ] + ); + + return ( +
+ { /* Background Image Section */ } +
+
+ { __( 'Background image', 'jetpack-publicize-components' ) } +
+ +
+ + { /* Template Section */ } +
+
+ { __( 'Template', 'jetpack-publicize-components' ) } +
+ +
+ + { /* Text Section */ } +
+
+ { __( 'Text', 'jetpack-publicize-components' ) } +
+ +
+ + { /* Font Section */ } +
+
+ { __( 'Font', 'jetpack-publicize-components' ) } +
+ +
+
+ ); +} diff --git a/projects/js-packages/publicize-components/src/components/unified-modal/edit-template/styles.module.scss b/projects/js-packages/publicize-components/src/components/unified-modal/edit-template/styles.module.scss new file mode 100644 index 0000000000000..877e8f72b0d1f --- /dev/null +++ b/projects/js-packages/publicize-components/src/components/unified-modal/edit-template/styles.module.scss @@ -0,0 +1,86 @@ +@use "@automattic/jetpack-base-styles/gutenberg-base-styles" as gb; + +.sidebar { + padding: 1.5rem; + overflow-y: auto; +} + +.section { + margin-bottom: 24px; + + &:last-child { + margin-bottom: 0; + } +} + +.sectionLabel { + font-size: 11px; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.5px; + color: gb.$gray-700; + margin-bottom: 12px; +} + +.content { + flex: 1; + background-color: gb.$gray-100; + display: flex; + align-items: center; + justify-content: center; + padding: 24px; + overflow: auto; +} + +// Preview container for the generated image +.preview { + position: relative; + display: flex; + align-items: center; + justify-content: center; + width: 100%; + max-width: 800px; + aspect-ratio: 1200 / 630; // SIG image aspect-ratio + background-color: gb.$white; +} + +.previewImage { + max-width: 100%; + max-height: 100%; + width: auto; + height: auto; + object-fit: contain; +} + +// Background Image Picker styles +.backgroundPicker { + display: flex; + flex-direction: column; + gap: 12px; +} + +.sourceLabel { + font-size: 13px; + color: gb.$gray-700; + margin: 0; +} + +.selectDropdown { + width: 100%; +} + +.selectButton { + width: 100%; + justify-content: center; +} + +.notice { + margin: 0; +} + +// Template picker grid - 2x2 layout in the modal sidebar +// Uses !important to override the picker's default responsive grid +.templateGrid { + grid-template-columns: repeat(2, 1fr) !important; + grid-gap: 8px !important; +} diff --git a/projects/js-packages/publicize-components/src/components/unified-modal/edit-template/test/sidebar.test.tsx b/projects/js-packages/publicize-components/src/components/unified-modal/edit-template/test/sidebar.test.tsx new file mode 100644 index 0000000000000..6eaeb77e8561a --- /dev/null +++ b/projects/js-packages/publicize-components/src/components/unified-modal/edit-template/test/sidebar.test.tsx @@ -0,0 +1,282 @@ +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import useMediaDetails from '../../../../hooks/use-media-details'; +import * as fontOptions from '../../../../hooks/use-social-image-font-options'; +import { Sidebar } from '../sidebar'; +import { LocalState } from '../types'; + +// Mock dependencies +jest.mock( '../../../../hooks/use-media-details', () => jest.fn() ); +jest.spyOn( fontOptions, 'useSocialImageFontOptions' ).mockImplementation(); + +// Mock TemplatePicker +jest.mock( '../../../social-image-generator/template-picker/picker', () => { + // eslint-disable-next-line jsdoc/require-jsdoc + function mockOnTemplateSelected( onTemplateSelected: ( template: string ) => void ) { + return function handleClick() { + onTemplateSelected( 'new-template' ); + }; + } + + return function MockTemplatePicker( { + onTemplateSelected, + }: { + onTemplateSelected: ( template: string ) => void; + } ) { + return ( +
+ +
+ ); + }; +} ); + +// Mock styles +jest.mock( '../styles.module.scss', () => ( { + sidebar: 'sidebar', + section: 'section', + sectionLabel: 'sectionLabel', + backgroundPicker: 'backgroundPicker', + sourceLabel: 'sourceLabel', + selectDropdown: 'selectDropdown', + selectButton: 'selectButton', + notice: 'notice', + templateGrid: 'templateGrid', +} ) ); + +const mockSetLocalState = jest.fn(); + +const defaultLocalState: LocalState = { + imageId: null, + imageType: 'featured', + customText: '', + template: 'highway', + font: '', +}; + +const setupMocks = () => { + ( useMediaDetails as jest.Mock ).mockReturnValue( [ + { + mediaData: { + sourceUrl: 'https://example.com/image.jpg', + }, + }, + ] ); + + ( fontOptions.useSocialImageFontOptions as jest.Mock ).mockReturnValue( { + isLoading: false, + fontOptions: [ + { label: 'Default', value: '' }, + { label: 'Font 1', value: 'font-1' }, + { label: 'Font 2', value: 'font-2' }, + ], + } ); +}; + +describe( 'Sidebar', () => { + beforeEach( () => { + jest.clearAllMocks(); + setupMocks(); + } ); + + it( 'should render all sections', () => { + render( + + ); + + // Check sections + expect( screen.getByText( 'Background image' ) ).toBeInTheDocument(); + expect( screen.getByText( 'Template' ) ).toBeInTheDocument(); + expect( screen.getByText( 'Text' ) ).toBeInTheDocument(); + expect( screen.getByText( 'Font' ) ).toBeInTheDocument(); + } ); + + it( 'should render template picker', () => { + render( + + ); + + expect( screen.getByTestId( 'template-picker' ) ).toBeInTheDocument(); + } ); + + it( 'should render custom text input', () => { + render( + + ); + + expect( screen.getByPlaceholderText( 'Custom text' ) ).toBeInTheDocument(); + } ); + + describe( 'Background image picker', () => { + it( 'should show current image source label', () => { + render( + + ); + + expect( screen.getByText( 'You are using your post featured image' ) ).toBeInTheDocument(); + } ); + + it( 'should show Replace and Remove buttons for image preview', () => { + render( + + ); + + expect( screen.getByText( 'Replace' ) ).toBeInTheDocument(); + expect( screen.getByText( 'Remove' ) ).toBeInTheDocument(); + } ); + + it( 'should show image options when Replace button is clicked', async () => { + const user = userEvent.setup(); + render( + + ); + + const replaceButton = screen.getByText( 'Replace' ); + await user.click( replaceButton ); + + await waitFor( () => { + expect( screen.getByText( 'Featured Image' ) ).toBeInTheDocument(); + expect( screen.getByText( 'Media Library' ) ).toBeInTheDocument(); + } ); + } ); + + it( 'should show Default Image option when defaultImageId is provided', async () => { + const user = userEvent.setup(); + render( + + ); + + const replaceButton = screen.getByText( 'Replace' ); + await user.click( replaceButton ); + + await waitFor( () => { + expect( screen.getByText( 'Default Image' ) ).toBeInTheDocument(); + } ); + } ); + + it( 'should show warning notice when featured image is not set', () => { + ( useMediaDetails as jest.Mock ).mockReturnValue( [ {} ] ); + render( + + ); + + const notices = screen.getAllByText( 'Your post does not have a featured image.' ); + expect( notices.length ).toBeGreaterThanOrEqual( 1 ); + } ); + } ); + + describe( 'Custom text input', () => { + it( 'should call setLocalState when custom text is changed', async () => { + const user = userEvent.setup(); + render( + + ); + + const textInput = screen.getByPlaceholderText( 'Custom text' ); + await user.type( textInput, 'M' ); + + await waitFor( () => { + expect( mockSetLocalState ).toHaveBeenCalled(); + // Get the updater function and call it with previous state + const updater = mockSetLocalState.mock.calls[ 0 ][ 0 ]; + const newState = updater( defaultLocalState ); + expect( newState ).toEqual( + expect.objectContaining( { + customText: 'M', + } ) + ); + } ); + } ); + } ); + + describe( 'Template picker', () => { + it( 'should call setLocalState when template is selected', async () => { + const user = userEvent.setup(); + render( + + ); + + const pickButton = screen.getByText( 'Pick Template' ); + await user.click( pickButton ); + + await waitFor( () => { + expect( mockSetLocalState ).toHaveBeenCalled(); + // Get the updater function and call it with previous state + const updater = mockSetLocalState.mock.calls[ 0 ][ 0 ]; + const newState = updater( defaultLocalState ); + expect( newState ).toEqual( + expect.objectContaining( { + template: 'new-template', + } ) + ); + } ); + } ); + } ); + + describe( 'Font selection', () => { + it( 'should render font select control', () => { + render( + + ); + + // Check for a combobox element (the font dropdown) + const fontSelect = screen.getByRole( 'combobox' ); + expect( fontSelect ).toBeInTheDocument(); + } ); + } ); +} ); diff --git a/projects/js-packages/publicize-components/src/components/unified-modal/edit-template/types.ts b/projects/js-packages/publicize-components/src/components/unified-modal/edit-template/types.ts new file mode 100644 index 0000000000000..0d8647d0865c1 --- /dev/null +++ b/projects/js-packages/publicize-components/src/components/unified-modal/edit-template/types.ts @@ -0,0 +1,26 @@ +export type LocalState = { + /** + * ID of the image in the generated image. + */ + imageId: number | null; + + /** + * Type of the image in the generated image. + */ + imageType: 'default' | 'featured' | 'custom' | 'none'; + + /** + * Custom text for the generated image. + */ + customText: string; + + /** + * Template for the generated image. + */ + template: string | null; + + /** + * Font for the generated image. + */ + font: string; +}; diff --git a/projects/js-packages/publicize-components/src/components/unified-modal/edit-template/use-modal-screen.tsx b/projects/js-packages/publicize-components/src/components/unified-modal/edit-template/use-modal-screen.tsx new file mode 100644 index 0000000000000..34107b170db84 --- /dev/null +++ b/projects/js-packages/publicize-components/src/components/unified-modal/edit-template/use-modal-screen.tsx @@ -0,0 +1,71 @@ +import { useDispatch, useSelect } from '@wordpress/data'; +import { __ } from '@wordpress/i18n'; +import { useCallback, useMemo, useState } from 'react'; +import useFeaturedImage from '../../../hooks/use-featured-image'; +import useImageGeneratorConfig from '../../../hooks/use-image-generator-config'; +import { store as socialStore } from '../../../social-store'; +import { ScreenDetails } from '../types'; +import { Content } from './content'; +import { Sidebar } from './sidebar'; +import { LocalState } from './types'; + +/** + * Hook to get modal screen details for edit template. + * + * @return screen details + */ +export function useModalScreen(): ScreenDetails { + const isScreenLocked = useSelect( + select => select( socialStore ).isUnifiedModalScreenLocked(), + [] + ); + const { closeUnifiedModal } = useDispatch( socialStore ); + + const featuredImageId = useFeaturedImage(); + const { customText, imageType, imageId, template, font, defaultImageId, updateSettings } = + useImageGeneratorConfig(); + + const [ localState, setLocalState ] = useState< LocalState >( () => ( { + imageId: imageId ?? null, + imageType: ( imageType ?? 'featured' ) as LocalState[ 'imageType' ], + customText: customText ?? '', + template: template ?? null, + font: font ?? '', + } ) ); + + const handleSave = useCallback( () => { + updateSettings( { + image_type: localState.imageType, + image_id: localState.imageId, + custom_text: localState.customText, + template: localState.template, + font: localState.font, + } ); + closeUnifiedModal(); + }, [ localState, updateSettings, closeUnifiedModal ] ); + + return useMemo( + () => ( { + path: '/edit-template', + title: __( 'Edit social image template', 'jetpack-publicize-components' ), + isScreenLocked, + sidebar: ( + + ), + content: , + footerActions: [ + { + text: __( 'Save Changes', 'jetpack-publicize-components' ), + variant: 'primary', + onClick: handleSave, + }, + ], + } ), + [ localState, setLocalState, isScreenLocked, defaultImageId, featuredImageId, handleSave ] + ); +} diff --git a/projects/js-packages/publicize-components/src/components/unified-modal/edit-template/utils.ts b/projects/js-packages/publicize-components/src/components/unified-modal/edit-template/utils.ts new file mode 100644 index 0000000000000..8eb6c6f615257 --- /dev/null +++ b/projects/js-packages/publicize-components/src/components/unified-modal/edit-template/utils.ts @@ -0,0 +1,12 @@ +import { LocalState } from './types'; + +export const getLocalImageType = ( + featuredImageId: number, + defaultImageId: number +): LocalState[ 'imageType' ] => { + if ( ! featuredImageId && defaultImageId ) { + return 'default'; + } + + return 'featured'; +}; diff --git a/projects/js-packages/publicize-components/src/components/unified-modal/index.tsx b/projects/js-packages/publicize-components/src/components/unified-modal/index.tsx index 7f7fc2e1ef349..3dcd69c3b6efc 100644 --- a/projects/js-packages/publicize-components/src/components/unified-modal/index.tsx +++ b/projects/js-packages/publicize-components/src/components/unified-modal/index.tsx @@ -2,6 +2,7 @@ import { NavigatorModal, ThemeProvider } from '@automattic/jetpack-components'; import { useDispatch, useSelect } from '@wordpress/data'; import { useCallback } from 'react'; import { store as socialStore } from '../../social-store'; +import { useModalScreen as useEditTemplateModalScreen } from './edit-template/use-modal-screen'; import { useModalScreen as useSocialPostPreviewModalScreen } from './social-post-preview/use-modal-screen'; /** @@ -13,6 +14,7 @@ function ThemedUnifiedModal() { const initialPath = useSelect( select => select( socialStore ).getUnifiedModalInitialPath(), [] ); const socialPostPreviewModalScreen = useSocialPostPreviewModalScreen(); + const editTemplateModalScreen = useEditTemplateModalScreen(); const { closeUnifiedModal } = useDispatch( socialStore ); @@ -24,6 +26,7 @@ function ThemedUnifiedModal() { + { /* Generate with AI screen goes here */ } diff --git a/projects/js-packages/publicize-components/src/components/unified-modal/social-post-preview/sidebar.tsx b/projects/js-packages/publicize-components/src/components/unified-modal/social-post-preview/sidebar.tsx index 9927ca0bfa599..bfff8e18ddaf7 100644 --- a/projects/js-packages/publicize-components/src/components/unified-modal/social-post-preview/sidebar.tsx +++ b/projects/js-packages/publicize-components/src/components/unified-modal/social-post-preview/sidebar.tsx @@ -1,5 +1,5 @@ import { useAnalytics } from '@automattic/jetpack-shared-extension-utils'; -import { Panel, PanelBody, PanelRow } from '@wordpress/components'; +import { Button, Panel, PanelBody, PanelRow, useNavigator } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { useCallback } from 'react'; import useSocialMediaConnections from '../../../hooks/use-social-media-connections'; @@ -43,6 +43,12 @@ export function Sidebar( { onClickConnection, selectedConnection }: SidebarProps [ selectedConnection ] ); + const navigator = useNavigator(); + + const gotoEditTemplate = useCallback( () => { + navigator.goTo( '/edit-template' ); + }, [ navigator ] ); + return (
@@ -61,6 +67,9 @@ export function Sidebar( { onClickConnection, selectedConnection }: SidebarProps > { /* TODO: Replace Edit template button with full image editor UI when SIG integration is complete. */ } + diff --git a/projects/js-packages/publicize-components/src/hooks/use-image-generator-config/index.js b/projects/js-packages/publicize-components/src/hooks/use-image-generator-config/index.js index 5877ffefde872..d444600effbdf 100644 --- a/projects/js-packages/publicize-components/src/hooks/use-image-generator-config/index.js +++ b/projects/js-packages/publicize-components/src/hooks/use-image-generator-config/index.js @@ -26,6 +26,7 @@ const getCurrentSettings = ( sigSettings, isPostPublished ) => ( { * @property {number} defaultImageId - Optional. ID of the default image. * @property {Function} setIsEnabled - Callback to enable or disable the image generator for a post. * @property {Function} updateProperty - Callback to update various SIG settings. + * @property {Function} updateSettings - Callback to update multiple SIG settings at once. * @property {Function} setToken - Callback to change the token. */