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) => { )} - {altText} + + {hasImage && !!hasInlineControls && ( +
+
+ + {!!isOptional && ( + + {__('Remove')} + + )} +
+
+ )} + {altText} +
); }; @@ -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';