diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js
index b5a919515ac15f..d917b79132217a 100644
--- a/packages/block-library/src/image/edit.native.js
+++ b/packages/block-library/src/image/edit.native.js
@@ -24,6 +24,7 @@ import {
ToggleControl,
ToolbarButton,
ToolbarGroup,
+ Badgeable,
} from '@wordpress/components';
import {
@@ -49,7 +50,6 @@ import styles from './styles.scss';
import SvgIcon, { editImageIcon } from './icon';
import SvgIconRetry from './icon-retry';
import { getUpdatedLinkTargetSettings } from './utils';
-
import {
LINK_DESTINATION_CUSTOM,
LINK_DESTINATION_NONE,
@@ -382,92 +382,94 @@ export class ImageEdit extends React.Component {
};
const imageContainerHeight = Dimensions.get( 'window' ).width / IMAGE_ASPECT_RATIO;
-
const getImageComponent = ( openMediaOptions, getMediaOptions ) => (
-
-
- { getInspectorControls() }
- { getMediaOptions() }
- { ( ! this.state.isCaptionSelected ) &&
- getToolbarEditButton( openMediaOptions )
- }
- {
- const opacity = isUploadInProgress ? 0.3 : 1;
- const icon = this.getIcon( isUploadFailed );
- const imageBorderOnSelectedStyle = isSelected && ! ( isUploadInProgress || isUploadFailed || this.state.isCaptionSelected ) ? styles.imageBorder : '';
-
- const iconContainer = (
-
- { icon }
-
- );
-
- return (
-
- { ! imageWidthWithinContainer &&
-
- { this.getIcon( false ) }
- }
-
- { isUploadFailed &&
-
- { iconContainer }
- { retryMessage }
-
- }
-
-
- );
- } }
- />
-
- isEmpty( caption ) ?
- /* translators: accessibility text. Empty image caption. */
- ( 'Image caption. Empty' ) :
- sprintf(
- /* translators: accessibility text. %s: image caption. */
- __( 'Image caption. %s' ),
- caption )
+
+
+
+ { getInspectorControls() }
+ { getMediaOptions() }
+ { ( ! this.state.isCaptionSelected ) &&
+ getToolbarEditButton( openMediaOptions )
}
- onFocus={ this.onFocusCaption }
- onBlur={ this.props.onBlur } // always assign onBlur as props
- />
-
-
+ {
+ const opacity = isUploadInProgress ? 0.3 : 1;
+ const icon = this.getIcon( isUploadFailed );
+ const imageBorderOnSelectedStyle = isSelected && ! ( isUploadInProgress || isUploadFailed || this.state.isCaptionSelected ) ? styles.imageBorder : '';
+
+ const iconContainer = (
+
+ { icon }
+
+ );
+
+ return (
+
+ { ! imageWidthWithinContainer &&
+
+ { this.getIcon( false ) }
+ }
+
+ { isUploadFailed &&
+
+ { iconContainer }
+ { retryMessage }
+
+ }
+
+
+ );
+ } }
+ />
+
+ isEmpty( caption ) ?
+ /* translators: accessibility text. Empty image caption. */
+ ( 'Image caption. Empty' ) :
+ sprintf(
+ /* translators: accessibility text. %s: image caption. */
+ __( 'Image caption. %s' ),
+ caption )
+ }
+ onFocus={ this.onFocusCaption }
+ onBlur={ this.props.onBlur } // always assign onBlur as props
+ />
+
+
+
+
);
return (
diff --git a/packages/components/src/index.native.js b/packages/components/src/index.native.js
index 66e5e609cb7539..858e2ec812aa49 100644
--- a/packages/components/src/index.native.js
+++ b/packages/components/src/index.native.js
@@ -38,3 +38,4 @@ export { default as KeyboardAwareFlatList } from './mobile/keyboard-aware-flat-l
export { default as Picker } from './mobile/picker';
export { default as ReadableContentView } from './mobile/readable-content-view';
export { default as StepperControl } from './mobile/stepper-control';
+export { default as Badgeable } from './mobile/badgeable';
diff --git a/packages/components/src/mobile/badgeable/README.md b/packages/components/src/mobile/badgeable/README.md
new file mode 100644
index 00000000000000..e025b4f14ecfbe
--- /dev/null
+++ b/packages/components/src/mobile/badgeable/README.md
@@ -0,0 +1,39 @@
+# Badgeable
+
+Badgeable wraps a component and adds a `Badge` overlay to that component. It looks like this:
+
+![badgeable example](./example.png)
+
+A Badgeable component allows us to easily add badges to existing components without duplicating code for overlaying and positioning the Badge component. Moreover, with this approach, changing the positioning of badges can be done in one place instead of every place where a badge is visible.
+
+## Usage
+
+```jsx
+import { Image } from 'react-native';
+import { Badgeable } from '@wordpress/components';
+const BadgeableImage = () => (
+
+
+
+)} >
+```
+
+### Props
+
+#### text
+
+- Type: `String`
+- Required: Yes
+
+The text to display within the badge. An uppercase transform will be applied.
+
+#### show
+
+- Type: `Boolean`
+- Required: No
+- Default: `true`
+
+Whether to overlay the badge.
\ No newline at end of file
diff --git a/packages/components/src/mobile/badgeable/example.png b/packages/components/src/mobile/badgeable/example.png
new file mode 100644
index 00000000000000..bfcd97c24901d3
Binary files /dev/null and b/packages/components/src/mobile/badgeable/example.png differ
diff --git a/packages/components/src/mobile/badgeable/index.native.js b/packages/components/src/mobile/badgeable/index.native.js
new file mode 100644
index 00000000000000..da3f5c642c55d0
--- /dev/null
+++ b/packages/components/src/mobile/badgeable/index.native.js
@@ -0,0 +1,23 @@
+/**
+ * External dependencies
+ */
+import { Text, View } from 'react-native';
+
+/**
+ * Internal dependencies
+ */
+import styles from './styles.scss';
+
+const Badge = ( { text } ) => (
+
+ { text }
+
+);
+const Badgeable = ( { text, children, show = true } ) => (
+
+ { children }
+ { show && }
+
+);
+
+export default Badgeable;
diff --git a/packages/components/src/mobile/badgeable/styles.native.scss b/packages/components/src/mobile/badgeable/styles.native.scss
new file mode 100644
index 00000000000000..6636beb17932a9
--- /dev/null
+++ b/packages/components/src/mobile/badgeable/styles.native.scss
@@ -0,0 +1,15 @@
+.badge {
+ position: absolute;
+ top: 10px;
+ left: 10px;
+ background-color: rgba(85, 85, 85, 0.7);
+ border-radius: 6px;
+ padding: 3px 6px;
+}
+
+.badgeText {
+ text-transform: uppercase;
+ color: #fff;
+ font-size: 12px;
+ font-weight: 500;
+}