diff --git a/docs/manifest.json b/docs/manifest.json index 5d71772ac69dd..42f2c0ae80a8e 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -1145,6 +1145,12 @@ "markdown_source": "../packages/components/src/scrollable/README.md", "parent": "components" }, + { + "title": "SearchControl", + "slug": "search-control", + "markdown_source": "../packages/components/src/search-control/README.md", + "parent": "components" + }, { "title": "SelectControl", "slug": "select-control", diff --git a/packages/block-editor/src/components/index.js b/packages/block-editor/src/components/index.js index a781b86dc2524..6daa8ddd4df35 100644 --- a/packages/block-editor/src/components/index.js +++ b/packages/block-editor/src/components/index.js @@ -121,7 +121,6 @@ export { default as DefaultBlockAppender } from './default-block-appender'; export { default as __unstableEditorStyles } from './editor-styles'; export { default as Inserter } from './inserter'; export { default as __experimentalLibrary } from './inserter/library'; -export { default as __experimentalSearchForm } from './inserter/search-form'; export { default as BlockEditorKeyboardShortcuts } from './keyboard-shortcuts'; export { MultiSelectScrollIntoView } from './selection-scroll-into-view'; export { default as NavigableToolbar } from './navigable-toolbar'; diff --git a/packages/block-editor/src/components/inserter/menu.js b/packages/block-editor/src/components/inserter/menu.js index 9db480c49d5b0..19b01b5a66b3e 100644 --- a/packages/block-editor/src/components/inserter/menu.js +++ b/packages/block-editor/src/components/inserter/menu.js @@ -2,7 +2,7 @@ * WordPress dependencies */ import { useState, useCallback, useMemo } from '@wordpress/element'; -import { VisuallyHidden } from '@wordpress/components'; +import { VisuallyHidden, SearchControl } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { useSelect } from '@wordpress/data'; @@ -10,7 +10,6 @@ import { useSelect } from '@wordpress/data'; * Internal dependencies */ import Tips from './tips'; -import InserterSearchForm from './search-form'; import InserterPreviewPanel from './preview-panel'; import BlockTypesTab from './block-types-tab'; import BlockPatternsTabs from './block-patterns-tab'; @@ -171,7 +170,8 @@ function InserterMenu( {
{ /* the following div is necessary to fix the sticky position of the search form */ }
- { if ( hoveredItem ) setHoveredItem( null ); setFilterValue( value ); diff --git a/packages/block-editor/src/components/inserter/menu.native.js b/packages/block-editor/src/components/inserter/menu.native.js index ffac41438fb03..afaffa950028f 100644 --- a/packages/block-editor/src/components/inserter/menu.native.js +++ b/packages/block-editor/src/components/inserter/menu.native.js @@ -14,13 +14,16 @@ import { import { useEffect, useState, useCallback } from '@wordpress/element'; import { useSelect, useDispatch } from '@wordpress/data'; import { createBlock } from '@wordpress/blocks'; -import { BottomSheet, BottomSheetConsumer } from '@wordpress/components'; +import { + BottomSheet, + BottomSheetConsumer, + SearchControl, +} from '@wordpress/components'; /** * Internal dependencies */ import InserterSearchResults from './search-results'; -import InserterSearchForm from './search-form'; import { store as blockEditorStore } from '../../store'; import InserterTabs from './tabs'; import styles from './style.scss'; @@ -192,7 +195,7 @@ function InserterMenu( { header={ <> { showSearchForm && ( - diff --git a/packages/block-editor/src/components/inserter/quick-inserter.js b/packages/block-editor/src/components/inserter/quick-inserter.js index ca4ff4c2fa274..e9060e827a938 100644 --- a/packages/block-editor/src/components/inserter/quick-inserter.js +++ b/packages/block-editor/src/components/inserter/quick-inserter.js @@ -8,13 +8,12 @@ import classnames from 'classnames'; */ import { useState, useEffect } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; -import { Button } from '@wordpress/components'; +import { Button, SearchControl } from '@wordpress/components'; import { useSelect } from '@wordpress/data'; /** * Internal dependencies */ -import InserterSearchForm from './search-form'; import InserterSearchResults from './search-results'; import useInsertionPoint from './hooks/use-insertion-point'; import usePatternsState from './hooks/use-patterns-state'; @@ -87,7 +86,8 @@ export default function QuickInserter( { } ) } > { showSearch && ( - { setFilterValue( value ); diff --git a/packages/block-editor/src/components/inserter/search-form.js b/packages/block-editor/src/components/inserter/search-form.js deleted file mode 100644 index 7914e842fd3dc..0000000000000 --- a/packages/block-editor/src/components/inserter/search-form.js +++ /dev/null @@ -1,65 +0,0 @@ -/** - * External dependencies - */ -import classnames from 'classnames'; - -/** - * WordPress dependencies - */ -import { useInstanceId } from '@wordpress/compose'; -import { __ } from '@wordpress/i18n'; -import { VisuallyHidden, Button } from '@wordpress/components'; -import { Icon, search, closeSmall } from '@wordpress/icons'; -import { useRef } from '@wordpress/element'; - -function InserterSearchForm( { - className, - onChange, - value, - label, - placeholder, -} ) { - const instanceId = useInstanceId( InserterSearchForm ); - const searchInput = useRef(); - - return ( -
- - { label || placeholder } - - onChange( event.target.value ) } - autoComplete="off" - value={ value || '' } - /> -
- { !! value && ( -
-
- ); -} - -export default InserterSearchForm; diff --git a/packages/block-editor/src/components/inserter/style.native.scss b/packages/block-editor/src/components/inserter/style.native.scss index a0f017931a62a..1ed3d8ab6a3bc 100644 --- a/packages/block-editor/src/components/inserter/style.native.scss +++ b/packages/block-editor/src/components/inserter/style.native.scss @@ -13,37 +13,6 @@ padding-top: 8; } -.searchForm { - height: 46px; - border-radius: 8px; - color: $gray-dark; - margin: $grid-unit-30; - background-color: $gray-light; - flex-direction: row; - justify-content: space-between; -} - -.searchFormDark { - background-color: rgba($white, 0.07); -} - -.searchFormInput { - color: $gray-dark; - flex: 2; -} - -.searchFormInputDark { - color: $white; -} - -.searchFormPlaceholder { - color: $gray; -} - -.searchFormPlaceholderDark { - color: rgba($white, 0.8); -} - .inserter-tabs__wrapper { overflow: hidden; } diff --git a/packages/block-editor/src/components/inserter/style.scss b/packages/block-editor/src/components/inserter/style.scss index cb8f94f93eb6e..8792dfe969909 100644 --- a/packages/block-editor/src/components/inserter/style.scss +++ b/packages/block-editor/src/components/inserter/style.scss @@ -99,55 +99,14 @@ $block-inserter-tabs-height: 44px; } .block-editor-inserter__search { + background: $white; padding: $grid-unit-20; position: sticky; top: 0; - background: $white; z-index: z-index(".block-editor-inserter__search"); - input[type="search"].block-editor-inserter__search-input { - @include input-control; - display: block; - padding: $grid-unit-20 $grid-unit-60 $grid-unit-20 $grid-unit-20; - background: $gray-100; - border: none; - width: 100%; - height: $grid-unit-60; - - /* Fonts smaller than 16px causes mobile safari to zoom. */ - font-size: $mobile-text-min-font-size; - @include break-small { - font-size: $default-font-size; - } - - &:focus { - background: $white; - box-shadow: 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); - } - - &::placeholder { - color: $gray-700; - } - - &::-webkit-search-decoration, - &::-webkit-search-cancel-button, - &::-webkit-search-results-button, - &::-webkit-search-results-decoration { - -webkit-appearance: none; - } - } -} - -.block-editor-inserter__search-icon { - position: absolute; - top: 0; - right: $grid-unit-10 + ($grid-unit-60 - $icon-size) / 2; - bottom: 0; - display: flex; - align-items: center; - - > svg { - margin: $grid-unit-10; + .components-search-control__icon { + right: $grid-unit-10 + ($grid-unit-60 - $icon-size) / 2; } } diff --git a/packages/block-library/src/template-part/edit/selection/index.js b/packages/block-library/src/template-part/edit/selection/index.js index 2ed5ac835f81f..4bdef9130e33d 100644 --- a/packages/block-library/src/template-part/edit/selection/index.js +++ b/packages/block-library/src/template-part/edit/selection/index.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { __experimentalSearchForm as SearchForm } from '@wordpress/block-editor'; +import { SearchControl } from '@wordpress/components'; import { useState } from '@wordpress/element'; import { LEFT, RIGHT, UP, DOWN, BACKSPACE, ENTER } from '@wordpress/keycodes'; /** @@ -34,7 +34,7 @@ export default function TemplatePartSelection( { onKeyPress={ stopKeyPropagation } onKeyDown={ preventArrowKeysPropagation } > - + ); +} +``` + +### Props + +The set of props accepted by the component will be specified below. +Props not included in this set will be applied to the input element. + +#### label + +If this property is added, a label will be generated using label property as the content. + +- Type: `String` +- Required: Yes + +#### placeholder + +If this property is added, a specific placeholder will be used for the input. + +- Type: `String` +- Required: No +#### value + +The current value of the input. + +- Type: `String | Number` +- Required: Yes + +#### className + +The class that will be added to the classes of the wrapper div. + +- Type: `String` +- Required: No + +#### onChange + +A function that receives the value of the input. + +- Type: `function` +- Required: Yes + +#### help + +If this property is added, a help text will be generated using help property as the content. + +- Type: `String|WPElement` +- Required: No +### hideLabelFromVision + +If true, the label will only be visible to screen readers. + +- Type: `Boolean` +- Required: No + +## Related components + +- To offer users more constrained options for input, use TextControl, SelectControl, RadioControl, CheckboxControl, or RangeControl. diff --git a/packages/components/src/search-control/index.js b/packages/components/src/search-control/index.js new file mode 100644 index 0000000000000..fc0bd900f3b15 --- /dev/null +++ b/packages/components/src/search-control/index.js @@ -0,0 +1,70 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { useInstanceId } from '@wordpress/compose'; +import { __ } from '@wordpress/i18n'; +import { Icon, search, closeSmall } from '@wordpress/icons'; +import { useRef } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { Button } from '../'; +import BaseControl from '../base-control'; + +function SearchControl( { + className, + onChange, + value, + label, + placeholder = __( 'Search' ), + hideLabelFromVision = true, + help, +} ) { + const instanceId = useInstanceId( SearchControl ); + const searchInput = useRef(); + const id = `components-search-control-${ instanceId }`; + + return ( + +
+ onChange( event.target.value ) } + autoComplete="off" + value={ value || '' } + /> +
+ { !! value && ( +
+
+
+ ); +} + +export default SearchControl; diff --git a/packages/block-editor/src/components/inserter/search-form.native.js b/packages/components/src/search-control/index.native.js similarity index 91% rename from packages/block-editor/src/components/inserter/search-form.native.js rename to packages/components/src/search-control/index.native.js index f23d6b95b9100..0ab3e4e69112b 100644 --- a/packages/block-editor/src/components/inserter/search-form.native.js +++ b/packages/components/src/search-control/index.native.js @@ -22,7 +22,12 @@ import { */ import styles from './style.scss'; -function InserterSearchForm( { value, onChange } ) { +function SearchControl( { + value, + onChange, + label, + placeholder = __( 'Search' ), +} ) { const [ isActive, setIsActive ] = useState( false ); const inputRef = useRef(); @@ -57,7 +62,7 @@ function InserterSearchForm( { value, onChange } ) { /> ) : ( { inputRef.current.focus(); @@ -72,7 +77,7 @@ function InserterSearchForm( { value, onChange } ) { onChangeText={ onChange } onFocus={ () => setIsActive( true ) } value={ value } - placeholder={ __( 'Search blocks' ) } + placeholder={ placeholder } /> { !! value && ( @@ -89,4 +94,4 @@ function InserterSearchForm( { value, onChange } ) { ); } -export default InserterSearchForm; +export default SearchControl; diff --git a/packages/components/src/search-control/stories/index.js b/packages/components/src/search-control/stories/index.js new file mode 100644 index 0000000000000..2710542a5d6a2 --- /dev/null +++ b/packages/components/src/search-control/stories/index.js @@ -0,0 +1,36 @@ +/** + * External dependencies + */ +import { boolean, text } from '@storybook/addon-knobs'; + +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import SearchControl from '../'; + +export default { + title: 'Components/SearchControl', + component: SearchControl, +}; + +export const _default = () => { + const [ value, setValue ] = useState(); + const label = text( 'Label', 'Label Text' ); + const hideLabelFromVision = boolean( 'Hide Label From Vision', true ); + const help = text( 'Help Text', 'Help text to explain the input.' ); + + return ( + + ); +}; diff --git a/packages/components/src/search-control/style.native.scss b/packages/components/src/search-control/style.native.scss new file mode 100644 index 0000000000000..8617ad232552f --- /dev/null +++ b/packages/components/src/search-control/style.native.scss @@ -0,0 +1,30 @@ +.searchForm { + height: 46px; + border-radius: 8px; + color: $gray-dark; + margin: $grid-unit-30; + background-color: $gray-light; + flex-direction: row; + justify-content: space-between; +} + +.searchFormDark { + background-color: rgba($white, 0.07); +} + +.searchFormInput { + color: $gray-dark; + flex: 2; +} + +.searchFormInputDark { + color: $white; +} + +.searchFormPlaceholder { + color: $gray; +} + +.searchFormPlaceholderDark { + color: rgba($white, 0.8); +} diff --git a/packages/components/src/search-control/style.scss b/packages/components/src/search-control/style.scss new file mode 100644 index 0000000000000..247b726fc9dd9 --- /dev/null +++ b/packages/components/src/search-control/style.scss @@ -0,0 +1,52 @@ +.components-search-control { + position: relative; + + input[type="search"].components-search-control__input { + @include input-control; + display: block; + padding: $grid-unit-20 $grid-unit-60 $grid-unit-20 $grid-unit-20; + background: $gray-100; + border: none; + width: 100%; + height: $grid-unit-60; + + /* Fonts smaller than 16px causes mobile safari to zoom. */ + font-size: $mobile-text-min-font-size; + @include break-small { + font-size: $default-font-size; + } + + &:focus { + background: $white; + box-shadow: 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); + } + + &::placeholder { + color: $gray-700; + } + + &::-webkit-search-decoration, + &::-webkit-search-cancel-button, + &::-webkit-search-results-button, + &::-webkit-search-results-decoration { + -webkit-appearance: none; + } + } +} + +.components-search-control__icon { + position: absolute; + top: 0; + right: ( $grid-unit-60 - $icon-size ) / 2; + bottom: 0; + display: flex; + align-items: center; + + > svg { + margin: $grid-unit-10 0; + } +} + +.components-search-control__input-wrapper { + position: relative; +} diff --git a/packages/components/src/style.scss b/packages/components/src/style.scss index 13c2049b894bf..21f13e7d492f5 100644 --- a/packages/components/src/style.scss +++ b/packages/components/src/style.scss @@ -35,6 +35,7 @@ @import "./responsive-wrapper/style.scss"; @import "./sandbox/style.scss"; @import "./scroll-lock/style.scss"; +@import "./search-control/style.scss"; @import "./select-control/style.scss"; @import "./snackbar/style.scss"; @import "./swatch/style.scss"; diff --git a/packages/e2e-test-utils/src/inserter.js b/packages/e2e-test-utils/src/inserter.js index 48a4985bfe62f..1bcce329f07df 100644 --- a/packages/e2e-test-utils/src/inserter.js +++ b/packages/e2e-test-utils/src/inserter.js @@ -7,7 +7,7 @@ import { canvas } from './canvas'; // This selector is written to support the current and old inserter markup // because the performance tests need to be able to run across versions. const INSERTER_SEARCH_SELECTOR = - '.block-editor-inserter__search-input,input.block-editor-inserter__search'; + '.block-editor-inserter__search input,.block-editor-inserter__search-input,input.block-editor-inserter__search'; /** * Opens the global block inserter. diff --git a/packages/e2e-tests/specs/editor/plugins/cpt-locking.test.js b/packages/e2e-tests/specs/editor/plugins/cpt-locking.test.js index aee6398871a9b..03abfc56d189b 100644 --- a/packages/e2e-tests/specs/editor/plugins/cpt-locking.test.js +++ b/packages/e2e-tests/specs/editor/plugins/cpt-locking.test.js @@ -123,12 +123,12 @@ describe( 'cpt locking', () => { await page.click( '.wp-block-column .block-editor-button-block-appender' ); - await page.type( '.block-editor-inserter__search-input', 'image' ); + await page.type( '.block-editor-inserter__search input', 'image' ); await pressKeyTimes( 'Tab', 2 ); await page.keyboard.press( 'Enter' ); await page.click( '.edit-post-header-toolbar__inserter-toggle' ); await page.type( - '.block-editor-inserter__search-input', + '.block-editor-inserter__search input', 'gallery' ); await pressKeyTimes( 'Tab', 2 ); diff --git a/packages/e2e-tests/specs/editor/various/inserting-blocks.test.js b/packages/e2e-tests/specs/editor/various/inserting-blocks.test.js index e4a0e07a13d81..acc7965c5621d 100644 --- a/packages/e2e-tests/specs/editor/various/inserting-blocks.test.js +++ b/packages/e2e-tests/specs/editor/various/inserting-blocks.test.js @@ -147,7 +147,7 @@ describe( 'Inserting blocks', () => { () => document.activeElement && document.activeElement.classList.contains( - 'block-editor-inserter__search-input' + 'components-search-control__input' ) ); await page.keyboard.type( 'para' ); @@ -185,7 +185,7 @@ describe( 'Inserting blocks', () => { () => document.activeElement.classList ); expect( Object.values( activeElementClassList ) ).toContain( - 'block-editor-inserter__search-input' + 'components-search-control__input' ); // Try using the up arrow key (vertical navigation triggers the issue described in #9583). @@ -196,7 +196,7 @@ describe( 'Inserting blocks', () => { () => document.activeElement.classList ); expect( Object.values( activeElementClassList ) ).toContain( - 'block-editor-inserter__search-input' + 'components-search-control__input' ); // Tab to the block list @@ -250,7 +250,7 @@ describe( 'Inserting blocks', () => { ); // Insert a paragraph block. - await page.waitForSelector( '.block-editor-inserter__search-input' ); + await page.waitForSelector( '.block-editor-inserter__search input' ); // Search for the paragraph block if it's not in the list of blocks shown. if ( ! page.$( '.editor-block-list-item-paragraph' ) ) { diff --git a/packages/e2e-tests/specs/editor/various/writing-flow.test.js b/packages/e2e-tests/specs/editor/various/writing-flow.test.js index 3b8e6746ac866..566303fabfeaf 100644 --- a/packages/e2e-tests/specs/editor/various/writing-flow.test.js +++ b/packages/e2e-tests/specs/editor/various/writing-flow.test.js @@ -29,7 +29,7 @@ const addParagraphsAndColumnsDemo = async () => { await page.keyboard.press( 'Enter' ); await page.click( ':focus [aria-label="Two columns; equal split"]' ); await page.click( ':focus .block-editor-button-block-appender' ); - await page.waitForSelector( ':focus.block-editor-inserter__search-input' ); + await page.waitForSelector( '.block-editor-inserter__search input:focus' ); await page.keyboard.type( 'Paragraph' ); await pressKeyTimes( 'Tab', 2 ); // Tab to paragraph result. await page.keyboard.press( 'Enter' ); // Insert paragraph. @@ -40,7 +40,7 @@ const addParagraphsAndColumnsDemo = async () => { // is a temporary solution. await page.focus( '.wp-block[data-type="core/column"]:nth-child(2)' ); await page.click( ':focus .block-editor-button-block-appender' ); - await page.waitForSelector( ':focus.block-editor-inserter__search-input' ); + await page.waitForSelector( '.block-editor-inserter__search input:focus' ); await page.keyboard.type( 'Paragraph' ); await pressKeyTimes( 'Tab', 2 ); // Tab to paragraph result. await page.keyboard.press( 'Enter' ); // Insert paragraph. diff --git a/packages/edit-post/src/components/block-manager/index.js b/packages/edit-post/src/components/block-manager/index.js index 8dc3aea8ec91d..b183f8f7e1874 100644 --- a/packages/edit-post/src/components/block-manager/index.js +++ b/packages/edit-post/src/components/block-manager/index.js @@ -8,10 +8,9 @@ import { filter, includes, isArray } from 'lodash'; */ import { store as blocksStore } from '@wordpress/blocks'; import { withSelect } from '@wordpress/data'; -import { VisuallyHidden, TextControl } from '@wordpress/components'; +import { SearchControl } from '@wordpress/components'; import { __, _n, sprintf } from '@wordpress/i18n'; import { useState } from '@wordpress/element'; -import { useInstanceId } from '@wordpress/compose'; /** * Internal dependencies @@ -27,7 +26,6 @@ function BlockManager( { numberOfHiddenBlocks, } ) { const [ search, setSearch ] = useState( '' ); - const instanceId = useInstanceId( BlockManager ); // Filtering occurs here (as opposed to `withSelect`) to avoid // wasted renders by consequence of `Array#filter` producing @@ -55,15 +53,8 @@ function BlockManager( { ) }
) } - - { __( 'Search for a block' ) } - - setSearch( nextSearch ) } diff --git a/packages/edit-post/src/components/block-manager/style.scss b/packages/edit-post/src/components/block-manager/style.scss index e375b5091df2b..3905eeea059f7 100644 --- a/packages/edit-post/src/components/block-manager/style.scss +++ b/packages/edit-post/src/components/block-manager/style.scss @@ -6,19 +6,6 @@ .edit-post-block-manager__search { margin: $grid-unit-20 0; - - .components-base-control__field { - margin-bottom: 0; - } - - .components-base-control__label { - margin-top: -0.25 * $grid-unit-20; - } - - input[type="search"].components-text-control__input { - padding: $grid-unit-10; - border-radius: $radius-block-ui; - } } .edit-post-block-manager__disabled-blocks-count {