diff --git a/packages/block-editor/src/components/link-control/constants.js b/packages/block-editor/src/components/link-control/constants.js
index eaf07aea73703..e70ff7b04c747 100644
--- a/packages/block-editor/src/components/link-control/constants.js
+++ b/packages/block-editor/src/components/link-control/constants.js
@@ -8,7 +8,7 @@ import { __ } from '@wordpress/i18n';
// order to handle it as a unique case.
export const CREATE_TYPE = '__CREATE__';
export const TEL_TYPE = 'tel';
-export const URL_TYPE = 'URL';
+export const URL_TYPE = 'link';
export const MAILTO_TYPE = 'mailto';
export const INTERNAL_TYPE = 'internal';
diff --git a/packages/block-editor/src/components/link-control/search-create-button.js b/packages/block-editor/src/components/link-control/search-create-button.js
index 1786b516df5c0..6a53fd36cf893 100644
--- a/packages/block-editor/src/components/link-control/search-create-button.js
+++ b/packages/block-editor/src/components/link-control/search-create-button.js
@@ -1,21 +1,15 @@
-/**
- * External dependencies
- */
-import classnames from 'classnames';
-
/**
* WordPress dependencies
*/
import { __, sprintf } from '@wordpress/i18n';
-import { Button } from '@wordpress/components';
+import { MenuItem } from '@wordpress/components';
import { createInterpolateElement } from '@wordpress/element';
-import { Icon, plus } from '@wordpress/icons';
+import { plus } from '@wordpress/icons';
export const LinkControlSearchCreate = ( {
searchTerm,
onClick,
itemProps,
- isSelected,
buttonText,
} ) => {
if ( ! searchTerm ) {
@@ -40,27 +34,15 @@ export const LinkControlSearchCreate = ( {
}
return (
-
+ { text }
+
);
};
diff --git a/packages/block-editor/src/components/link-control/search-item.js b/packages/block-editor/src/components/link-control/search-item.js
index 250b28596f4f3..976bb4420cb0c 100644
--- a/packages/block-editor/src/components/link-control/search-item.js
+++ b/packages/block-editor/src/components/link-control/search-item.js
@@ -1,14 +1,8 @@
-/**
- * External dependencies
- */
-import classnames from 'classnames';
-
/**
* WordPress dependencies
*/
-import { safeDecodeURI, filterURLForDisplay } from '@wordpress/url';
import { __ } from '@wordpress/i18n';
-import { Button, TextHighlight } from '@wordpress/components';
+import { MenuItem, TextHighlight } from '@wordpress/components';
import {
Icon,
globe,
@@ -19,6 +13,7 @@ import {
file,
} from '@wordpress/icons';
import { __unstableStripHTML as stripHTML } from '@wordpress/dom';
+import { safeDecodeURI, filterURLForDisplay } from '@wordpress/url';
const ICONS_MAP = {
post: postList,
@@ -52,50 +47,33 @@ function SearchItemIcon( { isURL, suggestion } ) {
export const LinkControlSearchItem = ( {
itemProps,
suggestion,
- isSelected = false,
+ searchTerm,
onClick,
isURL = false,
- searchTerm = '',
shouldShowType = false,
} ) => {
+ const info = isURL
+ ? __( 'Press ENTER to add this link' )
+ : filterURLForDisplay( safeDecodeURI( suggestion?.url ) );
+
return (
-
+ }
onClick={ onClick }
- className={ classnames( 'block-editor-link-control__search-item', {
- 'is-selected': isSelected,
- 'is-url': isURL,
- 'is-entity': ! isURL,
- } ) }
+ shortcut={ shouldShowType && getVisualTypeName( suggestion ) }
+ className="block-editor-link-control__search-item"
>
-
-
-
-
-
-
-
- { ! isURL &&
- ( filterURLForDisplay(
- safeDecodeURI( suggestion.url )
- ) ||
- '' ) }
- { isURL && __( 'Press ENTER to add this link' ) }
-
-
- { shouldShowType && suggestion.type && (
-
- { getVisualTypeName( suggestion ) }
-
- ) }
-
+
+
);
};
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 9d7ee7ca41abd..71e258c769bf1 100644
--- a/packages/block-editor/src/components/link-control/search-results.js
+++ b/packages/block-editor/src/components/link-control/search-results.js
@@ -2,7 +2,7 @@
* WordPress dependencies
*/
import { __, sprintf } from '@wordpress/i18n';
-import { VisuallyHidden } from '@wordpress/components';
+import { VisuallyHidden, MenuGroup } from '@wordpress/components';
/**
* External dependencies
@@ -72,59 +72,61 @@ export default function LinkControlSearchResults( {
className={ resultsListClasses }
aria-labelledby={ searchResultsLabelId }
>
- { suggestions.map( ( suggestion, index ) => {
- if (
- shouldShowCreateSuggestion &&
- CREATE_TYPE === suggestion.type
- ) {
+
+ { suggestions.map( ( suggestion, index ) => {
+ if (
+ shouldShowCreateSuggestion &&
+ CREATE_TYPE === suggestion.type
+ ) {
+ return (
+
+ handleSuggestionClick( suggestion )
+ }
+ // Intentionally only using `type` here as
+ // the constant is enough to uniquely
+ // identify the single "CREATE" suggestion.
+ key={ suggestion.type }
+ itemProps={ buildSuggestionItemProps(
+ suggestion,
+ index
+ ) }
+ isSelected={ index === selectedSuggestion }
+ />
+ );
+ }
+
+ // If we're not handling "Create" suggestions above then
+ // we don't want them in the main results so exit early.
+ if ( CREATE_TYPE === suggestion.type ) {
+ return null;
+ }
+
return (
-
- handleSuggestionClick( suggestion )
- }
- // Intentionally only using `type` here as
- // the constant is enough to uniquely
- // identify the single "CREATE" suggestion.
- key={ suggestion.type }
+ {
+ handleSuggestionClick( suggestion );
+ } }
isSelected={ index === selectedSuggestion }
+ isURL={ LINK_ENTRY_TYPES.includes(
+ suggestion.type
+ ) }
+ searchTerm={ currentInputValue }
+ shouldShowType={ shouldShowSuggestionsTypes }
+ isFrontPage={ suggestion?.isFrontPage }
/>
);
- }
-
- // If we're not handling "Create" suggestions above then
- // we don't want them in the main results so exit early.
- if ( CREATE_TYPE === suggestion.type ) {
- return null;
- }
-
- return (
- {
- handleSuggestionClick( suggestion );
- } }
- isSelected={ index === selectedSuggestion }
- isURL={ LINK_ENTRY_TYPES.includes(
- suggestion.type
- ) }
- searchTerm={ currentInputValue }
- shouldShowType={ shouldShowSuggestionsTypes }
- isFrontPage={ suggestion?.isFrontPage }
- />
- );
- } ) }
+ } ) }
+
);
diff --git a/packages/block-editor/src/components/link-control/style.scss b/packages/block-editor/src/components/link-control/style.scss
index 2dd57e56c3422..2c4b77f342310 100644
--- a/packages/block-editor/src/components/link-control/style.scss
+++ b/packages/block-editor/src/components/link-control/style.scss
@@ -41,6 +41,7 @@ $preview-image-height: 140px;
// Provides positioning context for reset button. Without this then when an
// error notice is displayed the input's reset button is incorrectly positioned.
.block-editor-link-control__search-input-wrapper {
+ margin-bottom: $grid-unit-10;
position: relative;
}
@@ -77,10 +78,11 @@ $preview-image-height: 140px;
@include input-control;
width: calc(100% - #{$grid-unit-20 * 2});
display: block;
- padding: 11px $grid-unit-20;
+ padding: $grid-unit-10 $grid-unit-20;
margin: 0;
position: relative;
- border: 1px solid $gray-300;
+ border: 1px solid $gray-600;
+ height: 40px;
border-radius: $radius-block-ui;
}
}
@@ -97,37 +99,9 @@ $preview-image-height: 140px;
order: 20;
}
-.block-editor-link-control__search-results-wrapper {
- position: relative;
- margin-top: -$grid-unit-20 + 1px;
-
- &::before,
- &::after {
- content: "";
- position: absolute;
- left: -1px;
- right: $grid-unit-20; // avoid overlaying scrollbars
- display: block;
- pointer-events: none;
- z-index: 100;
- }
-
- &::before {
- height: $grid-unit-20 * 0.5;
- top: 0;
- bottom: auto;
- }
-
- &::after {
- height: $grid-unit-20;
- bottom: 0;
- top: auto;
- }
-}
-
.block-editor-link-control__search-results {
- margin: 0;
- padding: $grid-unit-20 * 0.5 $grid-unit-20 $grid-unit-20 * 0.5;
+ margin-top: -$grid-unit-20;
+ padding: $grid-unit-10;
max-height: 200px;
overflow-y: auto; // allow results list to scroll
@@ -137,39 +111,28 @@ $preview-image-height: 140px;
}
.block-editor-link-control__search-item {
- position: relative;
- display: flex;
- align-items: flex-start; // when link text is very long it is important this indicator remains visible and thus should be aligned top.
- font-size: $default-font-size;
- cursor: pointer;
- background: $white;
- width: 100%;
- border: none;
- text-align: left;
- padding: $grid-unit-15 $grid-unit-20;
- border-radius: 2px;
- height: auto;
- &:hover,
- &:focus {
- background-color: $gray-100;
+ &.components-button.components-menu-item__button {
+ height: auto;
+ text-align: left;
+ }
- .block-editor-link-control__search-item-type {
- background: $white;
- }
+ .components-menu-item__item {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ // Inline block required to preserve white space
+ // between `` elements and text nodes.
+ display: inline-block;
}
- // The added specificity is needed to override.
- &:focus:not(:disabled) {
- box-shadow: 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color) inset;
+ .components-menu-item__shortcut {
+ color: $gray-700;
+ text-transform: capitalize;
+ white-space: nowrap; // tags shouldn't go over two lines.
}
- &.is-selected {
+ &[aria-selected] {
background: $gray-100;
-
- .block-editor-link-control__search-item-type {
- background: $white;
- }
}
&.is-current {
@@ -209,7 +172,6 @@ $preview-image-height: 140px;
.block-editor-link-control__search-item-icon {
position: relative;
- top: 0.2em;
margin-right: $grid-unit-10;
max-height: 24px;
flex-shrink: 0;
@@ -228,18 +190,6 @@ $preview-image-height: 140px;
max-height: 32px;
}
- .block-editor-link-control__search-item-info,
- .block-editor-link-control__search-item-title {
- overflow: hidden;
- text-overflow: ellipsis;
-
- .components-external-link__icon {
- position: absolute;
- right: 0;
- margin-top: 0;
- }
- }
-
.block-editor-link-control__search-item-title {
display: block;
margin-bottom: 0.2em;
@@ -261,28 +211,6 @@ $preview-image-height: 140px;
}
}
- .block-editor-link-control__search-item-info {
- display: block;
- color: $gray-700;
- font-size: 0.9em;
- line-height: 1.3;
- }
-
- .block-editor-link-control__search-item-error-notice {
- font-style: italic;
- font-size: 1.1em;
- }
-
- .block-editor-link-control__search-item-type {
- display: block;
- padding: 3px 6px;
- margin-left: auto;
- font-size: 0.9em;
- background-color: $gray-100;
- border-radius: 2px;
- white-space: nowrap; // tags shouldn't go over two lines.
- }
-
.block-editor-link-control__search-item-description {
padding-top: 12px;
margin: 0;
@@ -422,11 +350,6 @@ $preview-image-height: 140px;
}
}
-// Specificity override
-.block-editor-link-control__search-results div[role="menu"] > .block-editor-link-control__search-item.block-editor-link-control__search-item {
- padding: 10px;
-}
-
.block-editor-link-control__drawer {
display: flex; // allow for ordering.
order: 30;
diff --git a/packages/block-editor/src/components/link-control/test/index.js b/packages/block-editor/src/components/link-control/test/index.js
index e5084844946be..8e4cd1634b2e7 100644
--- a/packages/block-editor/src/components/link-control/test/index.js
+++ b/packages/block-editor/src/components/link-control/test/index.js
@@ -476,16 +476,16 @@ describe( 'Searching for a link', () => {
// The fallback URL suggestion should not be shown when input is not URL-like.
expect(
searchResultElements[ searchResultElements.length - 1 ]
- ).not.toHaveTextContent( 'URL' );
+ ).not.toHaveTextContent( 'Press ENTER to add this link' );
}
);
it.each( [
- [ 'https://wordpress.org', 'URL' ],
- [ 'http://wordpress.org', 'URL' ],
- [ 'www.wordpress.org', 'URL' ],
- [ 'wordpress.org', 'URL' ],
- [ 'ftp://wordpress.org', 'URL' ],
+ [ 'https://wordpress.org', 'link' ],
+ [ 'http://wordpress.org', 'link' ],
+ [ 'www.wordpress.org', 'link' ],
+ [ 'wordpress.org', 'link' ],
+ [ 'ftp://wordpress.org', 'link' ],
[ 'mailto:hello@wordpress.org', 'mailto' ],
[ 'tel:123456789', 'tel' ],
[ '#internal', 'internal' ],
@@ -582,6 +582,37 @@ describe( 'Searching for a link', () => {
expect( mockFetchSearchSuggestions ).not.toHaveBeenCalled();
} );
+ it( 'should not display a URL suggestion when input is not likely to be a URL.', async () => {
+ const searchTerm = 'unlikelytobeaURL';
+ const user = userEvent.setup();
+ render( );
+
+ // Search Input UI.
+ const searchInput = screen.getByRole( 'combobox', { name: 'URL' } );
+
+ // Simulate searching for a term.
+ await user.type( searchInput, searchTerm );
+
+ const searchResultElements = within(
+ await screen.findByRole( 'listbox', {
+ name: /Search results for.*/,
+ } )
+ ).getAllByRole( 'option' );
+
+ const lastSearchResultItem =
+ searchResultElements[ searchResultElements.length - 1 ];
+
+ // We should see a search result for each of the expect search suggestions.
+ expect( searchResultElements ).toHaveLength(
+ fauxEntitySuggestions.length
+ );
+
+ // The URL search suggestion should not exist.
+ expect( lastSearchResultItem ).not.toHaveTextContent(
+ 'Press ENTER to add this link'
+ );
+ } );
+
it( 'should not display a URL suggestion as a default fallback when noURLSuggestion is passed.', async () => {
const user = userEvent.setup();
render( );
@@ -630,7 +661,6 @@ describe( 'Manual link entry', () => {
expect( searchResultElements ).toBeVisible();
expect( searchResultElements ).toHaveTextContent( searchTerm );
- expect( searchResultElements ).toHaveTextContent( 'URL' );
expect( searchResultElements ).toHaveTextContent(
'Press ENTER to add this link'
);
diff --git a/packages/format-library/src/link/inline.js b/packages/format-library/src/link/inline.js
index 064ed81943de1..384fa4c653e53 100644
--- a/packages/format-library/src/link/inline.js
+++ b/packages/format-library/src/link/inline.js
@@ -243,7 +243,7 @@ function InlineLinkUI( {
return createInterpolateElement(
sprintf(
/* translators: %s: search term. */
- __( 'Create Page: %s' ),
+ __( 'Create page: %s' ),
searchTerm
),
{ mark: }
diff --git a/test/e2e/specs/editor/blocks/navigation.spec.js b/test/e2e/specs/editor/blocks/navigation.spec.js
index bdfa4de3131d9..13ea27b60e77e 100644
--- a/test/e2e/specs/editor/blocks/navigation.spec.js
+++ b/test/e2e/specs/editor/blocks/navigation.spec.js
@@ -1304,7 +1304,9 @@ class LinkControl {
await expect( result ).toBeVisible();
return result
- .locator( '.block-editor-link-control__search-item-title' ) // this is the only way to get the label text without the URL.
+ .locator(
+ '.components-menu-item__info-wrapper .components-menu-item__item'
+ ) // this is the only way to get the label text without the URL.
.innerText();
}
}