diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/delete-modal.js b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/delete-modal.js new file mode 100644 index 0000000000000..23737a434e699 --- /dev/null +++ b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/delete-modal.js @@ -0,0 +1,46 @@ +/** + * WordPress dependencies + */ +import { + __experimentalHStack as HStack, + __experimentalVStack as VStack, + Button, + Modal, +} from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +export default function RenameModal( { onClose, onConfirm } ) { + return ( + +
+ +

+ { __( + 'Are you sure you want to delete this Navigation menu?' + ) } +

+ + + + + +
+
+
+ ); +} diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/index.js b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/index.js index 06bc660057f93..09dc9106af192 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/index.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/index.js @@ -1,19 +1,21 @@ /** * WordPress dependencies */ -import { useEntityRecord } from '@wordpress/core-data'; +import { useEntityRecord, store as coreStore } from '@wordpress/core-data'; import { __experimentalUseNavigator as useNavigator, Spinner, } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; import { useCallback, useMemo } from '@wordpress/element'; -import { useSelect } from '@wordpress/data'; +import { useSelect, useDispatch } from '@wordpress/data'; import { privateApis as routerPrivateApis } from '@wordpress/router'; import { BlockEditorProvider } from '@wordpress/block-editor'; import { createBlock } from '@wordpress/blocks'; import { decodeEntities } from '@wordpress/html-entities'; +import { store as noticesStore } from '@wordpress/notices'; + /** * Internal dependencies */ @@ -25,24 +27,167 @@ import { } from '../../utils/is-previewing-theme'; import { SidebarNavigationScreenWrapper } from '../sidebar-navigation-screen-navigation-menus'; import NavigationMenuContent from '../sidebar-navigation-screen-navigation-menus/navigation-menu-content'; +import ScreenNavigationMoreMenu from './more-menu'; const { useHistory } = unlock( routerPrivateApis ); const noop = () => {}; export default function SidebarNavigationScreenNavigationMenu() { + const { + deleteEntityRecord, + saveEntityRecord, + editEntityRecord, + saveEditedEntityRecord, + } = useDispatch( coreStore ); + + const { createSuccessNotice, createErrorNotice } = + useDispatch( noticesStore ); + const postType = `wp_navigation`; const { + goTo, params: { postId }, } = useNavigator(); - const { record: navigationMenu, isResolving: isLoading } = useEntityRecord( + const { record: navigationMenu, isResolving } = useEntityRecord( 'postType', postType, postId ); + const { getEditedEntityRecord, isSaving, isDeleting } = useSelect( + ( select ) => { + const { + isSavingEntityRecord, + isDeletingEntityRecord, + getEditedEntityRecord: getEditedEntityRecordSelector, + } = select( coreStore ); + + return { + isSaving: isSavingEntityRecord( 'postType', postType, postId ), + isDeleting: isDeletingEntityRecord( + 'postType', + postType, + postId + ), + getEditedEntityRecord: getEditedEntityRecordSelector, + }; + }, + [ postId, postType ] + ); + + const isLoading = isResolving || isSaving || isDeleting; + const menuTitle = navigationMenu?.title?.rendered || navigationMenu?.slug; + const handleSave = async ( edits = {} ) => { + // Prepare for revert in case of error. + const originalRecord = getEditedEntityRecord( + 'postType', + 'wp_navigation', + postId + ); + + // Apply the edits. + editEntityRecord( 'postType', postType, postId, edits ); + + // Attempt to persist. + try { + await saveEditedEntityRecord( 'postType', postType, postId, { + throwOnError: true, + } ); + createSuccessNotice( __( 'Renamed Navigation menu' ), { + type: 'snackbar', + } ); + } catch ( error ) { + // Revert to original in case of error. + editEntityRecord( 'postType', postType, postId, originalRecord ); + + createErrorNotice( + sprintf( + /* translators: %s: error message describing why the navigation menu could not be renamed. */ + __( `Unable to rename Navigation menu (%s).` ), + error?.message + ), + + { + type: 'snackbar', + } + ); + } + }; + + const handleDelete = async () => { + try { + await deleteEntityRecord( + 'postType', + postType, + postId, + { + force: true, + }, + { + throwOnError: true, + } + ); + createSuccessNotice( __( 'Deleted Navigation menu' ), { + type: 'snackbar', + } ); + goTo( '/navigation' ); + } catch ( error ) { + createErrorNotice( + sprintf( + /* translators: %s: error message describing why the navigation menu could not be deleted. */ + __( `Unable to delete Navigation menu (%s).` ), + error?.message + ), + + { + type: 'snackbar', + } + ); + } + }; + const handleDuplicate = async () => { + try { + const savedRecord = await saveEntityRecord( + 'postType', + postType, + { + title: sprintf( + /* translators: %s: Navigation menu title */ + __( '%s (Copy)' ), + menuTitle + ), + content: navigationMenu?.content?.raw, + status: 'publish', + }, + { + throwOnError: true, + } + ); + + if ( savedRecord ) { + createSuccessNotice( __( 'Duplicated Navigation menu' ), { + type: 'snackbar', + } ); + goTo( `/navigation/${ postType }/${ savedRecord.id }` ); + } + } catch ( error ) { + createErrorNotice( + sprintf( + /* translators: %s: error message describing why the navigation menu could not be deleted. */ + __( `Unable to duplicate Navigation menu (%s).` ), + error?.message + ), + + { + type: 'snackbar', + } + ); + } + }; + if ( isLoading ) { return ( + } title={ decodeEntities( menuTitle ) } description={ __( 'Navigation menus are a curated collection of blocks that allow visitors to get around your site.' diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/more-menu.js b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/more-menu.js new file mode 100644 index 0000000000000..4b3a37a52f942 --- /dev/null +++ b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/more-menu.js @@ -0,0 +1,89 @@ +/** + * WordPress dependencies + */ +import { DropdownMenu, MenuItem, MenuGroup } from '@wordpress/components'; +import { moreVertical } from '@wordpress/icons'; +import { __ } from '@wordpress/i18n'; +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import RenameModal from './rename-modal'; +import DeleteModal from './delete-modal'; + +const POPOVER_PROPS = { + position: 'bottom right', +}; + +export default function ScreenNavigationMoreMenu( props ) { + const { onDelete, onSave, onDuplicate, menuTitle } = props; + + const [ renameModalOpen, setRenameModalOpen ] = useState( false ); + const [ deleteModalOpen, setDeleteModalOpen ] = useState( false ); + + const closeModals = () => { + setRenameModalOpen( false ); + setDeleteModalOpen( false ); + }; + const openRenameModal = () => setRenameModalOpen( true ); + const openDeleteModal = () => setDeleteModalOpen( true ); + + return ( + <> + + { ( { onClose } ) => ( +
+ + { + openRenameModal(); + // Close the dropdown after opening the modal. + onClose(); + } } + > + { __( 'Rename' ) } + + { + onDuplicate(); + onClose(); + } } + > + { __( 'Duplicate' ) } + + { + openDeleteModal(); + + // Close the dropdown after opening the modal. + onClose(); + } } + > + { __( 'Delete' ) } + + +
+ ) } +
+ + { deleteModalOpen && ( + + ) } + + { renameModalOpen && ( + + ) } + + ); +} diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/rename-modal.js b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/rename-modal.js new file mode 100644 index 0000000000000..99f674ba8bee3 --- /dev/null +++ b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/rename-modal.js @@ -0,0 +1,51 @@ +/** + * WordPress dependencies + */ +import { + __experimentalHStack as HStack, + __experimentalVStack as VStack, + Button, + TextControl, + Modal, +} from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { useState } from '@wordpress/element'; + +export default function RenameModal( { menuTitle, onClose, onSave } ) { + const [ editedMenuTitle, setEditedMenuTitle ] = useState( menuTitle ); + + return ( + +
+ + + + + + + + +
+
+ ); +} diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/style.scss b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/style.scss new file mode 100644 index 0000000000000..e00f9210b4e4b --- /dev/null +++ b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/style.scss @@ -0,0 +1,15 @@ +.sidebar-navigation__more-menu { + .components-button { + color: $gray-600; + &:hover, + &:focus, + &[aria-current] { + color: $white; + } + } +} + +.sidebar-navigation__rename-modal-form { + // Fix for input focus style being cut off by the modal. + padding-top: 1px; +} diff --git a/packages/edit-site/src/style.scss b/packages/edit-site/src/style.scss index 1004684c17cc2..0d91942e5ca22 100644 --- a/packages/edit-site/src/style.scss +++ b/packages/edit-site/src/style.scss @@ -28,6 +28,7 @@ @import "./components/sidebar-navigation-item/style.scss"; @import "./components/sidebar-navigation-screen/style.scss"; @import "./components/sidebar-navigation-screen-global-styles/style.scss"; +@import "./components/sidebar-navigation-screen-navigation-menu/style.scss"; @import "./components/sidebar-navigation-screen-page/style.scss"; @import "./components/sidebar-navigation-screen-template/style.scss"; @import "./components/sidebar-navigation-screen-templates/style.scss";