Skip to content

Commit

Permalink
[B2BP-753] - Implementing 'Video' EC inside B2BP (#350)
Browse files Browse the repository at this point in the history
* commit add storeButtons to Hero

* commit changeset

* commit review changes

* commit review changes

* commit package-lock

* commit file test fix

* commit video component

* commit changeset

* commit video example

* commit color fix

* commit update video component

* commit video variation

* Commit review changes

* commit review changes

* Update smooth-balloons-compare.md

* commit review changes

* commit implement video functions

* Commit review changes

* commit review changes
  • Loading branch information
Meolocious authored Jul 19, 2024
1 parent 7a1db57 commit 66789c1
Show file tree
Hide file tree
Showing 12 changed files with 593 additions and 1 deletion.
5 changes: 5 additions & 0 deletions .changeset/smooth-balloons-compare.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"nextjs-website": minor
---

Implement Video-Image from EC to B2BP
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import { Typography } from '@mui/material';
import { isJSX } from '@react-components/types/common/Common.types';
import {
VideoCaptionProps,
VideoTextProps,
RenderVideoProps,
ImageSrc,
ImageSrcObject,
ImageProps,
} from '@react-components/types/VideoImage/VideoImage.types';
import { TextColor } from '../common/Common.helpers';
import { useTheme } from '@mui/material/styles';
import Image from 'next/image';

export const renderVideo = ({
videoRef,
error,
setError,
src,
loop,
autoplay,
fallback,
onVideoEnd,
}: RenderVideoProps) => {
// Determine the type based on the structure of src
const type =
typeof src === 'string'
? src.split('.').pop() ?? 'mp4'
: src?.mime.split('/').pop() ?? 'mp4';

if (error) {
return isJSX(fallback) ? (
fallback
) : (
<Typography variant='h6' color='background.paper' textAlign='center'>
{fallback}
</Typography>
);
}
return (
<video
onContextMenu={(e) => e.preventDefault()}
playsInline
ref={videoRef}
muted
loop={loop}
autoPlay={autoplay}
onEnded={onVideoEnd}
style={{
overflow: 'hidden',
width: '100%',
height: '100%',
}}
>
<source
src={typeof src === 'object' ? src.url : src}
type={`video/${type}`}
onError={() => setError(true)}
/>
</video>
);
};

// Type guard function to check if src is an object
function isImageSrcObject(src: ImageSrc): src is ImageSrcObject {
return typeof src === 'object' && src !== null && 'url' in src;
}

// Refactored renderImage function
export const renderImage = ({ alt = 'image alt', src }: ImageProps) => {
let imageUrl = isImageSrcObject(src) ? src.url : src;

if (!imageUrl) {
return null;
}

return (
<Image
alt={alt}
src={imageUrl}
width={0}
height={0}
style={{
objectFit: 'contain',
width: '100%',
height: '100%',
userSelect: 'none',
overflow: 'hidden',
}}
/>
);
};

export const VideoText = ({
title,
subtitle,
theme = 'dark',
}: VideoTextProps) => {
const textColor = TextColor(theme);
return (
<>
{title && (
<Typography variant='h5' mb={4} color={textColor}>
{title}
</Typography>
)}
{subtitle && (
<Typography
paragraph
sx={{ fontSize: '16px' }}
mb={3}
color={textColor}
>
{subtitle}
</Typography>
)}
</>
);
};

export const VideoCaption = ({ caption, toBeCentered }: VideoCaptionProps) => {
const { palette } = useTheme();
return (
<div
style={{
height: '116px',
marginLeft: toBeCentered ? '0' : '7em',
marginRight: toBeCentered ? '0' : '7em',
marginTop: '1em',
textAlign: toBeCentered ? 'center' : 'left',
}}
>
{caption && (
<Typography
paragraph
sx={{ fontSize: '16px', fontWeight: 600 }}
mb={3}
color={palette.text.primary}
>
{caption}
</Typography>
)}
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
import { useEffect, useRef, useState } from 'react';
import { useIsVisible } from '@react-components/types/common/Common.types';
import { VideoImageProps } from '@react-components/types';
import { TextColor } from '../common/Common.helpers';
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
import {
renderImage,
renderVideo,
VideoCaption,
VideoText,
} from './VideoImage.helpers';

const VideoImage = (props: VideoImageProps) => {
const {
title,
subtitle,
caption,
src,
alt,
autoplay = false,
loop = false,
full = false,
theme,
fallback,
playButtonLabel,
isCentered = false,
} = props;
const videoRef = useRef<HTMLVideoElement>(null);
const isVisible = useIsVisible(videoRef);
const [error, setError] = useState(false);
const [isPlaying, setIsPlaying] = useState(false);

const textColor = TextColor(theme);

useEffect(() => {
if (!isVisible) return;
const startVideoWhenVisible = async () => {
if (autoplay && isVisible) play();
};
startVideoWhenVisible().catch();
}, [isVisible]);

const play = (e?: React.MouseEvent) => {
try {
e?.preventDefault();
videoRef.current?.play();
setIsPlaying(true);
} catch (error) {}
};

const handleVideoEnd = () => {
setIsPlaying(false);
};

const isImage = (src: string) => {
const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp'];
return imageExtensions.some((extension) => src.endsWith(extension));
};

const isVideo = (src: string) => {
const videoExtensions = ['.mp4', '.webm', '.ogg'];
return videoExtensions.some((extension) => src.endsWith(extension));
};

return (
<>
<div
style={{ maxHeight: '600px', position: 'relative', overflow: 'hidden' }}
>
{!full && !isPlaying && (
<div
style={{
position: 'absolute',
top: 0,
bottom: 0,
left: 0,
right: 0,
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
padding: '20px',
background: `rgba(0, 0, 0, ${
typeof src === 'string'
? isImage(src)
? '0.20'
: '0.60'
: isImage(src.url)
? '0.20'
: '0.60'
})`,
alignItems: isCentered ? 'center' : 'left',
}}
>
<div
style={{
marginLeft: title || subtitle ? '6em' : '0',
zIndex: 50,
}}
>
<VideoText title={title} subtitle={subtitle} theme={theme} />
{typeof src === 'string'
? !isImage(src) &&
isVideo(src) && (
<div
onClick={play}
style={{
display: 'flex',
flexDirection: 'row',
width: 'fit-content',
gap: '0.5em',
alignItems: 'center',
justifyContent: isCentered ? 'center' : 'left',
color: textColor,
}}
>
<p
style={{
display: 'flex',
fontWeight: 700,
fontSize: '24px',
textDecoration: 'none',
color: textColor,
cursor: 'pointer',
alignSelf: 'flex-start',
}}
>
{playButtonLabel}
</p>
<PlayArrowIcon
sx={{
height: '2em',
cursor: 'pointer',
color: textColor,
}}
/>
</div>
)
: !isImage(src.url) &&
isVideo(src.url) && (
<div
onClick={play}
style={{
display: 'flex',
flexDirection: 'row',
width: 'fit-content',
gap: '0.5em',
alignItems: 'center',
justifyContent: isCentered ? 'center' : 'left',
color: textColor,
}}
>
<p
style={{
display: 'flex',
fontWeight: 700,
fontSize: '24px',
textDecoration: 'none',
color: textColor,
cursor: 'pointer',
alignSelf: 'flex-start',
}}
>
{playButtonLabel}
</p>
<PlayArrowIcon
sx={{
height: '2em',
cursor: 'pointer',
color: textColor,
}}
/>
</div>
)}
</div>
</div>
)}
{typeof src === 'string'
? isImage(src)
? renderImage({
src,
alt,
})
: renderVideo({
videoRef,
error,
setError,
src,
loop,
autoplay,
fallback,
onVideoEnd: handleVideoEnd,
})
: isImage(src.url)
? renderImage({
src: src.url,
alt,
})
: renderVideo({
videoRef,
error,
setError,
src: src.url,
loop,
autoplay,
fallback,
onVideoEnd: handleVideoEnd,
})}
</div>
{caption && (
<VideoCaption
caption={caption}
theme={theme}
toBeCentered={isCentered}
/>
)}
</>
);
};

export default VideoImage;
2 changes: 2 additions & 0 deletions apps/nextjs-website/react-components/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import Form from './Form/Form';
import PreFooter from './PreFooter/PreFooter';
import HeroCounter from './HeroCounter/HeroCounter';
import MegaHeader from './MegaHeader/MegaHeader';
import VideoImage from './VideoImage/VideoImage';

export {
Hero,
Expand All @@ -34,4 +35,5 @@ export {
PreFooter,
HeroCounter,
MegaHeader,
VideoImage,
};
Loading

0 comments on commit 66789c1

Please sign in to comment.