UNPKG

@wix/design-system

Version:

@wix/design-system

209 lines 9.65 kB
import React, { createRef, lazy } from 'react'; import CSSTransition from 'react-transition-group/CSSTransition'; import { withFocusable } from '../common/Focusable'; import Content from './Content'; import DragHandle from './DragHandle'; import { Layer, Skin, Placement, Visible } from './constants'; import { st, classes } from './MediaOverlay.st.css.js'; import IconButton from '../IconButton'; import { PauseFilled, PlayFilled } from '@wix/wix-ui-icons-common'; const layerToVisiblePropMap = { [Layer.Default]: Visible.Default, [Layer.Hover]: Visible.Hover, [Layer.Top]: Visible.Always, }; const placementToRowAlignment = { [Placement.TopStart]: 'top', [Placement.TopEnd]: 'top', [Placement.Middle]: 'middle', [Placement.BottomStart]: 'bottom', [Placement.BottomEnd]: 'bottom', }; /** MediaOverlay */ class MediaOverlay extends React.PureComponent { constructor() { super(...arguments); this.state = { isHovered: false, isFocused: false, isVideoPlaying: false, }; this.videoRef = createRef(); this._onMouseEnter = () => { if (this.props.hovered === undefined) { this.setState({ isHovered: true }); } }; this._onMouseLeave = () => { if (this.props.hovered === undefined) { this.setState({ isHovered: false }); } }; this._onFocus = () => { this.setState({ isFocused: true }); if (this.props.focusableOnFocus) { this.props.focusableOnFocus(); } }; this._onBlur = () => { this.setState({ isFocused: false }); if (this.props.focusableOnBlur) { this.props.focusableOnBlur(); } }; this._handleVideoPlay = () => { this.setState({ isVideoPlaying: true }); }; this._handleVideoPause = () => { this.setState({ isVideoPlaying: false }); }; this._handleVideoEnd = () => { this.setState({ isVideoPlaying: false }); }; this._toggleVideoPlay = event => { event.stopPropagation(); const video = this.videoRef.current; if (!video) return; if (this.state.isVideoPlaying) { video.pause(); } else { const playPromise = video.play(); if (playPromise !== undefined) { playPromise.catch(error => { console.warn('Video play was prevented:', error); }); } } }; this._getFocusProps = () => { const { onClick, ariaHidden } = this.props; if (onClick) { return { onFocus: this._onFocus, onBlur: this._onBlur, tabIndex: ariaHidden ? -1 : 0, }; } return { tabIndex: -1, onFocus: this._onFocus, onBlur: this._onBlur, }; }; this._getHoverSkin = () => { const { skin, hoverSkin } = this.props; return hoverSkin || skin; // hoverSkin defaults to skin prop value if not provided }; this._hasSingleSkin = () => this.props.skin === this._getHoverSkin(); this._filterContent = layer => { const { children } = this.props; const contentElements = React.Children.map(children, child => child) || []; const filterProps = { visible: layerToVisiblePropMap[layer] }; return contentElements.filter(child => React.isValidElement(child) && child.type.displayName === Content.displayName && Object.keys(filterProps).every(prop => filterProps[prop] === child.props[prop])); }; this._isContentEmpty = layer => !this._filterContent(layer).length; this._renderDefaultLayer = () => { const skin = this._hasSingleSkin() ? Skin.None : this.props.skin; return this._renderTransitionOverlay(Layer.Default, skin); }; this._renderHoverLayer = () => { const skin = this._hasSingleSkin() ? Skin.None : this._getHoverSkin(); return this._renderTransitionOverlay(Layer.Hover, skin); }; this._renderTopLayer = () => this._renderOverlay(Layer.Top, Skin.None); // When both skins for default and hover layers are the same - we don't want to // animate them with opacity transition as this will produce an undesired effect // (but we still want to animate all the content inside an overlay layer). As a // workaround we create this background layer that has no content or animations // and will render here only the common skin background. this._renderSingleSkinLayer = () => this._hasSingleSkin() && this._renderOverlay(Layer.SingleSkin, this.props.skin); this._shouldRenderOverlay = (layer, skin) => skin !== Skin.None || !this._isContentEmpty(layer); this._renderOverlay = (layer, skin) => { if (!this._shouldRenderOverlay(layer, skin)) { return; } return (React.createElement("div", { className: st(classes.overlay, { layer, skin }) }, this._renderContent(layer))); }; this._getIsVisible = () => { const { hovered } = this.props; return hovered !== undefined ? hovered : this.state.isHovered || this.state.isFocused; }; this._renderTransitionOverlay = (layer, skin) => { if (!this._shouldRenderOverlay(layer, skin)) { return; } const isVisible = this._getIsVisible(); const transitionProps = { in: isVisible, timeout: 200, classNames: { enter: classes.hoverEnter, enterActive: classes.hoverEnterActive, enterDone: classes.hoverEnterDone, exit: classes.hoverExit, }, }; return (React.createElement(CSSTransition, { ...transitionProps }, this._renderOverlay(layer, skin))); }; this._renderContent = layer => { if (this._isContentEmpty(layer)) { return; } const contentElements = this._filterContent(layer); if (!contentElements.length) { return; } return contentElements.map(({ props }, index) => (React.createElement("div", { key: index, className: st(classes.contentRow, { placement: props.placement, row: placementToRowAlignment[props.placement], }) }, React.createElement("div", { className: st(classes.contentArea, { placement: props.placement }), "data-hook": "content-area" }, props.children)))); }; this._renderVideoElement = () => { const { videoSrc, videoProps = {} } = this.props; return (React.createElement("video", { "data-hook": "media-overlay-video", ref: this.videoRef, src: videoSrc, onPlay: this._handleVideoPlay, onPause: this._handleVideoPause, onEnded: this._handleVideoEnd, className: classes.video, playsInline: true, ...videoProps })); }; this._renderPlayButton = () => { const { showPlayButton } = this.props; const { isVideoPlaying } = this.state; if (!showPlayButton) { return null; } return (React.createElement("div", { className: classes.playButtonContainer }, React.createElement("div", { className: classes.playButtonWrapper }, React.createElement(IconButton, { size: "large", priority: "secondary", skin: "inverted", onClick: this._toggleVideoPlay, dataHook: "media-overlay-play-button" }, isVideoPlaying ? React.createElement(PauseFilled, null) : React.createElement(PlayFilled, null))))); }; } render() { const { dataHook, skin, media, onClick, removeRoundedBorders, className, borderRadius, ariaHidden, isVideo, } = this.props; const isMediaImageUrl = typeof media === 'string'; const Component = onClick ? 'button' : 'div'; return (React.createElement(Component, { "aria-hidden": ariaHidden, "data-hook": dataHook, onMouseEnter: this._onMouseEnter, onMouseLeave: this._onMouseLeave, onClick: onClick, ...this._getFocusProps(), className: st(classes.root, { clickable: !!onClick, removeRadius: removeRoundedBorders }, className), "data-skin": skin, "data-hoverskin": this._getHoverSkin(), "data-isvisible": this._getIsVisible(), style: { backgroundImage: !isVideo && isMediaImageUrl && `url(${media})`, borderRadius, } }, isVideo && this._renderVideoElement(), !isVideo && !isMediaImageUrl && React.isValidElement(media) && media, this._renderSingleSkinLayer(), this._renderDefaultLayer(), this._renderHoverLayer(), this._renderTopLayer(), isVideo && this._renderPlayButton())); } } MediaOverlay.displayName = 'MediaOverlay'; MediaOverlay.Content = Content; MediaOverlay.DragHandle = DragHandle; MediaOverlay.defaultProps = { skin: 'none', showPlayButton: true, }; export default withFocusable(MediaOverlay); //# sourceMappingURL=MediaOverlay.js.map