diff --git a/.travis.yml b/.travis.yml
index 99f72bbbd5f4e..b8184b6f8b8fe 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -20,6 +20,7 @@ cache:
branches:
only:
- master
+ - rnmobile/master
env:
global:
diff --git a/packages/api-fetch/src/index.native.js b/packages/api-fetch/src/index.native.js
new file mode 100644
index 0000000000000..802ac8f7c42ae
--- /dev/null
+++ b/packages/api-fetch/src/index.native.js
@@ -0,0 +1,10 @@
+
+function apiFetch( options ) {
+ // eslint-disable-next-line no-console
+ console.warn( 'apiFetch called with options', options );
+
+ // return a promise that never resolves
+ return new Promise( () => {} );
+}
+
+export default apiFetch;
diff --git a/packages/block-directory/package.json b/packages/block-directory/package.json
index 541f276f0df45..7c6af26534df9 100644
--- a/packages/block-directory/package.json
+++ b/packages/block-directory/package.json
@@ -19,6 +19,7 @@
},
"main": "build/index.js",
"module": "build-module/index.js",
+ "react-native": "src/index",
"dependencies": {
"@wordpress/api-fetch": "file:../api-fetch",
"@wordpress/block-editor": "file:../block-editor",
diff --git a/packages/block-editor/CHANGELOG.md b/packages/block-editor/CHANGELOG.md
index 07abd2b81221b..8b2a631518de5 100644
--- a/packages/block-editor/CHANGELOG.md
+++ b/packages/block-editor/CHANGELOG.md
@@ -1,3 +1,9 @@
+## Master
+
+### Deprecation
+
+- `dropZoneUIOnly` prop in `MediaPlaceholder` component has been deprecated in favor of `disableMediaButtons` prop.
+
## 3.0.0 (2019-08-05)
### New Features
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 0000000000000..4dddda5ecce8f
--- /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 0000000000000..6d8fb4e6cee97
--- /dev/null
+++ b/packages/block-editor/src/components/block-list-appender/index.native.js
@@ -0,0 +1,61 @@
+/**
+ * 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,
+ renderAppender: CustomAppender,
+} ) {
+ if ( isLocked ) {
+ return null;
+ }
+
+ if ( CustomAppender ) {
+ return (
+
+ );
+ }
+
+ 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 0000000000000..edb2c6f809ebc
--- /dev/null
+++ b/packages/block-editor/src/components/block-list-appender/style.native.scss
@@ -0,0 +1,8 @@
+
+.blockListAppender {
+ 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/block.native.js b/packages/block-editor/src/components/block-list/block.native.js
index b87f95445e11f..69503bf7151e2 100644
--- a/packages/block-editor/src/components/block-list/block.native.js
+++ b/packages/block-editor/src/components/block-list/block.native.js
@@ -131,7 +131,7 @@ class BlockListBlock extends Component {
>
{ isValid && this.getBlockForType() }
{ ! isValid &&
-
+
}
{ isSelected && }
diff --git a/packages/block-editor/src/components/block-list/block.native.scss b/packages/block-editor/src/components/block-list/block.native.scss
index b997cddc72307..a7dbae3b46516 100644
--- a/packages/block-editor/src/components/block-list/block.native.scss
+++ b/packages/block-editor/src/components/block-list/block.native.scss
@@ -3,7 +3,6 @@
}
.blockContainer {
- background-color: $white;
padding-left: 16px;
padding-right: 16px;
padding-top: 12px;
@@ -11,7 +10,6 @@
}
.blockContainerFocused {
- background-color: $white;
padding-left: 16px;
padding-right: 16px;
padding-top: 12px;
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 d25dcc85a9075..0eefa1274378c 100644
--- a/packages/block-editor/src/components/block-list/index.native.js
+++ b/packages/block-editor/src/components/block-list/index.native.js
@@ -10,7 +10,7 @@ import { Text, View, Platform, TouchableWithoutFeedback } from 'react-native';
import { Component } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { withDispatch, withSelect } from '@wordpress/data';
-import { compose } from '@wordpress/compose';
+import { compose, withPreferredColorScheme } from '@wordpress/compose';
import { createBlock, isUnmodifiedDefaultBlock } from '@wordpress/blocks';
import { KeyboardAwareFlatList, ReadableContentView } from '@wordpress/components';
@@ -19,7 +19,7 @@ import { KeyboardAwareFlatList, ReadableContentView } from '@wordpress/component
*/
import styles from './style.scss';
import BlockListBlock from './block';
-import DefaultBlockAppender from '../default-block-appender';
+import BlockListAppender from '../block-list-appender';
const innerToolbarHeight = 44;
@@ -60,41 +60,47 @@ export class BlockList extends Component {
renderDefaultBlockAppender() {
return (
-
);
}
render() {
+ const { clearSelectedBlock, blockClientIds, isFullyBordered, title, header, withFooter = true, renderAppender } = this.props;
+
return (
+
+ { renderAppender && blockClientIds.length > 0 &&
+
+ }
);
}
@@ -107,6 +113,7 @@ export class BlockList extends Component {
}
renderItem( { item: clientId, index } ) {
+ const blockHolderFocusedStyle = this.props.getStylesFromColorScheme( styles.blockHolderFocused, styles.blockHolderFocusedDark );
const { shouldShowBlockAtIndex, shouldShowInsertionPoint } = this.props;
return (
@@ -119,18 +126,20 @@ export class BlockList extends Component {
rootClientId={ this.props.rootClientId }
onCaretVerticalPositionChange={ this.onCaretVerticalPositionChange }
borderStyle={ this.blockHolderBorderStyle() }
- focusedBorderColor={ styles.blockHolderFocused.borderColor }
+ focusedBorderColor={ blockHolderFocusedStyle.borderColor }
/> ) }
);
}
renderAddBlockSeparator() {
+ const lineStyle = this.props.getStylesFromColorScheme( styles.lineStyleAddHere, styles.lineStyleAddHereDark );
+ const labelStyle = this.props.getStylesFromColorScheme( styles.labelStyleAddHere, styles.labelStyleAddHereDark );
return (
-
- { __( 'ADD BLOCK HERE' ) }
-
+
+ { __( 'ADD BLOCK HERE' ) }
+
);
}
@@ -156,12 +165,15 @@ export default compose( [
getSelectedBlockClientId,
getBlockInsertionPoint,
isBlockInsertionPointVisible,
+ getSelectedBlock,
} = select( 'core/block-editor' );
const selectedBlockClientId = getSelectedBlockClientId();
const blockClientIds = getBlockOrder( rootClientId );
const insertionPoint = getBlockInsertionPoint();
const blockInsertionPointIsVisible = isBlockInsertionPointVisible();
+ const selectedBlock = getSelectedBlock();
+ const isSelectedGroup = selectedBlock && selectedBlock.name === 'core/group';
const shouldShowInsertionPoint = ( clientId ) => {
return (
blockInsertionPointIsVisible &&
@@ -173,7 +185,7 @@ export default compose( [
const selectedBlockIndex = getBlockIndex( selectedBlockClientId );
const shouldShowBlockAtIndex = ( index ) => {
const shouldHideBlockAtIndex = (
- blockInsertionPointIsVisible &&
+ ! isSelectedGroup && blockInsertionPointIsVisible &&
// if `index` === `insertionPoint.index`, then block is replaceable
index === insertionPoint.index &&
// only hide selected block
@@ -204,5 +216,6 @@ export default compose( [
replaceBlock,
};
} ),
+ withPreferredColorScheme,
] )( BlockList );
diff --git a/packages/block-editor/src/components/block-list/insertion-point.native.js b/packages/block-editor/src/components/block-list/insertion-point.native.js
new file mode 100644
index 0000000000000..d6caa88f407fb
--- /dev/null
+++ b/packages/block-editor/src/components/block-list/insertion-point.native.js
@@ -0,0 +1,46 @@
+/**
+ * External dependencies
+ */
+import { Text, View } from 'react-native';
+
+/**
+ * WordPress dependencies
+ */
+import { __ } from '@wordpress/i18n';
+import { withSelect } from '@wordpress/data';
+
+/**
+ * Internal dependencies
+ */
+import styles from './style.scss';
+
+const BlockInsertionPoint = ( { showInsertionPoint } ) => {
+ 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-list/style.native.scss b/packages/block-editor/src/components/block-list/style.native.scss
index b6961600b95c6..36be89c4c067c 100644
--- a/packages/block-editor/src/components/block-list/style.native.scss
+++ b/packages/block-editor/src/components/block-list/style.native.scss
@@ -26,6 +26,10 @@
height: 2px;
}
+.lineStyleAddHereDark {
+ background-color: $gray-50;
+}
+
.labelStyleAddHere {
flex: 1;
text-align: center;
@@ -34,9 +38,12 @@
font-weight: bold;
}
+.labelStyleAddHereDark {
+ color: $gray-20;
+}
+
.containerStyleAddHere {
flex-direction: row;
- background-color: $white;
}
.blockHolderSemiBordered {
@@ -54,7 +61,6 @@
}
.blockContainerFocused {
- background-color: $white;
padding-left: 16;
padding-right: 16;
padding-top: 12;
@@ -65,6 +71,10 @@
border-color: $gray-lighten-30;
}
+.blockHolderFocusedDark {
+ border-color: $gray-70;
+}
+
.blockListFooter {
height: 80px;
}
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 8962dc22f49e4..40bc4d550b7ab 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/block-toolbar/index.native.js b/packages/block-editor/src/components/block-toolbar/index.native.js
index 5413e4e1f9cde..38908cf68335a 100644
--- a/packages/block-editor/src/components/block-toolbar/index.native.js
+++ b/packages/block-editor/src/components/block-toolbar/index.native.js
@@ -4,9 +4,10 @@
import { withSelect } from '@wordpress/data';
/**
- * WordPress dependencies
+ * Internal dependencies
*/
-import { BlockFormatControls, BlockControls } from '@wordpress/block-editor';
+import BlockControls from '../block-controls';
+import BlockFormatControls from '../block-format-controls';
export const BlockToolbar = ( { blockClientIds, isValid, mode } ) => {
if ( blockClientIds.length === 0 ) {
diff --git a/packages/block-editor/src/components/button-block-appender/index.native.js b/packages/block-editor/src/components/button-block-appender/index.native.js
new file mode 100644
index 0000000000000..b01d3b6510985
--- /dev/null
+++ b/packages/block-editor/src/components/button-block-appender/index.native.js
@@ -0,0 +1,48 @@
+/**
+ * External dependencies
+ */
+import { View } from 'react-native';
+
+/**
+ * WordPress dependencies
+ */
+import { Button, Dashicon } from '@wordpress/components';
+
+/**
+ * Internal dependencies
+ */
+import Inserter from '../inserter';
+import styles from './styles.scss';
+
+function ButtonBlockAppender( { rootClientId } ) {
+ return (
+ <>
+ (
+
+ ) }
+ isAppender
+ />
+ >
+ );
+}
+
+/**
+ * @see https://github.com/WordPress/gutenberg/blob/master/packages/block-editor/src/components/button-block-appender/README.md
+ */
+export default ButtonBlockAppender;
diff --git a/packages/block-editor/src/components/button-block-appender/styles.native.scss b/packages/block-editor/src/components/button-block-appender/styles.native.scss
new file mode 100644
index 0000000000000..3a6549980af87
--- /dev/null
+++ b/packages/block-editor/src/components/button-block-appender/styles.native.scss
@@ -0,0 +1,16 @@
+.appender {
+ align-items: center;
+ justify-content: center;
+ padding: 12px;
+ background-color: $white;
+ border: $border-width solid $light-gray-500;
+ border-radius: 4px;
+}
+
+.addBlockButton {
+ color: $white;
+ background-color: $gray;
+ border-radius: $icon-button-size-small / 2;
+ overflow: hidden;
+ size: $icon-button-size-small;
+}
diff --git a/packages/block-editor/src/components/default-block-appender/style.native.scss b/packages/block-editor/src/components/default-block-appender/style.native.scss
index 5193611fa45d5..9e2ffd2f293b0 100644
--- a/packages/block-editor/src/components/default-block-appender/style.native.scss
+++ b/packages/block-editor/src/components/default-block-appender/style.native.scss
@@ -5,7 +5,6 @@
}
.blockContainer {
- background-color: $white;
padding-top: 0;
padding-left: 16px;
padding-right: 16px;
diff --git a/packages/block-editor/src/components/index.native.js b/packages/block-editor/src/components/index.native.js
index 5f13de7d91b4c..b45904361595d 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 {
@@ -15,6 +18,7 @@ export {
} from './rich-text';
export { default as MediaPlaceholder } from './media-placeholder';
export { default as MediaUpload, MEDIA_TYPE_IMAGE, MEDIA_TYPE_VIDEO } from './media-upload';
+export { default as MediaUploadProgress } from './media-upload-progress';
export { default as URLInput } from './url-input';
export { default as BlockInvalidWarning } from './block-list/block-invalid-warning';
export { default as Caption } from './caption';
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 0000000000000..2b6a6c9320c92
--- /dev/null
+++ b/packages/block-editor/src/components/inner-blocks/index.native.js
@@ -0,0 +1,183 @@
+/**
+ * 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/inserter/index.native.js b/packages/block-editor/src/components/inserter/index.native.js
index b20eb8387992d..4c06402af3821 100644
--- a/packages/block-editor/src/components/inserter/index.native.js
+++ b/packages/block-editor/src/components/inserter/index.native.js
@@ -5,7 +5,7 @@ import { __ } from '@wordpress/i18n';
import { Dropdown, ToolbarButton, Dashicon } from '@wordpress/components';
import { Component } from '@wordpress/element';
import { withSelect } from '@wordpress/data';
-import { compose } from '@wordpress/compose';
+import { compose, withPreferredColorScheme } from '@wordpress/compose';
import { getUnregisteredTypeHandlerName } from '@wordpress/blocks';
/**
@@ -14,10 +14,10 @@ import { getUnregisteredTypeHandlerName } from '@wordpress/blocks';
import styles from './style.scss';
import InserterMenu from './menu';
-const defaultRenderToggle = ( { onToggle, disabled } ) => (
+const defaultRenderToggle = ( { onToggle, disabled, style } ) => (
) }
+ icon={ ( ) }
onClick={ onToggle }
extraProps={ { hint: __( 'Double tap to add a block' ) } }
isDisabled={ disabled }
@@ -56,9 +56,10 @@ class Inserter extends Component {
const {
disabled,
renderToggle = defaultRenderToggle,
+ getStylesFromColorScheme,
} = this.props;
-
- return renderToggle( { onToggle, isOpen, disabled } );
+ const style = getStylesFromColorScheme( styles.addBlockButton, styles.addBlockButtonDark );
+ return renderToggle( { onToggle, isOpen, disabled, style } );
}
/**
@@ -118,4 +119,5 @@ export default compose( [
items: inserterItems.filter( ( { name } ) => name !== getUnregisteredTypeHandlerName() ),
};
} ),
+ withPreferredColorScheme,
] )( Inserter );
diff --git a/packages/block-editor/src/components/inserter/menu.native.js b/packages/block-editor/src/components/inserter/menu.native.js
index 7870f41954afe..2a990445b284c 100644
--- a/packages/block-editor/src/components/inserter/menu.native.js
+++ b/packages/block-editor/src/components/inserter/menu.native.js
@@ -13,7 +13,7 @@ import {
isUnmodifiedDefaultBlock,
} from '@wordpress/blocks';
import { withDispatch, withSelect } from '@wordpress/data';
-import { withInstanceId, compose } from '@wordpress/compose';
+import { withInstanceId, compose, withPreferredColorScheme } from '@wordpress/compose';
import { BottomSheet, Icon } from '@wordpress/components';
/**
@@ -61,8 +61,12 @@ export class InserterMenu extends Component {
}
render() {
+ const { getStylesFromColorScheme } = this.props;
const numberOfColumns = this.calculateNumberOfColumns();
const bottomPadding = styles.contentBottomPadding;
+ const modalIconWrapperStyle = getStylesFromColorScheme( styles.modalIconWrapper, styles.modalIconWrapperDark );
+ const modalIconStyle = getStylesFromColorScheme( styles.modalIcon, styles.modalIconDark );
+ const modalItemLabelStyle = getStylesFromColorScheme( styles.modalItemLabel, styles.modalItemLabelDark );
return (
this.props.onSelect( item ) }>
-
-
-
+
+
+
- { item.title }
+ { item.title }
}
@@ -213,4 +217,5 @@ export default compose(
};
} ),
withInstanceId,
+ withPreferredColorScheme,
)( InserterMenu );
diff --git a/packages/block-editor/src/components/inserter/style.native.scss b/packages/block-editor/src/components/inserter/style.native.scss
index e10b685dda406..899b3f676827d 100644
--- a/packages/block-editor/src/components/inserter/style.native.scss
+++ b/packages/block-editor/src/components/inserter/style.native.scss
@@ -37,6 +37,10 @@
align-items: center;
}
+.modalIconWrapperDark {
+ background-color: rgba($white, 0.07);
+}
+
.modalIcon {
width: 32px;
height: 32px;
@@ -45,6 +49,10 @@
fill: $gray-dark;
}
+.modalIconDark {
+ fill: $white;
+}
+
.modalItemLabel {
background-color: transparent;
padding-left: 2;
@@ -56,9 +64,18 @@
color: $gray-dark;
}
+.modalItemLabelDark {
+ color: $white;
+}
+
.addBlockButton {
color: $blue-wordpress;
border: 2px;
border-radius: 10px;
border-color: $blue-wordpress;
}
+
+.addBlockButtonDark {
+ color: $blue-30;
+ border-color: $blue-30;
+}
diff --git a/packages/block-editor/src/components/media-placeholder/README.md b/packages/block-editor/src/components/media-placeholder/README.md
index b35b00b7d7a53..b730b9af5cbc9 100644
--- a/packages/block-editor/src/components/media-placeholder/README.md
+++ b/packages/block-editor/src/components/media-placeholder/README.md
@@ -39,6 +39,7 @@ This property is similar to the `allowedTypes` property. The difference is the f
- Type: `String`
- Required: No
+- Platform: Web
### addToGallery
@@ -48,6 +49,7 @@ If false the gallery media modal opens in the edit mode where the user can edit
- Type: `Boolean`
- Required: No
- Default: `false`
+- Platform: Web
### allowedTypes
@@ -59,6 +61,7 @@ This property is similar to the `accept` property. The difference is the format
- Type: `Array`
- Required: No
+- Platform: Web | Mobile
### className
@@ -66,6 +69,7 @@ Class name added to the placeholder.
- Type: `String`
- Required: No
+- Platform: Web
### disableDropZone
@@ -90,6 +94,7 @@ Icon to display left of the title. When passed as a `String`, the icon will be r
- Type: `String|WPComponent`
- Required: No
+- Platform: Web | Mobile
### isAppender
@@ -99,6 +104,16 @@ If false the default placeholder style is used.
- Type: `Boolean`
- Required: No
- Default: `false`
+- Platform: Web | Mobile
+
+### disableMediaButtons
+
+If true, only the Drop Zone will be rendered. No UI controls to upload the media will be shown
+
+- Type: `Boolean`
+- Required: No
+- Default: `false`
+- Platform: Web | Mobile
### labels
@@ -106,7 +121,7 @@ An object that can contain a `title` and `instructions` properties. These proper
- Type: `Object`
- Required: No
-
+- Platform: Web | Mobile
### multiple
@@ -115,6 +130,7 @@ Whether to allow multiple selection of files or not.
- Type: `Boolean`
- Required: No
- Default: `false`
+- Platform: Web
### onError
@@ -122,6 +138,7 @@ Callback called when an upload error happens.
- Type: `Function`
- Required: No
+- Platform: Web
### onSelect
@@ -130,6 +147,11 @@ The call back receives an array with the new files. Each element of the collecti
- Type: `Function`
- Required: Yes
+- Platform: Web | Mobile
+
+The argument of the callback is an object containing the following properties:
+- Web: `{ url, alt, id, link, caption, sizes, media_details }`
+- Mobile: `{ id, url }`
### value
@@ -137,6 +159,7 @@ Media ID (or media IDs if multiple is true) to be selected by default when openi
- Type: `Number|Array`
- Required: No
+- Platform: Web
## Extend
diff --git a/packages/block-editor/src/components/media-placeholder/index.js b/packages/block-editor/src/components/media-placeholder/index.js
index df92ab71ee6ec..d142d6d6c7000 100644
--- a/packages/block-editor/src/components/media-placeholder/index.js
+++ b/packages/block-editor/src/components/media-placeholder/index.js
@@ -25,6 +25,7 @@ import { __ } from '@wordpress/i18n';
import { Component } from '@wordpress/element';
import { compose } from '@wordpress/compose';
import { withSelect } from '@wordpress/data';
+import deprecated from '@wordpress/deprecated';
/**
* Internal dependencies
@@ -396,10 +397,17 @@ export class MediaPlaceholder extends Component {
render() {
const {
+ disableMediaButtons,
dropZoneUIOnly,
} = this.props;
- if ( dropZoneUIOnly ) {
+ if ( dropZoneUIOnly || disableMediaButtons ) {
+ if ( dropZoneUIOnly ) {
+ deprecated( 'wp.blockEditor.MediaPlaceholder dropZoneUIOnly prop', {
+ alternative: 'disableMediaButtons',
+ } );
+ }
+
return (
{ this.renderDropZone() }
diff --git a/packages/block-editor/src/components/media-placeholder/index.native.js b/packages/block-editor/src/components/media-placeholder/index.native.js
index a19a28588200c..4c92ccab44b79 100644
--- a/packages/block-editor/src/components/media-placeholder/index.native.js
+++ b/packages/block-editor/src/components/media-placeholder/index.native.js
@@ -7,7 +7,13 @@ import { View, Text, TouchableWithoutFeedback } from 'react-native';
* WordPress dependencies
*/
import { __, sprintf } from '@wordpress/i18n';
-import { MediaUpload, MEDIA_TYPE_IMAGE, MEDIA_TYPE_VIDEO } from '@wordpress/block-editor';
+import {
+ MediaUpload,
+ MEDIA_TYPE_IMAGE,
+ MEDIA_TYPE_VIDEO,
+} from '@wordpress/block-editor';
+import { Dashicon } from '@wordpress/components';
+import { withPreferredColorScheme } from '@wordpress/compose';
/**
* Internal dependencies
@@ -15,10 +21,19 @@ import { MediaUpload, MEDIA_TYPE_IMAGE, MEDIA_TYPE_VIDEO } from '@wordpress/bloc
import styles from './styles.scss';
function MediaPlaceholder( props ) {
- const { mediaType, labels = {}, icon, onSelectURL } = props;
+ const {
+ allowedTypes = [],
+ labels = {},
+ icon,
+ onSelect,
+ isAppender,
+ disableMediaButtons,
+ getStylesFromColorScheme,
+ } = props;
- const isImage = MEDIA_TYPE_IMAGE === mediaType;
- const isVideo = MEDIA_TYPE_VIDEO === mediaType;
+ const isOneType = allowedTypes.length === 1;
+ const isImage = isOneType && allowedTypes.includes( MEDIA_TYPE_IMAGE );
+ const isVideo = isOneType && allowedTypes.includes( MEDIA_TYPE_VIDEO );
let placeholderTitle = labels.title;
if ( placeholderTitle === undefined ) {
@@ -46,41 +61,74 @@ function MediaPlaceholder( props ) {
accessibilityHint = __( 'Double tap to select a video' );
}
+ const emptyStateTitleStyle = getStylesFromColorScheme( styles.emptyStateTitle, styles.emptyStateTitleDark );
+
+ const renderContent = () => {
+ if ( isAppender === undefined || ! isAppender ) {
+ return (
+ <>
+
+ { icon }
+
+
+ { placeholderTitle }
+
+
+ { instructions }
+
+ >
+ );
+ } else if ( isAppender && ! disableMediaButtons ) {
+ return (
+
+ );
+ }
+ };
+
+ if ( isAppender && disableMediaButtons ) {
+ return null;
+ }
+
+ const emptyStateContainerStyle = getStylesFromColorScheme( styles.emptyStateContainer, styles.emptyStateContainerDark );
+
return (
- {
- return (
- {
- props.onFocus( event );
- open();
- } }
- >
-
- { getMediaOptions() }
-
- { icon }
+
+ {
+ return (
+ {
+ props.onFocus( event );
+ open();
+ } }>
+
+ { getMediaOptions() }
+ { renderContent() }
-
- { placeholderTitle }
-
-
- { instructions }
-
-
-
- );
- } } />
+
+ );
+ } }
+ />
+
);
}
-export default MediaPlaceholder;
+export default withPreferredColorScheme( MediaPlaceholder );
diff --git a/packages/block-editor/src/components/media-placeholder/styles.native.scss b/packages/block-editor/src/components/media-placeholder/styles.native.scss
index 1d55d93c6cd70..bebceb30f15cf 100644
--- a/packages/block-editor/src/components/media-placeholder/styles.native.scss
+++ b/packages/block-editor/src/components/media-placeholder/styles.native.scss
@@ -15,6 +15,18 @@
border-bottom-right-radius: 4;
}
+.emptyStateContainerDark {
+ background-color: $background-dark-secondary;
+}
+
+.emptyStateContainerDark {
+ background-color: $background-dark-secondary;
+}
+
+.emptyStateContainerDark {
+ background-color: $background-dark-secondary;
+}
+
.emptyStateTitle {
text-align: center;
margin-top: 8;
@@ -23,6 +35,10 @@
color: #2e4453;
}
+.emptyStateTitleDark {
+ color: $white;
+}
+
.emptyStateDescription {
text-align: center;
color: $blue-wordpress;
@@ -37,3 +53,18 @@
align-items: center;
fill: $gray-dark;
}
+
+.isAppender {
+ height: auto;
+ background-color: $white;
+ border: $border-width solid $light-gray-500;
+ border-radius: 4px;
+}
+
+.addBlockButton {
+ color: $white;
+ background-color: $dark-gray-500;
+ border-radius: $icon-button-size-small / 2;
+ overflow: hidden;
+ size: $icon-button-size-small;
+}
diff --git a/packages/block-editor/src/components/media-upload-progress/README.md b/packages/block-editor/src/components/media-upload-progress/README.md
new file mode 100644
index 0000000000000..89ef924371777
--- /dev/null
+++ b/packages/block-editor/src/components/media-upload-progress/README.md
@@ -0,0 +1,140 @@
+MediaUploadProgress
+===================
+
+`MediaUploadProgress` shows a progress bar while a media file associated with a block is being uploaded.
+
+## Usage
+
+Usage example
+
+```jsx
+import { ImageBackground, Text, View } from 'react-native';
+import {
+ MediaUploadProgress,
+} from '@wordpress/block-editor';
+
+function MediaProgress( { height, width, url, id } ) {
+ return (
+ {
+ return (
+
+ { isUploadFailed &&
+
+ { retryMessage }
+
+ }
+
+ );
+ } }
+ />
+ );
+}
+```
+
+## Props
+
+### mediaId
+
+A media ID that identifies the current upload.
+
+- Type: `Number`
+- Required: Yes
+- Platform: Mobile
+
+### coverUrl
+
+By passing an image url, it'll calculate the right size depending on the container of the component maintaining its aspect ratio, it'll pass these values through `renderContent`.
+
+- Type: `String`
+- Required: No
+- Platform: Mobile
+
+### renderContent
+
+Content to be rendered along with the progress bar, usually the thumbnail of the media being uploaded.
+
+- Type: `React components`
+- Required: Yes
+- Platform: Mobile
+
+It passes an object containing the following properties:
+
+**With** `coverUrl` as a parameter:
+
+`{ isUploadInProgress, isUploadFailed, finalWidth, finalHeight, imageWidthWithinContainer, retryMessage }`
+
+**Without** `coverUrl` as a parameter:
+
+`{ isUploadInProgress, isUploadFailed, retryMessage }`
+
+
+### width
+
+Forces the final width to be returned in `finalWidth`
+
+- Type: `Number`
+- Required: No
+- Platform: Mobile
+
+### height
+
+Forces the final height to be returned in `finalHeight`
+
+- Type: `Number`
+- Required: No
+- Platform: Mobile
+
+### onUpdateMediaProgress
+
+Callback called when the progress of the upload is updated.
+
+- Type: `Function`
+- Required: No
+- Platform: Mobile
+
+The argument of the callback is an object containing the following properties:
+
+`{ mediaId, mediaUrl, progress, state }`
+
+### onFinishMediaUploadWithSuccess
+
+Callback called when the media file has been uploaded successfully.
+
+- Type: `Function`
+- Required: No
+- Platform: Mobile
+
+The argument of the callback is an object containing the following properties:
+
+`{ mediaId, mediaServerId, mediaUrl, progress, state }`
+
+### onFinishMediaUploadWithFailure
+
+Callback called when the media file couldn't be uploaded.
+
+- Type: `Function`
+- Required: No
+- Platform: Mobile
+
+The argument of the callback is an object containing the following properties:
+
+`{ mediaId, progress, state }`
+
+
+### onMediaUploadStateReset
+
+Callback called when the media upload is reset
+
+- Type: `Function`
+- Required: No
+- Platform: Mobile
+
diff --git a/packages/block-library/src/image/image-size.native.js b/packages/block-editor/src/components/media-upload-progress/image-size.native.js
similarity index 83%
rename from packages/block-library/src/image/image-size.native.js
rename to packages/block-editor/src/components/media-upload-progress/image-size.native.js
index a337a5d14548c..ade5984c1cada 100644
--- a/packages/block-library/src/image/image-size.native.js
+++ b/packages/block-editor/src/components/media-upload-progress/image-size.native.js
@@ -11,7 +11,15 @@ import { View, Image } from 'react-native';
/**
* Internal dependencies
*/
-import { calculatePreferedImageSize } from './utils';
+
+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 ImageSize extends Component {
constructor() {
diff --git a/packages/block-library/src/image/media-upload-progress.native.js b/packages/block-editor/src/components/media-upload-progress/index.native.js
similarity index 100%
rename from packages/block-library/src/image/media-upload-progress.native.js
rename to packages/block-editor/src/components/media-upload-progress/index.native.js
diff --git a/packages/block-editor/src/components/media-upload-progress/styles.native.scss b/packages/block-editor/src/components/media-upload-progress/styles.native.scss
new file mode 100644
index 0000000000000..5dea1caaf5aef
--- /dev/null
+++ b/packages/block-editor/src/components/media-upload-progress/styles.native.scss
@@ -0,0 +1,4 @@
+.mediaUploadProgress {
+ flex: 1;
+ background-color: $gray-lighten-30;
+}
diff --git a/packages/block-library/src/image/test/media-upload-progress.native.js b/packages/block-editor/src/components/media-upload-progress/test/index.native.js
similarity index 99%
rename from packages/block-library/src/image/test/media-upload-progress.native.js
rename to packages/block-editor/src/components/media-upload-progress/test/index.native.js
index 042f571aeefe5..e7c48e80d05df 100644
--- a/packages/block-library/src/image/test/media-upload-progress.native.js
+++ b/packages/block-editor/src/components/media-upload-progress/test/index.native.js
@@ -15,7 +15,7 @@ import {
MEDIA_UPLOAD_STATE_SUCCEEDED,
MEDIA_UPLOAD_STATE_FAILED,
MEDIA_UPLOAD_STATE_RESET,
-} from '../media-upload-progress';
+} from '../';
jest.mock( 'react-native-gutenberg-bridge', () => {
const callUploadCallback = ( payload ) => {
diff --git a/packages/block-editor/src/components/media-upload/README.md b/packages/block-editor/src/components/media-upload/README.md
index 56714034ec0ba..d445348fa0b15 100644
--- a/packages/block-editor/src/components/media-upload/README.md
+++ b/packages/block-editor/src/components/media-upload/README.md
@@ -63,6 +63,7 @@ If allowedTypes is unset all mime types should be allowed.
- Type: `Array`
- Required: No
+- Platform: Web | Mobile
### multiple
@@ -71,6 +72,7 @@ Whether to allow multiple selections or not.
- Type: `Boolean`
- Required: No
- Default: false
+- Platform: Web
### value
@@ -78,6 +80,7 @@ Media ID (or media IDs if multiple is true) to be selected by default when openi
- Type: `Number|Array`
- Required: No
+- Platform: Web
### onClose
@@ -96,6 +99,7 @@ This is called subsequent to `onClose` when media is selected. The selected medi
- Type: `Function`
- Required: Yes
+- Platform: Web | Mobile
### title
@@ -104,6 +108,7 @@ Title displayed in the media modal.
- Type: `String`
- Required: No
- Default: `Select or Upload Media`
+- Platform: Web
### modalClass
@@ -111,7 +116,7 @@ CSS class added to the media modal frame.
- Type: `String`
- Required: No
-
+- Platform: Web
### addToGallery
@@ -122,6 +127,7 @@ Only applies if `gallery === true`.
- Type: `Boolean`
- Required: No
- Default: `false`
+- Platform: Web
### gallery
@@ -130,6 +136,7 @@ If true, the component will initiate all the states required to represent a gall
- Type: `Boolean`
- Required: No
- Default: `false`
+- Platform: Web
## render
@@ -137,6 +144,7 @@ A callback invoked to render the Button opening the media library.
- Type: `Function`
- Required: Yes
+- Platform: Web | Mobile
The first argument of the callback is an object containing the following properties:
diff --git a/packages/block-editor/src/components/media-upload/index.native.js b/packages/block-editor/src/components/media-upload/index.native.js
index 14ed5d8dbe623..88208d961c3d2 100644
--- a/packages/block-editor/src/components/media-upload/index.native.js
+++ b/packages/block-editor/src/components/media-upload/index.native.js
@@ -23,16 +23,27 @@ export const MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_WORD_PRESS_LIBRARY = 'wordpress_med
export const OPTION_TAKE_VIDEO = __( 'Take a Video' );
export const OPTION_TAKE_PHOTO = __( 'Take a Photo' );
+export const OPTION_TAKE_PHOTO_OR_VIDEO = __( 'Take a Photo or Video' );
export class MediaUpload extends React.Component {
+ constructor( props ) {
+ super( props );
+ this.onPickerPresent = this.onPickerPresent.bind( this );
+ this.onPickerChange = this.onPickerChange.bind( this );
+ this.onPickerSelect = this.onPickerSelect.bind( this );
+ }
getTakeMediaLabel() {
- const { mediaType } = this.props;
+ const { allowedTypes = [] } = this.props;
+
+ const isOneType = allowedTypes.length === 1;
+ const isImage = isOneType && allowedTypes.includes( MEDIA_TYPE_IMAGE );
+ const isVideo = isOneType && allowedTypes.includes( MEDIA_TYPE_VIDEO );
- if ( mediaType === MEDIA_TYPE_IMAGE ) {
+ if ( isImage ) {
return OPTION_TAKE_PHOTO;
- } else if ( mediaType === MEDIA_TYPE_VIDEO ) {
+ } else if ( isVideo ) {
return OPTION_TAKE_VIDEO;
- }
+ } return OPTION_TAKE_PHOTO_OR_VIDEO;
}
getMediaOptionsItems() {
@@ -44,11 +55,15 @@ export class MediaUpload extends React.Component {
}
getChooseFromDeviceIcon() {
- const { mediaType } = this.props;
+ const { allowedTypes = [] } = this.props;
+
+ const isOneType = allowedTypes.length === 1;
+ const isImage = isOneType && allowedTypes.includes( MEDIA_TYPE_IMAGE );
+ const isVideo = isOneType && allowedTypes.includes( MEDIA_TYPE_VIDEO );
- if ( mediaType === MEDIA_TYPE_IMAGE ) {
+ if ( isImage || ! isOneType ) {
return 'format-image';
- } else if ( mediaType === MEDIA_TYPE_VIDEO ) {
+ } else if ( isVideo ) {
return 'format-video';
}
}
@@ -61,58 +76,44 @@ export class MediaUpload extends React.Component {
return 'wordpress-alt';
}
- render() {
- const { mediaType } = this.props;
-
- const onMediaLibraryButtonPressed = () => {
- requestMediaPickFromMediaLibrary( [ mediaType ], ( mediaId, mediaUrl ) => {
- if ( mediaId ) {
- this.props.onSelectURL( mediaId, mediaUrl );
- }
- } );
- };
-
- const onMediaUploadButtonPressed = () => {
- requestMediaPickFromDeviceLibrary( [ mediaType ], ( mediaId, mediaUrl ) => {
- if ( mediaId ) {
- this.props.onSelectURL( mediaId, mediaUrl );
- }
- } );
- };
-
- const onMediaCaptureButtonPressed = () => {
- requestMediaPickFromDeviceCamera( [ mediaType ], ( mediaId, mediaUrl ) => {
- if ( mediaId ) {
- this.props.onSelectURL( mediaId, mediaUrl );
- }
- } );
- };
+ onPickerPresent() {
+ if ( this.picker ) {
+ this.picker.presentPicker();
+ }
+ }
- const mediaOptions = this.getMediaOptionsItems();
+ onPickerSelect( requestFunction ) {
+ const { allowedTypes = [], onSelect } = this.props;
+ requestFunction( allowedTypes, ( id, url ) => {
+ if ( id ) {
+ onSelect( { id, url } );
+ }
+ } );
+ }
- let picker;
+ onPickerChange( value ) {
+ if ( value === MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_CHOOSE_FROM_DEVICE ) {
+ this.onPickerSelect( requestMediaPickFromDeviceLibrary );
+ } else if ( value === MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_TAKE_MEDIA ) {
+ this.onPickerSelect( requestMediaPickFromDeviceCamera );
+ } else if ( value === MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_WORD_PRESS_LIBRARY ) {
+ this.onPickerSelect( requestMediaPickFromMediaLibrary );
+ }
+ }
- const onPickerPresent = () => {
- picker.presentPicker();
- };
+ render() {
+ const mediaOptions = this.getMediaOptionsItems();
const getMediaOptions = () => (
picker = instance }
+ ref={ ( instance ) => this.picker = instance }
options={ mediaOptions }
- onChange={ ( value ) => {
- if ( value === MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_CHOOSE_FROM_DEVICE ) {
- onMediaUploadButtonPressed();
- } else if ( value === MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_TAKE_MEDIA ) {
- onMediaCaptureButtonPressed();
- } else if ( value === MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_WORD_PRESS_LIBRARY ) {
- onMediaLibraryButtonPressed();
- }
- } }
+ onChange={ this.onPickerChange }
/>
);
- return this.props.render( { open: onPickerPresent, getMediaOptions } );
+
+ return this.props.render( { open: this.onPickerPresent, getMediaOptions } );
}
}
diff --git a/packages/block-editor/src/components/media-upload/test/index.native.js b/packages/block-editor/src/components/media-upload/test/index.native.js
index 6eca7575ac408..d1eec5a560b7b 100644
--- a/packages/block-editor/src/components/media-upload/test/index.native.js
+++ b/packages/block-editor/src/components/media-upload/test/index.native.js
@@ -29,14 +29,14 @@ const MEDIA_ID = 123;
describe( 'MediaUpload component', () => {
it( 'renders without crashing', () => {
const wrapper = shallow(
- {} } />
+ {} } />
);
expect( wrapper ).toBeTruthy();
} );
it( 'opens media options picker', () => {
const wrapper = shallow(
- {
+ {
return (
{ getMediaOptions() }
@@ -51,7 +51,7 @@ describe( 'MediaUpload component', () => {
const expectOptionForMediaType = ( mediaType, expectedOption ) => {
const wrapper = shallow(
{
return (
@@ -72,12 +72,12 @@ describe( 'MediaUpload component', () => {
callback( MEDIA_ID, MEDIA_URL );
} );
- const onSelectURL = jest.fn();
+ const onSelect = jest.fn();
const wrapper = shallow(
{
return (
@@ -87,10 +87,12 @@ describe( 'MediaUpload component', () => {
} } />
);
wrapper.find( 'Picker' ).simulate( 'change', option );
+ const media = { id: MEDIA_ID, url: MEDIA_URL };
+
expect( requestFunction ).toHaveBeenCalledTimes( 1 );
- expect( onSelectURL ).toHaveBeenCalledTimes( 1 );
- expect( onSelectURL ).toHaveBeenCalledWith( MEDIA_ID, MEDIA_URL );
+ expect( onSelect ).toHaveBeenCalledTimes( 1 );
+ expect( onSelect ).toHaveBeenCalledWith( media );
};
it( 'can select media from device library', () => {
diff --git a/packages/block-editor/src/components/rich-text/index.native.js b/packages/block-editor/src/components/rich-text/index.native.js
index 30e882ed49d80..feab0f747a922 100644
--- a/packages/block-editor/src/components/rich-text/index.native.js
+++ b/packages/block-editor/src/components/rich-text/index.native.js
@@ -11,7 +11,7 @@ import { RawHTML } from '@wordpress/element';
import { withDispatch, withSelect } from '@wordpress/data';
import { pasteHandler, isUnmodifiedDefaultBlock } from '@wordpress/blocks';
import { withInstanceId, compose } from '@wordpress/compose';
-import { RichText } from '@wordpress/rich-text';
+import { __experimentalRichText as RichText } from '@wordpress/rich-text';
/**
* Internal dependencies
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 427ea0ca8531e..8088d61ea5016 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-editor/src/components/warning/index.native.js b/packages/block-editor/src/components/warning/index.native.js
index 7d0cced3408cc..dc7e83d16509a 100644
--- a/packages/block-editor/src/components/warning/index.native.js
+++ b/packages/block-editor/src/components/warning/index.native.js
@@ -7,6 +7,7 @@ import { View, Text } from 'react-native';
* WordPress dependencies
*/
import { Icon } from '@wordpress/components';
+import { withPreferredColorScheme } from '@wordpress/compose';
import { normalizeIconObject } from '@wordpress/blocks';
/**
@@ -14,29 +15,33 @@ import { normalizeIconObject } from '@wordpress/blocks';
*/
import styles from './style.scss';
-function Warning( { title, message, icon, iconClass, ...viewProps } ) {
+function Warning( { title, message, icon, iconClass, preferredColorScheme, getStylesFromColorScheme, ...viewProps } ) {
icon = icon && normalizeIconObject( icon );
+ const internalIconClass = 'warning-icon' + '-' + preferredColorScheme;
+ const titleStyle = getStylesFromColorScheme( styles.title, styles.titleDark );
+ const messageStyle = getStylesFromColorScheme( styles.message, styles.messageDark );
+
return (
{ icon && (
) }
{ title && (
- { title }
+ { title }
) }
{ message && (
- { message }
+ { message }
) }
);
}
-export default Warning;
+export default withPreferredColorScheme( Warning );
diff --git a/packages/block-editor/src/components/warning/style.native.scss b/packages/block-editor/src/components/warning/style.native.scss
index 1897d79890c6a..7c4a288151a10 100644
--- a/packages/block-editor/src/components/warning/style.native.scss
+++ b/packages/block-editor/src/components/warning/style.native.scss
@@ -14,6 +14,10 @@
justify-content: center;
}
+.containerDark {
+ background-color: $background-dark-secondary;
+}
+
.icon {
width: 24px;
height: 24px;
@@ -28,8 +32,16 @@
color: $gray-dark;
}
+.titleDark {
+ color: $white;
+}
+
.message {
text-align: center;
color: $gray-text-min;
font-size: 12;
}
+
+.messageDark {
+ color: $white;
+}
diff --git a/packages/block-library/src/code/edit.native.js b/packages/block-library/src/code/edit.native.js
index 3f021790c2a40..d8641b2270d20 100644
--- a/packages/block-library/src/code/edit.native.js
+++ b/packages/block-library/src/code/edit.native.js
@@ -6,12 +6,13 @@ import { View } from 'react-native';
/**
* WordPress dependencies
*/
+import { PlainText } from '@wordpress/block-editor';
import { __ } from '@wordpress/i18n';
+import { withPreferredColorScheme } from '@wordpress/compose';
/**
* Internal dependencies
*/
-import { PlainText } from '@wordpress/block-editor';
import { escape, unescape } from './utils';
/**
@@ -21,14 +22,16 @@ import styles from './theme.scss';
// Note: styling is applied directly to the (nested) PlainText component. Web-side components
// apply it to the container 'div' but we don't have a proper proposal for cascading styling yet.
-export default function CodeEdit( props ) {
- const { attributes, setAttributes, style, onFocus, onBlur } = props;
+export function CodeEdit( props ) {
+ const { attributes, setAttributes, style, onFocus, onBlur, getStylesFromColorScheme } = props;
+ const codeStyle = getStylesFromColorScheme( styles.blockCode, styles.blockCodeDark );
+ const placeholderStyle = getStylesFromColorScheme( styles.placeholder, styles.placeholderDark );
return (
setAttributes( { content: escape( content ) } ) }
@@ -38,8 +41,10 @@ export default function CodeEdit( props ) {
onFocus={ onFocus }
onBlur={ onBlur }
fontFamily={ ( styles.blockCode.fontFamily ) }
+ placeholderTextColor={ placeholderStyle.color }
/>
);
}
+export default withPreferredColorScheme( CodeEdit );
diff --git a/packages/block-library/src/code/theme.native.scss b/packages/block-library/src/code/theme.native.scss
index 668b9f92dd1f5..40a4ba9bfcbfd 100644
--- a/packages/block-library/src/code/theme.native.scss
+++ b/packages/block-library/src/code/theme.native.scss
@@ -4,3 +4,14 @@
font-family: $default-monospace-font;
}
+.blockCodeDark {
+ color: $white;
+}
+
+.placeholder {
+ color: $gray;
+}
+
+.placeholderDark {
+ color: $gray-50;
+}
diff --git a/packages/block-library/src/gallery/edit.js b/packages/block-library/src/gallery/edit.js
index fc6a6cd841628..449711a356ea4 100644
--- a/packages/block-library/src/gallery/edit.js
+++ b/packages/block-library/src/gallery/edit.js
@@ -273,7 +273,7 @@ class GalleryEdit extends Component {
addToGallery={ hasImagesWithId }
isAppender={ hasImages }
className={ className }
- dropZoneUIOnly={ hasImages && ! isSelected }
+ disableMediaButtons={ hasImages && ! isSelected }
icon={ ! hasImages && }
labels={ {
title: ! hasImages && __( 'Gallery' ),
diff --git a/packages/block-library/src/group/edit.native.js b/packages/block-library/src/group/edit.native.js
new file mode 100644
index 0000000000000..32c87e3863c88
--- /dev/null
+++ b/packages/block-library/src/group/edit.native.js
@@ -0,0 +1,51 @@
+
+/**
+ * External dependencies
+ */
+import { View } from 'react-native';
+
+/**
+ * WordPress dependencies
+ */
+import { withSelect } from '@wordpress/data';
+import { compose } from '@wordpress/compose';
+import {
+ InnerBlocks,
+ withColors,
+} from '@wordpress/block-editor';
+/**
+ * Internal dependencies
+ */
+import styles from './editor.scss';
+
+function GroupEdit( {
+ hasInnerBlocks,
+ isSelected,
+} ) {
+ if ( ! isSelected && ! hasInnerBlocks ) {
+ return (
+
+ );
+ }
+
+ return (
+
+ );
+}
+
+export default compose( [
+ withColors( 'backgroundColor' ),
+ withSelect( ( select, { clientId } ) => {
+ const {
+ getBlock,
+ } = select( 'core/block-editor' );
+
+ const block = getBlock( clientId );
+
+ return {
+ hasInnerBlocks: !! ( block && block.innerBlocks.length ),
+ };
+ } ),
+] )( GroupEdit );
diff --git a/packages/block-library/src/group/editor.native.scss b/packages/block-library/src/group/editor.native.scss
new file mode 100644
index 0000000000000..5edfa582287ef
--- /dev/null
+++ b/packages/block-library/src/group/editor.native.scss
@@ -0,0 +1,6 @@
+.groupPlaceholder {
+ padding: 12px;
+ background-color: $white;
+ border: $border-width dashed $light-gray-500;
+ border-radius: 4px;
+}
diff --git a/packages/block-library/src/heading/edit.native.js b/packages/block-library/src/heading/edit.native.js
index 389791663b054..ac8a0e755278c 100644
--- a/packages/block-library/src/heading/edit.native.js
+++ b/packages/block-library/src/heading/edit.native.js
@@ -28,9 +28,10 @@ const HeadingEdit = ( {
setAttributes( { level: newLevel } ) }
+ isCollapsed={ false }
/>
);
if ( isEditing || ! url ) {
diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js
index 3eb19a6bfa991..1839c19c0a58d 100644
--- a/packages/block-library/src/image/edit.native.js
+++ b/packages/block-library/src/image/edit.native.js
@@ -20,10 +20,12 @@ import {
Toolbar,
ToolbarButton,
} from '@wordpress/components';
+
import {
Caption,
MediaPlaceholder,
MediaUpload,
+ MediaUploadProgress,
MEDIA_TYPE_IMAGE,
BlockControls,
InspectorControls,
@@ -31,12 +33,12 @@ import {
import { __, sprintf } from '@wordpress/i18n';
import { isURL } from '@wordpress/url';
import { doAction, hasAction } from '@wordpress/hooks';
+import { withPreferredColorScheme } from '@wordpress/compose';
/**
* Internal dependencies
*/
import styles from './styles.scss';
-import MediaUploadProgress from './media-upload-progress';
import SvgIcon from './icon';
import SvgIconRetry from './icon-retry';
@@ -81,9 +83,9 @@ class ImageEdit extends React.Component {
if ( attributes.id && attributes.url && ! isURL( attributes.url ) ) {
if ( attributes.url.indexOf( 'file:' ) === 0 ) {
- requestMediaImport( attributes.url, ( mediaId, mediaUri ) => {
- if ( mediaUri ) {
- setAttributes( { url: mediaUri, id: mediaId } );
+ requestMediaImport( attributes.url, ( id, url ) => {
+ if ( url ) {
+ setAttributes( { id, url } );
}
} );
}
@@ -175,9 +177,9 @@ class ImageEdit extends React.Component {
} );
}
- onSelectMediaUploadOption( mediaId, mediaUrl ) {
+ onSelectMediaUploadOption( { id, url } ) {
const { setAttributes } = this.props;
- setAttributes( { url: mediaUrl, id: mediaId } );
+ setAttributes( { id, url } );
}
onFocusCaption() {
@@ -196,7 +198,8 @@ class ImageEdit extends React.Component {
return ;
}
- return ;
+ const iconStyle = this.props.getStylesFromColorScheme( styles.icon, styles.iconDark );
+ return ;
}
render() {
@@ -260,8 +263,8 @@ class ImageEdit extends React.Component {
return (
@@ -358,8 +361,8 @@ class ImageEdit extends React.Component {
);
return (
- {
return getImageComponent( open, getMediaOptions );
} }
@@ -368,4 +371,4 @@ class ImageEdit extends React.Component {
}
}
-export default ImageEdit;
+export default withPreferredColorScheme( ImageEdit );
diff --git a/packages/block-library/src/image/styles.native.scss b/packages/block-library/src/image/styles.native.scss
index 81578bd734ba3..9c5a9cbf5c45e 100644
--- a/packages/block-library/src/image/styles.native.scss
+++ b/packages/block-library/src/image/styles.native.scss
@@ -22,11 +22,6 @@
color: $alert-red;
}
-.mediaUploadProgress {
- flex: 1;
- background-color: $gray-lighten-30;
-}
-
.modalIcon {
width: 80px;
height: 80px;
@@ -45,3 +40,7 @@
width: 100%;
height: 100%;
}
+
+.iconDark {
+ fill: $white;
+}
diff --git a/packages/block-library/src/index.native.js b/packages/block-library/src/index.native.js
index 8d5eab4182e50..41806df19dc17 100644
--- a/packages/block-library/src/index.native.js
+++ b/packages/block-library/src/index.native.js
@@ -48,6 +48,7 @@ import * as textColumns from './text-columns';
import * as verse from './verse';
import * as video from './video';
import * as tagCloud from './tag-cloud';
+import * as group from './group';
export const coreBlocks = [
// Common blocks are grouped at the top to prioritize their display
@@ -99,6 +100,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,
@@ -112,13 +140,12 @@ export const registerCoreBlocks = () => {
separator,
list,
quote,
- ].forEach( ( { metadata, name, settings } ) => {
- registerBlockType( name, {
- ...metadata,
- ...settings,
- } );
- } );
-};
+ // eslint-disable-next-line no-undef
+ !! __DEV__ ? mediaText : null,
+ // eslint-disable-next-line no-undef
+ !! __DEV__ ? group : 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 0000000000000..3cfb91b48ce94
--- /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 0000000000000..0b47812e5f5ac
--- /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.onSelectMediaUploadOption = this.onSelectMediaUploadOption.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 );
+ }
+
+ onSelectMediaUploadOption( { id, url } ) {
+ const { onSelectMedia } = this.props;
+
+ onSelectMedia( {
+ media_type: 'image',
+ id,
+ url,
+ } );
+ }
+
+ 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' ),
+ } }
+ onSelect={ this.onSelectMediaUploadOption }
+ allowedTypes={ [ 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 0000000000000..f1c3550f29c1e
--- /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/block-library/src/missing/edit.native.js b/packages/block-library/src/missing/edit.native.js
index 838c64d88b56c..cd4898a4e8c69 100644
--- a/packages/block-library/src/missing/edit.native.js
+++ b/packages/block-library/src/missing/edit.native.js
@@ -7,6 +7,7 @@ import { View, Text } from 'react-native';
* WordPress dependencies
*/
import { Icon } from '@wordpress/components';
+import { withPreferredColorScheme } from '@wordpress/compose';
import { coreBlocks } from '@wordpress/block-library';
import { normalizeIconObject } from '@wordpress/blocks';
import { Component } from '@wordpress/element';
@@ -17,18 +18,25 @@ import { __ } from '@wordpress/i18n';
*/
import styles from './style.scss';
-export default class UnsupportedBlockEdit extends Component {
+export class UnsupportedBlockEdit extends Component {
render() {
const { originalName } = this.props.attributes;
+ const { getStylesFromColorScheme, preferredColorScheme } = this.props;
const blockType = coreBlocks[ originalName ];
+
const title = blockType ? blockType.settings.title : __( 'Unsupported' );
- const icon = blockType ? normalizeIconObject( blockType.settings.icon ) : 'admin-plugins';
+ const titleStyle = getStylesFromColorScheme( styles.unsupportedBlockMessage, styles.unsupportedBlockMessageDark );
+ const icon = blockType ? normalizeIconObject( blockType.settings.icon ) : 'admin-plugins';
+ const iconStyle = getStylesFromColorScheme( styles.unsupportedBlockIcon, styles.unsupportedBlockIconDark );
+ const iconClassName = 'unsupported-icon' + '-' + preferredColorScheme;
return (
-
-
- { title }
+
+
+ { title }
);
}
}
+
+export default withPreferredColorScheme( UnsupportedBlockEdit );
diff --git a/packages/block-library/src/missing/style.native.scss b/packages/block-library/src/missing/style.native.scss
index 6d587beb4f2e7..63cd4258cd23b 100644
--- a/packages/block-library/src/missing/style.native.scss
+++ b/packages/block-library/src/missing/style.native.scss
@@ -14,13 +14,25 @@
justify-content: center;
}
+.unsupportedBlockDark {
+ background-color: $background-dark-secondary;
+}
+
.unsupportedBlockIcon {
color: $gray-dark;
}
+.unsupportedBlockIconDark {
+ color: $white;
+}
+
.unsupportedBlockMessage {
margin-top: 2;
text-align: center;
color: $gray-dark;
font-size: 14;
}
+
+.unsupportedBlockMessageDark {
+ color: $white;
+}
diff --git a/packages/block-library/src/more/edit.native.js b/packages/block-library/src/more/edit.native.js
index 8b369284cb596..88908d3f44837 100644
--- a/packages/block-library/src/more/edit.native.js
+++ b/packages/block-library/src/more/edit.native.js
@@ -9,13 +9,14 @@ import Hr from 'react-native-hr';
*/
import { __ } from '@wordpress/i18n';
import { Component } from '@wordpress/element';
+import { withPreferredColorScheme } from '@wordpress/compose';
/**
* Internal dependencies
*/
import styles from './editor.scss';
-export default class MoreEdit extends Component {
+export class MoreEdit extends Component {
constructor() {
super( ...arguments );
@@ -25,9 +26,13 @@ export default class MoreEdit extends Component {
}
render() {
- const { customText } = this.props.attributes;
+ const { attributes, getStylesFromColorScheme } = this.props;
+ const { customText } = attributes;
const { defaultText } = this.state;
+
const content = customText || defaultText;
+ const textStyle = getStylesFromColorScheme( styles.moreText, styles.moreTextDark );
+ const lineStyle = getStylesFromColorScheme( styles.moreLine, styles.moreLineDark );
return (
@@ -35,10 +40,12 @@ export default class MoreEdit extends Component {
text={ content }
marginLeft={ 0 }
marginRight={ 0 }
- textStyle={ styles[ 'block-library-more__text' ] }
- lineStyle={ styles[ 'block-library-more__line' ] }
+ textStyle={ textStyle }
+ lineStyle={ lineStyle }
/>
);
}
}
+
+export default withPreferredColorScheme( MoreEdit );
diff --git a/packages/block-library/src/more/editor.native.scss b/packages/block-library/src/more/editor.native.scss
index eb4a1d60d9431..b0dece50736e6 100644
--- a/packages/block-library/src/more/editor.native.scss
+++ b/packages/block-library/src/more/editor.native.scss
@@ -1,13 +1,21 @@
// @format
-.block-library-more__line {
+.moreLine {
background-color: $gray-lighten-20;
height: 2;
}
-.block-library-more__text {
+.moreLineDark {
+ background-color: $gray-50;
+}
+
+.moreText {
color: $gray;
text-decoration-style: solid;
text-transform: uppercase;
}
+.moreTextDark {
+ color: $gray-20;
+}
+
diff --git a/packages/block-library/src/nextpage/edit.native.js b/packages/block-library/src/nextpage/edit.native.js
index e3aa69b15e5e4..25591a00269cb 100644
--- a/packages/block-library/src/nextpage/edit.native.js
+++ b/packages/block-library/src/nextpage/edit.native.js
@@ -8,16 +8,19 @@ import Hr from 'react-native-hr';
* WordPress dependencies
*/
import { __, sprintf } from '@wordpress/i18n';
+import { withPreferredColorScheme } from '@wordpress/compose';
/**
* Internal dependencies
*/
import styles from './editor.scss';
-export default function NextPageEdit( { attributes, isSelected, onFocus } ) {
+export function NextPageEdit( { attributes, isSelected, onFocus, getStylesFromColorScheme } ) {
const { customText = __( 'Page break' ) } = attributes;
const accessibilityTitle = attributes.customText || '';
const accessibilityState = isSelected ? [ 'selected' ] : [];
+ const textStyle = getStylesFromColorScheme( styles.nextpageText, styles.nextpageTextDark );
+ const lineStyle = getStylesFromColorScheme( styles.nextpageLine, styles.nextpageLineDark );
return (
+ textStyle={ textStyle }
+ lineStyle={ lineStyle } />
);
}
+
+export default withPreferredColorScheme( NextPageEdit );
diff --git a/packages/block-library/src/nextpage/editor.native.scss b/packages/block-library/src/nextpage/editor.native.scss
index 869851fdd37c6..01ed97ac57747 100644
--- a/packages/block-library/src/nextpage/editor.native.scss
+++ b/packages/block-library/src/nextpage/editor.native.scss
@@ -1,12 +1,21 @@
// @format
-.block-library-nextpage__line {
+.nextpageLine {
background-color: $gray-lighten-20;
height: 2;
}
-.block-library-nextpage__text {
+.nextpageLineDark {
+ background-color: $gray-50;
+}
+
+.nextpageText {
color: $gray;
text-decoration-style: solid;
text-transform: uppercase;
}
+
+.nextpageTextDark {
+ color: $gray-20;
+}
+
diff --git a/packages/block-library/src/separator/edit.js b/packages/block-library/src/separator/edit.js
index 050bd7423fb66..30dd0e85d7154 100644
--- a/packages/block-library/src/separator/edit.js
+++ b/packages/block-library/src/separator/edit.js
@@ -6,13 +6,12 @@ import classnames from 'classnames';
/**
* WordPress dependencies
*/
-import { __ } from '@wordpress/i18n';
import { HorizontalRule } from '@wordpress/components';
-import {
- InspectorControls,
- withColors,
- PanelColorSettings,
-} from '@wordpress/block-editor';
+import { withColors } from '@wordpress/block-editor';
+/**
+ * Internal dependencies
+ */
+import SeparatorSettings from './separator-settings';
function SeparatorEdit( { color, setColor, className } ) {
return (
@@ -29,19 +28,10 @@ function SeparatorEdit( { color, setColor, className } ) {
color: color.color,
} }
/>
-
-
-
-
+
>
);
}
diff --git a/packages/block-library/src/separator/separator-settings.js b/packages/block-library/src/separator/separator-settings.js
new file mode 100644
index 0000000000000..bb3a3a57aa1fa
--- /dev/null
+++ b/packages/block-library/src/separator/separator-settings.js
@@ -0,0 +1,26 @@
+/**
+ * WordPress dependencies
+ */
+import { __ } from '@wordpress/i18n';
+import {
+ InspectorControls,
+ PanelColorSettings,
+} from '@wordpress/block-editor';
+
+const SeparatorSettings = ( { color, setColor } ) => (
+
+
+
+
+);
+
+export default SeparatorSettings;
diff --git a/packages/block-library/src/separator/separator-settings.native.js b/packages/block-library/src/separator/separator-settings.native.js
new file mode 100644
index 0000000000000..d2bdd8ef6443a
--- /dev/null
+++ b/packages/block-library/src/separator/separator-settings.native.js
@@ -0,0 +1,3 @@
+// Mobile has no separator settings at this time, so render nothing
+const SeparatorSettings = () => null;
+export default SeparatorSettings;
diff --git a/packages/block-library/src/video/edit.native.js b/packages/block-library/src/video/edit.native.js
index 3b0a9d186d308..c69ae7bb39e8e 100644
--- a/packages/block-library/src/video/edit.native.js
+++ b/packages/block-library/src/video/edit.native.js
@@ -21,10 +21,12 @@ import {
Toolbar,
ToolbarButton,
} from '@wordpress/components';
+import { withPreferredColorScheme } from '@wordpress/compose';
import {
Caption,
MediaPlaceholder,
MediaUpload,
+ MediaUploadProgress,
MEDIA_TYPE_VIDEO,
BlockControls,
InspectorControls,
@@ -36,7 +38,6 @@ import { doAction, hasAction } from '@wordpress/hooks';
/**
* Internal dependencies
*/
-import MediaUploadProgress from '../image/media-upload-progress';
import style from './style.scss';
import SvgIcon from './icon';
import SvgIconRetry from './icon-retry';
@@ -133,9 +134,9 @@ class VideoEdit extends React.Component {
this.setState( { isUploadInProgress: false } );
}
- onSelectMediaUploadOption( mediaId, mediaUrl ) {
+ onSelectMediaUploadOption( { id, url } ) {
const { setAttributes } = this.props;
- setAttributes( { id: mediaId, src: mediaUrl } );
+ setAttributes( { id, src: url } );
}
onVideoContanerLayout( event ) {
@@ -151,7 +152,8 @@ class VideoEdit extends React.Component {
return ;
}
- return ;
+ const iconStyle = this.props.getStylesFromColorScheme( style.icon, style.iconDark );
+ return ;
}
render() {
@@ -160,8 +162,8 @@ class VideoEdit extends React.Component {
const { videoContainerHeight } = this.state;
const toolbarEditButton = (
- {
return (
@@ -181,8 +183,8 @@ class VideoEdit extends React.Component {
return (
@@ -262,4 +264,4 @@ class VideoEdit extends React.Component {
}
}
-export default VideoEdit;
+export default withPreferredColorScheme( VideoEdit );
diff --git a/packages/block-library/src/video/style.native.scss b/packages/block-library/src/video/style.native.scss
index dd4d70ae0a475..5eb36be46605e 100644
--- a/packages/block-library/src/video/style.native.scss
+++ b/packages/block-library/src/video/style.native.scss
@@ -59,6 +59,10 @@
height: 100%;
}
+.iconDark {
+ fill: $white;
+}
+
.iconUploading {
fill: $gray-lighten-20;
width: 100%;
diff --git a/packages/blocks/src/api/index.native.js b/packages/blocks/src/api/index.native.js
index 3b3be8f28c3a4..f8d4c03298aeb 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/blocks/src/api/raw-handling/list-reducer.js b/packages/blocks/src/api/raw-handling/list-reducer.js
index a5f079dcb2488..a5e85949aea62 100644
--- a/packages/blocks/src/api/raw-handling/list-reducer.js
+++ b/packages/blocks/src/api/raw-handling/list-reducer.js
@@ -8,7 +8,7 @@ function isList( node ) {
}
function shallowTextContent( element ) {
- return [ ...element.childNodes ]
+ return Array.from( element.childNodes )
.map( ( { nodeValue = '' } ) => nodeValue )
.join( '' );
}
diff --git a/packages/blocks/src/api/validation/index.js b/packages/blocks/src/api/validation/index.js
index 01e5f0857a1ea..fdf3cb67fac1f 100644
--- a/packages/blocks/src/api/validation/index.js
+++ b/packages/blocks/src/api/validation/index.js
@@ -51,7 +51,7 @@ const REGEXP_STYLE_URL_TYPE = /^url\s*\(['"\s]*(.*?)['"\s]*\)$/;
* See: https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#boolean-attributes
* Extracted from: https://html.spec.whatwg.org/multipage/indices.html#attributes-3
*
- * Object.keys( [ ...document.querySelectorAll( '#attributes-1 > tbody > tr' ) ]
+ * Object.keys( Array.from( document.querySelectorAll( '#attributes-1 > tbody > tr' ) )
* .filter( ( tr ) => tr.lastChild.textContent.indexOf( 'Boolean attribute' ) !== -1 )
* .reduce( ( result, tr ) => Object.assign( result, {
* [ tr.firstChild.textContent.trim() ]: true
@@ -98,7 +98,7 @@ const BOOLEAN_ATTRIBUTES = [
* See: https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#enumerated-attribute
* Extracted from: https://html.spec.whatwg.org/multipage/indices.html#attributes-3
*
- * Object.keys( [ ...document.querySelectorAll( '#attributes-1 > tbody > tr' ) ]
+ * Object.keys( Array.from( document.querySelectorAll( '#attributes-1 > tbody > tr' ) )
* .filter( ( tr ) => /^("(.+?)";?\s*)+/.test( tr.lastChild.textContent.trim() ) )
* .reduce( ( result, tr ) => Object.assign( result, {
* [ tr.firstChild.textContent.trim() ]: true
@@ -165,9 +165,9 @@ const TEXT_NORMALIZATIONS = [
* Tested aginst "12.5 Named character references":
*
* ```
- * const references = [ ...document.querySelectorAll(
+ * const references = Array.from( document.querySelectorAll(
* '#named-character-references-table tr[id^=entity-] td:first-child'
- * ) ].map( ( code ) => code.textContent )
+ * ) ).map( ( code ) => code.textContent )
* references.every( ( reference ) => /^[\da-z]+$/i.test( reference ) )
* ```
*
diff --git a/packages/components/src/button/index.native.js b/packages/components/src/button/index.native.js
index 7a90dd5313166..f7fdd94d13f85 100644
--- a/packages/components/src/button/index.native.js
+++ b/packages/components/src/button/index.native.js
@@ -19,8 +19,9 @@ const styles = StyleSheet.create( {
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
+ },
+ fixedRatio: {
aspectRatio: 1,
- backgroundColor: 'white',
},
buttonActive: {
flex: 1,
@@ -29,7 +30,6 @@ const styles = StyleSheet.create( {
alignItems: 'center',
borderRadius: 6,
borderColor: '#2e4453',
- aspectRatio: 1,
backgroundColor: '#2e4453',
},
subscriptInactive: {
@@ -56,6 +56,7 @@ export default function Button( props ) {
onClick,
disabled,
hint,
+ fixedRatio = true,
'aria-disabled': ariaDisabled,
'aria-label': ariaLabel,
'aria-pressed': ariaPressed,
@@ -63,8 +64,10 @@ export default function Button( props ) {
} = props;
const isDisabled = ariaDisabled || disabled;
+
const buttonViewStyle = {
opacity: isDisabled ? 0.2 : 1,
+ ...( fixedRatio && styles.fixedRatio ),
...( ariaPressed ? styles.buttonActive : styles.buttonInactive ),
};
diff --git a/packages/components/src/disabled/test/index.js b/packages/components/src/disabled/test/index.js
index bd7c556163e4e..54120a61a61bc 100644
--- a/packages/components/src/disabled/test/index.js
+++ b/packages/components/src/disabled/test/index.js
@@ -25,7 +25,7 @@ jest.mock( '@wordpress/dom', () => {
// In JSDOM, all elements have zero'd widths and height.
// This is a metric for focusable's `isVisible`, so find
// and apply an arbitrary non-zero width.
- [ ...context.querySelectorAll( '*' ) ].forEach( ( element ) => {
+ Array.from( context.querySelectorAll( '*' ) ).forEach( ( element ) => {
Object.defineProperties( element, {
offsetWidth: {
get: () => 1,
diff --git a/packages/components/src/draggable/index.js b/packages/components/src/draggable/index.js
index ac077468d8377..cd7d8c310e953 100644
--- a/packages/components/src/draggable/index.js
+++ b/packages/components/src/draggable/index.js
@@ -116,7 +116,7 @@ class Draggable extends Component {
}
// Hack: Remove iFrames as it's causing the embeds drag clone to freeze
- [ ...clone.querySelectorAll( 'iframe' ) ].forEach( ( child ) => child.parentNode.removeChild( child ) );
+ Array.from( clone.querySelectorAll( 'iframe' ) ).forEach( ( child ) => child.parentNode.removeChild( child ) );
this.cloneWrapper.appendChild( clone );
elementWrapper.appendChild( this.cloneWrapper );
diff --git a/packages/components/src/higher-order/navigate-regions/index.js b/packages/components/src/higher-order/navigate-regions/index.js
index c716e2d1ea9f1..b8b010a7414f4 100644
--- a/packages/components/src/higher-order/navigate-regions/index.js
+++ b/packages/components/src/higher-order/navigate-regions/index.js
@@ -34,7 +34,7 @@ export default createHigherOrderComponent(
}
focusRegion( offset ) {
- const regions = [ ...this.container.querySelectorAll( '[role="region"]' ) ];
+ const regions = Array.from( this.container.querySelectorAll( '[role="region"]' ) );
if ( ! regions.length ) {
return;
}
diff --git a/packages/components/src/icon-button/index.js b/packages/components/src/icon-button/index.js
index f64d7f6777e93..377feab7601cf 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 e824ed1d556ec..f4a8d4330d3cc 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 61a4fb0a2d6c4..5e762d1d5c8a9 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 053b0cf390ff5..a645568c1e2ee 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/index.native.js b/packages/components/src/index.native.js
index 4984cdde2fd36..a12d2e8baad21 100644
--- a/packages/components/src/index.native.js
+++ b/packages/components/src/index.native.js
@@ -10,6 +10,7 @@ export { default as Spinner } from './spinner';
export { createSlotFill, Slot, Fill, Provider as SlotFillProvider } from './slot-fill';
export { default as BaseControl } from './base-control';
export { default as TextareaControl } from './textarea-control';
+export { default as Button } from './button';
// Higher-Order Components
export { default as withConstrainedTabbing } from './higher-order/with-constrained-tabbing';
diff --git a/packages/components/src/mobile/bottom-sheet/cell.native.js b/packages/components/src/mobile/bottom-sheet/cell.native.js
index cebfc6a91c182..af74a25e2cee8 100644
--- a/packages/components/src/mobile/bottom-sheet/cell.native.js
+++ b/packages/components/src/mobile/bottom-sheet/cell.native.js
@@ -10,6 +10,7 @@ import { isEmpty } from 'lodash';
import { Dashicon } from '@wordpress/components';
import { Component } from '@wordpress/element';
import { __, _x, sprintf } from '@wordpress/i18n';
+import { withPreferredColorScheme } from '@wordpress/compose';
/**
* Internal dependencies
@@ -17,7 +18,7 @@ import { __, _x, sprintf } from '@wordpress/i18n';
import styles from './styles.scss';
import platformStyles from './cellStyles.scss';
-export default class BottomSheetCell extends Component {
+class BottomSheetCell extends Component {
constructor( props ) {
super( ...arguments );
this.state = {
@@ -48,12 +49,15 @@ export default class BottomSheetCell extends Component {
editable = true,
separatorType,
style = {},
+ getStylesFromColorScheme,
...valueProps
} = this.props;
const showValue = value !== undefined;
const isValueEditable = editable && onChangeValue !== undefined;
- const defaultLabelStyle = showValue || icon !== undefined ? styles.cellLabel : styles.cellLabelCentered;
+ const cellLabelStyle = getStylesFromColorScheme( styles.cellLabel, styles.cellTextDark );
+ const cellLabelCenteredStyle = getStylesFromColorScheme( styles.cellLabelCentered, styles.cellTextDark );
+ const defaultLabelStyle = showValue || icon !== undefined ? cellLabelStyle : cellLabelCenteredStyle;
const drawSeparator = ( separatorType && separatorType !== 'none' ) || separatorStyle === undefined;
const onCellPress = () => {
@@ -75,22 +79,26 @@ export default class BottomSheetCell extends Component {
};
const separatorStyle = () => {
- const leftMarginStyle = { ...styles.cellSeparator, ...platformStyles.separatorMarginLeft };
+ //eslint-disable-next-line @wordpress/no-unused-vars-before-return
+ const defaultSeparatorStyle = this.props.getStylesFromColorScheme( styles.separator, styles.separatorDark );
+ const cellSeparatorStyle = this.props.getStylesFromColorScheme( styles.cellSeparator, styles.cellSeparatorDark );
+ const leftMarginStyle = { ...cellSeparatorStyle, ...platformStyles.separatorMarginLeft };
switch ( separatorType ) {
case 'leftMargin':
return leftMarginStyle;
case 'fullWidth':
- return styles.separator;
+ return defaultSeparatorStyle;
case 'none':
return undefined;
case undefined:
- return showValue ? leftMarginStyle : styles.separator;
+ return showValue ? leftMarginStyle : defaultSeparatorStyle;
}
};
const getValueComponent = () => {
const styleRTL = I18nManager.isRTL && styles.cellValueRTL;
- const finalStyle = { ...styles.cellValue, ...valueStyle, ...styleRTL };
+ const cellValueStyle = this.props.getStylesFromColorScheme( styles.cellValue, styles.cellTextDark );
+ const finalStyle = { ...cellValueStyle, ...valueStyle, ...styleRTL };
// To be able to show the `middle` ellipsizeMode on editable cells
// we show the TextInput just when the user wants to edit the value,
@@ -114,7 +122,7 @@ export default class BottomSheetCell extends Component {
/>
) : (
@@ -142,6 +150,8 @@ export default class BottomSheetCell extends Component {
);
};
+ const iconStyle = getStylesFromColorScheme( styles.icon, styles.iconDark );
+
return (
{ icon && (
-
+
) }
@@ -177,3 +187,5 @@ export default class BottomSheetCell extends Component {
);
}
}
+
+export default withPreferredColorScheme( BottomSheetCell );
diff --git a/packages/components/src/mobile/bottom-sheet/index.native.js b/packages/components/src/mobile/bottom-sheet/index.native.js
index 9106709cea25e..f17284b49e730 100644
--- a/packages/components/src/mobile/bottom-sheet/index.native.js
+++ b/packages/components/src/mobile/bottom-sheet/index.native.js
@@ -9,6 +9,7 @@ import SafeArea from 'react-native-safe-area';
* WordPress dependencies
*/
import { Component } from '@wordpress/element';
+import { withPreferredColorScheme } from '@wordpress/compose';
/**
* Internal dependencies
@@ -63,6 +64,7 @@ class BottomSheet extends Component {
hideHeader,
style = {},
contentStyle = {},
+ getStylesFromColorScheme,
} = this.props;
const panResponder = PanResponder.create( {
@@ -118,6 +120,8 @@ class BottomSheet extends Component {
},
};
+ const backgroundStyle = getStylesFromColorScheme( styles.background, styles.backgroundDark );
+
return (
@@ -160,10 +164,12 @@ function getWidth() {
return Math.min( Dimensions.get( 'window' ).width, styles.background.maxWidth );
}
-BottomSheet.getWidth = getWidth;
-BottomSheet.Button = Button;
-BottomSheet.Cell = Cell;
-BottomSheet.PickerCell = PickerCell;
-BottomSheet.SwitchCell = SwitchCell;
+const ThemedBottomSheet = withPreferredColorScheme( BottomSheet );
+
+ThemedBottomSheet.getWidth = getWidth;
+ThemedBottomSheet.Button = Button;
+ThemedBottomSheet.Cell = Cell;
+ThemedBottomSheet.PickerCell = PickerCell;
+ThemedBottomSheet.SwitchCell = SwitchCell;
-export default BottomSheet;
+export default ThemedBottomSheet;
diff --git a/packages/components/src/mobile/bottom-sheet/styles.native.scss b/packages/components/src/mobile/bottom-sheet/styles.native.scss
index 53764ee4fe38f..8f153715c1670 100644
--- a/packages/components/src/mobile/bottom-sheet/styles.native.scss
+++ b/packages/components/src/mobile/bottom-sheet/styles.native.scss
@@ -21,6 +21,10 @@
width: 100%;
}
+.separatorDark {
+ background-color: $gray-70;
+}
+
.emptyHeaderSpace {
height: 14;
}
@@ -34,6 +38,10 @@
padding-bottom: 0;
}
+.backgroundDark {
+ background-color: $background-dark-elevated;
+}
+
.content {
padding: 0 16px 0 16px;
}
@@ -86,6 +94,10 @@
width: 100%;
}
+.cellSeparatorDark {
+ background-color: $gray-70;
+}
+
.cellRowContainer {
flex-direction: row;
align-items: center;
@@ -115,6 +127,18 @@
flex: 1;
}
+.cellTextDark {
+ color: $white;
+}
+
.cellValueRTL {
text-align: left;
}
+
+.icon {
+ color: #7b9ab1;
+}
+
+.iconDark {
+ color: #c3c4c7;
+}
diff --git a/packages/components/src/mobile/html-text-input/index.native.js b/packages/components/src/mobile/html-text-input/index.native.js
index e25f7a1af71c4..7b7c648216ef3 100644
--- a/packages/components/src/mobile/html-text-input/index.native.js
+++ b/packages/components/src/mobile/html-text-input/index.native.js
@@ -10,7 +10,7 @@ import { Component } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { parse } from '@wordpress/blocks';
import { withDispatch, withSelect } from '@wordpress/data';
-import { withInstanceId, compose } from '@wordpress/compose';
+import { withInstanceId, compose, withPreferredColorScheme } from '@wordpress/compose';
/**
* Internal dependencies
@@ -60,6 +60,9 @@ export class HTMLTextInput extends Component {
}
render() {
+ const { getStylesFromColorScheme } = this.props;
+ const htmlStyle = getStylesFromColorScheme( styles.htmlView, styles.htmlViewDark );
+ const placeholderStyle = getStylesFromColorScheme( styles.placeholder, styles.placeholderDark );
return (
@@ -117,4 +122,5 @@ export default compose( [
};
} ),
withInstanceId,
+ withPreferredColorScheme,
] )( HTMLTextInput );
diff --git a/packages/components/src/mobile/html-text-input/style-common.native.scss b/packages/components/src/mobile/html-text-input/style-common.native.scss
index 4db5b98516140..c1ac9f155d4c7 100644
--- a/packages/components/src/mobile/html-text-input/style-common.native.scss
+++ b/packages/components/src/mobile/html-text-input/style-common.native.scss
@@ -1,6 +1,6 @@
$padding: 8;
-$backgroundColor: $white;
$htmlFont: $default-monospace-font;
+$textColorDark: $white;
.keyboardAvoidingView {
position: absolute;
@@ -13,3 +13,11 @@ $htmlFont: $default-monospace-font;
.container {
flex: 1;
}
+
+.placeholder {
+ color: $gray;
+}
+
+.placeholderDark {
+ color: $gray-50;
+}
diff --git a/packages/components/src/mobile/html-text-input/style.android.scss b/packages/components/src/mobile/html-text-input/style.android.scss
index 10594358722c3..1dca01274d75b 100644
--- a/packages/components/src/mobile/html-text-input/style.android.scss
+++ b/packages/components/src/mobile/html-text-input/style.android.scss
@@ -2,7 +2,6 @@
.htmlView {
font-family: $htmlFont;
- background-color: $backgroundColor;
padding-left: $padding;
padding-right: $padding;
padding-top: $padding;
@@ -11,7 +10,6 @@
.htmlViewTitle {
font-family: $htmlFont;
- background-color: $backgroundColor;
padding-left: $padding;
padding-right: $padding;
padding-top: $padding;
diff --git a/packages/components/src/mobile/html-text-input/style.ios.scss b/packages/components/src/mobile/html-text-input/style.ios.scss
index 8b13392b95a9a..97cf00a7512ff 100644
--- a/packages/components/src/mobile/html-text-input/style.ios.scss
+++ b/packages/components/src/mobile/html-text-input/style.ios.scss
@@ -4,15 +4,17 @@ $title-height: 32;
.htmlView {
font-family: $htmlFont;
- background-color: $backgroundColor;
padding-left: $padding;
padding-right: $padding;
padding-bottom: $title-height + $padding;
}
+.htmlViewDark {
+ color: $textColorDark;
+}
+
.htmlViewTitle {
font-family: $htmlFont;
- background-color: $backgroundColor;
padding-left: $padding;
padding-right: $padding;
padding-top: $padding;
diff --git a/packages/components/src/mobile/html-text-input/test/index.native.js b/packages/components/src/mobile/html-text-input/test/index.native.js
index 479846d3f6a96..2481e533a4fad 100644
--- a/packages/components/src/mobile/html-text-input/test/index.native.js
+++ b/packages/components/src/mobile/html-text-input/test/index.native.js
@@ -33,10 +33,14 @@ const findTitleTextInput = ( wrapper ) => {
return findTextInputInWrapper( wrapper, { placeholder } );
};
+const getStylesFromColorScheme = () => {
+ return { color: 'white' };
+};
+
describe( 'HTMLTextInput', () => {
it( 'HTMLTextInput renders', () => {
const wrapper = shallow(
-
+
);
expect( wrapper ).toBeTruthy();
} );
@@ -47,6 +51,7 @@ describe( 'HTMLTextInput', () => {
const wrapper = shallow(
);
@@ -71,6 +76,7 @@ describe( 'HTMLTextInput', () => {
);
@@ -101,6 +107,7 @@ describe( 'HTMLTextInput', () => {
const wrapper = shallow(
);
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 fb9dab39a03f2..33c5671526897 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 b0272e6b5a7b9..4ee8dbae9b798 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 e5a64ea140d0d..95dd5b9856bd7 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;
}
@@ -13,12 +15,22 @@
fill: currentColor;
}
-.unsupported-icon {
+.unsupported-icon-light {
color: $gray-dark;
fill: currentColor;
}
-.warning-icon {
+.unsupported-icon-dark {
+ color: $white;
+ fill: currentColor;
+}
+
+.warning-icon-light {
color: $gray-dark;
fill: currentColor;
}
+
+.warning-icon-dark {
+ color: $white;
+ fill: currentColor;
+}
diff --git a/packages/components/src/toolbar/style.native.scss b/packages/components/src/toolbar/style.native.scss
index 1e0d176e275d8..3038ea8e491d8 100644
--- a/packages/components/src/toolbar/style.native.scss
+++ b/packages/components/src/toolbar/style.native.scss
@@ -5,3 +5,7 @@
padding-left: 5px;
padding-right: 5px;
}
+
+.containerDark {
+ border-color: #515459;
+}
diff --git a/packages/components/src/toolbar/toolbar-container.native.js b/packages/components/src/toolbar/toolbar-container.native.js
index 887991d2ea123..33fe77d11db4c 100644
--- a/packages/components/src/toolbar/toolbar-container.native.js
+++ b/packages/components/src/toolbar/toolbar-container.native.js
@@ -3,15 +3,20 @@
*/
import { View } from 'react-native';
+/**
+ * WordPress dependencies
+ */
+import { withPreferredColorScheme } from '@wordpress/compose';
+
/**
* Internal dependencies
*/
import styles from './style.scss';
-const ToolbarContainer = ( props ) => (
-
- { props.children }
+const ToolbarContainer = ( { getStylesFromColorScheme, passedStyle, children } ) => (
+
+ { children }
);
-export default ToolbarContainer;
+export default withPreferredColorScheme( ToolbarContainer );
diff --git a/packages/compose/src/higher-order/with-preferred-color-scheme/index.native.js b/packages/compose/src/higher-order/with-preferred-color-scheme/index.native.js
new file mode 100644
index 0000000000000..3b393c2ecaf5c
--- /dev/null
+++ b/packages/compose/src/higher-order/with-preferred-color-scheme/index.native.js
@@ -0,0 +1,32 @@
+/**
+ * Internal dependencies
+ */
+import createHigherOrderComponent from '../../utils/create-higher-order-component';
+import usePreferredColorScheme from '../../hooks/use-preferred-color-scheme';
+
+const withPreferredColorScheme = createHigherOrderComponent(
+ ( WrappedComponent ) => ( props ) => {
+ const colorScheme = usePreferredColorScheme();
+ const isDarkMode = colorScheme === 'dark';
+
+ const getStyles = ( lightStyles, darkStyles ) => {
+ const finalDarkStyles = {
+ ...lightStyles,
+ ...darkStyles,
+ };
+
+ return isDarkMode ? finalDarkStyles : lightStyles;
+ };
+
+ return (
+
+ );
+ },
+ 'withPreferredColorScheme'
+);
+
+export default withPreferredColorScheme;
diff --git a/packages/compose/src/hooks/use-preferred-color-scheme/index.native.js b/packages/compose/src/hooks/use-preferred-color-scheme/index.native.js
new file mode 100644
index 0000000000000..417a9e0bb0e10
--- /dev/null
+++ b/packages/compose/src/hooks/use-preferred-color-scheme/index.native.js
@@ -0,0 +1,18 @@
+/**
+ * External dependencies
+ */
+import { useDarkModeContext, eventEmitter } from 'react-native-dark-mode';
+
+// Conditional needed to pass UI Tests on CI
+if ( eventEmitter.setMaxListeners ) {
+ eventEmitter.setMaxListeners( 150 );
+}
+
+/**
+ * Returns the color scheme value when it changes. Possible values: [ 'light', 'dark' ]
+ *
+ * @return {string} return current color scheme.
+ */
+const usePreferredColorScheme = useDarkModeContext;
+
+export default usePreferredColorScheme;
diff --git a/packages/compose/src/index.native.js b/packages/compose/src/index.native.js
new file mode 100644
index 0000000000000..f410b60094d00
--- /dev/null
+++ b/packages/compose/src/index.native.js
@@ -0,0 +1,9 @@
+
+// Web exports
+export * from './index.js';
+
+// Higher-order components
+export { default as withPreferredColorScheme } from './higher-order/with-preferred-color-scheme';
+
+// Hooks
+export { default as usePreferredColorScheme } from './hooks/use-preferred-color-scheme';
diff --git a/packages/dom/src/focusable.js b/packages/dom/src/focusable.js
index 133b89d366974..12688755cfe44 100644
--- a/packages/dom/src/focusable.js
+++ b/packages/dom/src/focusable.js
@@ -76,7 +76,7 @@ function isValidFocusableArea( element ) {
export function find( context ) {
const elements = context.querySelectorAll( SELECTOR );
- return [ ...elements ].filter( ( element ) => {
+ return Array.from( elements ).filter( ( element ) => {
if ( ! isVisible( element ) ) {
return false;
}
diff --git a/packages/edit-post/src/components/admin-notices/index.js b/packages/edit-post/src/components/admin-notices/index.js
index af04efaa9953a..d39c74c34a062 100644
--- a/packages/edit-post/src/components/admin-notices/index.js
+++ b/packages/edit-post/src/components/admin-notices/index.js
@@ -27,7 +27,7 @@ const NOTICE_CLASS_STATUSES = {
function getAdminNotices() {
// The order is reversed to match expectations of rendered order, since a
// NoticesList is itself rendered in reverse order (newest to oldest).
- return [ ...document.querySelectorAll( '#wpbody-content > .notice' ) ].reverse();
+ return Array.from( document.querySelectorAll( '#wpbody-content > .notice' ) ).reverse();
}
/**
diff --git a/packages/edit-post/src/components/header/header-toolbar/index.native.js b/packages/edit-post/src/components/header/header-toolbar/index.native.js
index 7c33856a62918..6f87cc7bfc7df 100644
--- a/packages/edit-post/src/components/header/header-toolbar/index.native.js
+++ b/packages/edit-post/src/components/header/header-toolbar/index.native.js
@@ -7,7 +7,7 @@ import { ScrollView, View } from 'react-native';
/**
* WordPress dependencies
*/
-import { compose } from '@wordpress/compose';
+import { compose, withPreferredColorScheme } from '@wordpress/compose';
import { withSelect, withDispatch } from '@wordpress/data';
import { withViewportMatch } from '@wordpress/viewport';
import { __ } from '@wordpress/i18n';
@@ -30,7 +30,8 @@ function HeaderToolbar( {
undo,
showInserter,
showKeyboardHideButton,
- clearSelectedBlock,
+ getStylesFromColorScheme,
+ onHideKeyboard,
} ) {
const scrollViewRef = useRef( null );
const scrollToStart = () => {
@@ -38,7 +39,7 @@ function HeaderToolbar( {
};
return (
-
+
@@ -93,10 +94,19 @@ export default compose( [
showInserter: select( 'core/edit-post' ).getEditorMode() === 'visual' && select( 'core/editor' ).getEditorSettings().richEditingEnabled,
isTextModeEnabled: select( 'core/edit-post' ).getEditorMode() === 'text',
} ) ),
- withDispatch( ( dispatch ) => ( {
- redo: dispatch( 'core/editor' ).redo,
- undo: dispatch( 'core/editor' ).undo,
- clearSelectedBlock: dispatch( 'core/block-editor' ).clearSelectedBlock,
- } ) ),
+ withDispatch( ( dispatch ) => {
+ const { clearSelectedBlock } = dispatch( 'core/block-editor' );
+ const { togglePostTitleSelection } = dispatch( 'core/editor' );
+
+ return {
+ redo: dispatch( 'core/editor' ).redo,
+ undo: dispatch( 'core/editor' ).undo,
+ onHideKeyboard() {
+ clearSelectedBlock();
+ togglePostTitleSelection( false );
+ },
+ };
+ } ),
withViewportMatch( { isLargeViewport: 'medium' } ),
+ withPreferredColorScheme,
] )( HeaderToolbar );
diff --git a/packages/edit-post/src/components/header/header-toolbar/style.native.scss b/packages/edit-post/src/components/header/header-toolbar/style.native.scss
index 0aa03b90ed81c..9210ce5506d62 100644
--- a/packages/edit-post/src/components/header/header-toolbar/style.native.scss
+++ b/packages/edit-post/src/components/header/header-toolbar/style.native.scss
@@ -7,6 +7,11 @@
border-top-width: 1px;
}
+.containerDark {
+ background-color: $background-dark-elevated;
+ border-top-color: $background-dark-elevated;
+}
+
.scrollableContent {
flex-grow: 1; // Fixes RTL issue on Android.
}
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 3f01ba28572d4..5096eaa7c803b 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/layout/index.native.js b/packages/edit-post/src/components/layout/index.native.js
index 3242628c021b8..44d9a2d51899c 100644
--- a/packages/edit-post/src/components/layout/index.native.js
+++ b/packages/edit-post/src/components/layout/index.native.js
@@ -10,8 +10,9 @@ import { sendNativeEditorDidLayout } from 'react-native-gutenberg-bridge';
*/
import { Component } from '@wordpress/element';
import { withSelect } from '@wordpress/data';
-import { compose } from '@wordpress/compose';
+import { compose, withPreferredColorScheme } from '@wordpress/compose';
import { HTMLTextInput, KeyboardAvoidingView, ReadableContentView } from '@wordpress/components';
+import { AutosaveMonitor } from '@wordpress/editor';
/**
* Internal dependencies
@@ -75,7 +76,7 @@ class Layout extends Component {
renderHTML() {
return (
-
+
);
}
@@ -99,10 +100,13 @@ class Layout extends Component {
render() {
const {
mode,
+ getStylesFromColorScheme,
} = this.props;
+ const isHtmlView = mode === 'text';
+
// add a margin view at the bottom for the header
- const marginBottom = Platform.OS === 'android' ? headerToolbarStyles.container.height : 0;
+ const marginBottom = Platform.OS === 'android' && ! isHtmlView ? headerToolbarStyles.container.height : 0;
const toolbarKeyboardAvoidingViewStyle = {
...styles.toolbarKeyboardAvoidingView,
@@ -112,18 +116,19 @@ class Layout extends Component {
};
return (
-
-
- { mode === 'text' ? this.renderHTML() : this.renderVisual() }
-
-
+
+
+
+ { isHtmlView ? this.renderHTML() : this.renderVisual() }
-
-
-
+
+ { ! isHtmlView && (
+
+
+ ) }
);
}
@@ -143,4 +148,5 @@ export default compose( [
mode: getEditorMode(),
};
} ),
+ withPreferredColorScheme,
] )( Layout );
diff --git a/packages/edit-post/src/components/layout/style.native.scss b/packages/edit-post/src/components/layout/style.native.scss
index e6d7e241bcd0d..7a5026d6664dc 100644
--- a/packages/edit-post/src/components/layout/style.native.scss
+++ b/packages/edit-post/src/components/layout/style.native.scss
@@ -5,6 +5,19 @@
background-color: #fff;
}
+.containerDark {
+ background-color: $background-dark-elevated;
+}
+
+.background {
+ flex: 1;
+ background-color: $white;
+}
+
+.backgroundDark {
+ background-color: $black;
+}
+
.toolbarKeyboardAvoidingView {
position: absolute;
bottom: 0;
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 15ca4ed9d451b..44d7c2902cef4 100644
--- a/packages/edit-post/src/components/visual-editor/index.native.js
+++ b/packages/edit-post/src/components/visual-editor/index.native.js
@@ -4,7 +4,7 @@
import { Component } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { withDispatch, withSelect } from '@wordpress/data';
-import { compose } from '@wordpress/compose';
+import { compose, withPreferredColorScheme } from '@wordpress/compose';
import { BlockList } from '@wordpress/block-editor';
import { PostTitle } from '@wordpress/editor';
import { ReadableContentView } from '@wordpress/components';
@@ -20,8 +20,9 @@ class VisualEditor extends Component {
editTitle,
setTitleRef,
title,
+ getStylesFromColorScheme,
} = this.props;
-
+ const blockHolderFocusedStyle = getStylesFromColorScheme( styles.blockHolderFocused, styles.blockHolderFocusedDark );
return (
@@ -52,6 +53,7 @@ class VisualEditor extends Component {
header={ this.renderHeader() }
isFullyBordered={ isFullyBordered }
safeAreaBottomInset={ safeAreaBottomInset }
+ autoScroll={ true }
/>
);
}
@@ -81,4 +83,5 @@ export default compose( [
},
};
} ),
+ withPreferredColorScheme,
] )( VisualEditor );
diff --git a/packages/edit-post/src/components/visual-editor/style.native.scss b/packages/edit-post/src/components/visual-editor/style.native.scss
index 02b49a1515584..4ade220b5dd9e 100644
--- a/packages/edit-post/src/components/visual-editor/style.native.scss
+++ b/packages/edit-post/src/components/visual-editor/style.native.scss
@@ -15,3 +15,7 @@
.blockHolderFocused {
border-color: $gray-lighten-30;
}
+
+.blockHolderFocusedDark {
+ border-color: $gray-70;
+}
diff --git a/packages/edit-post/src/editor.native.js b/packages/edit-post/src/editor.native.js
index 23e0bad6a9e60..74c2cec2e4b97 100644
--- a/packages/edit-post/src/editor.native.js
+++ b/packages/edit-post/src/editor.native.js
@@ -3,6 +3,7 @@
*/
import memize from 'memize';
import { size, map, without } from 'lodash';
+import { subscribeSetFocusOnTitle } from 'react-native-gutenberg-bridge';
/**
* WordPress dependencies
@@ -31,6 +32,8 @@ class Editor extends Component {
this.getEditorSettings = memize( this.getEditorSettings, {
maxSize: 1,
} );
+
+ this.setTitleRef = this.setTitleRef.bind( this );
}
getEditorSettings(
@@ -66,6 +69,24 @@ class Editor extends Component {
return settings;
}
+ componentDidMount() {
+ this.subscriptionParentSetFocusOnTitle = subscribeSetFocusOnTitle( () => {
+ if ( this.postTitleRef ) {
+ this.postTitleRef.focus();
+ }
+ } );
+ }
+
+ componentWillUnmount() {
+ if ( this.subscriptionParentSetFocusOnTitle ) {
+ this.subscriptionParentSetFocusOnTitle.remove();
+ }
+ }
+
+ setTitleRef( titleRef ) {
+ this.postTitleRef = titleRef;
+ }
+
render() {
const {
settings,
@@ -97,7 +118,9 @@ class Editor extends Component {
// For now, let's assume: serialize( parse( html ) ) !== html
raw: serialize( parse( props.initialHtml || '' ) ),
},
- type: 'draft',
+ type: 'post',
+ status: 'draft',
+ meta: [],
};
return (
diff --git a/packages/edit-post/src/test/editor.native.js b/packages/edit-post/src/test/editor.native.js
index cedd6bf8c94b3..a48884c52e9ea 100644
--- a/packages/edit-post/src/test/editor.native.js
+++ b/packages/edit-post/src/test/editor.native.js
@@ -3,6 +3,8 @@
*/
import RNReactNativeGutenbergBridge from 'react-native-gutenberg-bridge';
import { mount } from 'enzyme';
+import { act } from 'react-dom/test-utils';
+
/**
* WordPress dependencies
*/
@@ -28,9 +30,14 @@ describe( 'Editor', () => {
beforeAll( registerCoreBlocks );
it( 'detects unsupported block and sends hasUnsupportedBlocks true to native', () => {
+ jest.useFakeTimers();
RNReactNativeGutenbergBridge.editorDidMount = jest.fn();
const appContainer = renderEditorWith( unsupportedBlock );
+ // for some reason resetEditorBlocks() is asynchronous when dispatching editEntityRecord
+ act( () => {
+ jest.runAllTicks();
+ } );
appContainer.unmount();
expect( RNReactNativeGutenbergBridge.editorDidMount ).toHaveBeenCalledTimes( 1 );
diff --git a/packages/editor/src/components/index.native.js b/packages/editor/src/components/index.native.js
index 69035455d49f1..45156604efe6f 100644
--- a/packages/editor/src/components/index.native.js
+++ b/packages/editor/src/components/index.native.js
@@ -1,5 +1,6 @@
// Post Related Components
+export { default as AutosaveMonitor } from './autosave-monitor';
export { default as PostTitle } from './post-title';
export { default as EditorHistoryRedo } from './editor-history/redo';
export { default as EditorHistoryUndo } from './editor-history/undo';
diff --git a/packages/editor/src/components/post-title/index.native.js b/packages/editor/src/components/post-title/index.native.js
index ce845b8fb630f..a47de19cfaa1c 100644
--- a/packages/editor/src/components/post-title/index.native.js
+++ b/packages/editor/src/components/post-title/index.native.js
@@ -8,7 +8,7 @@ import { isEmpty } from 'lodash';
* WordPress dependencies
*/
import { Component } from '@wordpress/element';
-import { RichText } from '@wordpress/rich-text';
+import { __experimentalRichText as RichText } from '@wordpress/rich-text';
import { decodeEntities } from '@wordpress/html-entities';
import { withDispatch, withSelect } from '@wordpress/data';
import { withFocusOutside } from '@wordpress/components';
diff --git a/packages/editor/src/components/provider/index.native.js b/packages/editor/src/components/provider/index.native.js
index d19da119ef0ba..7835d9650781c 100644
--- a/packages/editor/src/components/provider/index.native.js
+++ b/packages/editor/src/components/provider/index.native.js
@@ -5,7 +5,6 @@ import RNReactNativeGutenbergBridge, {
subscribeParentGetHtml,
subscribeParentToggleHTMLMode,
subscribeUpdateHtml,
- subscribeSetFocusOnTitle,
subscribeSetTitle,
} from 'react-native-gutenberg-bridge';
@@ -17,19 +16,35 @@ import { parse, serialize, getUnregisteredTypeHandlerName } from '@wordpress/blo
import { withDispatch, withSelect } from '@wordpress/data';
import { compose } from '@wordpress/compose';
+const postTypeEntities = [
+ { name: 'post', baseURL: '/wp/v2/posts' },
+ { name: 'page', baseURL: '/wp/v2/pages' },
+ { name: 'attachment', baseURL: '/wp/v2/media' },
+ { name: 'wp_block', baseURL: '/wp/v2/blocks' },
+].map( ( postTypeEntity ) => ( {
+ kind: 'postType',
+ ...postTypeEntity,
+ transientEdits: {
+ blocks: true,
+ },
+ mergedEdits: {
+ meta: true,
+ },
+} ) );
+
/**
* Internal dependencies
*/
import EditorProvider from './index.js';
class NativeEditorProvider extends Component {
- constructor( props ) {
+ constructor() {
super( ...arguments );
// Keep a local reference to `post` to detect changes
- this.post = props.post;
-
- this.setTitleRef = this.setTitleRef.bind( this );
+ this.post = this.props.post;
+ this.props.addEntities( postTypeEntities );
+ this.props.receiveEntityRecords( 'postType', this.post.type, this.post );
}
componentDidMount() {
@@ -48,12 +63,6 @@ class NativeEditorProvider extends Component {
this.subscriptionParentUpdateHtml = subscribeUpdateHtml( ( payload ) => {
this.updateHtmlAction( payload.html );
} );
-
- this.subscriptionParentSetFocusOnTitle = subscribeSetFocusOnTitle( () => {
- if ( this.postTitleRef ) {
- this.postTitleRef.focus();
- }
- } );
}
componentWillUnmount() {
@@ -72,10 +81,6 @@ class NativeEditorProvider extends Component {
if ( this.subscriptionParentUpdateHtml ) {
this.subscriptionParentUpdateHtml.remove();
}
-
- if ( this.subscriptionParentSetFocusOnTitle ) {
- this.subscriptionParentSetFocusOnTitle.remove();
- }
}
componentDidUpdate( prevProps ) {
@@ -87,10 +92,6 @@ class NativeEditorProvider extends Component {
}
}
- setTitleRef( titleRef ) {
- this.postTitleRef = titleRef;
- }
-
serializeToNativeAction() {
if ( this.props.mode === 'text' ) {
this.updateHtmlAction( this.props.getEditedPostContent() );
@@ -169,12 +170,18 @@ export default compose( [
const {
switchEditorMode,
} = dispatch( 'core/edit-post' );
+ const {
+ addEntities,
+ receiveEntityRecords,
+ } = dispatch( 'core' );
return {
+ addEntities,
clearSelectedBlock,
editTitle( title ) {
editPost( { title } );
},
+ receiveEntityRecords,
resetEditorBlocksWithoutUndoLevel( blocks ) {
resetEditorBlocks( blocks, {
__unstableShouldCreateUndoLevel: false,
diff --git a/packages/editor/src/store/actions.native.js b/packages/editor/src/store/actions.native.js
index 51741ac9bc3b4..17733a0e47b40 100644
--- a/packages/editor/src/store/actions.native.js
+++ b/packages/editor/src/store/actions.native.js
@@ -1,3 +1,7 @@
+/**
+ * External dependencies
+ */
+import RNReactNativeGutenbergBridge from 'react-native-gutenberg-bridge';
export * from './actions.js';
@@ -14,3 +18,12 @@ export function togglePostTitleSelection( isSelected = true ) {
isSelected,
};
}
+
+/**
+ * Action generator used in signalling that the post should autosave.
+ *
+ * @param {Object?} options Extra flags to identify the autosave.
+ */
+export function* autosave( ) {
+ RNReactNativeGutenbergBridge.editorDidAutosave();
+}
diff --git a/packages/editor/src/store/reducer.native.js b/packages/editor/src/store/reducer.native.js
index 82b3689a98ead..a0f0be9c3cb05 100644
--- a/packages/editor/src/store/reducer.native.js
+++ b/packages/editor/src/store/reducer.native.js
@@ -12,27 +12,29 @@ import { combineReducers } from '@wordpress/data';
* Internal dependencies
*/
import {
- editor,
- initialEdits,
- currentPost,
+ postId,
+ postType,
preferences,
saving,
postLock,
+ postSavingLock,
reusableBlocks,
template,
- previewLink,
- postSavingLock,
isReady,
editorSettings,
} from './reducer.js';
+import { EDITOR_SETTINGS_DEFAULTS } from './defaults.js';
+
+EDITOR_SETTINGS_DEFAULTS.autosaveInterval = 0; // This is a way to override default behavior on mobile, and make it ping the native save at each keystroke
+
export * from './reducer.js';
/**
* Reducer returning the post title state.
*
- * @param {PostTitleState} state Current state.
- * @param {Object} action Dispatched action.
+ * @param {Object} state Current state.
+ * @param {Object} action Dispatched action.
*
* @return {Object} Updated state.
*/
@@ -48,17 +50,15 @@ export const postTitle = combineReducers( {
} );
export default optimist( combineReducers( {
- editor,
- initialEdits,
- currentPost,
+ postId,
+ postType,
+ postTitle,
preferences,
saving,
postLock,
+ postSavingLock,
reusableBlocks,
template,
- previewLink,
- postSavingLock,
isReady,
editorSettings,
- postTitle,
} ) );
diff --git a/packages/editor/src/store/selectors.native.js b/packages/editor/src/store/selectors.native.js
index 8c6ead8e97ba2..ae84e7b5afe5a 100644
--- a/packages/editor/src/store/selectors.native.js
+++ b/packages/editor/src/store/selectors.native.js
@@ -1,3 +1,16 @@
+/**
+ * WordPress dependencies
+ */
+import { createRegistrySelector } from '@wordpress/data';
+
+/**
+ * Internal dependencies
+ */
+import {
+ isEditedPostDirty,
+ isEditedPostSaveable,
+ hasChangedContent,
+} from './selectors.js';
export * from './selectors.js';
@@ -11,3 +24,32 @@ export * from './selectors.js';
export function isPostTitleSelected( state ) {
return state.postTitle.isSelected;
}
+
+/**
+ * Returns true if the post can be autosaved, or false otherwise.
+ *
+ * @param {Object} state Global application state.
+ * @param {Object} autosave A raw autosave object from the REST API.
+ *
+ * @return {boolean} Whether the post can be autosaved.
+ */
+export const isEditedPostAutosaveable = createRegistrySelector( ( ) => function( state ) {
+ // A post must contain a title, an excerpt, or non-empty content to be valid for autosaving.
+ if ( ! isEditedPostSaveable( state ) ) {
+ return false;
+ }
+
+ // To avoid an expensive content serialization, use the content dirtiness
+ // flag in place of content field comparison against the known autosave.
+ // This is not strictly accurate, and relies on a tolerance toward autosave
+ // request failures for unnecessary saves.
+ if ( hasChangedContent( state ) ) {
+ return true;
+ }
+
+ if ( isEditedPostDirty( state ) ) {
+ return true;
+ }
+
+ return false;
+} );
diff --git a/packages/format-library/src/link/test/modal.native.js b/packages/format-library/src/link/test/modal.native.js
index 03594a4ef1c38..9d84f9f903ffb 100644
--- a/packages/format-library/src/link/test/modal.native.js
+++ b/packages/format-library/src/link/test/modal.native.js
@@ -23,7 +23,7 @@ describe( 'LinksUI', () => {
onRemove={ onRemove }
onClose={ jest.fn() }
/>
- ).dive(); // -> dive() removes the HOC layer that was blocking access to ModalLinkUI
+ ).dive().dive(); // -> dive() removes the HOC layer that was blocking access to ModalLinkUI
// When
@@ -52,8 +52,9 @@ describe( 'LinksUI', () => {
// When
// Simulate user typing on the URL Cell.
- const bottomSheet = wrapper.find( 'BottomSheet' ).first();
- const cell = bottomSheet.find( 'BottomSheetCell' ).first();
+ const bottomSheet = wrapper.dive().find( 'BottomSheet' ).first();
+ const cell = bottomSheet.dive().find( 'WithPreferredColorScheme(BottomSheetCell)' ).first().dive();
+
cell.simulate( 'changeValue', 'wordpress.com' );
// Close the BottomSheet
diff --git a/packages/rich-text/src/component/index.native.js b/packages/rich-text/src/component/index.native.js
index fe705d8ad5b38..206680d3c8766 100644
--- a/packages/rich-text/src/component/index.native.js
+++ b/packages/rich-text/src/component/index.native.js
@@ -14,7 +14,7 @@ import memize from 'memize';
* WordPress dependencies
*/
import { Component } from '@wordpress/element';
-import { compose } from '@wordpress/compose';
+import { compose, withPreferredColorScheme } from '@wordpress/compose';
import { withSelect } from '@wordpress/data';
import { childrenBlock } from '@wordpress/blocks';
import { decodeEntities } from '@wordpress/html-entities';
@@ -770,25 +770,28 @@ export class RichText extends Component {
style,
__unstableIsSelected: isSelected,
children,
+ getStylesFromColorScheme,
} = this.props;
const record = this.getRecord();
const html = this.getHtmlToRender( record, tagName );
- let minHeight = styles[ 'rich-text' ].minHeight;
+ let minHeight = styles.richText.minHeight;
if ( style && style.minHeight ) {
minHeight = style.minHeight;
}
+ const placeholderStyle = getStylesFromColorScheme( styles.richTextPlaceholder, styles.richTextPlaceholderDark );
+
const {
color: defaultPlaceholderTextColor,
- } = styles[ 'rich-text-placeholder' ];
+ } = placeholderStyle;
const {
color: defaultColor,
textDecorationColor: defaultTextDecorationColor,
fontFamily: defaultFontFamily,
- } = styles[ 'rich-text' ];
+ } = getStylesFromColorScheme( styles.richText, styles.richTextDark );
let selection = null;
if ( this.needsSelectionUpdate ) {
@@ -817,6 +820,8 @@ export class RichText extends Component {
this.firedAfterTextChanged = false;
}
+ const dynamicStyle = getStylesFromColorScheme( style, styles.richTextDark );
+
return (
{ children && children( {
@@ -833,7 +838,7 @@ export class RichText extends Component {
}
} }
style={ {
- ...style,
+ ...dynamicStyle,
minHeight: Math.max( minHeight, this.state.height ),
} }
text={ { text: html, eventCount: this.lastEventCount, selection } }
@@ -878,4 +883,5 @@ export default compose( [
withSelect( ( select ) => ( {
formatTypes: select( 'core/rich-text' ).getFormatTypes(),
} ) ),
+ withPreferredColorScheme,
] )( RichText );
diff --git a/packages/rich-text/src/component/style.native.scss b/packages/rich-text/src/component/style.native.scss
index 6481c41569412..4ed93f7f70239 100644
--- a/packages/rich-text/src/component/style.native.scss
+++ b/packages/rich-text/src/component/style.native.scss
@@ -1,11 +1,21 @@
-.rich-text {
+.richText {
font-family: $default-regular-font;
min-height: $min-height-paragraph;
color: $gray-900;
text-decoration-color: $blue-500;
}
-.rich-text-placeholder {
+.richTextDark {
+ color: $white;
+ text-decoration-color: $blue-30;
+ background-color: $black;
+}
+
+.richTextPlaceholder {
color: $gray;
}
+
+.richTextPlaceholderDark {
+ color: $gray-50;
+}
diff --git a/packages/rich-text/src/component/test/index.native.js b/packages/rich-text/src/component/test/index.native.js
index ec0cbb7719524..6b2bc12f855ff 100644
--- a/packages/rich-text/src/component/test/index.native.js
+++ b/packages/rich-text/src/component/test/index.native.js
@@ -8,6 +8,10 @@ import { shallow } from 'enzyme';
*/
import { RichText } from '../index';
+const getStylesFromColorScheme = () => {
+ return { color: 'white' };
+};
+
describe( 'RichText Native', () => {
let richText;
@@ -40,6 +44,7 @@ describe( 'RichText Native', () => {
} }
formatTypes={ [] }
onSelectionChange={ jest.fn() }
+ getStylesFromColorScheme={ getStylesFromColorScheme }
/> );
const event = {
diff --git a/test/native/__mocks__/react-native-dark-mode/index.js b/test/native/__mocks__/react-native-dark-mode/index.js
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/test/native/__mocks__/styleMock.js b/test/native/__mocks__/styleMock.js
index 182af41388dc0..4e63b494feff9 100644
--- a/test/native/__mocks__/styleMock.js
+++ b/test/native/__mocks__/styleMock.js
@@ -15,7 +15,7 @@ module.exports = {
blockCode: {
fontFamily: 'serif',
},
- 'rich-text': {
+ richText: {
fontFamily: 'serif',
minHeight: 30,
},
@@ -66,4 +66,13 @@ module.exports = {
iconUploading: {
fill: 'gray',
},
+ placeholder: {
+ color: 'gray',
+ },
+ richTextPlaceholder: {
+ color: 'gray',
+ },
+ unsupportedBlockIcon: {
+ color: 'white',
+ },
};
diff --git a/test/native/setup.js b/test/native/setup.js
index cfd2417a11ad9..60fe7194c0aa6 100644
--- a/test/native/setup.js
+++ b/test/native/setup.js
@@ -14,6 +14,7 @@ jest.mock( 'react-native-gutenberg-bridge', () => {
subscribeUpdateHtml: jest.fn(),
subscribeMediaAppend: jest.fn(),
editorDidMount: jest.fn(),
+ editorDidAutosave: jest.fn(),
subscribeMediaUpload: jest.fn(),
requestMediaPickFromMediaLibrary: jest.fn(),
requestMediaPickFromDeviceLibrary: jest.fn(),
@@ -21,6 +22,16 @@ jest.mock( 'react-native-gutenberg-bridge', () => {
};
} );
+jest.mock( 'react-native-dark-mode', () => {
+ return {
+ initialMode: 'light',
+ eventEmitter: {
+ on: jest.fn(),
+ },
+ useDarkModeContext: () => 'light',
+ };
+} );
+
jest.mock( 'react-native-modal', () => () => 'Modal' );
jest.mock( 'react-native-hr', () => () => 'Hr' );