UNPKG

wix-style-react

Version:
280 lines 15.1 kB
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