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
40 changes: 37 additions & 3 deletions components/image/index.js
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -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
Expand Down Expand Up @@ -64,7 +74,25 @@ const Image = (props) => {
</PanelBody>
</InspectorControls>
)}
<img src={imageUrl} alt={altText} {...rest} />
<InlineControlsStyleWrapper className="inline-controls-wrapper">
{hasImage && !!hasInlineControls && (
<div className="inline-controls-sticky-wrapper">
<div className="inline-controls">
<MediaReplaceFlow
mediaUrl={imageUrl}
onSelect={onSelect}
name={__('Replace')}
/>
{!!isOptional && (
<ToolbarButton onClick={onRemove} className="remove-button">
{__('Remove')}
</ToolbarButton>
)}
</div>
</div>
)}
<img src={imageUrl} alt={altText} {...rest} />
</InlineControlsStyleWrapper>
</>
);
};
Expand All @@ -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,
};
Expand All @@ -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,
Expand Down
8 changes: 5 additions & 3 deletions components/image/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
79 changes: 79 additions & 0 deletions components/image/styles.js
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
`;
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"
}
}
}
}
50 changes: 50 additions & 0 deletions example/src/blocks/multiple-image-example/edit.js
Original file line number Diff line number Diff line change
@@ -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 (
<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}
/>

<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,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';
Expand Down
Loading