diff --git a/packages/block-editor/src/components/block-icon/index.native.js b/packages/block-editor/src/components/block-icon/index.native.js
new file mode 100644
index 00000000000000..4dddda5ecce8f0
--- /dev/null
+++ b/packages/block-editor/src/components/block-icon/index.native.js
@@ -0,0 +1,30 @@
+/**
+ * External dependencies
+ */
+import { get } from 'lodash';
+import { View } from 'react-native';
+
+/**
+ * WordPress dependencies
+ */
+import { Path, Icon, SVG } from '@wordpress/components';
+
+export default function BlockIcon( { icon, showColors = false } ) {
+ if ( get( icon, [ 'src' ] ) === 'block-default' ) {
+ icon = {
+ src: ,
+ };
+ }
+
+ const renderedIcon = ;
+ const style = showColors ? {
+ backgroundColor: icon && icon.background,
+ color: icon && icon.foreground,
+ } : {};
+
+ return (
+
+ { renderedIcon }
+
+ );
+}
diff --git a/packages/block-editor/src/components/block-list-appender/index.native.js b/packages/block-editor/src/components/block-list-appender/index.native.js
new file mode 100644
index 00000000000000..9a6a24c5575172
--- /dev/null
+++ b/packages/block-editor/src/components/block-list-appender/index.native.js
@@ -0,0 +1,54 @@
+/**
+ * External dependencies
+ */
+import { last } from 'lodash';
+
+/**
+ * WordPress dependencies
+ */
+import { withSelect } from '@wordpress/data';
+import { getDefaultBlockName } from '@wordpress/blocks';
+
+/**
+ * Internal dependencies
+ */
+import DefaultBlockAppender from '../default-block-appender';
+import styles from './style.scss';
+
+function BlockListAppender( {
+ blockClientIds,
+ rootClientId,
+ canInsertDefaultBlock,
+ isLocked,
+} ) {
+ if ( isLocked ) {
+ return null;
+ }
+
+ if ( canInsertDefaultBlock ) {
+ return (
+ 0 ? '' : null }
+ />
+ );
+ }
+
+ return null;
+}
+
+export default withSelect( ( select, { rootClientId } ) => {
+ const {
+ getBlockOrder,
+ canInsertBlockType,
+ getTemplateLock,
+ } = select( 'core/block-editor' );
+
+ return {
+ isLocked: !! getTemplateLock( rootClientId ),
+ blockClientIds: getBlockOrder( rootClientId ),
+ canInsertDefaultBlock: canInsertBlockType( getDefaultBlockName(), rootClientId ),
+ };
+} )( BlockListAppender );
diff --git a/packages/block-editor/src/components/block-list-appender/style.native.scss b/packages/block-editor/src/components/block-list-appender/style.native.scss
new file mode 100644
index 00000000000000..60734f5e9c2106
--- /dev/null
+++ b/packages/block-editor/src/components/block-list-appender/style.native.scss
@@ -0,0 +1,9 @@
+
+.blockListAppender {
+ background-color: $white;
+ padding-left: 16;
+ padding-right: 16;
+ padding-top: 12;
+ padding-bottom: 0; // will be flushed into inline toolbar height
+ border-color: transparent;
+}
diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js
index 10a5cfcbcf1bae..7c67de71c342f0 100644
--- a/packages/block-editor/src/components/block-list/index.native.js
+++ b/packages/block-editor/src/components/block-list/index.native.js
@@ -100,6 +100,7 @@ export class BlockList extends Component {
{
+ if ( ! showInsertionPoint ) {
+ return null;
+ }
+
+ return (
+
+
+ { __( 'ADD BLOCK HERE' ) }
+
+
+ );
+};
+
+export default withSelect( ( select, { clientId, rootClientId } ) => {
+ const {
+ getBlockIndex,
+ getBlockInsertionPoint,
+ isBlockInsertionPointVisible,
+ } = select( 'core/block-editor' );
+ const blockIndex = getBlockIndex( clientId, rootClientId );
+ const insertionPoint = getBlockInsertionPoint();
+ const showInsertionPoint = (
+ isBlockInsertionPointVisible() &&
+ insertionPoint.index === blockIndex &&
+ insertionPoint.rootClientId === rootClientId
+ );
+
+ return { showInsertionPoint };
+} )( BlockInsertionPoint );
diff --git a/packages/block-editor/src/components/block-mover/index.native.js b/packages/block-editor/src/components/block-mover/index.native.js
index 8962dc22f49e45..40bc4d550b7ab6 100644
--- a/packages/block-editor/src/components/block-mover/index.native.js
+++ b/packages/block-editor/src/components/block-mover/index.native.js
@@ -14,51 +14,59 @@ import { withInstanceId, compose } from '@wordpress/compose';
const BlockMover = ( {
isFirst,
isLast,
+ isLocked,
onMoveDown,
onMoveUp,
firstIndex,
-} ) => (
- <>
-
+ rootClientId,
+} ) => {
+ if ( isLocked || ( isFirst && isLast && ! rootClientId ) ) {
+ return null;
+ }
-
- >
-);
+ return (
+ <>
+
+
+
+ >
+ );
+};
export default compose(
withSelect( ( select, { clientIds } ) => {
- const { getBlockIndex, getBlockRootClientId, getBlockOrder } = select( 'core/block-editor' );
+ const { getBlockIndex, getTemplateLock, getBlockRootClientId, getBlockOrder } = select( 'core/block-editor' );
const normalizedClientIds = castArray( clientIds );
const firstClientId = first( normalizedClientIds );
- const rootClientId = getBlockRootClientId( first( normalizedClientIds ) );
+ const rootClientId = getBlockRootClientId( firstClientId );
const blockOrder = getBlockOrder( rootClientId );
const firstIndex = getBlockIndex( firstClientId, rootClientId );
const lastIndex = getBlockIndex( last( normalizedClientIds ), rootClientId );
@@ -67,6 +75,8 @@ export default compose(
firstIndex,
isFirst: firstIndex === 0,
isLast: lastIndex === blockOrder.length - 1,
+ isLocked: getTemplateLock( rootClientId ) === 'all',
+ rootClientId,
};
} ),
withDispatch( ( dispatch, { clientIds, rootClientId } ) => {
diff --git a/packages/block-editor/src/components/index.native.js b/packages/block-editor/src/components/index.native.js
index 5f13de7d91b4ce..7c6906da0066a7 100644
--- a/packages/block-editor/src/components/index.native.js
+++ b/packages/block-editor/src/components/index.native.js
@@ -2,9 +2,12 @@
export { default as BlockControls } from './block-controls';
export { default as BlockEdit } from './block-edit';
export { default as BlockFormatControls } from './block-format-controls';
+export { default as BlockIcon } from './block-icon';
+export { default as BlockVerticalAlignmentToolbar } from './block-vertical-alignment-toolbar';
export * from './colors';
export * from './font-sizes';
export { default as AlignmentToolbar } from './alignment-toolbar';
+export { default as InnerBlocks } from './inner-blocks';
export { default as InspectorControls } from './inspector-controls';
export { default as PlainText } from './plain-text';
export {
diff --git a/packages/block-editor/src/components/inner-blocks/index.native.js b/packages/block-editor/src/components/inner-blocks/index.native.js
new file mode 100644
index 00000000000000..d0515d6d8a58ac
--- /dev/null
+++ b/packages/block-editor/src/components/inner-blocks/index.native.js
@@ -0,0 +1,182 @@
+/**
+ * External dependencies
+ */
+import { pick, isEqual } from 'lodash';
+
+/**
+ * WordPress dependencies
+ */
+import { Component } from '@wordpress/element';
+import { withSelect, withDispatch } from '@wordpress/data';
+import { synchronizeBlocksWithTemplate, withBlockContentContext } from '@wordpress/blocks';
+import isShallowEqual from '@wordpress/is-shallow-equal';
+import { compose } from '@wordpress/compose';
+
+/**
+ * Internal dependencies
+ */
+import ButtonBlockAppender from './button-block-appender';
+import DefaultBlockAppender from './default-block-appender';
+
+/**
+ * Internal dependencies
+ */
+import BlockList from '../block-list';
+import { withBlockEditContext } from '../block-edit/context';
+
+class InnerBlocks extends Component {
+ constructor() {
+ super( ...arguments );
+ this.state = {
+ templateInProcess: !! this.props.template,
+ };
+ this.updateNestedSettings();
+ }
+
+ getTemplateLock() {
+ const {
+ templateLock,
+ parentLock,
+ } = this.props;
+ return templateLock === undefined ? parentLock : templateLock;
+ }
+
+ componentDidMount() {
+ const { innerBlocks } = this.props.block;
+ // only synchronize innerBlocks with template if innerBlocks are empty or a locking all exists
+ if ( innerBlocks.length === 0 || this.getTemplateLock() === 'all' ) {
+ this.synchronizeBlocksWithTemplate();
+ }
+
+ if ( this.state.templateInProcess ) {
+ this.setState( {
+ templateInProcess: false,
+ } );
+ }
+ }
+
+ componentDidUpdate( prevProps ) {
+ const { template, block } = this.props;
+ const { innerBlocks } = block;
+
+ this.updateNestedSettings();
+ // only synchronize innerBlocks with template if innerBlocks are empty or a locking all exists
+ if ( innerBlocks.length === 0 || this.getTemplateLock() === 'all' ) {
+ const hasTemplateChanged = ! isEqual( template, prevProps.template );
+ if ( hasTemplateChanged ) {
+ this.synchronizeBlocksWithTemplate();
+ }
+ }
+ }
+
+ /**
+ * Called on mount or when a mismatch exists between the templates and
+ * inner blocks, synchronizes inner blocks with the template, replacing
+ * current blocks.
+ */
+ synchronizeBlocksWithTemplate() {
+ const { template, block, replaceInnerBlocks } = this.props;
+ const { innerBlocks } = block;
+
+ // Synchronize with templates. If the next set differs, replace.
+ const nextBlocks = synchronizeBlocksWithTemplate( innerBlocks, template );
+ if ( ! isEqual( nextBlocks, innerBlocks ) ) {
+ replaceInnerBlocks( nextBlocks );
+ }
+ }
+
+ updateNestedSettings() {
+ const {
+ blockListSettings,
+ allowedBlocks,
+ updateNestedSettings,
+ } = this.props;
+
+ const newSettings = {
+ allowedBlocks,
+ templateLock: this.getTemplateLock(),
+ };
+
+ if ( ! isShallowEqual( blockListSettings, newSettings ) ) {
+ updateNestedSettings( newSettings );
+ }
+ }
+
+ render() {
+ const {
+ clientId,
+ renderAppender,
+ template,
+ __experimentalTemplateOptions: templateOptions,
+ } = this.props;
+ const { templateInProcess } = this.state;
+
+ const isPlaceholder = template === null && !! templateOptions;
+
+ return (
+ <>
+ { ! templateInProcess && (
+ isPlaceholder ?
+ null :
+
+ ) }
+ >
+ );
+ }
+}
+
+InnerBlocks = compose( [
+ withBlockEditContext( ( context ) => pick( context, [ 'clientId' ] ) ),
+ withSelect( ( select, ownProps ) => {
+ const {
+ isBlockSelected,
+ hasSelectedInnerBlock,
+ getBlock,
+ getBlockListSettings,
+ getBlockRootClientId,
+ getTemplateLock,
+ } = select( 'core/block-editor' );
+ const { clientId } = ownProps;
+ const block = getBlock( clientId );
+ const rootClientId = getBlockRootClientId( clientId );
+
+ return {
+ block,
+ blockListSettings: getBlockListSettings( clientId ),
+ hasOverlay: block.name !== 'core/template' && ! isBlockSelected( clientId ) && ! hasSelectedInnerBlock( clientId, true ),
+ parentLock: getTemplateLock( rootClientId ),
+ };
+ } ),
+ withDispatch( ( dispatch, ownProps ) => {
+ const {
+ replaceInnerBlocks,
+ updateBlockListSettings,
+ } = dispatch( 'core/block-editor' );
+ const { block, clientId, templateInsertUpdatesSelection = true } = ownProps;
+
+ return {
+ replaceInnerBlocks( blocks ) {
+ replaceInnerBlocks( clientId, blocks, block.innerBlocks.length === 0 && templateInsertUpdatesSelection );
+ },
+ updateNestedSettings( settings ) {
+ dispatch( updateBlockListSettings( clientId, settings ) );
+ },
+ };
+ } ),
+] )( InnerBlocks );
+
+// Expose default appender placeholders as components.
+InnerBlocks.DefaultBlockAppender = DefaultBlockAppender;
+InnerBlocks.ButtonBlockAppender = ButtonBlockAppender;
+
+InnerBlocks.Content = withBlockContentContext(
+ ( { BlockContent } ) =>
+);
+
+/**
+ * @see https://github.com/WordPress/gutenberg/blob/master/packages/block-editor/src/components/inner-blocks/README.md
+ */
+export default InnerBlocks;
diff --git a/packages/block-editor/src/components/url-input/test/button.js b/packages/block-editor/src/components/url-input/test/button.js
index 427ea0ca8531e8..8088d61ea5016e 100644
--- a/packages/block-editor/src/components/url-input/test/button.js
+++ b/packages/block-editor/src/components/url-input/test/button.js
@@ -63,17 +63,17 @@ describe( 'URLInputButton', () => {
} );
it( 'should close the form when user submits it', () => {
const wrapper = TestUtils.renderIntoDocument( );
- const buttonElement = () => TestUtils.findRenderedDOMComponentWithClass(
+ const buttonElement = () => TestUtils.scryRenderedDOMComponentsWithClass(
wrapper,
'components-toolbar__control'
);
- const formElement = () => TestUtils.findRenderedDOMComponentWithTag(
+ const formElement = () => TestUtils.scryRenderedDOMComponentsWithTag(
wrapper,
'form'
);
- TestUtils.Simulate.click( buttonElement() );
+ TestUtils.Simulate.click( buttonElement().shift() );
expect( wrapper.state.expanded ).toBe( true );
- TestUtils.Simulate.submit( formElement() );
+ TestUtils.Simulate.submit( formElement().shift() );
expect( wrapper.state.expanded ).toBe( false );
// eslint-disable-next-line react/no-find-dom-node
ReactDOM.unmountComponentAtNode( ReactDOM.findDOMNode( wrapper ).parentNode );
diff --git a/packages/block-library/src/index.native.js b/packages/block-library/src/index.native.js
index b3411411ee75fd..ebfeefe38b9dd1 100644
--- a/packages/block-library/src/index.native.js
+++ b/packages/block-library/src/index.native.js
@@ -101,6 +101,33 @@ export const coreBlocks = [
return memo;
}, {} );
+/**
+ * Function to register an individual block.
+ *
+ * @param {Object} block The block to be registered.
+ *
+ */
+const registerBlock = ( block ) => {
+ if ( ! block ) {
+ return;
+ }
+ const { metadata, settings, name } = block;
+ registerBlockType( name, {
+ ...metadata,
+ ...settings,
+ } );
+};
+
+/**
+ * Function to register core blocks provided by the block editor.
+ *
+ * @example
+ * ```js
+ * import { registerCoreBlocks } from '@wordpress/block-library';
+ *
+ * registerCoreBlocks();
+ * ```
+ */
export const registerCoreBlocks = () => {
[
paragraph,
@@ -114,13 +141,10 @@ export const registerCoreBlocks = () => {
separator,
list,
quote,
- ].forEach( ( { metadata, name, settings } ) => {
- registerBlockType( name, {
- ...metadata,
- ...settings,
- } );
- } );
-};
+ // eslint-disable-next-line no-undef
+ typeof __DEV__ !== 'undefined' && __DEV__ ? mediaText : null,
+ ].forEach( registerBlock );
-setDefaultBlockName( paragraph.name );
-setUnregisteredTypeHandlerName( missing.name );
+ setDefaultBlockName( paragraph.name );
+ setUnregisteredTypeHandlerName( missing.name );
+};
diff --git a/packages/block-library/src/media-text/edit.native.js b/packages/block-library/src/media-text/edit.native.js
new file mode 100644
index 00000000000000..3cfb91b48ce949
--- /dev/null
+++ b/packages/block-library/src/media-text/edit.native.js
@@ -0,0 +1,186 @@
+/**
+ * External dependencies
+ */
+import { get } from 'lodash';
+import { View } from 'react-native';
+
+/**
+ * WordPress dependencies
+ */
+import { __, _x } from '@wordpress/i18n';
+import {
+ BlockControls,
+ BlockVerticalAlignmentToolbar,
+ InnerBlocks,
+ withColors,
+} from '@wordpress/block-editor';
+import { Component } from '@wordpress/element';
+import {
+ Toolbar,
+} from '@wordpress/components';
+
+/**
+ * Internal dependencies
+ */
+import MediaContainer from './media-container';
+import styles from './style.scss';
+
+/**
+ * Constants
+ */
+const ALLOWED_BLOCKS = [ 'core/button', 'core/paragraph', 'core/heading', 'core/list' ];
+const TEMPLATE = [
+ [ 'core/paragraph', { fontSize: 'large', placeholder: _x( 'Content…', 'content placeholder' ) } ],
+];
+// this limits the resize to a safe zone to avoid making broken layouts
+const WIDTH_CONSTRAINT_PERCENTAGE = 15;
+const applyWidthConstraints = ( width ) => Math.max( WIDTH_CONSTRAINT_PERCENTAGE, Math.min( width, 100 - WIDTH_CONSTRAINT_PERCENTAGE ) );
+
+class MediaTextEdit extends Component {
+ constructor() {
+ super( ...arguments );
+
+ this.onSelectMedia = this.onSelectMedia.bind( this );
+ this.onWidthChange = this.onWidthChange.bind( this );
+ this.commitWidthChange = this.commitWidthChange.bind( this );
+ this.state = {
+ mediaWidth: null,
+ };
+ }
+
+ onSelectMedia( media ) {
+ const { setAttributes } = this.props;
+
+ let mediaType;
+ let src;
+ // for media selections originated from a file upload.
+ if ( media.media_type ) {
+ if ( media.media_type === 'image' ) {
+ mediaType = 'image';
+ } else {
+ // only images and videos are accepted so if the media_type is not an image we can assume it is a video.
+ // video contain the media type of 'file' in the object returned from the rest api.
+ mediaType = 'video';
+ }
+ } else { // for media selections originated from existing files in the media library.
+ mediaType = media.type;
+ }
+
+ if ( mediaType === 'image' ) {
+ // Try the "large" size URL, falling back to the "full" size URL below.
+ src = get( media, [ 'sizes', 'large', 'url' ] ) || get( media, [ 'media_details', 'sizes', 'large', 'source_url' ] );
+ }
+
+ setAttributes( {
+ mediaAlt: media.alt,
+ mediaId: media.id,
+ mediaType,
+ mediaUrl: src || media.url,
+ imageFill: undefined,
+ focalPoint: undefined,
+ } );
+ }
+
+ onWidthChange( width ) {
+ this.setState( {
+ mediaWidth: applyWidthConstraints( width ),
+ } );
+ }
+
+ commitWidthChange( width ) {
+ const { setAttributes } = this.props;
+
+ setAttributes( {
+ mediaWidth: applyWidthConstraints( width ),
+ } );
+ this.setState( {
+ mediaWidth: null,
+ } );
+ }
+
+ renderMediaArea() {
+ const { attributes } = this.props;
+ const { mediaAlt, mediaId, mediaPosition, mediaType, mediaUrl, mediaWidth, imageFill, focalPoint } = attributes;
+
+ return (
+
+ );
+ }
+
+ render() {
+ const {
+ attributes,
+ backgroundColor,
+ setAttributes,
+ } = this.props;
+ const {
+ isStackedOnMobile,
+ mediaPosition,
+ mediaWidth,
+ verticalAlignment,
+ } = attributes;
+ const temporaryMediaWidth = this.state.mediaWidth || mediaWidth;
+ const widthString = `${ temporaryMediaWidth }%`;
+ const containerStyles = {
+ ...styles[ 'wp-block-media-text' ],
+ ...styles[ `is-vertically-aligned-${ verticalAlignment }` ],
+ ...( mediaPosition === 'right' ? styles[ 'has-media-on-the-right' ] : {} ),
+ ...( isStackedOnMobile ? styles[ 'is-stacked-on-mobile' ] : {} ),
+ ...( isStackedOnMobile && mediaPosition === 'right' ? styles[ 'is-stacked-on-mobile.has-media-on-the-right' ] : {} ),
+ backgroundColor: backgroundColor.color,
+ };
+ const innerBlockWidth = 100 - temporaryMediaWidth;
+ const innerBlockWidthString = `${ innerBlockWidth }%`;
+
+ const toolbarControls = [ {
+ icon: 'align-pull-left',
+ title: __( 'Show media on left' ),
+ isActive: mediaPosition === 'left',
+ onClick: () => setAttributes( { mediaPosition: 'left' } ),
+ }, {
+ icon: 'align-pull-right',
+ title: __( 'Show media on right' ),
+ isActive: mediaPosition === 'right',
+ onClick: () => setAttributes( { mediaPosition: 'right' } ),
+ } ];
+
+ const onVerticalAlignmentChange = ( alignment ) => {
+ setAttributes( { verticalAlignment: alignment } );
+ };
+
+ return (
+ <>
+
+
+
+
+
+
+ { this.renderMediaArea() }
+
+
+
+
+
+ >
+ );
+ }
+}
+
+export default withColors( 'backgroundColor' )( MediaTextEdit );
diff --git a/packages/block-library/src/media-text/media-container.native.js b/packages/block-library/src/media-text/media-container.native.js
new file mode 100644
index 00000000000000..ab9056cf46dc37
--- /dev/null
+++ b/packages/block-library/src/media-text/media-container.native.js
@@ -0,0 +1,191 @@
+/**
+ * External dependencies
+ */
+import { View, Image, ImageBackground } from 'react-native';
+
+/**
+ * WordPress dependencies
+ */
+import { IconButton, Toolbar, withNotices } from '@wordpress/components';
+import {
+ BlockControls,
+ BlockIcon,
+ MediaPlaceholder,
+ MEDIA_TYPE_IMAGE,
+ MediaUpload,
+} from '@wordpress/block-editor';
+import { Component } from '@wordpress/element';
+import { __ } from '@wordpress/i18n';
+
+/**
+ * Internal dependencies
+ */
+import icon from './media-container-icon';
+
+export function calculatePreferedImageSize( image, container ) {
+ const maxWidth = container.clientWidth;
+ const exceedMaxWidth = image.width > maxWidth;
+ const ratio = image.height / image.width;
+ const width = exceedMaxWidth ? maxWidth : image.width;
+ const height = exceedMaxWidth ? maxWidth * ratio : image.height;
+ return { width, height };
+}
+
+class MediaContainer extends Component {
+ constructor() {
+ super( ...arguments );
+ this.onUploadError = this.onUploadError.bind( this );
+ this.calculateSize = this.calculateSize.bind( this );
+ this.onLayout = this.onLayout.bind( this );
+ this.onSelectURL = this.onSelectURL.bind( this );
+
+ this.state = {
+ width: 0,
+ height: 0,
+ };
+
+ if ( this.props.mediaUrl ) {
+ this.onMediaChange();
+ }
+ }
+
+ onUploadError( message ) {
+ const { noticeOperations } = this.props;
+ noticeOperations.removeAllNotices();
+ noticeOperations.createErrorNotice( message );
+ }
+
+ onSelectURL( mediaId, mediaUrl ) {
+ const { onSelectMedia } = this.props;
+
+ onSelectMedia( {
+ media_type: 'image',
+ id: mediaId,
+ src: mediaUrl,
+ } );
+ }
+
+ renderToolbarEditButton() {
+ const { mediaId } = this.props;
+ return (
+
+
+ (
+
+ ) }
+ />
+
+
+ );
+ }
+
+ componentDidUpdate( prevProps ) {
+ if ( prevProps.mediaUrl !== this.props.mediaUrl ) {
+ this.onMediaChange();
+ }
+ }
+
+ onMediaChange() {
+ const mediaType = this.props.mediaType;
+ if ( mediaType === 'video' ) {
+
+ } else if ( mediaType === 'image' ) {
+ Image.getSize( this.props.mediaUrl, ( width, height ) => {
+ this.media = { width, height };
+ this.calculateSize();
+ } );
+ }
+ }
+
+ calculateSize() {
+ if ( this.media === undefined || this.container === undefined ) {
+ return;
+ }
+
+ const { width, height } = calculatePreferedImageSize( this.media, this.container );
+ this.setState( { width, height } );
+ }
+
+ onLayout( event ) {
+ const { width, height } = event.nativeEvent.layout;
+ this.container = {
+ clientWidth: width,
+ clientHeight: height,
+ };
+ this.calculateSize();
+ }
+
+ renderImage() {
+ const { mediaAlt, mediaUrl } = this.props;
+
+ return (
+
+
+
+
+ );
+ }
+
+ renderVideo() {
+ const style = { videoContainer: {} };
+ return (
+
+
+ { /* TODO: show video preview */ }
+
+
+ );
+ }
+
+ renderPlaceholder() {
+ return (
+ }
+ labels={ {
+ title: __( 'Media area' ),
+ } }
+ onSelectURL={ this.onSelectURL }
+ mediaType={ MEDIA_TYPE_IMAGE }
+ onFocus={ this.props.onFocus }
+ />
+ );
+ }
+
+ render() {
+ const { mediaUrl, mediaType } = this.props;
+ if ( mediaType && mediaUrl ) {
+ let mediaElement = null;
+ switch ( mediaType ) {
+ case 'image':
+ mediaElement = this.renderImage();
+ break;
+ case 'video':
+ mediaElement = this.renderVideo();
+ break;
+ }
+ return mediaElement;
+ }
+ return this.renderPlaceholder();
+ }
+}
+
+export default withNotices( MediaContainer );
diff --git a/packages/block-library/src/media-text/style.native.scss b/packages/block-library/src/media-text/style.native.scss
new file mode 100644
index 00000000000000..f1c3550f29c1e9
--- /dev/null
+++ b/packages/block-library/src/media-text/style.native.scss
@@ -0,0 +1,29 @@
+.wp-block-media-text {
+ display: flex;
+ align-items: flex-start;
+ flex-direction: row;
+}
+
+.has-media-on-the-right {
+ flex-direction: row-reverse;
+}
+
+.is-stacked-on-mobile {
+ flex-direction: column;
+
+ &.has-media-on-the-right {
+ flex-direction: column-reverse;
+ }
+}
+
+.is-vertically-aligned-top {
+ align-items: flex-start;
+}
+
+.is-vertically-aligned-center {
+ align-items: center;
+}
+
+.is-vertically-aligned-bottom {
+ align-items: flex-end;
+}
diff --git a/packages/blocks/src/api/index.native.js b/packages/blocks/src/api/index.native.js
index 3b3be8f28c3a46..f8d4c03298aebe 100644
--- a/packages/blocks/src/api/index.native.js
+++ b/packages/blocks/src/api/index.native.js
@@ -35,5 +35,9 @@ export {
isUnmodifiedDefaultBlock,
normalizeIconObject,
} from './utils';
+export {
+ doBlocksMatchTemplate,
+ synchronizeBlocksWithTemplate,
+} from './templates';
export { pasteHandler, getPhrasingContentSchema } from './raw-handling';
export { default as children } from './children';
diff --git a/packages/components/src/icon-button/index.js b/packages/components/src/icon-button/index.js
index f64d7f6777e934..377feab7601cfc 100644
--- a/packages/components/src/icon-button/index.js
+++ b/packages/components/src/icon-button/index.js
@@ -2,7 +2,7 @@
* External dependencies
*/
import classnames from 'classnames';
-import { isArray, isString } from 'lodash';
+import { isArray } from 'lodash';
/**
* WordPress dependencies
@@ -14,7 +14,7 @@ import { forwardRef } from '@wordpress/element';
*/
import Tooltip from '../tooltip';
import Button from '../button';
-import Dashicon from '../dashicon';
+import Icon from '../icon';
function IconButton( props, ref ) {
const {
@@ -56,7 +56,7 @@ function IconButton( props, ref ) {
className={ classes }
ref={ ref }
>
- { isString( icon ) ? : icon }
+
{ children }
);
diff --git a/packages/components/src/icon-button/test/index.js b/packages/components/src/icon-button/test/index.js
index e824ed1d556ec9..f4a8d4330d3cc2 100644
--- a/packages/components/src/icon-button/test/index.js
+++ b/packages/components/src/icon-button/test/index.js
@@ -24,7 +24,7 @@ describe( 'IconButton', () => {
it( 'should render a Dashicon component matching the wordpress icon', () => {
const iconButton = shallow( );
- expect( iconButton.find( 'Dashicon' ).shallow().hasClass( 'dashicons-wordpress' ) ).toBe( true );
+ expect( iconButton.find( 'Icon' ).dive().shallow().hasClass( 'dashicons-wordpress' ) ).toBe( true );
} );
it( 'should render child elements when passed as children', () => {
diff --git a/packages/components/src/icon/index.js b/packages/components/src/icon/index.js
index 61a4fb0a2d6c47..5e762d1d5c8a98 100644
--- a/packages/components/src/icon/index.js
+++ b/packages/components/src/icon/index.js
@@ -6,20 +6,26 @@ import { cloneElement, createElement, Component, isValidElement } from '@wordpre
/**
* Internal dependencies
*/
-import { Dashicon, SVG } from '../';
+import Dashicon from '../dashicon';
+import { SVG } from '../primitives';
function Icon( { icon = null, size, ...additionalProps } ) {
- let iconSize;
+ // Dashicons should be 20x20 by default.
+ const dashiconSize = size || 20;
if ( 'string' === typeof icon ) {
- // Dashicons should be 20x20 by default
- iconSize = size || 20;
- return ;
+ return ;
}
- // Any other icons should be 24x24 by default
- iconSize = size || 24;
+ if ( icon && Dashicon === icon.type ) {
+ return cloneElement( icon, {
+ size: dashiconSize,
+ ...additionalProps,
+ } );
+ }
+ // Icons should be 24x24 by default.
+ const iconSize = size || 24;
if ( 'function' === typeof icon ) {
if ( icon.prototype instanceof Component ) {
return createElement( icon, { size: iconSize, ...additionalProps } );
diff --git a/packages/components/src/icon/test/index.js b/packages/components/src/icon/test/index.js
index 053b0cf390ff58..a645568c1e2eee 100644
--- a/packages/components/src/icon/test/index.js
+++ b/packages/components/src/icon/test/index.js
@@ -11,6 +11,7 @@ import { Component } from '@wordpress/element';
/**
* Internal dependencies
*/
+import Dashicon from '../../dashicon';
import Icon from '../';
import { Path, SVG } from '../../';
@@ -31,12 +32,18 @@ describe( 'Icon', () => {
expect( wrapper.find( 'Dashicon' ).prop( 'icon' ) ).toBe( 'format-image' );
} );
- it( 'renders a dashicon and with a default size of 20', () => {
+ it( 'renders a dashicon by slug and with a default size of 20', () => {
const wrapper = shallow( );
expect( wrapper.find( 'Dashicon' ).prop( 'size' ) ).toBe( 20 );
} );
+ it( 'renders a dashicon by element and with a default size of 20', () => {
+ const wrapper = shallow( } /> );
+
+ expect( wrapper.find( 'Dashicon' ).prop( 'size' ) ).toBe( 20 );
+ } );
+
it( 'renders a function', () => {
const wrapper = shallow( } /> );
@@ -98,6 +105,7 @@ describe( 'Icon', () => {
describe.each( [
[ 'dashicon', { icon: 'format-image' } ],
+ [ 'dashicon element', { icon: } ],
[ 'element', { icon: } ],
[ 'svg element', { icon: svg } ],
[ 'component', { icon: MyComponent } ],
diff --git a/packages/components/src/mobile/keyboard-aware-flat-list/index.ios.js b/packages/components/src/mobile/keyboard-aware-flat-list/index.ios.js
index fb9dab39a03f2b..33c56715268971 100644
--- a/packages/components/src/mobile/keyboard-aware-flat-list/index.ios.js
+++ b/packages/components/src/mobile/keyboard-aware-flat-list/index.ios.js
@@ -8,6 +8,7 @@ export const KeyboardAwareFlatList = ( {
extraScrollHeight,
shouldPreventAutomaticScroll,
innerRef,
+ autoScroll,
...listProps
} ) => (
{
this.scrollViewRef = ref;
innerRef( ref );
diff --git a/packages/components/src/primitives/svg/index.native.js b/packages/components/src/primitives/svg/index.native.js
index b0272e6b5a7b9f..4ee8dbae9b798d 100644
--- a/packages/components/src/primitives/svg/index.native.js
+++ b/packages/components/src/primitives/svg/index.native.js
@@ -18,7 +18,9 @@ export {
export const SVG = ( props ) => {
const stylesFromClasses = ( props.className || '' ).split( ' ' ).map( ( element ) => styles[ element ] ).filter( Boolean );
- const styleValues = Object.assign( {}, props.style, ...stylesFromClasses );
+ const stylesFromAriaPressed = props.ariaPressed ? styles[ 'is-active' ] : styles[ 'components-toolbar__control' ];
+ const styleValues = Object.assign( {}, props.style, stylesFromAriaPressed, ...stylesFromClasses );
+
const safeProps = { ...props, style: styleValues };
return (
diff --git a/packages/components/src/primitives/svg/style.native.scss b/packages/components/src/primitives/svg/style.native.scss
index 595372b06329e1..95dd5b9856bd7a 100644
--- a/packages/components/src/primitives/svg/style.native.scss
+++ b/packages/components/src/primitives/svg/style.native.scss
@@ -1,9 +1,11 @@
-.dashicon {
+.dashicon,
+.components-toolbar__control {
color: #7b9ab1;
fill: currentColor;
}
-.dashicon-active {
+.dashicon-active,
+.is-active {
color: #fff;
fill: currentColor;
}
diff --git a/packages/edit-post/src/components/header/more-menu/test/__snapshots__/index.js.snap b/packages/edit-post/src/components/header/more-menu/test/__snapshots__/index.js.snap
index 3f01ba28572d48..5096eaa7c803b5 100644
--- a/packages/edit-post/src/components/header/more-menu/test/__snapshots__/index.js.snap
+++ b/packages/edit-post/src/components/header/more-menu/test/__snapshots__/index.js.snap
@@ -74,22 +74,16 @@ exports[`MoreMenu should match snapshot 1`] = `
onMouseLeave={[Function]}
type="button"
>
-
-
-
+ >
+
+
+
+
+
+
diff --git a/packages/edit-post/src/components/visual-editor/index.native.js b/packages/edit-post/src/components/visual-editor/index.native.js
index 9d1925356a9138..b7c53b12aad545 100644
--- a/packages/edit-post/src/components/visual-editor/index.native.js
+++ b/packages/edit-post/src/components/visual-editor/index.native.js
@@ -53,6 +53,7 @@ class VisualEditor extends Component {
header={ this.renderHeader() }
isFullyBordered={ isFullyBordered }
safeAreaBottomInset={ safeAreaBottomInset }
+ autoScroll={ true }
/>
);
}