1import { css } from '@emotion/react'; 2import { borderRadius, breakpoints } from '@expo/styleguide-base'; 3import { PropsWithChildren, useState } from 'react'; 4import ReactPlayer from 'react-player'; 5import VisibilitySensor from 'react-visibility-sensor'; 6 7const PLAYER_WIDTH = '100%' as const; 8const PLAYER_HEIGHT = 400 as const; 9const YOUTUBE_DOMAINS = ['youtube.com', 'youtu.be'] as const; 10 11type VideoProps = PropsWithChildren<{ 12 controls?: any; 13 spaceAfter?: boolean | number; 14 url?: string; 15 file?: string; 16 loop?: boolean; 17}>; 18 19const Video = ({ controls, spaceAfter, url, file, loop = true }: VideoProps) => { 20 const [hover, setHover] = useState(false); 21 const [forceShowControls, setForceShowControls] = useState(isYouTubeDomain(url)); 22 23 return ( 24 <div 25 onClick={() => { 26 if (typeof controls === 'undefined' && !forceShowControls) { 27 setForceShowControls(true); 28 } 29 }} 30 style={hover ? { cursor: 'pointer' } : undefined} 31 onMouseEnter={() => setHover(true)} 32 onMouseLeave={() => setHover(false)}> 33 <VisibilitySensor partialVisibility> 34 {({ isVisible }: { isVisible: boolean }) => ( 35 <div css={[videoWrapperStyle, { marginBottom: getInitialMarginBottom(spaceAfter) }]}> 36 <ReactPlayer 37 url={isVisible ? url || `/static/videos/${file}` : undefined} 38 className="react-player" 39 width={PLAYER_WIDTH} 40 height={PLAYER_HEIGHT} 41 style={playerStyle} 42 muted 43 playing={isVisible && !!file} 44 controls={typeof controls === 'undefined' ? forceShowControls : controls} 45 playsinline 46 loop={loop} 47 /> 48 <div 49 css={[ 50 videoWrapperStyle, 51 dimmerStyle, 52 { 53 opacity: isVisible ? 0 : 0.7, 54 }, 55 ]} 56 /> 57 </div> 58 )} 59 </VisibilitySensor> 60 </div> 61 ); 62}; 63 64const getInitialMarginBottom = (spaceAfter: VideoProps['spaceAfter']) => { 65 if (typeof spaceAfter === 'undefined') { 66 return 30; 67 } else if (typeof spaceAfter === 'number') { 68 return spaceAfter; 69 } else if (spaceAfter) { 70 return 50; 71 } 72 return 0; 73}; 74 75const isYouTubeDomain = (url?: string) => { 76 return url ? YOUTUBE_DOMAINS.some(domain => url.includes(domain)) : false; 77}; 78 79const videoWrapperStyle = css({ 80 position: 'relative', 81 width: PLAYER_WIDTH, 82 height: PLAYER_HEIGHT, 83 backgroundColor: '#000', 84}); 85 86const playerStyle = css({ 87 outline: 'none', 88 backgroundColor: '#000', 89 borderRadius: borderRadius.md, 90}); 91 92const dimmerStyle = css({ 93 pointerEvents: 'none', 94 position: 'absolute', 95 top: 0, 96 left: 0, 97 bottom: 0, 98 right: 0, 99 transition: 'opacity 0.5s ease-out', 100 101 [`@media screen and (max-width: ${breakpoints.medium + 124}px)`]: { 102 display: 'none', 103 }, 104}); 105 106export default Video; 107