diff --git a/packages/components/src/select-control/index.js b/packages/components/src/select-control/index.js index 369cc079f7f849..55c507753eda4b 100644 --- a/packages/components/src/select-control/index.js +++ b/packages/components/src/select-control/index.js @@ -41,6 +41,7 @@ function SelectControl( options = [], size = 'default', value: valueProp, + labelPosition, ...props }, ref @@ -100,6 +101,7 @@ function SelectControl( } + labelPosition={ labelPosition } > { - if ( - // eslint-disable-next-line no-alert - window.confirm( - __( 'Are you sure you want to delete this navigation?' ) - ) - ) { - onDelete(); - } - }; - - return ( - - { __( 'Delete navigation' ) } - - ); -} diff --git a/packages/edit-navigation/src/components/delete-menu-button/style.scss b/packages/edit-navigation/src/components/delete-menu-button/style.scss deleted file mode 100644 index 5e314b1a727fc0..00000000000000 --- a/packages/edit-navigation/src/components/delete-menu-button/style.scss +++ /dev/null @@ -1,7 +0,0 @@ -.components-button.is-link.menu-editor-button__delete { - color: $alert-red; - &:hover { - background: transparent; - color: $alert-red; - } -} diff --git a/packages/edit-navigation/src/components/editor/block-view.js b/packages/edit-navigation/src/components/editor/block-view.js new file mode 100644 index 00000000000000..416f38c9fe900c --- /dev/null +++ b/packages/edit-navigation/src/components/editor/block-view.js @@ -0,0 +1,39 @@ +/** + * WordPress dependencies + */ +import { useSelect, useDispatch } from '@wordpress/data'; +import { useEffect } from '@wordpress/element'; +import { WritingFlow, ObserveTyping, BlockList } from '@wordpress/block-editor'; +import { Spinner } from '@wordpress/components'; + +export default function BlockView( { isPending } ) { + const rootClientId = useSelect( + ( select ) => select( 'core/block-editor' ).getBlocks()[ 0 ]?.clientId, + [] + ); + + const { selectBlock } = useDispatch( 'core/block-editor' ); + + // Select the root Navigation block when it becomes available. + useEffect( () => { + if ( rootClientId ) { + selectBlock( rootClientId ); + } + }, [ rootClientId, selectBlock ] ); + + return ( + + { isPending ? ( + + ) : ( + + + + + + + + ) } + + ); +} diff --git a/packages/edit-navigation/src/components/editor/index.js b/packages/edit-navigation/src/components/editor/index.js new file mode 100644 index 00000000000000..9b4f756489b61e --- /dev/null +++ b/packages/edit-navigation/src/components/editor/index.js @@ -0,0 +1,14 @@ +/** + * Internal dependencies + */ +import BlockView from './block-view'; +import ListView from './list-view'; + +export default function Editor( { isPending, blocks } ) { + return ( + + + + + ); +} diff --git a/packages/edit-navigation/src/components/editor/list-view.js b/packages/edit-navigation/src/components/editor/list-view.js new file mode 100644 index 00000000000000..8d20e1c1fa5d7e --- /dev/null +++ b/packages/edit-navigation/src/components/editor/list-view.js @@ -0,0 +1,34 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { useState } from '@wordpress/element'; +import { Spinner } from '@wordpress/components'; +import { __experimentalBlockNavigationTree } from '@wordpress/block-editor'; + +export default function ListView( { isPending, blocks } ) { + const [ selectedBlockId, setSelectedBlockId ] = useState( + blocks[ 0 ]?.clientId + ); + + return ( + + + { __( 'List view' ) } + + { isPending ? ( + + ) : ( + <__experimentalBlockNavigationTree + blocks={ blocks } + selectedBlockClientId={ selectedBlockId } + selectBlock={ setSelectedBlockId } + __experimentalFeatures + showNestedBlocks + showAppender + showBlockMovers + /> + ) } + + ); +} diff --git a/packages/edit-navigation/src/components/editor/style.scss b/packages/edit-navigation/src/components/editor/style.scss new file mode 100644 index 00000000000000..386a51bb04df1e --- /dev/null +++ b/packages/edit-navigation/src/components/editor/style.scss @@ -0,0 +1,45 @@ +.edit-navigation-editor { + margin: $grid-unit-20; + + @include break-medium() { + align-items: flex-start; + display: flex; + } +} + +.edit-navigation-editor__block-view { + @include break-medium() { + flex-grow: 1; + } + + .components-spinner { + display: block; + margin: 10px auto; + } +} + +.edit-navigation-editor__list-view { + border-top: 1px solid $gray-200; + margin-top: $grid-unit-20; + padding-top: $grid-unit-20; + + @include break-medium() { + border-left: 1px solid $gray-200; + border-top: none; + margin-left: $grid-unit-20; + margin-top: 0; + padding-left: $grid-unit-20; + padding-top: 0; + width: 300px; + } + + .edit-navigation-editor__list-view-title { + font-size: 1.2em; + margin: 0; + } + + .components-spinner { + display: block; + margin: 100px auto; + } +} diff --git a/packages/edit-navigation/src/components/header/add-menu-form.js b/packages/edit-navigation/src/components/header/add-menu-form.js new file mode 100644 index 00000000000000..3f47f594bd9626 --- /dev/null +++ b/packages/edit-navigation/src/components/header/add-menu-form.js @@ -0,0 +1,84 @@ +/** + * External dependencies + */ +import { some } from 'lodash'; + +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; +import { useDispatch } from '@wordpress/data'; +import { TextControl, Button } from '@wordpress/components'; +import { __, sprintf } from '@wordpress/i18n'; + +const menuNameMatches = ( menuName ) => ( menu ) => + menu.name.toLowerCase() === menuName.toLowerCase(); + +export default function AddMenuForm( { menus, onCreate } ) { + const [ menuName, setMenuName ] = useState( '' ); + + const { createErrorNotice, createInfoNotice } = useDispatch( + 'core/notices' + ); + + const [ isCreatingMenu, setIsCreatingMenu ] = useState( false ); + + const { saveMenu } = useDispatch( 'core' ); + + const createMenu = async ( event ) => { + event.preventDefault(); + + if ( ! menuName.length ) { + return; + } + + if ( some( menus, menuNameMatches( menuName ) ) ) { + const message = sprintf( + // translators: %s: the name of a menu. + __( + 'The menu name %s conflicts with another menu name. Please try another.' + ), + menuName + ); + createErrorNotice( message, { id: 'edit-navigation-error' } ); + return; + } + + setIsCreatingMenu( true ); + + const menu = await saveMenu( { name: menuName } ); + if ( menu ) { + createInfoNotice( __( 'Menu created' ), { + type: 'snackbar', + isDismissible: true, + } ); + onCreate( menu.id ); + } + + setIsCreatingMenu( false ); + }; + + return ( + + + + + { __( 'Create menu' ) } + + + ); +} diff --git a/packages/edit-navigation/src/components/header/index.js b/packages/edit-navigation/src/components/header/index.js new file mode 100644 index 00000000000000..1713b4b04988f1 --- /dev/null +++ b/packages/edit-navigation/src/components/header/index.js @@ -0,0 +1,83 @@ +/** + * WordPress dependencies + */ +import { useViewportMatch } from '@wordpress/compose'; +import { __ } from '@wordpress/i18n'; +import { Button, SelectControl, Dropdown } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import ManageLocations from './manage-locations'; +import AddMenuForm from './add-menu-form'; + +export default function Header( { menus, selectedMenuId, onSelectMenu } ) { + const isMobileViewport = useViewportMatch( 'small', '<' ); + + return ( + + + { __( 'Navigation' ) } + + + + + ( { + value: menu.id, + label: menu.name, + } ) ) + : [ + { + value: 0, + label: '-', + }, + ] + } + onChange={ onSelectMenu } + /> + + + ( + + { __( 'Add new' ) } + + ) } + renderContent={ () => ( + + ) } + /> + + ( + + { __( 'Manage locations' ) } + + ) } + renderContent={ () => } + /> + + + ); +} diff --git a/packages/edit-navigation/src/components/header/manage-locations.js b/packages/edit-navigation/src/components/header/manage-locations.js new file mode 100644 index 00000000000000..480917f901924c --- /dev/null +++ b/packages/edit-navigation/src/components/header/manage-locations.js @@ -0,0 +1,48 @@ +/** + * WordPress dependencies + */ +import { useSelect } from '@wordpress/data'; +import { Spinner, SelectControl } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import useMenuLocations from './use-menu-locations'; + +export default function ManageLocations() { + const menus = useSelect( ( select ) => select( 'core' ).getMenus(), [] ); + + const [ menuLocations, assignMenuToLocation ] = useMenuLocations(); + + if ( ! menus || ! menuLocations ) { + return ; + } + + if ( ! menus.length ) { + return { __( 'There are no available menus.' ) }; + } + + if ( ! menuLocations.length ) { + return { __( 'There are no available menu locations.' ) }; + } + + return menuLocations.map( ( menuLocation ) => ( + ( { + value: menu.id, + label: menu.name, + } ) ), + ] } + onChange={ ( menuId ) => { + assignMenuToLocation( menuLocation.name, Number( menuId ) ); + } } + /> + ) ); +} diff --git a/packages/edit-navigation/src/components/header/style.scss b/packages/edit-navigation/src/components/header/style.scss new file mode 100644 index 00000000000000..093e9805b6e16b --- /dev/null +++ b/packages/edit-navigation/src/components/header/style.scss @@ -0,0 +1,37 @@ +.edit-navigation-header { + padding: $grid-unit-20; + background: #f1f1f1; // Need to find a variable for this + + .edit-navigation-header__title { + font-size: 23px; + font-weight: 400; + line-height: $default-line-height; + margin: 0 0 $grid-unit-10; + } +} + +// Lining up all the actions in a nice row. +.edit-navigation-header__actions { + align-items: center; + display: flex; + + // Spacing out the actions a little. + .edit-navigation-header__current-menu, + .components-dropdown { + margin-right: $grid-unit-20; + } +} + +// The select menu for choosing which menu to edit. +.edit-navigation-header__current-menu .components-base-control__field { + margin: 0; +} + +.edit-navigation-header__create-menu-button { + margin-top: $grid-unit-10; +} + +.edit-navigation-header__manage-locations .components-spinner { + display: block; + margin: $grid-unit-20 auto; +} diff --git a/packages/edit-navigation/src/components/header/use-menu-locations.js b/packages/edit-navigation/src/components/header/use-menu-locations.js new file mode 100644 index 00000000000000..bfd10225904069 --- /dev/null +++ b/packages/edit-navigation/src/components/header/use-menu-locations.js @@ -0,0 +1,82 @@ +/** + * WordPress dependencies + */ +import { useState, useEffect, useMemo, useCallback } from '@wordpress/element'; +import apiFetch from '@wordpress/api-fetch'; +import { useDispatch } from '@wordpress/data'; + +export default function useMenuLocations() { + const [ menuLocationsByName, setMenuLocationsByName ] = useState( null ); + + useEffect( () => { + let isMounted = true; + + const fetchMenuLocationsByName = async () => { + const newMenuLocationsByName = await apiFetch( { + method: 'GET', + path: '/__experimental/menu-locations', + } ); + + if ( isMounted ) { + setMenuLocationsByName( newMenuLocationsByName ); + } + }; + + fetchMenuLocationsByName(); + + return () => ( isMounted = false ); + }, [] ); + + const { saveMenu } = useDispatch( 'core' ); + + const assignMenuToLocation = useCallback( + async ( locationName, newMenuId ) => { + const oldMenuId = menuLocationsByName[ locationName ].menu; + + const newMenuLocationsByName = { + ...menuLocationsByName, + [ locationName ]: { + ...menuLocationsByName[ locationName ], + menu: newMenuId, + }, + }; + + setMenuLocationsByName( newMenuLocationsByName ); + + const promises = []; + + if ( oldMenuId ) { + promises.push( + saveMenu( { + id: oldMenuId, + locations: Object.values( newMenuLocationsByName ) + .filter( ( { menu } ) => menu === oldMenuId ) + .map( ( { name } ) => name ), + } ) + ); + } + + if ( newMenuId ) { + promises.push( + saveMenu( { + id: newMenuId, + locations: Object.values( newMenuLocationsByName ) + .filter( ( { menu } ) => menu === newMenuId ) + .map( ( { name } ) => name ), + } ) + ); + } + + await Promise.all( promises ); + }, + [ menuLocationsByName ] + ); + + const menuLocations = useMemo( + () => + menuLocationsByName ? Object.values( menuLocationsByName ) : null, + [ menuLocationsByName ] + ); + + return [ menuLocations, assignMenuToLocation ]; +} diff --git a/packages/edit-navigation/src/components/inspector-additions/auto-add-pages-panel.js b/packages/edit-navigation/src/components/inspector-additions/auto-add-pages-panel.js new file mode 100644 index 00000000000000..62ca9ffb2db989 --- /dev/null +++ b/packages/edit-navigation/src/components/inspector-additions/auto-add-pages-panel.js @@ -0,0 +1,39 @@ +/** + * WordPress dependencies + */ +import { useSelect, useDispatch } from '@wordpress/data'; +import { useState, useEffect } from '@wordpress/element'; +import { PanelBody, CheckboxControl } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +export default function AutoAddPagesPanel( { menuId } ) { + const menu = useSelect( ( select ) => select( 'core' ).getMenu( menuId ), [ + menuId, + ] ); + + const [ autoAddPages, setAutoAddPages ] = useState( null ); + + useEffect( () => { + if ( autoAddPages === null && menu ) { + setAutoAddPages( menu.auto_add ); + } + }, [ autoAddPages, menu ] ); + + const { saveMenu } = useDispatch( 'core' ); + + return ( + + { + setAutoAddPages( newAutoAddPages ); + saveMenu( { + ...menu, + auto_add: newAutoAddPages, + } ); + } } + /> + + ); +} diff --git a/packages/edit-navigation/src/components/inspector-additions/delete-menu-panel.js b/packages/edit-navigation/src/components/inspector-additions/delete-menu-panel.js new file mode 100644 index 00000000000000..c112840b621e20 --- /dev/null +++ b/packages/edit-navigation/src/components/inspector-additions/delete-menu-panel.js @@ -0,0 +1,30 @@ +/** + * WordPress dependencies + */ +import { PanelBody, Button } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +export default function DeleteMenuPanel( { onDeleteMenu } ) { + return ( + + { + if ( + // eslint-disable-next-line no-alert + window.confirm( + __( + 'Are you sure you want to delete this navigation?' + ) + ) + ) { + onDeleteMenu(); + } + } } + > + { __( 'Delete menu' ) } + + + ); +} diff --git a/packages/edit-navigation/src/components/inspector-additions/index.js b/packages/edit-navigation/src/components/inspector-additions/index.js new file mode 100644 index 00000000000000..e9cc696bd7c1fd --- /dev/null +++ b/packages/edit-navigation/src/components/inspector-additions/index.js @@ -0,0 +1,29 @@ +/** + * WordPress dependencies + */ +import { useSelect } from '@wordpress/data'; +import { InspectorControls } from '@wordpress/block-editor'; + +/** + * Internal dependencies + */ +import AutoAddPagesPanel from './auto-add-pages-panel'; +import DeleteMenuPanel from './delete-menu-panel'; + +export default function InspectorAdditions( { menuId, onDeleteMenu } ) { + const selectedBlock = useSelect( + ( select ) => select( 'core/block-editor' ).getSelectedBlock(), + [] + ); + + if ( selectedBlock?.name !== 'core/navigation' ) { + return null; + } + + return ( + + + + + ); +} diff --git a/packages/edit-navigation/src/components/inspector-additions/style.scss b/packages/edit-navigation/src/components/inspector-additions/style.scss new file mode 100644 index 00000000000000..359310cfb6e0b9 --- /dev/null +++ b/packages/edit-navigation/src/components/inspector-additions/style.scss @@ -0,0 +1,3 @@ +.edit-navigation-inspector-additions__delete-menu-panel { + text-align: center; +} diff --git a/packages/edit-navigation/src/components/layout/index.js b/packages/edit-navigation/src/components/layout/index.js index 780afd26e22eb7..8fb1da3aba0071 100644 --- a/packages/edit-navigation/src/components/layout/index.js +++ b/packages/edit-navigation/src/components/layout/index.js @@ -6,59 +6,87 @@ import { FocusReturnProvider, Popover, SlotFillProvider, - TabPanel, } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; +import { + BlockEditorKeyboardShortcuts, + BlockEditorProvider, +} from '@wordpress/block-editor'; /** * Internal dependencies */ -import Notices from '../notices'; -import MenusEditor from '../menus-editor'; -import MenuLocationsEditor from '../menu-locations-editor'; +import useNavigationEditor from './use-navigation-editor'; +import useNavigationBlockEditor from './use-navigation-block-editor'; +import useMenuNotifications from './use-menu-notifications'; import ErrorBoundary from '../error-boundary'; +import NavigationEditorShortcuts from './shortcuts'; +import Header from '../header'; +import Notices from '../notices'; +import Toolbar from '../toolbar'; +import Editor from '../editor'; +import InspectorAdditions from '../inspector-additions'; export default function Layout( { blockEditorSettings } ) { + const { + menus, + selectedMenuId, + navigationPost, + selectMenu, + deleteMenu, + } = useNavigationEditor(); + + const [ blocks, onInput, onChange ] = useNavigationBlockEditor( + navigationPost + ); + + useMenuNotifications( selectedMenuId ); + return ( - <> - - - - - - + + + + + + + + + + + + - { ( tab ) => ( - - { tab.name === 'menus' && ( - - ) } - { tab.name === 'menu-locations' && ( - - ) } - - ) } - - - - - - - > + + + + + + + + + + + ); } diff --git a/packages/edit-navigation/src/components/navigation-editor/shortcuts.js b/packages/edit-navigation/src/components/layout/shortcuts.js similarity index 100% rename from packages/edit-navigation/src/components/navigation-editor/shortcuts.js rename to packages/edit-navigation/src/components/layout/shortcuts.js diff --git a/packages/edit-navigation/src/components/layout/style.scss b/packages/edit-navigation/src/components/layout/style.scss index b533f3cd3b20a3..288b3e97647052 100644 --- a/packages/edit-navigation/src/components/layout/style.scss +++ b/packages/edit-navigation/src/components/layout/style.scss @@ -1,12 +1,9 @@ -.edit-navigation-layout__tab-panel { - // Matches the padding-left applied by default to the `#wpcontent` element. - margin-right: 10px; +// Overriding the wp-admin background and padding +// to give the navigation editor more room to breathe. +.gutenberg_page_gutenberg-navigation { + background: $white; - @include break-medium { - margin-right: 20px; - } - - .components-tab-panel__tabs { - margin-bottom: 10px; + #wpcontent { + padding-left: 0; } } diff --git a/packages/edit-navigation/src/components/layout/use-menu-notifications.js b/packages/edit-navigation/src/components/layout/use-menu-notifications.js new file mode 100644 index 00000000000000..34adaa51e67ecd --- /dev/null +++ b/packages/edit-navigation/src/components/layout/use-menu-notifications.js @@ -0,0 +1,45 @@ +/** + * WordPress dependencies + */ +import { useSelect, useDispatch } from '@wordpress/data'; +import { useEffect } from '@wordpress/element'; + +export default function useMenuNotifications( menuId ) { + const { lastSaveError, lastDeleteError } = useSelect( + ( select ) => ( { + lastSaveError: select( 'core' ).getLastEntitySaveError( + 'root', + 'menu' + ), + lastDeleteError: select( 'core' ).getLastEntityDeleteError( + 'root', + 'menu', + menuId + ), + } ), + [ menuId ] + ); + + const { createErrorNotice } = useDispatch( 'core/notices' ); + + const processError = ( error ) => { + const document = new window.DOMParser().parseFromString( + error.message, + 'text/html' + ); + const errorText = document.body.textContent || ''; + createErrorNotice( errorText, { id: 'edit-navigation-error' } ); + }; + + useEffect( () => { + if ( lastSaveError ) { + processError( lastSaveError ); + } + }, [ lastSaveError ] ); + + useEffect( () => { + if ( lastDeleteError ) { + processError( lastDeleteError ); + } + }, [ lastDeleteError ] ); +} diff --git a/packages/edit-navigation/src/components/navigation-editor/use-navigation-block-editor.js b/packages/edit-navigation/src/components/layout/use-navigation-block-editor.js similarity index 97% rename from packages/edit-navigation/src/components/navigation-editor/use-navigation-block-editor.js rename to packages/edit-navigation/src/components/layout/use-navigation-block-editor.js index 57968c10b78d43..d2d5034878ff61 100644 --- a/packages/edit-navigation/src/components/navigation-editor/use-navigation-block-editor.js +++ b/packages/edit-navigation/src/components/layout/use-navigation-block-editor.js @@ -16,8 +16,9 @@ export default function useNavigationBlockEditor( post ) { const [ blocks, onInput, _onChange ] = useEntityBlockEditor( KIND, POST_TYPE, - { id: post.id } + { id: post?.id } ); + const onChange = useCallback( async ( updatedBlocks ) => { await _onChange( updatedBlocks ); diff --git a/packages/edit-navigation/src/components/layout/use-navigation-editor.js b/packages/edit-navigation/src/components/layout/use-navigation-editor.js new file mode 100644 index 00000000000000..590f66ac0df9f7 --- /dev/null +++ b/packages/edit-navigation/src/components/layout/use-navigation-editor.js @@ -0,0 +1,51 @@ +/** + * WordPress dependencies + */ +import { useSelect, useDispatch } from '@wordpress/data'; +import { useState, useEffect } from '@wordpress/element'; + +export default function useNavigationEditor() { + const menus = useSelect( + ( select ) => select( 'core' ).getMenus( { per_page: -1 } ), + [] + ); + + const [ selectedMenuId, setSelectedMenuId ] = useState( null ); + + useEffect( () => { + if ( ! selectedMenuId && menus?.length ) { + setSelectedMenuId( menus[ 0 ].id ); + } + }, [ selectedMenuId, menus ] ); + + const navigationPost = useSelect( + ( select ) => + select( 'core/edit-navigation' ).getNavigationPostForMenu( + selectedMenuId + ), + [ selectedMenuId ] + ); + + const selectMenu = ( menuId ) => { + setSelectedMenuId( menuId ); + }; + + const { deleteMenu: _deleteMenu } = useDispatch( 'core' ); + + const deleteMenu = async () => { + const didDeleteMenu = await _deleteMenu( selectedMenuId, { + force: true, + } ); + if ( didDeleteMenu ) { + setSelectedMenuId( null ); + } + }; + + return { + menus, + selectedMenuId, + navigationPost, + selectMenu, + deleteMenu, + }; +} diff --git a/packages/edit-navigation/src/components/menu-locations-editor/index.js b/packages/edit-navigation/src/components/menu-locations-editor/index.js deleted file mode 100644 index 356bc6a8ee4407..00000000000000 --- a/packages/edit-navigation/src/components/menu-locations-editor/index.js +++ /dev/null @@ -1,107 +0,0 @@ -/** - * WordPress dependencies - */ -import { useSelect } from '@wordpress/data'; -import { - SelectControl, - Button, - Card, - CardHeader, - CardBody, - Spinner, -} from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; - -/** - * Internal dependencies - */ -import useMenuLocations from './use-menu-locations'; - -export default function MenuLocationsEditor() { - const menus = useSelect( ( select ) => select( 'core' ).getMenus() ); - - const [ - menuLocations, - saveMenuLocations, - assignMenuToLocation, - ] = useMenuLocations(); - - if ( ! menus || ! menuLocations ) { - return ; - } - - const menuSelectControlOptions = [ - { value: 0, label: __( '— Select a Menu —' ) }, - ...menus.map( ( { id, name } ) => ( { - value: id, - label: name, - } ) ), - ]; - - if ( menuLocations.length === 0 ) { - return ( - - { __( 'Menu locations' ) } - - { __( 'There are no available menu locations' ) } - - - ); - } - - if ( menus.length === 0 ) { - return ( - - { __( 'Menu locations' ) } - - { __( 'There are no available menus' ) } - - - ); - } - - return ( - - { __( 'Menu locations' ) } - - { - event.preventDefault(); - saveMenuLocations(); - } } - > - - - - { __( 'Theme Location' ) } - { __( 'Assigned Menu' ) } - - - - { menuLocations.map( ( location ) => ( - - { location.description } - - { - assignMenuToLocation( - location.name, - parseInt( newMenuId ) - ); - } } - /> - - - ) ) } - - - - { __( 'Save' ) } - - - - - ); -} diff --git a/packages/edit-navigation/src/components/menu-locations-editor/use-menu-locations.js b/packages/edit-navigation/src/components/menu-locations-editor/use-menu-locations.js deleted file mode 100644 index 16200984819d03..00000000000000 --- a/packages/edit-navigation/src/components/menu-locations-editor/use-menu-locations.js +++ /dev/null @@ -1,120 +0,0 @@ -/** - * External dependencies - */ -import { - includes, - map, - find, - findKey, - mapValues, - flatMap, - groupBy, -} from 'lodash'; -/** - * WordPress dependencies - */ -import apiFetch from '@wordpress/api-fetch'; -import { useDispatch } from '@wordpress/data'; -import { useState, useEffect } from '@wordpress/element'; - -export default function useMenuLocations() { - const { saveMenu } = useDispatch( 'core' ); - const [ menuLocations, setMenuLocations ] = useState( null ); - const [ emptyLocations, setEmptyLocations ] = useState( [] ); - - // a local state which maps menus to locations - // so that we can send one call per menu when - // updating locations, otherwise, without this local state - // we'd send one call per location - const [ menuLocationMap, setMenuLocationMap ] = useState( null ); - - const initMenuLocations = async () => { - const path = '/__experimental/menu-locations'; - const apiLocations = await apiFetch( { - path, - method: 'GET', - } ); - setMenuLocations( flatMap( apiLocations ) ); - }; - - // we need to fetch the list of locations - // because the menu location entity - // caches their menu associations - useEffect( () => { - initMenuLocations(); - }, [] ); - - // as soon as we have the menus we group - // all locations by the menuId they are assigned to - useEffect( () => { - if ( menuLocations ) { - const locationsByMenu = mapValues( - groupBy( menuLocations, 'menu' ), - ( locations ) => map( locations, 'name' ) - ); - setMenuLocationMap( locationsByMenu ); - } - }, [ menuLocations ] ); - - const assignMenuToLocation = ( newLocation, newMenuId ) => { - newMenuId = parseInt( newMenuId ); - - // we need the old menu ID so that we can set empty locations - const oldMenuId = findKey( menuLocationMap, ( locations ) => { - return includes( locations, newLocation ); - } ); - - // we save a list on menus that were unassigned from their location - // and the location is now empty because we need to send - // an update to the API for these menus with an empty location set - const newEmptyLocations = [ ...emptyLocations ]; - if ( newMenuId === 0 ) { - if ( ! includes( newEmptyLocations, oldMenuId ) ) { - newEmptyLocations.push( oldMenuId ); - } - } else if ( includes( newEmptyLocations, oldMenuId ) ) { - // if the menu is assigned to another location - // we remove it from this list because the API - // will unassign it from the past location - delete newEmptyLocations[ oldMenuId ]; - } - setEmptyLocations( newEmptyLocations ); - - const updatedLocation = { - ...find( menuLocations, { name: newLocation } ), - }; - updatedLocation.menu = newMenuId; - - const updatedLocationKey = findKey( menuLocations, { - name: newLocation, - } ); - - const newMenuLocations = [ ...menuLocations ]; - newMenuLocations[ updatedLocationKey ] = { ...updatedLocation }; - - setMenuLocations( newMenuLocations ); - }; - - const saveMenuLocations = async () => { - // first call the API to empty the locations of unset menus - for ( const menuId of emptyLocations ) { - await saveMenu( { - id: menuId, - locations: [], - } ); - } - - // then save the new ones - for ( const menuId in menuLocationMap ) { - // sometimes menuId is 0 for unassigned locations - if ( menuId > 0 ) { - await saveMenu( { - id: menuId, - locations: menuLocationMap[ menuId ], - } ); - } - } - }; - - return [ menuLocations, saveMenuLocations, assignMenuToLocation ]; -} diff --git a/packages/edit-navigation/src/components/menus-editor/create-menu-area.js b/packages/edit-navigation/src/components/menus-editor/create-menu-area.js deleted file mode 100644 index 01b83bcb2c6e49..00000000000000 --- a/packages/edit-navigation/src/components/menus-editor/create-menu-area.js +++ /dev/null @@ -1,135 +0,0 @@ -/** - * External dependencies - */ -import { some } from 'lodash'; - -/** - * WordPress dependencies - */ -import { - Button, - Card, - CardHeader, - CardBody, - TextControl, - withFocusReturn, -} from '@wordpress/components'; -import { useDispatch, useSelect } from '@wordpress/data'; -import { useCallback, useEffect, useState } from '@wordpress/element'; -import { __, sprintf } from '@wordpress/i18n'; -const { DOMParser } = window; - -const noticeId = 'edit-navigation-create-menu-error'; - -const menuNameMatches = ( menuName ) => ( menu ) => - menu.name.toLowerCase() === menuName.toLowerCase(); - -export function CreateMenuArea( { onCancel, onCreateMenu, menus } ) { - const [ menuName, setMenuName ] = useState( '' ); - const [ isCreatingMenu, setIsCreatingMenu ] = useState( false ); - const menuSaveError = useSelect( ( select ) => - select( 'core' ).getLastEntitySaveError( 'root', 'menu' ) - ); - const { saveMenu } = useDispatch( 'core' ); - const { createInfoNotice, createErrorNotice, removeNotice } = useDispatch( - 'core/notices' - ); - - // Handle REST API Error messages. - useEffect( () => { - if ( menuSaveError ) { - // Error messages from the REST API often contain HTML. - // createErrorNotice does not support HTML in error text, so first - // strip HTML out using DOMParser. - const document = new DOMParser().parseFromString( - menuSaveError.message, - 'text/html' - ); - const errorText = document.body.textContent || ''; - createErrorNotice( errorText, { id: noticeId } ); - } - }, [ menuSaveError ] ); - - const createMenu = useCallback( - async ( event ) => { - // Prevent form submission. - event.preventDefault(); - - // Remove existing notices. - removeNotice( noticeId ); - - if ( menuName.length === 0 ) { - // Button is aria-disabled, do nothing. - return; - } - - // Validate the menu name doesn't match an existing menu. - if ( some( menus, menuNameMatches( menuName ) ) ) { - const message = sprintf( - // translators: %s: the name of a menu. - __( - 'The menu name %s conflicts with another menu name. Please try another.' - ), - menuName - ); - createErrorNotice( message, { id: noticeId } ); - return; - } - - setIsCreatingMenu( true ); - - const menu = await saveMenu( { name: menuName } ); - if ( menu ) { - createInfoNotice( __( 'Menu created' ), { - type: 'snackbar', - isDismissible: true, - } ); - onCreateMenu( menu.id ); - } - - setIsCreatingMenu( false ); - }, - [ menuName, menus ] - ); - - return ( - - { __( 'Create navigation menu' ) } - - - - - { __( 'Create menu' ) } - - { onCancel && ( - - { __( 'Cancel' ) } - - ) } - - - - ); -} - -export default withFocusReturn( CreateMenuArea ); diff --git a/packages/edit-navigation/src/components/menus-editor/index.js b/packages/edit-navigation/src/components/menus-editor/index.js deleted file mode 100644 index 7b22b5a295c710..00000000000000 --- a/packages/edit-navigation/src/components/menus-editor/index.js +++ /dev/null @@ -1,167 +0,0 @@ -/** - * External dependencies - */ -import { uniqueId } from 'lodash'; - -/** - * WordPress dependencies - */ -import { useDispatch, useSelect } from '@wordpress/data'; -import { useState, useEffect, useRef } from '@wordpress/element'; -import { - Button, - Card, - CardBody, - Spinner, - SelectControl, -} from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -const { DOMParser } = window; - -/** - * Internal dependencies - */ -import CreateMenuArea from './create-menu-area'; -import NavigationEditor from '../navigation-editor'; - -export default function MenusEditor( { blockEditorSettings } ) { - const [ menuId, setMenuId ] = useState(); - const [ showCreateMenuPanel, setShowCreateMenuPanel ] = useState( false ); - const [ hasCompletedFirstLoad, setHasCompletedFirstLoad ] = useState( - false - ); - - const noticeId = useRef(); - - const { menus, hasLoadedMenus, menuDeleteError } = useSelect( - ( select ) => { - const { - getMenus, - hasFinishedResolution, - getLastEntityDeleteError, - } = select( 'core' ); - const query = { per_page: -1 }; - return { - menus: getMenus( query ), - hasLoadedMenus: hasFinishedResolution( 'getMenus', [ query ] ), - menuDeleteError: getLastEntityDeleteError( - 'root', - 'menu', - menuId - ), - }; - }, - [ menuId ] - ); - - const { deleteMenu } = useDispatch( 'core' ); - const { createErrorNotice, removeNotice } = useDispatch( 'core/notices' ); - - useEffect( () => { - if ( ! hasCompletedFirstLoad && hasLoadedMenus ) { - setHasCompletedFirstLoad( true ); - } - }, [ hasLoadedMenus ] ); - - // Handle REST API Error messages. - useEffect( () => { - if ( menuDeleteError ) { - // Error messages from the REST API often contain HTML. - // createErrorNotice does not support HTML in error text, so first - // strip HTML out using DOMParser. - const document = new DOMParser().parseFromString( - menuDeleteError.message, - 'text/html' - ); - const errorText = document.body.textContent || ''; - noticeId.current = uniqueId( - 'navigation-editor/menu-editor/edit-navigation-delete-menu-error' - ); - createErrorNotice( errorText, { id: noticeId.current } ); - } - }, [ menuDeleteError ] ); - - useEffect( () => { - if ( menus?.length ) { - // Only set menuId if it's currently unset. - if ( ! menuId ) { - setMenuId( menus[ 0 ].id ); - } - } - }, [ menus, menuId ] ); - - if ( ! hasCompletedFirstLoad ) { - return ; - } - - const hasMenus = !! menus?.length; - const isCreateMenuPanelVisible = - hasCompletedFirstLoad && ( ! hasMenus || showCreateMenuPanel ); - - return ( - <> - - - { hasCompletedFirstLoad && ! hasMenus && ( - - { __( 'Create your first menu below.' ) } - - ) } - { hasMenus && ( - <> - ( { - value: menu.id, - label: menu.name, - } ) ) } - onChange={ ( selectedMenuId ) => - setMenuId( Number( selectedMenuId ) ) - } - value={ menuId } - /> - setShowCreateMenuPanel( true ) } - > - { __( 'Create a new menu' ) } - - > - ) } - - - { isCreateMenuPanelVisible && ( - setShowCreateMenuPanel( false ) - : undefined - } - onCreateMenu={ ( newMenuId ) => { - setMenuId( newMenuId ); - setShowCreateMenuPanel( false ); - } } - /> - ) } - { hasMenus && ( - { - removeNotice( noticeId.current ); - const deletedMenu = await deleteMenu( menuId, { - force: 'true', - } ); - if ( deletedMenu ) { - setMenuId( false ); - } - } } - /> - ) } - > - ); -} diff --git a/packages/edit-navigation/src/components/menus-editor/style.scss b/packages/edit-navigation/src/components/menus-editor/style.scss deleted file mode 100644 index 4609d50c0c9599..00000000000000 --- a/packages/edit-navigation/src/components/menus-editor/style.scss +++ /dev/null @@ -1,56 +0,0 @@ -.edit-navigation-menus-editor__menu-selection-card { - margin-bottom: 10px; -} - -.edit-navigation-menus-editor__menu-selection-card-body { - display: flex; - flex-direction: column; - align-items: flex-start; - - .edit-navigation-menus-editor__menu-select-control { - margin-right: 0; - margin-bottom: 10px; - align-self: normal; - } - - @include break-large { - flex-direction: row; - align-items: center; - - .edit-navigation-menus-editor__menu-select-control { - align-self: center; - margin-right: 1ch; - margin-bottom: 0; - } - } -} - -.edit-navigation-menus-editor__menu-select-control { - flex: 1; - margin-right: 1ch; - - @include break-small { - .components-base-control__field { - display: flex; - flex-direction: row; - align-items: baseline; - margin-bottom: 0; - } - .components-base-control__label { - margin-right: 1ch; - } - } -} - -.edit-navigation-menus-editor__menu-selection-card-instructional-text { - margin: 0; -} - -.edit-navigation-menus-editor__create-menu-area { - margin-bottom: 10px; -} - -.edit-navigation-menus-editor__cancel-create-menu-button { - margin-left: 8px; -} - diff --git a/packages/edit-navigation/src/components/navigation-editor/block-editor-area.js b/packages/edit-navigation/src/components/navigation-editor/block-editor-area.js deleted file mode 100644 index 2bb3fe85696299..00000000000000 --- a/packages/edit-navigation/src/components/navigation-editor/block-editor-area.js +++ /dev/null @@ -1,163 +0,0 @@ -/** - * External dependencies - */ -import classnames from 'classnames'; - -/** - * WordPress dependencies - */ -import { - BlockList, - BlockToolbar, - NavigableToolbar, - ObserveTyping, - WritingFlow, - BlockInspector, -} from '@wordpress/block-editor'; -import { useEffect, useState } from '@wordpress/element'; -import { - Button, - Card, - CardHeader, - CardBody, - CardFooter, - CheckboxControl, - Dropdown, - Popover, -} from '@wordpress/components'; -import { useSelect, useDispatch } from '@wordpress/data'; -import { __ } from '@wordpress/i18n'; -import { cog } from '@wordpress/icons'; - -/** - * Internal dependencies - */ -import DeleteMenuButton from '../delete-menu-button'; - -export default function BlockEditorArea( { - onDeleteMenu, - menuId, - saveBlocks, -} ) { - const { - rootBlockId, - isNavigationModeActive, - isRootBlockSelected, - hasSelectedBlock, - } = useSelect( ( select ) => { - const { - isNavigationMode, - getBlockSelectionStart, - getBlock, - getBlocks, - } = select( 'core/block-editor' ); - - const selectionStartClientId = getBlockSelectionStart(); - const rootClientId = getBlocks()[ 0 ]?.clientId; - - return { - selectionStartClientId, - rootBlockId: rootClientId, - isNavigationModeActive: isNavigationMode(), - isRootBlockSelected: - !! selectionStartClientId && - rootClientId === selectionStartClientId, - hasSelectedBlock: - !! selectionStartClientId && - !! getBlock( selectionStartClientId ), - }; - }, [] ); - - const { saveMenu } = useDispatch( 'core' ); - const menu = useSelect( ( select ) => select( 'core' ).getMenu( menuId ), [ - menuId, - ] ); - - const [ autoAddPages, setAutoAddPages ] = useState( false ); - - useEffect( () => { - if ( menu ) { - setAutoAddPages( menu.auto_add ); - } - }, [ menuId ] ); - - // Select the navigation block when it becomes available - const { selectBlock } = useDispatch( 'core/block-editor' ); - useEffect( () => { - if ( rootBlockId ) { - selectBlock( rootBlockId ); - } - }, [ rootBlockId ] ); - - return ( - - - - { __( 'Navigation menu' ) } - - - - { __( 'Save navigation' ) } - - ( - - ) } - renderContent={ () => ( - - ) } - /> - - - - { hasSelectedBlock && ! isRootBlockSelected && ( - - ) } - - - - - - - - - - - - { - setAutoAddPages( ! autoAddPages ); - saveMenu( { - ...menu, - auto_add: ! autoAddPages, - } ); - } } - checked={ autoAddPages } - /> - - - - ); -} diff --git a/packages/edit-navigation/src/components/navigation-editor/helpers.js b/packages/edit-navigation/src/components/navigation-editor/helpers.js deleted file mode 100644 index 1937a6f4a0f027..00000000000000 --- a/packages/edit-navigation/src/components/navigation-editor/helpers.js +++ /dev/null @@ -1,4 +0,0 @@ -export const flattenBlocks = ( blocks ) => - blocks.flatMap( ( item ) => - [ item ].concat( flattenBlocks( item.innerBlocks || [] ) ) - ); diff --git a/packages/edit-navigation/src/components/navigation-editor/index.js b/packages/edit-navigation/src/components/navigation-editor/index.js deleted file mode 100644 index 6602f2f5913caa..00000000000000 --- a/packages/edit-navigation/src/components/navigation-editor/index.js +++ /dev/null @@ -1,81 +0,0 @@ -/** - * WordPress dependencies - */ -import { - BlockEditorKeyboardShortcuts, - BlockEditorProvider, -} from '@wordpress/block-editor'; -import { useViewportMatch } from '@wordpress/compose'; -import { Spinner } from '@wordpress/components'; - -/** - * Internal dependencies - */ -import NavigationEditorShortcuts from './shortcuts'; -import BlockEditorArea from './block-editor-area'; -import NavigationStructureArea from './navigation-structure-area'; -import useNavigationBlockEditor from './use-navigation-block-editor'; -import { useDispatch, useSelect } from '@wordpress/data'; - -export default function NavigationEditor( { - menuId, - blockEditorSettings, - onDeleteMenu, -} ) { - const { post, hasResolved } = useSelect( ( select ) => ( { - post: select( 'core/edit-navigation' ).getNavigationPostForMenu( - menuId - ), - hasResolved: select( 'core/edit-navigation' ).hasResolvedNavigationPost( - menuId - ), - } ) ); - return ( - - - - - { ! hasResolved ? ( - - ) : ( - - ) } - - ); -} - -function NavigationPostEditor( { post, blockEditorSettings, onDeleteMenu } ) { - const isLargeViewport = useViewportMatch( 'medium' ); - const [ blocks, onInput, onChange ] = useNavigationBlockEditor( post ); - const { saveNavigationPost } = useDispatch( 'core/edit-navigation' ); - const save = () => saveNavigationPost( post ); - return ( - - - - - - - ); -} diff --git a/packages/edit-navigation/src/components/navigation-editor/navigation-structure-area.js b/packages/edit-navigation/src/components/navigation-editor/navigation-structure-area.js deleted file mode 100644 index 7c01de144d7353..00000000000000 --- a/packages/edit-navigation/src/components/navigation-editor/navigation-structure-area.js +++ /dev/null @@ -1,54 +0,0 @@ -/** - * WordPress dependencies - */ -import { useViewportMatch } from '@wordpress/compose'; -import { __experimentalBlockNavigationTree } from '@wordpress/block-editor'; -import { - Card, - CardHeader, - CardBody, - Panel, - PanelBody, -} from '@wordpress/components'; -import { useState } from '@wordpress/element'; -import { __ } from '@wordpress/i18n'; - -export default function NavigationStructureArea( { blocks, initialOpen } ) { - const [ selectedBlockId, setSelectedBlockId ] = useState( - blocks[ 0 ]?.clientId - ); - const isSmallScreen = useViewportMatch( 'medium', '<' ); - const showNavigationStructure = !! blocks.length; - - const content = showNavigationStructure && ( - <__experimentalBlockNavigationTree - blocks={ blocks } - selectedBlockClientId={ selectedBlockId } - selectBlock={ ( id ) => { - setSelectedBlockId( id ); - } } - __experimentalFeatures - showNestedBlocks - showAppender - showBlockMovers - /> - ); - - return isSmallScreen ? ( - - - { content } - - - ) : ( - - - { __( 'Navigation structure' ) } - - { content } - - ); -} diff --git a/packages/edit-navigation/src/components/navigation-editor/style.scss b/packages/edit-navigation/src/components/navigation-editor/style.scss deleted file mode 100644 index 26a20bfce1680e..00000000000000 --- a/packages/edit-navigation/src/components/navigation-editor/style.scss +++ /dev/null @@ -1,130 +0,0 @@ -.edit-navigation-editor { - display: grid; - align-items: self-start; - grid-gap: 10px; - - @include break-medium { - grid-template-columns: 280px 1fr; - } - - // Make the block list take up the full width of the panel. - .block-editor-block-list__layout.is-root-container { - padding: 0; - } -} - -.edit-navigation-editor__block-editor-toolbar { - height: 46px; - margin-bottom: 12px; - border: 1px solid #e2e4e7; - - // Borders around toolbar segments. - .components-toolbar-group, - .components-toolbar { - background: none; - // IE11 has thick paddings without this. - line-height: 0; - - // These margins make the buttons themselves overlap the chrome of the toolbar. - // This helps make them square, and maximize the hit area. - margin-top: -$border-width; - margin-bottom: -$border-width; - - // The component is born with a border, but we only need some of them. - border: 0; - - // Add a border after item groups to show as separator in the block toolbar. - border-right: $border-width solid $gray-200; - } - - - // When entering navigation mode, hide the toolbar, but do so in a way where the - // outer container retains its height to avoid the blocks moving upwards. - &.is-hidden { - border-color: transparent; - - .block-editor-block-toolbar { - display: none; - } - } -} - -.block-editor-block-navigation-leaf { - > :first-child { - padding-left: 0; - } - > :first-child button { - padding-left: 0; - } - - &.is-selected .block-editor-block-icon svg, - &.is-selected:focus .block-editor-block-icon svg { - color: $dark-gray-600; - background: transparent; - box-shadow: none; - } -} - -.edit-navigation-editor__navigation-structure-panel { - // IE11 requires the column to be explicitly declared. - grid-column: 1; - - // Make panels collapsible in IE. The IE analogue of align-items: self-start;. - -ms-grid-row-align: start; - - .components-card__header { - font-weight: bold; - border-bottom: 0; - padding-top: $grid-unit-30 !important; - padding-bottom: $grid-unit-30 !important; - } -} - -.edit-navigation-editor__navigation-structure-header { - font-weight: bold; -} - -.edit-navigation-editor__block-editor-area { - @include break-medium { - // IE11 requires the column to be explicitly declared. - // Only shift this into the second column on desktop. - grid-column: 2; - } - - .components-card__footer, - .components-card__header { - display: flex; - align-items: center; - justify-content: space-between; - } - - .edit-navigation-editor__block-editor-area-header-text { - flex-grow: 1; - font-weight: bold; - } -} - -.components-panel__header-actions { - margin-bottom: $grid-unit-60; - width: 100%; - text-align: right; -} - -.components-panel__footer-actions { - margin-top: $grid-unit-60; - padding-top: $grid-unit-20; - width: 100%; - text-align: left; - border-top: 1px solid $gray-200; -} - -.edit-navigation-editor__block-inspector { - .components-button { - margin-left: $grid-unit-20; - - &[aria-expanded="true"] { - color: $white; - background-color: $gray-900; - } - } -} diff --git a/packages/edit-navigation/src/components/notices/style.scss b/packages/edit-navigation/src/components/notices/style.scss index a5f3848cd8b106..c952988f1a09a1 100644 --- a/packages/edit-navigation/src/components/notices/style.scss +++ b/packages/edit-navigation/src/components/notices/style.scss @@ -1,6 +1,7 @@ .edit-navigation-notices__snackbar-list { position: fixed; bottom: 20px; + margin-left: 20px; } .edit-navigation-notices__notice-list { diff --git a/packages/edit-navigation/src/components/toolbar/block-inspector-dropdown.js b/packages/edit-navigation/src/components/toolbar/block-inspector-dropdown.js new file mode 100644 index 00000000000000..dece6bd9d8bbb8 --- /dev/null +++ b/packages/edit-navigation/src/components/toolbar/block-inspector-dropdown.js @@ -0,0 +1,27 @@ +/** + * WordPress dependencies + */ +import { Dropdown, Button } from '@wordpress/components'; +import { cog } from '@wordpress/icons'; +import { __ } from '@wordpress/i18n'; +import { BlockInspector } from '@wordpress/block-editor'; + +export default function BlockInspectorDropdown() { + return ( + ( + + ) } + renderContent={ () => ( + + ) } + /> + ); +} diff --git a/packages/edit-navigation/src/components/toolbar/index.js b/packages/edit-navigation/src/components/toolbar/index.js new file mode 100644 index 00000000000000..9837b1e2635100 --- /dev/null +++ b/packages/edit-navigation/src/components/toolbar/index.js @@ -0,0 +1,37 @@ +/** + * WordPress dependencies + */ +import { Spinner, Popover } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { NavigableToolbar, BlockToolbar } from '@wordpress/block-editor'; + +/** + * Internal dependencies + */ +import SaveButton from './save-button'; +import BlockInspectorDropdown from './block-inspector-dropdown'; + +export default function Toolbar( { isPending, navigationPost } ) { + return ( + + { isPending ? ( + + ) : ( + <> + + + + + + + > + ) } + + ); +} diff --git a/packages/edit-navigation/src/components/toolbar/save-button.js b/packages/edit-navigation/src/components/toolbar/save-button.js new file mode 100644 index 00000000000000..c5adfef8b8ac75 --- /dev/null +++ b/packages/edit-navigation/src/components/toolbar/save-button.js @@ -0,0 +1,22 @@ +/** + * WordPress dependencies + */ +import { useDispatch } from '@wordpress/data'; +import { Button } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +export default function SaveButton( { navigationPost } ) { + const { saveNavigationPost } = useDispatch( 'core/edit-navigation' ); + + return ( + { + saveNavigationPost( navigationPost ); + } } + > + { __( 'Save' ) } + + ); +} diff --git a/packages/edit-navigation/src/components/toolbar/style.scss b/packages/edit-navigation/src/components/toolbar/style.scss new file mode 100644 index 00000000000000..2e07c14ef09462 --- /dev/null +++ b/packages/edit-navigation/src/components/toolbar/style.scss @@ -0,0 +1,42 @@ +.edit-navigation-toolbar { + align-items: center; + background: $white; + border: $border-width solid $gray-200; + display: flex; + height: $grid-unit-60 * 2; + padding-bottom: $grid-unit-60; + position: relative; + + @include break-medium() { + height: $grid-unit-60; + padding-bottom: 0; + } + + .components-spinner { + left: 50%; + margin: 0; + position: absolute; + top: 50%; + transform: translate(-50%, -50%); + } +} + +.edit-navigation-toolbar__block-tools { + border-bottom: none; + border-left: none; + border-right: none; + border-top: 1px solid $gray-200; + position: absolute; + top: $grid-unit-60; + width: 100%; + + @include break-medium() { + border-top: none; + position: static; + width: auto; + } +} + +.edit-navigation-toolbar__save-button { + margin: 0 $grid-unit-10 0 auto; +} diff --git a/packages/edit-navigation/src/store/resolvers.js b/packages/edit-navigation/src/store/resolvers.js index 99a0f76ae4b055..af3b59a02b8ac6 100644 --- a/packages/edit-navigation/src/store/resolvers.js +++ b/packages/edit-navigation/src/store/resolvers.js @@ -51,14 +51,14 @@ export function* getNavigationPostForMenu( menuId ) { yield dispatch( 'core', 'finishResolution', 'getEntityRecord', args ); } -const createStubPost = ( menuId, navigationBlock ) => { +const createStubPost = ( menuId, navigationBlock = null ) => { const id = buildNavigationPostId( menuId ); return { id, slug: id, status: 'draft', type: 'page', - blocks: [ navigationBlock ], + blocks: navigationBlock ? [ navigationBlock ] : [], meta: { menuId, }, diff --git a/packages/edit-navigation/src/store/test/resolvers.js b/packages/edit-navigation/src/store/test/resolvers.js index a46d624bba727f..9d3b8485417b2b 100644 --- a/packages/edit-navigation/src/store/test/resolvers.js +++ b/packages/edit-navigation/src/store/test/resolvers.js @@ -35,7 +35,7 @@ describe( 'getNavigationPostForMenu', () => { slug: id, status: 'draft', type: 'page', - blocks: [ undefined ], + blocks: [], meta: { menuId, }, diff --git a/packages/edit-navigation/src/style.scss b/packages/edit-navigation/src/style.scss index 9e179196deadff..da4b488480c7db 100644 --- a/packages/edit-navigation/src/style.scss +++ b/packages/edit-navigation/src/style.scss @@ -4,9 +4,10 @@ box-sizing: border-box; } +@import "./components/editor/style.scss"; +@import "./components/error-boundary/style.scss"; +@import "./components/header/style.scss"; +@import "./components/inspector-additions/style.scss"; @import "./components/layout/style.scss"; -@import "./components/navigation-editor/style.scss"; -@import "./components/menus-editor/style.scss"; -@import "./components/delete-menu-button/style.scss"; @import "./components/notices/style.scss"; -@import "./components/error-boundary/style.scss"; +@import "./components/toolbar/style.scss";
{ __( 'There are no available menus.' ) }
{ __( 'There are no available menu locations.' ) }
{ __( 'There are no available menu locations' ) }
{ __( 'There are no available menus' ) }
- { __( 'Create your first menu below.' ) } -