wix-style-react
Version:
wix-style-react
280 lines • 15.1 kB
JavaScript
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Delete, Replace, Download, More } from '@wix/wix-ui-icons-common';
import StatusIndicator from '../StatusIndicator';
import Loader from '../Loader';
import { st, classes } from './ImageViewer.st.css';
import Tooltip from '../Tooltip';
import IconButton from '../IconButton';
import AddItem from '../AddItem/AddItem';
import Box from '../Box';
import PopoverMenu from '../PopoverMenu';
import classnames from 'classnames';
import { dataHooks } from './constants';
import { TooltipCommonProps } from '../common/PropTypes/TooltipCommon';
import { StatusContext, getStatusFromContext, } from '../FormField/StatusContext';
class ImageViewer extends Component {
constructor(props) {
super(props);
this._renderAddImage = () => {
const { onAddImage, addImageInfo, tooltipProps = {}, disabled, } = this.props;
return (React.createElement(AddItem, { ref: this.focusNode, onClick: onAddImage, theme: "image", dataHook: dataHooks.addItem, disabled: disabled, tooltipProps: { ...tooltipProps, content: addImageInfo }, ariaLabel: addImageInfo }));
};
/** `display: none` is used to prefetch an image == it fetches the image but doesn't show it */
this._renderImageElement = ({ imageUrl, shouldDisplay, onLoad, onError, key, dataHook, }) => {
const dataAttributes = {
'data-hook': dataHook,
'data-image-visible': shouldDisplay,
};
return (React.createElement("img", { className: classnames([
classes.image,
classes.stretch,
shouldDisplay && classes.imageVisible,
]), src: imageUrl, onLoad: onLoad, onError: onError, key: key, ...dataAttributes }));
};
this._resetImageLoading = () => {
this.setState({
imageLoading: false,
});
};
this._onImageLoad = e => {
const { onImageLoad } = this.props;
this.setState({
imageLoading: false,
}, () => onImageLoad(e));
};
this._getCurrentAndPreviousImages = () => {
const { imageUrl: currentImageUrl } = this.props;
const { previousImageUrl } = this.state;
return {
currentImageUrl,
previousImageUrl,
};
};
this._renderImage = () => {
const { imageLoading } = this.state;
if (!this.props.imageUrl) {
return;
}
const { currentImageUrl, previousImageUrl } = this._getCurrentAndPreviousImages();
const shouldDisplayContainer = !!(currentImageUrl || previousImageUrl);
const generateKey = (imageName, imageUrl) => `${imageName}-${imageUrl}`;
return (React.createElement("div", { className: st(classes.imageContainer, {
/** hide container when no image provided, so AddItem behind it can be clickable */
shouldDisplay: shouldDisplayContainer,
}), "data-container-visible": shouldDisplayContainer, "data-hook": dataHooks.imagesContainer },
this._renderImageElement({
imageUrl: currentImageUrl,
shouldDisplay: !!currentImageUrl && !imageLoading,
onLoad: this._onImageLoad,
onError: () => {
this._resetImageLoading();
},
dataHook: dataHooks.image,
key: generateKey(dataHooks.image, currentImageUrl),
}),
this._renderImageElement({
imageUrl: previousImageUrl,
shouldDisplay: imageLoading && !!previousImageUrl,
dataHook: dataHooks.previousImage,
key: generateKey(dataHooks.previousImage, previousImageUrl),
})));
};
this._renderUpdateButton = ref => {
const { updateImageInfo, onUpdateImage, tooltipProps } = this.props;
return (React.createElement(Tooltip, { ...tooltipProps, timeout: 0, dataHook: dataHooks.updateTooltip, content: updateImageInfo },
React.createElement(IconButton, { ref: ref, dataHook: dataHooks.update, onClick: onUpdateImage, skin: "light", priority: "secondary" },
React.createElement(Replace, null))));
};
this._resetPreviousImage = () => this.setState({ previousImageUrl: undefined });
this._renderRemoveButton = ref => {
const { removeImageInfo, onRemoveImage, tooltipProps } = this.props;
return (React.createElement(Tooltip, { ...tooltipProps, timeout: 0, dataHook: dataHooks.removeTooltip, content: removeImageInfo },
React.createElement(IconButton, { ref: ref, dataHook: dataHooks.remove, skin: "light", priority: "secondary", onClick: e => {
this._resetPreviousImage();
onRemoveImage && onRemoveImage(e);
} },
React.createElement(Delete, null))));
};
this._renderDownloadButton = ref => {
const { downloadImageInfo, onDownloadImage, tooltipProps } = this.props;
return (React.createElement(Tooltip, { ...tooltipProps, timeout: 0, dataHook: dataHooks.downloadTooltip, content: downloadImageInfo },
React.createElement(IconButton, { ref: ref, dataHook: dataHooks.download, skin: "light", priority: "secondary", onClick: e => {
onDownloadImage && onDownloadImage(e);
} },
React.createElement(Download, null))));
};
this._hidePopover = () => this.setState({ popoverOpen: false });
this._showPopover = () => this.setState({ popoverOpen: true });
this._renderMoreButton = () => {
const { tooltipProps, moreImageInfo, downloadImageInfo, onDownloadImage, removeImageInfo, onRemoveImage, } = this.props;
return (React.createElement(PopoverMenu, { dataHook: dataHooks.actionsMenu, onHide: this._hidePopover, onShow: this._showPopover, triggerElement: ({ toggle }) => (React.createElement(Tooltip, { ...tooltipProps, timeout: 0, dataHook: dataHooks.moreTooltip, content: moreImageInfo },
React.createElement(IconButton, { onClick: toggle, dataHook: dataHooks.more, skin: "light", priority: this.state.popoverOpen ? 'primary' : 'secondary' },
React.createElement(More, null)))) },
React.createElement(PopoverMenu.MenuItem, { prefixIcon: React.createElement(Download, null), text: downloadImageInfo, onClick: onDownloadImage }),
React.createElement(PopoverMenu.MenuItem, { prefixIcon: React.createElement(Delete, null), text: removeImageInfo, onClick: onRemoveImage })));
};
this._renderFirstButton = () => {
const { showUpdateButton, showRemoveButton, showDownloadButton } = this.props;
if (showUpdateButton)
return this._renderUpdateButton(this.focusNode);
if (showDownloadButton)
return this._renderDownloadButton(this.focusNode);
if (showRemoveButton)
return this._renderRemoveButton(this.focusNode);
return null;
};
this._renderSecondButton = () => {
const { showUpdateButton, showRemoveButton, showDownloadButton } = this.props;
// All three options - show more button
if (showUpdateButton && showDownloadButton && showRemoveButton) {
return this._renderMoreButton();
}
// Two options - show second button
if (showUpdateButton && showRemoveButton)
return this._renderRemoveButton();
if (showUpdateButton && showDownloadButton)
return this._renderDownloadButton();
if (showDownloadButton && showRemoveButton)
return this._renderRemoveButton();
return null;
};
this._renderLoader = () => (React.createElement(Box, { align: "center", verticalAlign: "middle", height: "100%", dataHook: dataHooks.loader },
React.createElement(Loader, { size: "small" })));
this._renderButtons = () => {
return (React.createElement("div", { className: classes.buttons },
this._renderFirstButton(),
this._renderSecondButton()));
};
this._renderOverlayWith = content => {
const { removeRoundedBorders } = this.props;
const { currentImageUrl, previousImageUrl } = this._getCurrentAndPreviousImages();
const shouldDisplayOverlay = !!(currentImageUrl || previousImageUrl);
return (React.createElement("div", { className: st(classes.overlay, {
removeRadius: removeRoundedBorders,
shouldDisplay: shouldDisplayOverlay,
}), "data-remove-radius": removeRoundedBorders, "data-hook": dataHooks.overlay },
content,
React.createElement("span", null)));
};
/**
* Sets focus on the element
*/
this.focus = () => {
this.focusNode.current && this.focusNode.current.focus();
};
const { imageUrl } = props;
this.focusNode = React.createRef();
this.state = {
imageLoading: !!imageUrl,
previousImageUrl: undefined,
popoverOpen: false,
};
}
UNSAFE_componentWillReceiveProps(nextProps) {
const { imageUrl: currentImageUrl } = this.props;
const { imageUrl: nextImageUrl } = nextProps;
if (nextImageUrl && currentImageUrl !== nextImageUrl) {
this.setState({
imageLoading: true,
previousImageUrl: currentImageUrl,
});
}
}
render() {
const { width, height, disabled, dataHook, removeRoundedBorders, imageUrl, status, statusMessage, className, } = this.props;
const { imageLoading, previousImageUrl, popoverOpen } = this.state;
const finalStatus = getStatusFromContext(this.context, status);
const hasImage = !!imageUrl;
const hasNoPreviousImageWhileLoading = imageLoading && !previousImageUrl;
const imageLoaded = hasImage && !imageLoading;
const cssStates = {
disabled,
status: !disabled && finalStatus,
removeRadius: removeRoundedBorders,
hasImage,
popoverOpen,
};
const rootDataAttributes = {
'data-disabled': disabled,
'data-image-loaded': imageLoaded,
'data-hook': dataHook,
};
return (React.createElement("div", { className: st(classes.root, cssStates, className), style: { width, height }, ...rootDataAttributes },
(hasNoPreviousImageWhileLoading || !hasImage) &&
this._renderAddImage(),
this._renderImage(),
!disabled &&
this._renderOverlayWith(imageLoading
? this._renderLoader()
: hasImage && this._renderButtons()),
(status || finalStatus === 'loading') && !disabled && (React.createElement("div", { className: classes.statusContainer },
React.createElement(StatusIndicator, { status: finalStatus, message: statusMessage, dataHook: dataHooks.errorTooltip })))));
}
}
ImageViewer.contextType = StatusContext;
ImageViewer.displayName = 'ImageViewer';
ImageViewer.defaultProps = {
showUpdateButton: true,
showDownloadButton: false,
showRemoveButton: true,
addImageInfo: 'Add Image',
updateImageInfo: 'Update',
downloadImageInfo: 'Download',
removeImageInfo: 'Remove',
moreImageInfo: 'More actions',
onImageLoad: () => ({}),
};
ImageViewer.propTypes = {
/** Applies a data-hook HTML attribute that can be used in the tests. */
dataHook: PropTypes.string,
/** Specifies a CSS class name to be appended to the component’s root element. */
className: PropTypes.string,
/** Links to image asset source (URL). Leave it blank when image is not uploaded yet. */
imageUrl: PropTypes.string,
/** Specifies the status of a viewer. */
status: PropTypes.oneOf(['error', 'warning', 'loading']),
/** Defines the message to display on status icon hover. If not given or empty there will be no tooltip. */
statusMessage: PropTypes.node,
/** Allows to pass all common tooltip props.
* @linkTypeTo components-overlays--tooltip
* @setTypeName TooltipCommonProps
*/
tooltipProps: PropTypes.shape(TooltipCommonProps),
/** Specifies whether the update button is visible. */
showUpdateButton: PropTypes.bool,
/** Specifies whether the download button is visible. */
showDownloadButton: PropTypes.bool,
/** Specifies whether the remove button is visible. */
showRemoveButton: PropTypes.bool,
/** Defines a click handler, which is called every time a user clicks on an empty viewer (when no `imageUrl` is provided). */
onAddImage: PropTypes.func,
/** Defines a handler function, which is called every time user clicks on the ‘Update image’ button. */
onUpdateImage: PropTypes.func,
/** Defines a handler function, which is called every time user clicks on the download button. */
onDownloadImage: PropTypes.func,
/** Defines a handler function, which is called every time user clicks on the ‘Remove image’ button. */
onRemoveImage: PropTypes.func,
/** Defines a handler function which is called right after image loads. */
onImageLoad: PropTypes.func,
/** Specifies a message to display in a tooltip when no image is uploaded yet. */
addImageInfo: PropTypes.string,
/** Defines a message to display in a tooltip when ‘Update’ action button is hovered. */
updateImageInfo: PropTypes.string,
/** Defines a message to display in a tooltip when ‘Download’ action button is hovered. */
downloadImageInfo: PropTypes.string,
/** Defines a message to display in a tooltip, when ‘remove’ action button is hovered. */
removeImageInfo: PropTypes.string,
/** Defines a message to display in a tooltip when the ‘More’ action button is hovered. Relevant only when all buttons are visible. */
moreImageInfo: PropTypes.string,
/** Removes default border radius. */
removeRoundedBorders: PropTypes.bool,
/** Sets the width of the viewer box. */
width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
/** Sets the height of the viewer box. */
height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
/** Specifies whether the component is disabled. */
disabled: PropTypes.bool,
};
export default ImageViewer;
//# sourceMappingURL=ImageViewer.js.map