Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to show inline media controls for the Image component #172

Closed
wants to merge 12 commits into from
Closed
20 changes: 20 additions & 0 deletions components/image/icons.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const Replace = () => (
<svg id="replace" xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64">
<path d="M59,22H48V5a5.006,5.006,0,0,0-5-5H5A5.006,5.006,0,0,0,0,5V37a5.006,5.006,0,0,0,5,5H16V59a5.006,5.006,0,0,0,5,5H59a5.006,5.006,0,0,0,5-5V27A5.006,5.006,0,0,0,59,22ZM5,40a3,3,0,0,1-3-3V5A3,3,0,0,1,5,2H43a3,3,0,0,1,3,3V22H21a5.006,5.006,0,0,0-5,5V40ZM62,59a3,3,0,0,1-3,3H21a3,3,0,0,1-3-3V27a3,3,0,0,1,3-3H59a3,3,0,0,1,3,3Z" />
<path d="M52,10h4a1,1,0,0,1,1,1v3.586l-.293-.293a1,1,0,0,0-1.414,1.414l2,2a1,1,0,0,0,1.416,0l2-2a1,1,0,0,0-1.414-1.414L59,14.586V11a3,3,0,0,0-3-3H52a1,1,0,0,0,0,2Z" />
<path d="M12,54H8a1,1,0,0,1-1-1V49.414l.293.293a1,1,0,0,0,1.414-1.414l-2-2a.99.99,0,0,0-.326-.217,1,1,0,0,0-1.09.217l-2,2a1,1,0,0,0,1.414,1.414L5,49.414V53a3,3,0,0,0,3,3h4a1,1,0,0,0,0-2Z" />
<path d="M48.3,36.345a5,5,0,0,0-9.062,0L38.279,38.4a4.99,4.99,0,0,0-7.984,1.313L22.32,55.659A3,3,0,0,0,25,60H54.625a3,3,0,0,0,2.719-4.269ZM25,58a1,1,0,0,1-.894-1.448L32.083,40.6a3,3,0,0,1,5.366,0l8.7,17.4Zm30.468-.463a.984.984,0,0,1-.844.463H48.385L39.567,40.364l1.481-3.174a3,3,0,0,1,5.438,0l9.047,19.387a.984.984,0,0,1-.063.96Z" />
<path d="M32.767,34a4,4,0,1,0-4-4A4,4,0,0,0,32.767,34Zm0-6a2,2,0,1,1-2,2A2,2,0,0,1,32.767,28Z" />
</svg>
);

const Remove = () => (
<svg xmlns="http://www.w3.org/2000/svg" width="44" height="44" viewBox="0 0 44 44">
<path
d="M175.976,132.887a1.524,1.524,0,0,0-2.154,0l-19.39,19.382-19.39-19.382A1.523,1.523,0,0,0,132.849,135l.038.038,19.39,19.382L132.887,173.8A1.523,1.523,0,1,0,135,175.994l.037-.037,19.39-19.382,19.39,19.382a1.523,1.523,0,0,0,2.154-2.153l-19.39-19.382,19.39-19.382A1.522,1.522,0,0,0,175.976,132.887Z"
transform="translate(-132.422 -132.422)"
/>
</svg>
);

export { Replace, Remove };
47 changes: 44 additions & 3 deletions components/image/index.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,40 @@
import { MediaPlaceholder, InspectorControls } from '@wordpress/block-editor';
import { Spinner, FocalPointPicker, PanelBody } from '@wordpress/components';
import { MediaPlaceholder, InspectorControls, MediaReplaceFlow } from '@wordpress/block-editor';
import { Spinner, FocalPointPicker, PanelBody, ToolbarButton } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import PropTypes from 'prop-types';
import { InlineControlsStyleWrapper } from './styles';
import { Replace, Remove } from './icons';

import { useMedia } from '../../hooks/use-media';

const ReplaceButton = () => {
return (
<>
<span className="screen-reader-text">{__('Replace Image')}</span>
<Replace />
</>
);
};

const RemoveButton = (onRemove) => {
return (
<ToolbarButton onClick={onRemove} className="remove-button">
<span className="screen-reader-text">{__('Remove Image')}</span>
<Remove />
</ToolbarButton>
);
};

const Image = (props) => {
const {
id,
size = 'full',
onSelect,
focalPoint = undefined,
onChangeFocalPoint,
hasInlineControls = false,
onRemove,
isOptional = true,
...rest
} = props;
const hasImage = !!id;
Expand Down Expand Up @@ -61,7 +84,19 @@ const Image = (props) => {
</PanelBody>
</InspectorControls>
)}
<img src={imageUrl} alt={altText} {...rest} />
<InlineControlsStyleWrapper className="inline-controls-wrapper">
{hasImage && !!hasInlineControls && (
<div className="inline-controls">
<MediaReplaceFlow
mediaUrl={imageUrl}
onSelect={onSelect}
name={<ReplaceButton />}
/>
{!!isOptional && <RemoveButton onRemove={onRemove} />}
</div>
)}
<img src={imageUrl} alt={altText} {...rest} />
</InlineControlsStyleWrapper>
</>
);
};
Expand All @@ -72,15 +107,21 @@ Image.defaultProps = {
size: 'large',
focalPoint: undefined,
onChangeFocalPoint: undefined,
hasInlineControls: false,
onRemove: undefined,
isOptional: true,
};

Image.propTypes = {
id: PropTypes.number.isRequired,
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,
};
19 changes: 11 additions & 8 deletions components/image/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,14 @@ function BlockEdit(props) {

## Props

| Name | Type | Default | Description |
| ---------- | ----------------- | -------- | -------------------------------------------------------------- |
| `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` | `undefined` | optional focal point object |
| `onChangeFocalPoint` | `function` | `undefined` | Callback that gets called with the new focal point when it changes |
| `...rest` | `*` | `null` | any additional attributes you want to pass to the underlying `img` tag |
| Name | Type | Default | Required | Description |
| ---------- | ----------------- | -------- | -------- |-------------------------------------------------------------- |
| `id` | `number` | `null` | Yes | image id |
| `onSelect` | `function` | `null` | Yes | Callback that gets called with the new image when one is selected |
| `size` | `string` | `large` | No | name of the image size to be displayed |
| `focalPoint` | `object` | `undefined` | No | optional focal point object |
| `onChangeFocalPoint` | `function` | `undefined` | No | Callback that gets called with the new focal point when it changes |
| `hasInlineControls` | `boolean` | `false` | No | When `true`, it will display inline media flow controls |
| `onRemove` | `function` | `undefined` | No | Callback that gets called and passed to the Remove Image button inside the inline controls. ***NOTE:*** it has no effect if `hasInlineControls` is `false` |
| `isOptional` | `boolean` | `false` | No | Wether or not the inline controls' Remove Image button should be shown. ***NOTE:*** it has no effect if `hasInlineControls` is `false` |
| `...rest` | `*` | `null` | No | any additional attributes you want to pass to the underlying `img` tag |
82 changes: 82 additions & 0 deletions components/image/styles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import styled from '@emotion/styled';

export const InlineControlsStyleWrapper = styled('div')`
line-height: 0;
position: relative;

&:hover,
&:focus,
&:focus-visible,
&:focus-within {
& .inline-controls {
opacity: 1;
pointer-events: all;
}
}

& .inline-controls {
position: absolute;
top: 0;
left: 0;
display: flex;
flex-direction: row;
gap: 5px;
width: 100%;
height: 100%;
justify-content: center;
align-items: center;
opacity: 0;
pointer-events: none;
transition: opacity 250ms ease-out;

&::after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 0;
}

& > * {
z-index: 1;
}

& .components-button {
--button-text: #fff;
--button-background: var(--wp-admin-theme-color);
white-space: nowrap;
background: var(--button-background);
color: var(--button-text);
text-decoration: none;
text-shadow: none;
outline: 1px solid transparent;
width: 60px;
height: 60px;
border-radius: 50%;
padding: 12px;
border: 1px solid var(--button-background);

& svg {
width: 100%;
height: 100%;
}

&:focus:not(.disabled) {
outline: var(--wp-admin-theme-color);
}

&:hover:not(.disabled),
&:active:not(.disabled) {
--button-text: #fff;
--button-background: var(--wp-admin-theme-color-darker-10);
}

&.remove-button {
padding: 18px;
}
}
}
`;
44 changes: 44 additions & 0 deletions example/src/blocks/multiple-image-example/block.json
Original file line number Diff line number Diff line change
@@ -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
}
}
}
}
25 changes: 25 additions & 0 deletions example/src/blocks/multiple-image-example/edit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
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 (
<div {...blockProps}>

<Image id={image1} size="large" onSelect={(image) => setAttributes({image1: image.id })} className="example-image" focalPoint={focalPoint1} onChangeFocalPoint={(value) => setAttributes({focalPoint1: value})} hasInlineControls={true} onRemove={() => setAttributes({image1: null})} isOptional={false} />
ncoetzer marked this conversation as resolved.
Show resolved Hide resolved

<Image id={image2} size="large" onSelect={(image) => setAttributes({image2: image.id })} className="example-image" focalPoint={focalPoint2} onChangeFocalPoint={(value) => setAttributes({focalPoint2: value})} hasInlineControls={true} onRemove={() => setAttributes({image2: null})} />

<Image id={image3} size="large" onSelect={(image) => setAttributes({image3: image.id })} className="example-image" focalPoint={focalPoint3} onChangeFocalPoint={(value) => setAttributes({focalPoint3: value})} hasInlineControls={true} onRemove={() => setAttributes({image3: null})} />
</div>
)
}
10 changes: 10 additions & 0 deletions example/src/blocks/multiple-image-example/index.js
Original file line number Diff line number Diff line change
@@ -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
} );
1 change: 1 addition & 0 deletions example/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ 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/rich-text-character-limit';