From ea61dfd78457c89e9463f31eb0418e907517f86d Mon Sep 17 00:00:00 2001 From: Mikael Korpela Date: Wed, 8 Oct 2025 15:55:12 +0300 Subject: [PATCH 1/8] Componentise view actions and use them in header on mobile --- .../components/response-actions/index.tsx | 155 ++++++++++++++++++ .../components/response-navigation/index.tsx | 63 +++++++ .../src/dashboard/inbox/dataviews/index.js | 14 ++ .../forms/src/dashboard/inbox/response.js | 25 ++- 4 files changed, 251 insertions(+), 6 deletions(-) create mode 100644 projects/packages/forms/src/dashboard/components/response-actions/index.tsx create mode 100644 projects/packages/forms/src/dashboard/components/response-navigation/index.tsx diff --git a/projects/packages/forms/src/dashboard/components/response-actions/index.tsx b/projects/packages/forms/src/dashboard/components/response-actions/index.tsx new file mode 100644 index 0000000000000..8875f1deb8dc3 --- /dev/null +++ b/projects/packages/forms/src/dashboard/components/response-actions/index.tsx @@ -0,0 +1,155 @@ +/** + * External dependencies + */ +import { Button } from '@wordpress/components'; +import { useRegistry } from '@wordpress/data'; +import { useCallback, useState } from '@wordpress/element'; +/** + * Internal dependencies + */ +import { + markAsSpamAction, + markAsNotSpamAction, + moveToTrashAction, + restoreAction, + deleteAction, +} from '../../inbox/dataviews/actions'; +/** + * Types + */ +import type { FormResponse } from '../../../types'; + +type ResponseNavigationProps = { + onActionComplete?: ( id: string ) => void; + response: FormResponse; +}; + +const ResponseActions = ( { + onActionComplete, + response, +}: ResponseNavigationProps ): JSX.Element => { + const [ isMarkingAsSpam, setIsMarkingAsSpam ] = useState( false ); + const [ isMarkingAsNotSpam, setIsMarkingAsNotSpam ] = useState( false ); + const [ isMovingToTrash, setIsMovingToTrash ] = useState( false ); + const [ isRestoring, setIsRestoring ] = useState( false ); + const [ isDeleting, setIsDeleting ] = useState( false ); + + const registry = useRegistry(); + + const handleMarkAsSpam = useCallback( async () => { + setIsMarkingAsSpam( true ); + await markAsSpamAction.callback( [ response ], { registry } ); + setIsMarkingAsSpam( false ); + onActionComplete?.( response.id.toString() ); + }, [ response, registry, onActionComplete ] ); + + const handleMarkAsNotSpam = useCallback( async () => { + setIsMarkingAsNotSpam( true ); + await markAsNotSpamAction.callback( [ response ], { registry } ); + setIsMarkingAsNotSpam( false ); + onActionComplete?.( response.id.toString() ); + }, [ response, registry, onActionComplete ] ); + + const handleMoveToTrash = useCallback( async () => { + setIsMovingToTrash( true ); + await moveToTrashAction.callback( [ response ], { registry } ); + setIsMovingToTrash( false ); + onActionComplete?.( response.id.toString() ); + }, [ response, registry, onActionComplete ] ); + + const handleRestore = useCallback( async () => { + setIsRestoring( true ); + await restoreAction.callback( [ response ], { registry } ); + setIsRestoring( false ); + onActionComplete?.( response.id.toString() ); + }, [ response, registry, onActionComplete ] ); + + const handleDelete = useCallback( async () => { + setIsDeleting( true ); + await deleteAction.callback( [ response ], { registry } ); + setIsDeleting( false ); + onActionComplete?.( response.id.toString() ); + }, [ response, registry, onActionComplete ] ); + + switch ( response.status ) { + case 'spam': + return ( + <> + + + + ); + + case 'trash': + return ( + <> + + + + ); + + default: // 'publish' (inbox) or any other status + return ( + <> + + + + ); + } +}; + +export default ResponseActions; diff --git a/projects/packages/forms/src/dashboard/components/response-navigation/index.tsx b/projects/packages/forms/src/dashboard/components/response-navigation/index.tsx new file mode 100644 index 0000000000000..81f93a3c604b1 --- /dev/null +++ b/projects/packages/forms/src/dashboard/components/response-navigation/index.tsx @@ -0,0 +1,63 @@ +/** + * External dependencies + */ +import { Button } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { close, chevronLeft, chevronRight } from '@wordpress/icons'; + +type ResponseNavigationProps = { + hasNext: boolean; + hasPrevious: boolean; + onClose: ( () => void ) | null; + onNext: () => void; + onPrevious: () => void; +}; + +const ResponseNavigation = ( { + hasNext, + hasPrevious, + onClose, + onNext, + onPrevious, +}: ResponseNavigationProps ): JSX.Element => { + return ( + <> + { onPrevious && ( + + ) } + { onNext && ( + + ) } + { onClose && ( + + ) } + + ); +}; + +export default ResponseNavigation; diff --git a/projects/packages/forms/src/dashboard/inbox/dataviews/index.js b/projects/packages/forms/src/dashboard/inbox/dataviews/index.js index 59eda66421561..dfdde5c882946 100644 --- a/projects/packages/forms/src/dashboard/inbox/dataviews/index.js +++ b/projects/packages/forms/src/dashboard/inbox/dataviews/index.js @@ -20,6 +20,8 @@ import { useSearchParams } from 'react-router'; * Internal dependencies */ import InboxStatusToggle from '../../components/inbox-status-toggle'; +import ResponseActions from '../../components/response-actions'; +import ResponseNavigation from '../../components/response-navigation'; import useInboxData from '../../hooks/use-inbox-data'; import EmptyResponses from '../empty-responses'; import InboxResponse from '../response'; @@ -475,6 +477,18 @@ const SingleResponse = ( { title={ __( 'Response', 'jetpack-forms' ) } size="medium" onRequestClose={ onRequestClose } + headerActions={ + <> + + + + } > { contents } diff --git a/projects/packages/forms/src/dashboard/inbox/response.js b/projects/packages/forms/src/dashboard/inbox/response.js index 964e90232f627..e8c76be6ba0cf 100644 --- a/projects/packages/forms/src/dashboard/inbox/response.js +++ b/projects/packages/forms/src/dashboard/inbox/response.js @@ -27,6 +27,8 @@ import clsx from 'clsx'; import useFormsConfig from '../../hooks/use-forms-config'; import CopyClipboardButton from '../components/copy-clipboard-button'; import Gravatar from '../components/gravatar'; +import ResponseActions from '../components/response-actions'; +import ResponseNavigation from '../components/response-navigation'; import { useMarkAsSpam } from '../hooks/use-mark-as-spam'; import { markAsSpamAction, @@ -206,6 +208,7 @@ const InboxResponse = ( { const [ hasMarkedSelfAsRead, setHasMarkedSelfAsRead ] = useState( false ); const { editEntityRecord } = useDispatch( 'core' ); + const registry = useRegistry(); const formsConfig = useFormsConfig(); const emptyTrashDays = formsConfig?.emptyTrashDays ?? 0; @@ -214,8 +217,6 @@ const InboxResponse = ( { const { isConfirmDialogOpen, onConfirmMarkAsSpam, onCancelMarkAsSpam } = useMarkAsSpam( response ); - const registry = useRegistry(); - const ref = useRef( undefined ); const openFilePreview = useCallback( @@ -600,10 +601,22 @@ const InboxResponse = ( { return ( <> - - { renderActionButtons() } - { renderNavigationButtons() } - + { ! isMobile && ( + + + + + + + + + ) }
From 3f063fdabb22ecbd6ed42ac385386c8d1fb93d4f Mon Sep 17 00:00:00 2001 From: Mikael Korpela Date: Wed, 8 Oct 2025 15:55:43 +0300 Subject: [PATCH 2/8] changelog --- .../packages/forms/changelog/update-forms-view-actions-modal | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 projects/packages/forms/changelog/update-forms-view-actions-modal diff --git a/projects/packages/forms/changelog/update-forms-view-actions-modal b/projects/packages/forms/changelog/update-forms-view-actions-modal new file mode 100644 index 0000000000000..0cf8bffec06fd --- /dev/null +++ b/projects/packages/forms/changelog/update-forms-view-actions-modal @@ -0,0 +1,4 @@ +Significance: minor +Type: changed + +Forms: move view actions to modal header on mobile From f32a043b72a909ea7f57cc9daa3dfcc970ce2d27 Mon Sep 17 00:00:00 2001 From: Mikael Korpela Date: Thu, 9 Oct 2025 10:57:33 +0300 Subject: [PATCH 3/8] Rebase unread/read action --- .../components/response-actions/index.tsx | 43 ++++ .../forms/src/dashboard/inbox/response.js | 225 +----------------- projects/packages/forms/src/types/index.ts | 2 + 3 files changed, 56 insertions(+), 214 deletions(-) diff --git a/projects/packages/forms/src/dashboard/components/response-actions/index.tsx b/projects/packages/forms/src/dashboard/components/response-actions/index.tsx index 8875f1deb8dc3..567853b48c8a8 100644 --- a/projects/packages/forms/src/dashboard/components/response-actions/index.tsx +++ b/projects/packages/forms/src/dashboard/components/response-actions/index.tsx @@ -13,6 +13,8 @@ import { moveToTrashAction, restoreAction, deleteAction, + markAsReadAction, + markAsUnreadAction, } from '../../inbox/dataviews/actions'; /** * Types @@ -21,11 +23,13 @@ import type { FormResponse } from '../../../types'; type ResponseNavigationProps = { onActionComplete?: ( id: string ) => void; + onMarkAsRead?: ( id: number | false ) => void; response: FormResponse; }; const ResponseActions = ( { onActionComplete, + onMarkAsRead, response, }: ResponseNavigationProps ): JSX.Element => { const [ isMarkingAsSpam, setIsMarkingAsSpam ] = useState( false ); @@ -71,10 +75,47 @@ const ResponseActions = ( { onActionComplete?.( response.id.toString() ); }, [ response, registry, onActionComplete ] ); + const handleMarkAsRead = useCallback( () => { + markAsReadAction.callback( [ response ], { registry } ); + }, [ response, registry ] ); + + const handleMarkAsUnread = useCallback( () => { + onMarkAsRead( response.id ); + markAsUnreadAction.callback( [ response ], { registry } ); + }, [ response, registry, onMarkAsRead ] ); + + const readUnreadButtons = onMarkAsRead ? ( + <> + { response.is_unread && ( + + ) } + { ! response.is_unread && ( + + ) } + + ) : null; + switch ( response.status ) { case 'spam': return ( <> + { readUnreadButtons } - ) } - { ! response.is_unread && ( - - ) } - - ); - - const renderActionButtons = () => { - switch ( response.status ) { - case 'spam': - return ( - <> - { readUnreadButtons } - - - - ); - - case 'trash': - return ( - <> - { readUnreadButtons } - - - - ); - - default: // 'publish' (inbox) or any other status - return ( - <> - { readUnreadButtons } - - - - ); - } - }; - - const renderNavigationButtons = () => { - return ( - <> - { onPrevious && ( - - ) } - { onNext && ( - - ) } - { ! isMobile && onClose && ( - - ) } - - ); - }; - const renderFieldValue = value => { if ( isImageSelectField( value ) ) { return ( @@ -579,6 +368,10 @@ const InboxResponse = ( { } ); }, [ response, editEntityRecord, hasMarkedSelfAsRead ] ); + const onMarkAsRead = useCallback( responseId => { + setHasMarkedSelfAsRead( responseId ); + }, [] ); + const handelImageLoaded = useCallback( () => { return setIsImageLoading( false ); }, [ setIsImageLoading ] ); @@ -604,7 +397,11 @@ const InboxResponse = ( { { ! isMobile && ( - + ; + /** Whether the response has been read. */ + is_unread: boolean; } /** From 55760addb64eb2443409220b0ee8c7842bacc89a Mon Sep 17 00:00:00 2001 From: Mikael Korpela Date: Thu, 9 Oct 2025 12:16:28 +0300 Subject: [PATCH 4/8] Conditionally call function --- .../forms/src/dashboard/components/response-actions/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/packages/forms/src/dashboard/components/response-actions/index.tsx b/projects/packages/forms/src/dashboard/components/response-actions/index.tsx index 567853b48c8a8..2a2115c070703 100644 --- a/projects/packages/forms/src/dashboard/components/response-actions/index.tsx +++ b/projects/packages/forms/src/dashboard/components/response-actions/index.tsx @@ -80,7 +80,7 @@ const ResponseActions = ( { }, [ response, registry ] ); const handleMarkAsUnread = useCallback( () => { - onMarkAsRead( response.id ); + onMarkAsRead?.( response.id ); markAsUnreadAction.callback( [ response ], { registry } ); }, [ response, registry, onMarkAsRead ] ); From 4ae86a2a7090a8e10598829f5df87a99548cf9d1 Mon Sep 17 00:00:00 2001 From: Christian Gastrell Date: Fri, 10 Oct 2025 12:42:50 -0300 Subject: [PATCH 5/8] add toggle for read/unread unconditionally, add loading state --- .../components/response-actions/index.tsx | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/projects/packages/forms/src/dashboard/components/response-actions/index.tsx b/projects/packages/forms/src/dashboard/components/response-actions/index.tsx index 2a2115c070703..aa6e52014bba5 100644 --- a/projects/packages/forms/src/dashboard/components/response-actions/index.tsx +++ b/projects/packages/forms/src/dashboard/components/response-actions/index.tsx @@ -37,6 +37,7 @@ const ResponseActions = ( { const [ isMovingToTrash, setIsMovingToTrash ] = useState( false ); const [ isRestoring, setIsRestoring ] = useState( false ); const [ isDeleting, setIsDeleting ] = useState( false ); + const [ isTogglingReadStatus, setIsTogglingReadStatus ] = useState( false ); const registry = useRegistry(); @@ -75,21 +76,26 @@ const ResponseActions = ( { onActionComplete?.( response.id.toString() ); }, [ response, registry, onActionComplete ] ); - const handleMarkAsRead = useCallback( () => { - markAsReadAction.callback( [ response ], { registry } ); - }, [ response, registry ] ); - - const handleMarkAsUnread = useCallback( () => { + const handleMarkAsRead = useCallback( async () => { + setIsTogglingReadStatus( true ); onMarkAsRead?.( response.id ); - markAsUnreadAction.callback( [ response ], { registry } ); + await markAsReadAction.callback( [ response ], { registry } ); + setIsTogglingReadStatus( false ); }, [ response, registry, onMarkAsRead ] ); - const readUnreadButtons = onMarkAsRead ? ( + const handleMarkAsUnread = useCallback( async () => { + setIsTogglingReadStatus( true ); + await markAsUnreadAction.callback( [ response ], { registry } ); + setIsTogglingReadStatus( false ); + }, [ response, registry ] ); + + const readUnreadButtons = ( <> { response.is_unread && ( ) } - ) : null; + ); switch ( response.status ) { case 'spam': From 38ba1afcc9183f247881f3dfd9ee5c2be539dd46 Mon Sep 17 00:00:00 2001 From: Christian Gastrell Date: Fri, 10 Oct 2025 13:15:24 -0300 Subject: [PATCH 6/8] switch to RenderModal on view action, compose with new component to wrap InboxResponse with actions and navigation --- .../src/dashboard/inbox/dataviews/actions.js | 5 - .../src/dashboard/inbox/dataviews/index.js | 200 ++++++++++++++---- .../forms/src/dashboard/inbox/style.scss | 8 + 3 files changed, 167 insertions(+), 46 deletions(-) diff --git a/projects/packages/forms/src/dashboard/inbox/dataviews/actions.js b/projects/packages/forms/src/dashboard/inbox/dataviews/actions.js index 2baf911a1867e..134b7dc3b9958 100644 --- a/projects/packages/forms/src/dashboard/inbox/dataviews/actions.js +++ b/projects/packages/forms/src/dashboard/inbox/dataviews/actions.js @@ -6,7 +6,6 @@ import { seen, unseen, trash, backup, commentContent } from '@wordpress/icons'; import { store as noticesStore } from '@wordpress/notices'; import { notSpam, spam } from '../../icons'; import { store as dashboardStore } from '../../store'; -import InboxResponse from '../response'; import { updateMenuCounter, updateMenuCounterOptimistically } from '../utils'; export const BULK_ACTIONS = { @@ -20,10 +19,6 @@ export const viewAction = { isPrimary: true, label: __( 'View response', 'jetpack-forms' ), modalHeader: __( 'Response', 'jetpack-forms' ), - RenderModal: ( { items } ) => { - const [ item ] = items; - return ; - }, }; // TODO: We should probably have better error messages in case of failure. diff --git a/projects/packages/forms/src/dashboard/inbox/dataviews/index.js b/projects/packages/forms/src/dashboard/inbox/dataviews/index.js index dfdde5c882946..0c603a62b7d87 100644 --- a/projects/packages/forms/src/dashboard/inbox/dataviews/index.js +++ b/projects/packages/forms/src/dashboard/inbox/dataviews/index.js @@ -342,10 +342,27 @@ export default function InboxView() { deleteAction, ]; if ( isMobile ) { - _actions.unshift( viewAction ); + _actions.unshift( { + ...viewAction, + RenderModal: ( { items, closeModal } ) => { + const [ item ] = items; + return ; + }, + hideModalHeader: true, + } ); + } else { + _actions.unshift( { + ...viewAction, + callback( items ) { + const [ item ] = items; + const selectedId = item.id.toString(); + const selectionWithoutSelectedId = selection.filter( id => id !== selectedId ); + onChangeSelection( [ ...selectionWithoutSelectedId, selectedId ] ); + }, + } ); } return _actions; - }, [ isMobile ] ); + }, [ isMobile, onChangeSelection, selection, data ] ); const resetPage = useCallback( () => { view.page = 1; @@ -389,6 +406,125 @@ export default function InboxView() { ); } +/** + * Component wrapper for InboxResponse in DataViews modal + * Renders response with navigation in modal header for mobile view + * @param {object} props - The props object. + * @param {Array} props.data - The responses list array. + * @param {object} props.response - The response item. + * @param {Function} props.closeModal - Function to close the DataViews modal. + * @return {import('react').JSX.Element} The DataViews component. + */ +const InboxResponseMobile = ( { response, data, closeModal } ) => { + const [ currentResponse, setCurrentResponse ] = useState( response ); + + const currentIndex = useMemo( + () => + currentResponse && data + ? data.findIndex( item => getItemId( item ) === getItemId( currentResponse ) ) + : -1, + [ currentResponse, data ] + ); + + const hasNext = currentIndex >= 0 && currentIndex < ( data?.length ?? 0 ) - 1; + const hasPrevious = currentIndex > 0; + + const handleNext = useCallback( () => { + if ( hasNext && data && currentIndex >= 0 ) { + const nextItem = data[ currentIndex + 1 ]; + if ( nextItem ) { + setCurrentResponse( nextItem ); + // onChangeSelection( [ getItemId( nextItem ) ] ); + } + } + }, [ hasNext, data, currentIndex ] ); + + const handlePrevious = useCallback( () => { + if ( hasPrevious && data && currentIndex >= 0 ) { + const prevItem = data[ currentIndex - 1 ]; + if ( prevItem ) { + setCurrentResponse( prevItem ); + // onChangeSelection( [ getItemId( prevItem ) ] ); + } + } + }, [ hasPrevious, data, currentIndex ] ); + + const handleActionComplete = useCallback( () => { + closeModal?.(); + }, [ closeModal ] ); + + return ( +
+ +

+ { __( 'Response', 'jetpack-forms' ) } +

+
+ + +
+
+ +
+ ); +}; + +const useResponseNavigation = ( { data, onChangeSelection, sidePanelItem, setSidePanelItem } ) => { + const currentIndex = useMemo( + () => + sidePanelItem && data + ? data.findIndex( item => getItemId( item ) === getItemId( sidePanelItem ) ) + : -1, + [ sidePanelItem, data ] + ); + + const hasNext = currentIndex >= 0 && currentIndex < ( data?.length ?? 0 ) - 1; + const hasPrevious = currentIndex > 0; + + const handleNext = useCallback( () => { + if ( hasNext && data && currentIndex >= 0 ) { + const nextItem = data[ currentIndex + 1 ]; + if ( nextItem ) { + setSidePanelItem( nextItem ); + onChangeSelection( [ getItemId( nextItem ) ] ); + } + } + }, [ hasNext, data, currentIndex, setSidePanelItem, onChangeSelection ] ); + + const handlePrevious = useCallback( () => { + if ( hasPrevious && data && currentIndex >= 0 ) { + const prevItem = data[ currentIndex - 1 ]; + if ( prevItem ) { + setSidePanelItem( prevItem ); + onChangeSelection( [ getItemId( prevItem ) ] ); + } + } + }, [ hasPrevious, data, currentIndex, setSidePanelItem, onChangeSelection ] ); + + return { + currentIndex, + hasNext, + hasPrevious, + handleNext, + handlePrevious, + }; +}; + const SingleResponse = ( { sidePanelItem, setSidePanelItem, @@ -424,54 +560,42 @@ const SingleResponse = ( { [ onChangeSelection, selection ] ); - // Navigation logic - const currentIndex = - sidePanelItem && data - ? data.findIndex( item => getItemId( item ) === getItemId( sidePanelItem ) ) - : -1; - const hasNext = currentIndex >= 0 && currentIndex < ( data?.length ?? 0 ) - 1; - const hasPrevious = currentIndex > 0; - - const handleNext = useCallback( () => { - if ( hasNext && data && currentIndex >= 0 ) { - const nextItem = data[ currentIndex + 1 ]; - if ( nextItem ) { - setSidePanelItem( nextItem ); - onChangeSelection( [ getItemId( nextItem ) ] ); - } - } - }, [ hasNext, data, currentIndex, setSidePanelItem, onChangeSelection ] ); - - const handlePrevious = useCallback( () => { - if ( hasPrevious && data && currentIndex >= 0 ) { - const prevItem = data[ currentIndex - 1 ]; - if ( prevItem ) { - setSidePanelItem( prevItem ); - onChangeSelection( [ getItemId( prevItem ) ] ); - } - } - }, [ hasPrevious, data, currentIndex, setSidePanelItem, onChangeSelection ] ); + // Use the navigation hook + const navigation = useResponseNavigation( { + data, + onChangeSelection, + sidePanelItem, + setSidePanelItem, + } ); if ( ! sidePanelItem ) { return null; } + + // Navigation props to pass to InboxResponse and ResponseNavigation + const navigationProps = { + hasNext: navigation.hasNext, + hasPrevious: navigation.hasPrevious, + onNext: navigation.handleNext, + onPrevious: navigation.handlePrevious, + }; + const contents = ( ); + if ( ! isMobile ) { return
{ contents }
; } + return ( - + } > diff --git a/projects/packages/forms/src/dashboard/inbox/style.scss b/projects/packages/forms/src/dashboard/inbox/style.scss index 66b181b47c311..1469bf10cd1a8 100644 --- a/projects/packages/forms/src/dashboard/inbox/style.scss +++ b/projects/packages/forms/src/dashboard/inbox/style.scss @@ -414,6 +414,14 @@ } } +.jp-forms__inbox__response-mobile { + + .jp-forms__inbox__response-mobile__header-heading { + font-size: 1.2rem; + font-weight: 600; + } +} + /* * We need to make the available canvas 100% tall. Without this, From 0bda478b7217111462deaf0e9ce762a9355e6b06 Mon Sep 17 00:00:00 2001 From: Christian Gastrell Date: Fri, 10 Oct 2025 13:32:03 -0300 Subject: [PATCH 7/8] remove unused code --- projects/packages/forms/src/dashboard/inbox/dataviews/index.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/projects/packages/forms/src/dashboard/inbox/dataviews/index.js b/projects/packages/forms/src/dashboard/inbox/dataviews/index.js index 0c603a62b7d87..6c08d5cb92847 100644 --- a/projects/packages/forms/src/dashboard/inbox/dataviews/index.js +++ b/projects/packages/forms/src/dashboard/inbox/dataviews/index.js @@ -434,7 +434,6 @@ const InboxResponseMobile = ( { response, data, closeModal } ) => { const nextItem = data[ currentIndex + 1 ]; if ( nextItem ) { setCurrentResponse( nextItem ); - // onChangeSelection( [ getItemId( nextItem ) ] ); } } }, [ hasNext, data, currentIndex ] ); @@ -444,7 +443,6 @@ const InboxResponseMobile = ( { response, data, closeModal } ) => { const prevItem = data[ currentIndex - 1 ]; if ( prevItem ) { setCurrentResponse( prevItem ); - // onChangeSelection( [ getItemId( prevItem ) ] ); } } }, [ hasPrevious, data, currentIndex ] ); From 41acff095ae615704e7699fd5cf72feb97044441 Mon Sep 17 00:00:00 2001 From: Enej Bajgoric Date: Fri, 10 Oct 2025 14:52:39 -0700 Subject: [PATCH 8/8] Try: mobile actions --- .../components/response-actions/index.tsx | 69 +++++++++++++------ .../src/dashboard/inbox/dataviews/index.js | 12 +++- .../forms/src/dashboard/inbox/style.scss | 10 +++ 3 files changed, 69 insertions(+), 22 deletions(-) diff --git a/projects/packages/forms/src/dashboard/components/response-actions/index.tsx b/projects/packages/forms/src/dashboard/components/response-actions/index.tsx index aa6e52014bba5..1c64ecbf7f956 100644 --- a/projects/packages/forms/src/dashboard/components/response-actions/index.tsx +++ b/projects/packages/forms/src/dashboard/components/response-actions/index.tsx @@ -4,6 +4,7 @@ import { Button } from '@wordpress/components'; import { useRegistry } from '@wordpress/data'; import { useCallback, useState } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ @@ -25,12 +26,14 @@ type ResponseNavigationProps = { onActionComplete?: ( id: string ) => void; onMarkAsRead?: ( id: number | false ) => void; response: FormResponse; + isMobile?: boolean; }; const ResponseActions = ( { onActionComplete, onMarkAsRead, response, + isMobile = false, }: ResponseNavigationProps ): JSX.Element => { const [ isMarkingAsSpam, setIsMarkingAsSpam ] = useState( false ); const [ isMarkingAsNotSpam, setIsMarkingAsNotSpam ] = useState( false ); @@ -89,11 +92,13 @@ const ResponseActions = ( { setIsTogglingReadStatus( false ); }, [ response, registry ] ); + const variant = isMobile ? 'secondary' : 'tertiary'; + const readUnreadButtons = ( <> { response.is_unread && ( + > + { isMobile ? __( 'Read', 'jetpack-forms' ) : null } + ) } { ! response.is_unread && ( + > + { isMobile ? __( 'Unread', 'jetpack-forms' ) : null } + ) } ); + let buttons; + switch ( response.status ) { case 'spam': - return ( + buttons = ( <> { readUnreadButtons } + > + { isMobile ? __( 'Restore', 'jetpack-forms' ) : null } + + > + { isMobile ? __( 'Trash', 'jetpack-forms' ) : null } + ); - + break; case 'trash': - return ( + buttons = ( <> { readUnreadButtons } + > + { isMobile ? __( 'Restore', 'jetpack-forms' ) : null } + + > + { isMobile ? __( 'Delete', 'jetpack-forms' ) : null } + ); + break; default: // 'publish' (inbox) or any other status - return ( + buttons = ( <> { readUnreadButtons } + > + { isMobile ? __( 'Spam', 'jetpack-forms' ) : null } + + > + { isMobile ? __( 'Trash', 'jetpack-forms' ) : null } + ); } + + if ( isMobile ) { + return
{ buttons }
; + } + return buttons; }; export default ResponseActions; diff --git a/projects/packages/forms/src/dashboard/inbox/dataviews/index.js b/projects/packages/forms/src/dashboard/inbox/dataviews/index.js index 6c08d5cb92847..2f8815af292a7 100644 --- a/projects/packages/forms/src/dashboard/inbox/dataviews/index.js +++ b/projects/packages/forms/src/dashboard/inbox/dataviews/index.js @@ -462,7 +462,6 @@ const InboxResponseMobile = ( { response, data, closeModal } ) => { { __( 'Response', 'jetpack-forms' ) }
- { response={ currentResponse } onClose={ null } /> +
); }; @@ -601,12 +605,16 @@ const SingleResponse = ( { onRequestClose={ onRequestClose } headerActions={ <> - } > { contents } +
); }; diff --git a/projects/packages/forms/src/dashboard/inbox/style.scss b/projects/packages/forms/src/dashboard/inbox/style.scss index 1469bf10cd1a8..4a8e477707194 100644 --- a/projects/packages/forms/src/dashboard/inbox/style.scss +++ b/projects/packages/forms/src/dashboard/inbox/style.scss @@ -536,3 +536,13 @@ body.jetpack_page_jetpack-forms-admin { margin-left: -12px; vertical-align: middle; } + +.jp-forms-response-actions-mobile { + display: flex; + justify-content: space-between; + padding: 8px 0; + position: fixed; + bottom: 12px; + margin: 0; + gap: 12px; +}