diff --git a/assets/src/stories-editor/components/font-family-picker/autocomplete.js b/assets/src/stories-editor/components/font-family-picker/autocomplete.js
new file mode 100644
index 00000000000..b8398ee8bee
--- /dev/null
+++ b/assets/src/stories-editor/components/font-family-picker/autocomplete.js
@@ -0,0 +1,169 @@
+/**
+ * External dependencies
+ */
+import OriginalAutocomplete from 'accessible-autocomplete/react';
+/**
+ * WordPress dependencies
+ */
+import {
+ IconButton,
+} from '@wordpress/components';
+import { __ } from '@wordpress/i18n';
+
+/**
+ * Internal dependencies
+ */
+import Status from './status';
+
+class Autocomplete extends OriginalAutocomplete {
+ /**
+ * Overrides default method to prevent an issue with
+ * scrollbars appearing inadvertently.
+ */
+ handleInputBlur() {}
+
+ handleClearClick() {
+ this.state.query = '';
+ this.forceUpdate();
+ this.props.onConfirm( null );
+ }
+
+ /**
+ * Override render method, to add clear font button.
+ *
+ */
+ render() {
+ const {
+ cssNamespace,
+ displayMenu,
+ id,
+ minLength,
+ name,
+ placeholder,
+ required,
+ tNoResults,
+ tStatusQueryTooShort,
+ tStatusSelectedOption,
+ tStatusResults,
+ } = this.props;
+ const { focused, hovered, menuOpen, options, query, selected } = this.state;
+ const autoselect = this.hasAutoselect();
+
+ const inputFocused = focused === -1;
+ const noOptionsAvailable = options.length === 0;
+ const queryNotEmpty = query.length !== 0;
+ const queryLongEnough = query.length >= minLength;
+ const showNoOptionsFound = this.props.showNoOptionsFound &&
+ inputFocused && noOptionsAvailable && queryNotEmpty && queryLongEnough;
+
+ const wrapperClassName = `${ cssNamespace }__wrapper`;
+
+ const inputClassName = `${ cssNamespace }__input`;
+ const componentIsFocused = focused !== null;
+ const inputModifierFocused = componentIsFocused ? ` ${ inputClassName }--focused` : '';
+ const inputModifierType = this.props.showAllValues ? ` ${ inputClassName }--show-all-values` : ` ${ inputClassName }--default`;
+ const optionFocused = focused !== -1 && focused !== null;
+
+ const menuClassName = `${ cssNamespace }__menu`;
+ const menuModifierDisplayMenu = `${ menuClassName }--${ displayMenu }`;
+ const menuIsVisible = menuOpen || showNoOptionsFound;
+ const menuModifierVisibility = `${ menuClassName }--${ ( menuIsVisible ) ? 'visible' : 'hidden' }`;
+
+ const optionClassName = `${ cssNamespace }__option`;
+
+ const hintClassName = `${ cssNamespace }__hint`;
+ const selectedOptionText = this.templateInputValue( options[ selected ] );
+ const optionBeginsWithQuery = selectedOptionText &&
+ selectedOptionText.toLowerCase().indexOf( query.toLowerCase() ) === 0;
+ const hintValue = ( optionBeginsWithQuery && autoselect ) ?
+ query + selectedOptionText.substr( query.length ) :
+ '';
+ const showHint = hintValue;
+
+ return (
+
+ );
+ }
+}
+
+export default Autocomplete;
diff --git a/assets/src/stories-editor/components/font-family-picker/edit.css b/assets/src/stories-editor/components/font-family-picker/edit.css
new file mode 100644
index 00000000000..8e047d3776b
--- /dev/null
+++ b/assets/src/stories-editor/components/font-family-picker/edit.css
@@ -0,0 +1,37 @@
+.autocomplete__wrapper .autocomplete__menu {
+ border-color: #007cba;
+ width: 101%;
+ border-bottom-left-radius: 4px !important;
+ border-bottom-right-radius: 4px !important;
+ margin-top: -3px;
+ padding-top: 3px;
+ max-height: 200px;
+}
+
+.autocomplete__icon {
+ position: absolute;
+ top: 2px;
+ right: 0;
+ padding: 4px;
+ box-shadow: none !important;
+ border: 0 none;
+}
+
+.autocomplete__option {
+ border-bottom: 0 none;
+ padding: 6px 8px;
+ font-size: 13px;
+}
+
+.autocomplete__option--focused,
+.autocomplete__option:hover {
+ background-color: #0071a1;
+ border-color: #0071a1;
+}
+
+.autocomplete__wrapper .autocomplete__input.autocomplete__input--focused {
+ color: #191e23;
+ border-color: #007cba;
+ box-shadow: 0 0 0 1px #007cba;
+ outline: 2px solid transparent;
+}
diff --git a/assets/src/stories-editor/components/font-family-picker/index.js b/assets/src/stories-editor/components/font-family-picker/index.js
index 4fc9cc876fa..82a836f92f5 100644
--- a/assets/src/stories-editor/components/font-family-picker/index.js
+++ b/assets/src/stories-editor/components/font-family-picker/index.js
@@ -6,13 +6,17 @@ import PropTypes from 'prop-types';
/**
* WordPress dependencies
*/
-import { __, sprintf } from '@wordpress/i18n';
+import { __, _n, sprintf } from '@wordpress/i18n';
+import { BaseControl } from '@wordpress/components';
+import { withInstanceId } from '@wordpress/compose';
/**
* Internal dependencies
*/
-import { AMP_STORY_FONT_IMAGES } from '../../constants';
-import { PreviewPicker } from '../';
+import { maybeEnqueueFontStyle } from '../../helpers';
+import Autocomplete from './autocomplete';
+import 'accessible-autocomplete/src/autocomplete.css';
+import './edit.css';
/**
* Font Family Picker component.
@@ -23,45 +27,76 @@ function FontFamilyPicker( {
fonts = [],
onChange = () => {},
value = '',
+ instanceId,
} ) {
- const defaultOption = {
- value: '',
- label: __( 'None', 'amp' ),
+ const results = fonts;
+ const suggest = ( query, populateResults ) => {
+ const searchResults = query ?
+ results.filter( ( result ) => result.name.toLowerCase().indexOf( query.toLowerCase() ) !== -1 ) :
+ [];
+ populateResults( searchResults );
};
- const options = fonts.map( ( font ) => ( {
- value: font.name,
- label: font.name,
- } ) );
+ const suggestionTemplate = ( font ) => {
+ maybeEnqueueFontStyle( font.name );
+ const fallbacks = ( font.fallbacks ) ? ', ' + font.fallbacks.join( ', ' ) : '';
+ return font && `${ font.name }`;
+ };
+
+ const inputValueTemplate = ( result ) => {
+ return result && result.name;
+ };
- const fontLabel = ( familyName ) => AMP_STORY_FONT_IMAGES[ familyName ] ?
- AMP_STORY_FONT_IMAGES[ familyName ]( { height: 13 } ) :
- familyName;
+ const id = `amp-stories-font-family-picker-${ instanceId }`;
return (
- onChange( '' === selectedValue ? undefined : selectedValue ) }
+ {
- return sprintf(
- /* translators: %s: font name */
- __( 'Font Family: %s', 'amp' ),
- currentOption.label
- );
- } }
- renderToggle={ ( { label } ) => fontLabel( label ) }
- renderOption={ ( option ) => {
- return (
-
- { fontLabel( option.label ) }
-
- );
- } }
- />
+ id={ id }
+ help={ __( 'Type to search for fonts', 'amp' ) }
+ >
+ '' }
+ preserveNullOptions={ true }
+ placeholder={ __( 'None', 'amp' ) }
+ displayMenu="overlay"
+ tNoResults={ () =>
+ __( 'No font found', 'amp' )
+ }
+ tStatusQueryTooShort={ ( minQueryLength ) =>
+ // translators: %d: the number characters required to initiate a font search.
+ sprintf( __( 'Type in %s or more characters for results', 'amp' ), minQueryLength )
+ }
+ tStatusSelectedOption={ ( selectedOption, length ) =>
+ // translators: 1: the index of the selected result. 2: The total number of results.
+ sprintf( __( '%s (1 of %s) is selected', 'amp' ), selectedOption, length )
+ }
+ tStatusResults={ ( length, contentSelectedOption ) => {
+ return (
+ sprintf(
+ // translators: %d: The total number of results.
+ _n( '%d font is available. %s', '%d fonts are available. %s', length, 'amp' ),
+ length,
+ contentSelectedOption
+ )
+ );
+ } }
+ />
+
);
}
@@ -72,6 +107,7 @@ FontFamilyPicker.propTypes = {
label: PropTypes.string,
} ) ),
onChange: PropTypes.func,
+ instanceId: PropTypes.number.isRequired,
};
-export default FontFamilyPicker;
+export default withInstanceId( FontFamilyPicker );
diff --git a/assets/src/stories-editor/components/font-family-picker/status.js b/assets/src/stories-editor/components/font-family-picker/status.js
new file mode 100644
index 00000000000..fbaad0d55d0
--- /dev/null
+++ b/assets/src/stories-editor/components/font-family-picker/status.js
@@ -0,0 +1,68 @@
+
+/**
+ * External dependencies
+ */
+import PropTypes from 'prop-types';
+
+const Status = ( {
+ length,
+ queryLength,
+ minQueryLength,
+ selectedOption,
+ selectedOptionIndex,
+ tQueryTooShort,
+ tNoResults,
+ tSelectedOption,
+ tResults,
+} ) => {
+ const queryTooShort = queryLength < minQueryLength;
+ const noResults = length === 0;
+
+ const contentSelectedOption = selectedOption ?
+ tSelectedOption( selectedOption, length, selectedOptionIndex ) :
+ '';
+
+ let content = null;
+ if ( queryTooShort ) {
+ content = tQueryTooShort( minQueryLength );
+ } else if ( noResults ) {
+ content = tNoResults();
+ } else {
+ content = tResults( length, contentSelectedOption );
+ }
+
+ return
+ { content }
+ { queryTooShort && ','.repeat( queryLength ) }
+
;
+};
+
+Status.propTypes = {
+ length: PropTypes.number.isRequired,
+ queryLength: PropTypes.number.isRequired,
+ minQueryLength: PropTypes.number.isRequired,
+ selectedOption: PropTypes.func,
+ selectedOptionIndex: PropTypes.number,
+ tQueryTooShort: PropTypes.func.isRequired,
+ tNoResults: PropTypes.func.isRequired,
+ tSelectedOption: PropTypes.func.isRequired,
+ tResults: PropTypes.func.isRequired,
+};
+
+export default Status;
diff --git a/assets/src/stories-editor/components/font-family-picker/test/__snapshots__/index.js.snap b/assets/src/stories-editor/components/font-family-picker/test/__snapshots__/index.js.snap
deleted file mode 100644
index 92357fa2dc3..00000000000
--- a/assets/src/stories-editor/components/font-family-picker/test/__snapshots__/index.js.snap
+++ /dev/null
@@ -1,116 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`FontFamilyPicker should render a default button if no font is selected 1`] = `
-
-`;
-
-exports[`FontFamilyPicker should render the selected font name 1`] = `
-
-`;
-
-exports[`FontFamilyPicker should render the selected font svg preview 1`] = `
-
-`;
diff --git a/assets/src/stories-editor/components/font-family-picker/test/index.js b/assets/src/stories-editor/components/font-family-picker/test/index.js
deleted file mode 100644
index 08907347b67..00000000000
--- a/assets/src/stories-editor/components/font-family-picker/test/index.js
+++ /dev/null
@@ -1,28 +0,0 @@
-/**
- * External dependencies
- */
-import { render } from 'enzyme';
-
-/**
- * Internal dependencies
- */
-import FontFamilyPicker from '../';
-
-const { ampStoriesFonts } = window;
-
-describe( 'FontFamilyPicker', () => {
- it( 'should render a default button if no font is selected', () => {
- const fontFamilyPicker = render( );
- expect( fontFamilyPicker ).toMatchSnapshot();
- } );
-
- it( 'should render the selected font name', () => {
- const fontFamilyPicker = render( );
- expect( fontFamilyPicker ).toMatchSnapshot();
- } );
-
- it( 'should render the selected font svg preview', () => {
- const fontFamilyPicker = render( );
- expect( fontFamilyPicker ).toMatchSnapshot();
- } );
-} );
diff --git a/assets/src/stories-editor/components/higher-order/with-amp-story-settings.js b/assets/src/stories-editor/components/higher-order/with-amp-story-settings.js
index f5d51f2a960..f1fa7dbed6d 100644
--- a/assets/src/stories-editor/components/higher-order/with-amp-story-settings.js
+++ b/assets/src/stories-editor/components/higher-order/with-amp-story-settings.js
@@ -432,9 +432,13 @@ export default createHigherOrderComponent(
{
- maybeEnqueueFontStyle( value );
- setAttributes( { ampFontFamily: value } );
+ onChange={ ( font ) => {
+ if ( ! font ) {
+ setAttributes( { ampFontFamily: null } );
+ return;
+ }
+ maybeEnqueueFontStyle( font.name );
+ setAttributes( { ampFontFamily: font.name } );
} }
/>
{
+ beforeAll( async () => {
+ await activateExperience( 'stories' );
+ } );
+
+ afterAll( async () => {
+ await deactivateExperience( 'stories' );
+ } );
+
+ beforeEach( async () => {
+ await createNewPost( { postType: 'amp_story' } );
+ await page.waitForSelector( `.${ textBlockClass }.is-not-editing` );
+ await selectBlockByClassName( textBlockClass );
+ const textToWrite = 'Hello';
+
+ await page.click( `.${ textBlockClass }` );
+ await page.keyboard.type( textToWrite );
+ await page.$eval( '.block-editor-block-list__layout .block-editor-block-list__block .wp-block-amp-amp-story-text', ( node ) => node.textContent );
+
+ await page.click( `.${ fontPickerID }` );
+ } );
+
+ it( 'should be able to search for ubuntu font', async () => {
+ await page.keyboard.type( 'Arimo' );
+
+ const nodes = await page.$x(
+ '//ul[contains(@class,"autocomplete__menu")]//li'
+ );
+ expect( nodes ).toHaveLength( 1 );
+ await expect( page ).toMatchElement( '#arimo-font' );
+ } );
+
+ it( 'should be able to search for Arial font and get multi results', async () => {
+ await page.keyboard.type( 'pt sans' );
+
+ const nodes = await page.$x(
+ '//ul[contains(@class,"autocomplete__menu")]//li'
+ );
+ expect( nodes ).toHaveLength( 3 );
+ await expect( page ).toMatchElement( '#pt-sans-font' );
+ await expect( page ).toMatchElement( '#pt-sans-narrow-font' );
+ await expect( page ).toMatchElement( '#pt-sans-caption-font' );
+ } );
+
+ it( 'should be able to search for none existing font', async () => {
+ await page.keyboard.type( 'Wibble' );
+ expect( await page.evaluate( () => {
+ return document.querySelector( '.autocomplete__option--no-results' ).innerHTML;
+ } ) ).toContain( 'No font found' );
+ } );
+
+ it( 'should be able to search for ubuntu font and select font', async () => {
+ await page.keyboard.type( 'Ubuntu' );
+
+ await page.waitForSelector( '.autocomplete__option' );
+ await page.click( '.autocomplete__option' );
+ const textBlockBefore = ( await getBlocksOnPage() )[ 0 ];
+ expect( textBlockBefore.attributes.ampFontFamily ).toStrictEqual( 'Ubuntu' );
+ } );
+
+ it( 'should be able to search for ubuntu font and select font with keyboard', async () => {
+ await page.keyboard.type( 'Ubuntu' );
+
+ await page.waitForSelector( '.autocomplete__option' );
+ await page.keyboard.press( 'ArrowDown' );
+ await page.keyboard.press( 'Enter' );
+ const textBlockBefore = ( await getBlocksOnPage() )[ 0 ];
+ expect( textBlockBefore.attributes.ampFontFamily ).toStrictEqual( 'Ubuntu' );
+ } );
+
+ it( 'should be able to search for ubuntu font and remove font', async () => {
+ await page.keyboard.type( 'Ubuntu' );
+
+ await page.waitForSelector( '.autocomplete__option' );
+ await page.click( '.autocomplete__option' );
+
+ await page.waitForSelector( '.autocomplete__icon' );
+ await page.click( '.autocomplete__icon' );
+ const textBlockBefore = ( await getBlocksOnPage() )[ 0 ];
+ expect( textBlockBefore.attributes.ampFontFamily ).toBeNull();
+ } );
+
+ it( 'should be able to search for ubuntu font and save post', async () => {
+ await page.keyboard.type( 'Ubuntu' );
+
+ await page.waitForSelector( '.autocomplete__option' );
+ await page.click( '.autocomplete__option' );
+
+ await saveDraft();
+ await page.reload();
+
+ const textBlockBefore = ( await getBlocksOnPage() )[ 0 ];
+ expect( textBlockBefore.attributes.ampFontFamily ).toStrictEqual( 'Ubuntu' );
+ } );
+} );