Skip to content

Commit

Permalink
Merge pull request #269 from 10up/feature/include-figure-in-image-com…
Browse files Browse the repository at this point in the history
…ponent-inline-controls

Add inline media controls for the Image component
  • Loading branch information
fabiankaegy authored Jan 29, 2024
2 parents 170f057 + df92c33 commit e845203
Show file tree
Hide file tree
Showing 11 changed files with 395 additions and 85 deletions.
25 changes: 25 additions & 0 deletions components/image/child-components/figure.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import PropTypes from 'prop-types';
import { StyledComponentContext } from '../../styled-components-context';
import { InlineControlsStyleWrapper } from '../styles';

export const Figure = (props) => {
const { style, children, ...rest } = props;

return (
<StyledComponentContext cacheKey="tenup-component-image">
<InlineControlsStyleWrapper style={{ ...style }} {...rest}>
{children}
</InlineControlsStyleWrapper>
</StyledComponentContext>
);
};

Figure.defaultProps = {
style: {},
children: undefined,
};

Figure.propTypes = {
style: PropTypes.object,
children: PropTypes.node,
};
5 changes: 5 additions & 0 deletions components/image/child-components/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Media, ImageContext } from './media';
import { Figure } from './figure';
import { InlineControls } from './inline-controls';

export { Media, ImageContext, Figure, InlineControls };
39 changes: 39 additions & 0 deletions components/image/child-components/inline-controls.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { __ } from '@wordpress/i18n';
import { MediaReplaceFlow } from '@wordpress/block-editor';
import { ToolbarButton } from '@wordpress/components';
import PropTypes from 'prop-types';

/**
* Internal Dependencies
*/

export const InlineControls = (props) => {
const { imageUrl, onSelect, isOptional, onRemove } = props;

return (
<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>
);
};

InlineControls.defaultProps = {
imageUrl: '',
onSelect: undefined,
isOptional: false,
onRemove: undefined,
};

InlineControls.propTypes = {
imageUrl: PropTypes.string,
onSelect: PropTypes.func,
isOptional: PropTypes.bool,
onRemove: PropTypes.func,
};
82 changes: 82 additions & 0 deletions components/image/child-components/media.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import PropTypes from 'prop-types';
import { useContext, createContext } from '@wordpress/element';
import { MediaPlaceholder, InspectorControls } from '@wordpress/block-editor';
import { Spinner, FocalPointPicker, PanelBody, Placeholder } from '@wordpress/components';
import { __ } from '@wordpress/i18n';

export const ImageContext = createContext();

export const Media = (props) => {
const { style, ...rest } = props;
const {
imageUrl,
altText,
labels,
onSelect,
isResolvingMedia,
shouldDisplayFocalPointPicker,
focalPoint,
onChangeFocalPoint,
canEditImage,
hasImage,
} = useContext(ImageContext);

let focalPointStyle = {};

if (shouldDisplayFocalPointPicker) {
focalPointStyle = {
objectFit: 'cover',
objectPosition: `${focalPoint.x * 100}% ${focalPoint.y * 100}%`,
};
}

if (isResolvingMedia) {
return <Spinner />;
}

if (!hasImage && !canEditImage) {
return <Placeholder className="block-editor-media-placeholder" withIllustration />;
}

return (
<>
{shouldDisplayFocalPointPicker && (
<InspectorControls>
<PanelBody title={__('Image Settings')}>
<FocalPointPicker
label={__('Focal Point Picker')}
url={imageUrl}
value={focalPoint}
onChange={onChangeFocalPoint}
/>
</PanelBody>
</InspectorControls>
)}
{hasImage && (
<img
src={imageUrl}
alt={altText}
style={{ ...style, ...focalPointStyle }}
{...rest}
/>
)}
{canEditImage && (
<MediaPlaceholder
labels={labels}
onSelect={onSelect}
accept="image"
multiple={false}
disableMediaButtons={imageUrl}
/>
)}
</>
);
};

Media.defaultProps = {
style: {},
};

Media.propTypes = {
style: PropTypes.object,
};
126 changes: 44 additions & 82 deletions components/image/index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { MediaPlaceholder, InspectorControls } from '@wordpress/block-editor';
import { useContext, useMemo, Children, createContext } from '@wordpress/element';
import { Spinner, FocalPointPicker, PanelBody, Placeholder } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { MediaPlaceholder } from '@wordpress/block-editor';
import { useMemo, Children } from '@wordpress/element';
import PropTypes from 'prop-types';

/**
* Internal Dependencies
*/
import { Media, ImageContext, Figure, InlineControls } from './child-components';
import { useMedia } from '../../hooks/use-media';

export const ImageContext = createContext();

const ImageWrapper = (props) => {
const {
id,
Expand All @@ -18,6 +18,10 @@ const ImageWrapper = (props) => {
labels = {},
canEditImage = true,
children,
hasInlineControls = false,
isOptional = true,
onRemove,
style,
...rest
} = props;
const hasImage = !!id;
Expand Down Expand Up @@ -45,6 +49,9 @@ const ImageWrapper = (props) => {
isResolvingMedia,
shouldDisplayFocalPointPicker,
hasImage,
hasInlineControls,
isOptional,
onRemove,
};
}, [
id,
Expand All @@ -59,6 +66,9 @@ const ImageWrapper = (props) => {
isResolvingMedia,
shouldDisplayFocalPointPicker,
hasImage,
hasInlineControls,
isOptional,
onRemove,
]);

if (hasRenderCallback) {
Expand All @@ -70,6 +80,9 @@ const ImageWrapper = (props) => {
labels,
canEditImage,
onSelect,
hasInlineControls,
isOptional,
onRemove,
});
}

Expand All @@ -91,13 +104,25 @@ const ImageWrapper = (props) => {

return (
<ImageContext.Provider value={imageContext}>
<Figure>
<Image {...rest} />
</Figure>
{hasImage && !!hasInlineControls ? (
<Figure style={{ ...style }} {...rest}>
<Media />
<InlineControls
imageUrl={imageUrl}
onSelect={onSelect}
isOptional={isOptional}
onRemove={onRemove}
/>
</Figure>
) : (
<Media style={{ display: 'block', ...style }} {...rest} />
)}
</ImageContext.Provider>
);
};

ImageWrapper.Figure = Figure;

export { ImageWrapper as Image };

ImageWrapper.defaultProps = {
Expand All @@ -106,6 +131,11 @@ ImageWrapper.defaultProps = {
onChangeFocalPoint: undefined,
labels: {},
canEditImage: true,
hasInlineControls: false,
isOptional: true,
onRemove: undefined,
children: undefined,
style: {},
};

ImageWrapper.propTypes = {
Expand All @@ -122,77 +152,9 @@ ImageWrapper.propTypes = {
instructions: PropTypes.string,
}),
canEditImage: PropTypes.bool,
};

const Figure = (props) => {
const { children, style, ...rest } = props;

return (
<figure style={{ position: 'relative', ...style }} {...rest}>
{children}
</figure>
);
};

const Image = (props) => {
const { style } = props;
const {
imageUrl,
altText,
labels,
onSelect,
isResolvingMedia,
shouldDisplayFocalPointPicker,
focalPoint,
onChangeFocalPoint,
canEditImage,
hasImage,
} = useContext(ImageContext);

if (shouldDisplayFocalPointPicker) {
const focalPointStyle = {
objectFit: 'cover',
objectPosition: `${focalPoint.x * 100}% ${focalPoint.y * 100}%`,
};

props.style = {
...style,
...focalPointStyle,
};
}

if (isResolvingMedia) {
return <Spinner />;
}

if (!hasImage && !canEditImage) {
return <Placeholder className="block-editor-media-placeholder" withIllustration />;
}

return (
<>
{shouldDisplayFocalPointPicker && (
<InspectorControls>
<PanelBody title={__('Image Settings')}>
<FocalPointPicker
label={__('Focal Point Picker')}
url={imageUrl}
value={focalPoint}
onChange={onChangeFocalPoint}
/>
</PanelBody>
</InspectorControls>
)}
{hasImage && <img src={imageUrl} alt={altText} {...props} />}
{canEditImage && (
<MediaPlaceholder
labels={labels}
onSelect={onSelect}
accept="image"
multiple={false}
disableMediaButtons={imageUrl}
/>
)}
</>
);
hasInlineControls: PropTypes.bool,
isOptional: PropTypes.bool,
onRemove: PropTypes.func,
children: PropTypes.node,
style: PropTypes.object,
};
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 |
Loading

0 comments on commit e845203

Please sign in to comment.