diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md index 3bb72598aabf41..699812332029a5 100644 --- a/packages/block-editor/README.md +++ b/packages/block-editor/README.md @@ -210,6 +210,10 @@ _Returns_ Undocumented declaration. +# **BlockToolbarLinkControl** + +Undocumented declaration. + # **BlockVerticalAlignmentToolbar** _Related_ diff --git a/packages/block-editor/src/components/block-toolbar-link-control/compute-display-url.js b/packages/block-editor/src/components/block-toolbar-link-control/compute-display-url.js new file mode 100644 index 00000000000000..5dfb684bbc9462 --- /dev/null +++ b/packages/block-editor/src/components/block-toolbar-link-control/compute-display-url.js @@ -0,0 +1,23 @@ +export default function computeDisplayUrl( url ) { + if ( ! url ) { + return ''; + } + + let urlData; + try { + urlData = new URL( url ); + } catch ( e ) { + return url; + } + let displayUrl = ''; + + const siteHost = document.location.host; + if ( urlData.host && urlData.host !== siteHost ) { + displayUrl += urlData.host; + } + displayUrl += urlData.pathname; + if ( urlData.search ) { + displayUrl += urlData.search; + } + return displayUrl; +} diff --git a/packages/block-editor/src/components/block-toolbar-link-control/context.js b/packages/block-editor/src/components/block-toolbar-link-control/context.js new file mode 100644 index 00000000000000..d7091d02d4a5f1 --- /dev/null +++ b/packages/block-editor/src/components/block-toolbar-link-control/context.js @@ -0,0 +1,7 @@ +/** + * WordPress dependencies + */ +import { createContext } from '@wordpress/element'; + +const ToolbarLinkControlContext = createContext( {} ); +export default ToolbarLinkControlContext; diff --git a/packages/block-editor/src/components/block-toolbar-link-control/index.js b/packages/block-editor/src/components/block-toolbar-link-control/index.js new file mode 100644 index 00000000000000..f244d65d6feab4 --- /dev/null +++ b/packages/block-editor/src/components/block-toolbar-link-control/index.js @@ -0,0 +1,69 @@ +/** + * WordPress dependencies + */ +import { Button } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { useCallback, useMemo, useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import BlockToolbarInlineEdit from '../block-toolbar-inline-edit'; +import SearchInput from './search-input'; +import SettingsMenu from './settings-menu'; +import computeDisplayUrl from './compute-display-url'; +import ToolbarLinkControlContext from './context'; + +export default function ToolbarLinkControl( { + initialLink, + createSuggestion, + close, + onChange, +} ) { + const [ currentLink, setCurrentLink ] = useState( { + ...initialLink, + url: computeDisplayUrl( initialLink.url ), + } ); + + const updateCurrentLink = useCallback( + ( data ) => { + const newLink = { + ...currentLink, + ...data, + }; + if ( currentLink.id && ! data.id ) { + delete newLink.id; + } + if ( 'url' in data ) { + newLink.url = computeDisplayUrl( data.url ); + } else { + newLink.url = currentLink.url; + } + if ( data.title && ! currentLink.label ) { + newLink.label = data.title; + } + setCurrentLink( newLink ); + onChange( newLink ); + }, + [ currentLink ] + ); + + const contextValue = useMemo( + () => ( { + createSuggestion, + currentLink, + updateCurrentLink, + } ), + [ createSuggestion, currentLink, updateCurrentLink ] + ); + + return ( + + + + + + + + ); +} diff --git a/packages/block-editor/src/components/block-toolbar-link-control/search-input.js b/packages/block-editor/src/components/block-toolbar-link-control/search-input.js new file mode 100644 index 00000000000000..17c947f27c82d9 --- /dev/null +++ b/packages/block-editor/src/components/block-toolbar-link-control/search-input.js @@ -0,0 +1,97 @@ +/** + * WordPress dependencies + */ +import { + Popover, + __experimentalInputControl as InputControl, + Icon, + Spinner, +} from '@wordpress/components'; +import { link as linkIcon } from '@wordpress/icons'; +import { useContext, useEffect, useRef, useState } from '@wordpress/element'; +import { useDispatch } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import LinkControlSearchResults from '../link-control/search-results'; +import LinkControlSearchInput from '../link-control/search-input'; +import useCreatePage from '../link-control/use-create-page'; +import ToolbarLinkControlContext from './context'; + +const renderSuggestions = ( suggestionsProps ) => ( + + + +); + +export default function SearchInput() { + const [ hasFocus, setHasFocus ] = useState( false ); + const { createSuggestion, currentLink, updateCurrentLink } = useContext( + ToolbarLinkControlContext + ); + + const { createPage, isCreatingPage, errorMessage } = useCreatePage( + createSuggestion + ); + + const { createErrorNotice } = useDispatch( 'core/notices' ); + useEffect( () => { + if ( errorMessage ) { + createErrorNotice( errorMessage, { type: 'snackbar' } ); + } + }, [ errorMessage ] ); + + const searchInputRef = useRef(); + + return ( + { + updateCurrentLink( { url } ); + } } + onSelect={ ( link ) => { + updateCurrentLink( link ); + } } + showInitialSuggestions={ false } + allowDirectEntry + withCreateSuggestion + renderControl={ ( controlProps, inputProps, { isLoading } ) => { + return ( + { + inputProps.onChange( event ); + } } + onFocus={ () => { + setHasFocus( true ); + } } + onBlur={ () => { + setHasFocus( false ); + } } + prefix={ +
+ +
+ } + suffix={ +
+ { ( isCreatingPage || isLoading ) && ( + + ) } +
+ } + /> + ); + } } + /> + ); +} diff --git a/packages/block-editor/src/components/block-toolbar-link-control/settings-menu.js b/packages/block-editor/src/components/block-toolbar-link-control/settings-menu.js new file mode 100644 index 00000000000000..3ebe07a2513d02 --- /dev/null +++ b/packages/block-editor/src/components/block-toolbar-link-control/settings-menu.js @@ -0,0 +1,75 @@ +/** + * WordPress dependencies + */ +import { DropdownMenu, MenuGroup, MenuItem } from '@wordpress/components'; +import { + chevronDown as arrowDownIcon, + check as checkIcon, +} from '@wordpress/icons'; +import { __ } from '@wordpress/i18n'; +import { useContext } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import ToolbarLinkControlContext from './context'; + +export default function SettingsMenu() { + const { currentLink, updateCurrentLink } = useContext( + ToolbarLinkControlContext + ); + const { opensInNewTab, rel } = currentLink; + return ( + + { ( { onClose } ) => ( + <> + + { + updateCurrentLink( { + opensInNewTab: ! opensInNewTab, + } ); + } } + > + { __( 'Open in new tab' ) } + + { + updateCurrentLink( { + rel: + currentLink.rel === 'nofollow' + ? '' + : 'nofollow', + } ); + } } + > + { __( 'Add nofollow attribute' ) } + + + + { + updateCurrentLink( { + url: '', + } ); + onClose(); + } } + > + { __( 'Remove link' ) } + + + + ) } + + ); +} diff --git a/packages/block-editor/src/components/block-toolbar-link-control/style.scss b/packages/block-editor/src/components/block-toolbar-link-control/style.scss new file mode 100644 index 00000000000000..53ebf2e4c3b1e8 --- /dev/null +++ b/packages/block-editor/src/components/block-toolbar-link-control/style.scss @@ -0,0 +1,56 @@ + +// Specificity hack +.block-editor-block-toolbar.block-editor-block-toolbar { + + [data-experimental-toolbar-item]:focus .components-input-control__container { + //box-shadow: inset 0 0 0 2px var(--wp-admin-theme-color); + outline: var(--wp-admin-theme-color) auto 2px; + } + + .toolbar-link-control__input-group { + flex-grow: 1; + margin: 0; + height: 45px; + min-height: 0; + } + + .toolbar-link-control__input-wrapper { + display: flex; + + .components-base-control { + display: flex; + height: 100%; + } + + .components-base-control__field { + display: flex; + margin: 0; + } + + .components-input-control__input { + min-width: 280px; + padding-left: 0; + } + } + + .toolbar-link-control__input-control { + height: 45px; + border: 1px solid #ccc; + flex-grow: 1; + align-self: center; + font-size: 13px; + } + + .toolbar-link-control__affix-wrapper { + width: $grid-unit-50; + text-align: center; + + .components-spinner { + margin: 0; + } + } +} + +.block-editor-link-control__search-results-wrapper.is-toolbar-results { + margin-top: 0; +} diff --git a/packages/block-editor/src/components/index.js b/packages/block-editor/src/components/index.js index 06b76dc2f95f55..dc727ffd49f46c 100644 --- a/packages/block-editor/src/components/index.js +++ b/packages/block-editor/src/components/index.js @@ -23,6 +23,7 @@ export { default as __experimentalBlockNavigationEditor } from './block-navigati export { default as __experimentalBlockNavigationTree } from './block-navigation/tree'; export { default as __experimentalBlockVariationPicker } from './block-variation-picker'; export { default as BlockVerticalAlignmentToolbar } from './block-vertical-alignment-toolbar'; +export { default as BlockToolbarLinkControl } from './block-toolbar-link-control'; export { default as ButtonBlockerAppender } from './button-block-appender'; export { default as ColorPalette } from './color-palette'; export { default as ColorPaletteControl } from './color-palette/control'; diff --git a/packages/block-editor/src/components/link-control/search-input.js b/packages/block-editor/src/components/link-control/search-input.js index c8ae882ae1aba8..354b1e07c0b142 100644 --- a/packages/block-editor/src/components/link-control/search-input.js +++ b/packages/block-editor/src/components/link-control/search-input.js @@ -7,7 +7,12 @@ import { noop, omit } from 'lodash'; * WordPress dependencies */ import { useInstanceId } from '@wordpress/compose'; -import { forwardRef, useState } from '@wordpress/element'; +import { + forwardRef, + useRef, + useState, + useImperativeHandle, +} from '@wordpress/element'; import { __ } from '@wordpress/i18n'; /** @@ -33,8 +38,12 @@ const LinkControlSearchInput = forwardRef( onSelect = noop, showSuggestions = true, renderSuggestions = ( props ) => ( - + ), + renderControl = null, fetchSuggestions = null, allowDirectEntry = true, showInitialSuggestions = false, @@ -117,6 +126,13 @@ const LinkControlSearchInput = forwardRef( } }; + const urlInputRef = useRef(); + useImperativeHandle( ref, () => ( { + selectFocusedSuggestion: () => { + onSuggestionSelected( focusedSuggestion || { url: value } ); + }, + } ) ); + return (
{ children } diff --git a/packages/block-editor/src/components/link-control/search-results.js b/packages/block-editor/src/components/link-control/search-results.js index cc24b81f18b179..b8e2eacc1b01f4 100644 --- a/packages/block-editor/src/components/link-control/search-results.js +++ b/packages/block-editor/src/components/link-control/search-results.js @@ -20,6 +20,7 @@ import { CREATE_TYPE } from './constants'; export default function LinkControlSearchResults( { instanceId, withCreateSuggestion, + className, currentInputValue, handleSuggestionClick, suggestionsListProps, @@ -75,8 +76,13 @@ export default function LinkControlSearchResults( { ); + const wrapperClass = classnames( + 'block-editor-link-control__search-results-wrapper', + className + ); + return ( -
+
{ searchResultsLabel }
{ - if ( isLinkOpen && url ) { + if ( isLinkOpen && url && ! isExperimentalNavScreen ) { // Does this look like a URL and have something TLD-ish? if ( isURL( prependHTTP( label ) ) && @@ -264,6 +266,14 @@ function NavigationLinkEdit( { return ( + { isExperimentalNavScreen && isLinkOpen && ( + setIsLinkOpen( false ) } + onChange={ ( args ) => setAttributes( args ) } + /> + ) } - { isLinkOpen && ( + { ! isExperimentalNavScreen && isLinkOpen && ( setIsLinkOpen( false ) } @@ -486,6 +496,11 @@ export default compose( [ getBlockParentsByBlockName( clientId, 'core/navigation' ) ); const navigationBlockAttributes = getBlockAttributes( rootBlock ); + const isExperimentalNavScreen = get( + getSettings(), + '__experimentalNavigationScreen', + false + ); const colors = get( getSettings(), 'colors', [] ); const hasDescendants = !! getClientIdsOfDescendants( [ clientId ] ) .length; @@ -502,6 +517,7 @@ export default compose( [ ] )?.length; return { + isExperimentalNavScreen, isParentOfSelectedBlock, isImmediateParentOfSelectedBlock, hasDescendants, diff --git a/packages/components/src/dropdown-menu/index.js b/packages/components/src/dropdown-menu/index.js index 2525939403799d..07a4ee43a570e2 100644 --- a/packages/components/src/dropdown-menu/index.js +++ b/packages/components/src/dropdown-menu/index.js @@ -40,6 +40,7 @@ function DropdownMenu( { icon = 'menu', label, popoverProps, + onToggle: dropdownOnToggle, toggleProps, menuProps, disableOpenOnArrowDown = false, @@ -86,6 +87,7 @@ function DropdownMenu( { { const openOnArrowDown = ( event ) => { if ( disableOpenOnArrowDown ) { diff --git a/packages/edit-navigation/src/index.js b/packages/edit-navigation/src/index.js index bc858e2e964a67..20c1e6936d87e6 100644 --- a/packages/edit-navigation/src/index.js +++ b/packages/edit-navigation/src/index.js @@ -126,6 +126,8 @@ export function initialize( id, settings ) { settings ); + settings.__experimentalNavigationScreen = true; + render( , document.getElementById( id )