diff --git a/components/image/index.js b/components/image/index.js
index 1979ac87..df9c386a 100644
--- a/components/image/index.js
+++ b/components/image/index.js
@@ -1,7 +1,14 @@
-import { MediaPlaceholder, InspectorControls } from '@wordpress/block-editor';
-import { Spinner, FocalPointPicker, PanelBody, Placeholder } from '@wordpress/components';
+import { MediaPlaceholder, InspectorControls, MediaReplaceFlow } from '@wordpress/block-editor';
+import {
+ Spinner,
+ FocalPointPicker,
+ PanelBody,
+ ToolbarButton,
+ Placeholder,
+} from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import PropTypes from 'prop-types';
+import { InlineControlsStyleWrapper } from './styles';
import { useMedia } from '../../hooks/use-media';
@@ -12,6 +19,9 @@ const Image = (props) => {
onSelect,
focalPoint = { x: 0.5, y: 0.5 },
onChangeFocalPoint,
+ hasInlineControls = false,
+ onRemove,
+ isOptional = true,
labels = {},
canEditImage = true,
...rest
@@ -64,7 +74,25 @@ const Image = (props) => {
)}
-
+
+ {hasImage && !!hasInlineControls && (
+
+
+
+ {!!isOptional && (
+
+ {__('Remove')}
+
+ )}
+
+
+ )}
+
+
>
);
};
@@ -75,6 +103,9 @@ Image.defaultProps = {
size: 'large',
focalPoint: { x: 0.5, y: 0.5 },
onChangeFocalPoint: undefined,
+ hasInlineControls: false,
+ onRemove: undefined,
+ isOptional: true,
labels: {},
canEditImage: true,
};
@@ -84,10 +115,13 @@ Image.propTypes = {
size: PropTypes.string,
onSelect: PropTypes.func.isRequired,
onChangeFocalPoint: PropTypes.func,
+ hasInlineControls: PropTypes.bool,
focalPoint: PropTypes.shape({
x: PropTypes.string,
y: PropTypes.string,
}),
+ onRemove: PropTypes.func,
+ isOptional: PropTypes.bool,
labels: PropTypes.shape({
title: PropTypes.string,
instructions: PropTypes.string,
diff --git a/components/image/readme.md b/components/image/readme.md
index 531f85a7..9eec72bf 100644
--- a/components/image/readme.md
+++ b/components/image/readme.md
@@ -45,11 +45,13 @@ function BlockEdit(props) {
| Name | Type | Default | Description |
| ---------- | ----------------- | -------- | -------------------------------------------------------------- |
-| `id` | `number` | `null` | Image ID |
+| `id` | `number` | `null` | Image ID |
| `onSelect` | `Function` | `null` | Callback that gets called with the new image when one is selected |
| `size` | `string` | `large` | Name of the image size to be displayed |
| `focalPoint` | `object` | `{x:0.5,y:0.5}` | Optional focal point object.
| `onChangeFocalPoint` | `function` | `undefined` | Callback that gets called with the new focal point when it changes. (Is required for the FocalPointPicker to appear) |
| `labels` | `object` | `{}` | Pass in an object of labels to be used by the `MediaPlaceholder` component under the hook. Allows the sub properties `title` and `instructions` |
-| `canEditImage` | `boolean` | `true` | whether or not the image can be edited by in the context its getting viewed. Controls whether a placeholder or upload controls should be shown when no image is present |
-| `...rest` | `*` | `null` | any additional attributes you want to pass to the underlying `img` tag |
+| `canEditImage` | `boolean` | `true` | Whether or not the image can be edited by in the context its getting viewed. Controls whether a placeholder or upload controls should be shown when no image is present |
+| `hasInlineControls` | `boolean` | `false` | When `true`, it will display inline media flow controls |
+| `isOptional` | `boolean` | `false` | Wether or not the inline controls' Remove Image button should be shown. ***NOTE:*** it has no effect if `hasInlineControls` is `false` |
+| `...rest` | `*` | `null` | Any additional attributes you want to pass to the underlying `img` tag |
diff --git a/components/image/styles.js b/components/image/styles.js
new file mode 100644
index 00000000..e1f1f815
--- /dev/null
+++ b/components/image/styles.js
@@ -0,0 +1,79 @@
+import styled from '@emotion/styled';
+
+export const InlineControlsStyleWrapper = styled('div')`
+ line-height: 0;
+ position: relative;
+
+ & *,
+ *::before,
+ *::after {
+ box-sizing: border-box;
+ }
+
+ &:hover,
+ &:focus,
+ &:focus-visible,
+ &:focus-within {
+ outline: 1px solid #1e1e1e;
+ outline-offset: -1px;
+
+ & .inline-controls {
+ opacity: 1;
+ pointer-events: all;
+ }
+ }
+
+ & .inline-controls-sticky-wrapper {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ }
+
+ & .inline-controls {
+ border: 1px solid #1e1e1e;
+ border-radius: 2px;
+ display: grid;
+ gap: 1px;
+ grid-auto-flow: column;
+ grid-template-columns: repeat(auto-fit, minmax(36px, 1fr));
+ margin: 10px 10px 10px auto;
+ opacity: 0;
+ overflow: hidden;
+ pointer-events: none;
+ position: sticky;
+ top: 10px;
+ transition: opacity 250ms ease-out;
+ width: max-content;
+
+ & > div:not(:last-child) {
+ border-right: 1px solid #1e1e1e;
+ display: block;
+ min-width: max-content;
+ position: relative;
+ }
+
+ & .components-button {
+ --button-text: inherit;
+ --button-background: var(--wp--preset--color--white);
+ background: var(--button-background);
+ border-radius: 0;
+ color: var(--button-text);
+ height: 46px;
+ outline: 1px solid transparent;
+ padding: 6px 12px;
+ text-decoration: none;
+ white-space: nowrap;
+
+ &:focus:not(.disabled) {
+ outline: var(--wp-admin-theme-color);
+ }
+
+ &:hover:not(.disabled),
+ &:active:not(.disabled) {
+ --button-text: var(--wp-admin-theme-color);
+ }
+ }
+ }
+`;
diff --git a/example/src/blocks/multiple-image-example/block.json b/example/src/blocks/multiple-image-example/block.json
new file mode 100644
index 00000000..aa25e9d2
--- /dev/null
+++ b/example/src/blocks/multiple-image-example/block.json
@@ -0,0 +1,44 @@
+{
+ "name": "example/multiple-image-example",
+ "apiVersion": 2,
+ "title": "Multiple Image Example",
+ "description": "Multiple images block to show the Image with inline controls in usage",
+ "icon": "smiley",
+ "category": "common",
+ "example": {},
+ "supports": {
+ "html": false
+ },
+ "attributes": {
+ "image1": {
+ "type": "number"
+ },
+ "image2": {
+ "type": "number"
+ },
+ "image3": {
+ "type": "number"
+ },
+ "focalPoint1": {
+ "type": "object",
+ "default": {
+ "x": "0.5",
+ "y": "0.5"
+ }
+ },
+ "focalPoint2": {
+ "type": "object",
+ "default": {
+ "x": "0.5",
+ "y": "0.5"
+ }
+ },
+ "focalPoint3": {
+ "type": "object",
+ "default": {
+ "x": "0.5",
+ "y": "0.5"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/example/src/blocks/multiple-image-example/edit.js b/example/src/blocks/multiple-image-example/edit.js
new file mode 100644
index 00000000..38add3dc
--- /dev/null
+++ b/example/src/blocks/multiple-image-example/edit.js
@@ -0,0 +1,50 @@
+import { __ } from '@wordpress/i18n';
+import { useBlockProps } from '@wordpress/block-editor';
+
+import { Image } from '@10up/block-components';
+
+export function BlockEdit(props) {
+ const {
+ attributes,
+ setAttributes
+ } = props;
+
+ const { image1, image2, image3, focalPoint1, focalPoint2, focalPoint3 } = attributes;
+ const blockProps = useBlockProps();
+
+ return (
+
+
+ setAttributes({image1: image.id })}
+ className="example-image"
+ focalPoint={focalPoint1}
+ onChangeFocalPoint={(value) => setAttributes({focalPoint1: value})}
+ hasInlineControls={true} onRemove={() => setAttributes({image1: null})}
+ isOptional={false}
+ />
+
+ setAttributes({image2: image.id })}
+ className="example-image"
+ focalPoint={focalPoint2}
+ onChangeFocalPoint={(value) => setAttributes({focalPoint2: value})}
+ hasInlineControls={true} onRemove={() => setAttributes({image2: null})}
+ />
+
+ setAttributes({image3: image.id })}
+ className="example-image"
+ focalPoint={focalPoint3}
+ onChangeFocalPoint={(value) => setAttributes({focalPoint3: value})}
+ hasInlineControls={true} onRemove={() => setAttributes({image3: null})}
+ />
+
+ )
+}
\ No newline at end of file
diff --git a/example/src/blocks/multiple-image-example/index.js b/example/src/blocks/multiple-image-example/index.js
new file mode 100644
index 00000000..cd17beb4
--- /dev/null
+++ b/example/src/blocks/multiple-image-example/index.js
@@ -0,0 +1,10 @@
+import { registerBlockType } from '@wordpress/blocks';
+import { __ } from '@wordpress/i18n';
+
+import { BlockEdit } from './edit';
+import metadata from './block.json';
+
+registerBlockType( metadata, {
+ edit: BlockEdit,
+ save: () => null
+} );
diff --git a/example/src/index.js b/example/src/index.js
index d0048610..267fc930 100644
--- a/example/src/index.js
+++ b/example/src/index.js
@@ -4,6 +4,7 @@ import './blocks/icon-picker-example';
import './blocks/repeater-component-example';
import './blocks/link-example';
import './blocks/image-example';
+import './blocks/multiple-image-example';
import './blocks/content-search-example';
import './blocks/rich-text-character-limit';
import './blocks/post-title';