First Commit
This commit is contained in:
@@ -0,0 +1,250 @@
|
||||
'use client';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
// @mui
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import Button from '@mui/material/Button';
|
||||
import Chip from '@mui/material/Chip';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import Box from '@mui/material/Box';
|
||||
|
||||
// @third-party
|
||||
import { motion, useScroll, useTransform } from 'motion/react';
|
||||
|
||||
// @project
|
||||
import ButtonAnimationWrapper from '@/components/ButtonAnimationWrapper';
|
||||
import { GraphicsCard } from '@/components/cards';
|
||||
import ContainerWrapper from '@/components/ContainerWrapper';
|
||||
import GraphicsImage from '@/components/GraphicsImage';
|
||||
import SvgIcon from '@/components/SvgIcon';
|
||||
import { SECTION_COMMON_PY } from '@/utils/constant';
|
||||
import { getBackgroundDots } from '@/utils/getBackgroundDots';
|
||||
import { withAlpha } from '@/utils/colorUtils';
|
||||
|
||||
// @assets
|
||||
import Wave from '@/images/graphics/Wave';
|
||||
|
||||
// threshold - adjust threshold as needed
|
||||
const options = { root: null, rootMargin: '0px', threshold: 0.6 };
|
||||
|
||||
/*************************** HERO - 17 ***************************/
|
||||
|
||||
/**
|
||||
*
|
||||
* Demos:
|
||||
* - [Hero17](https://www.saasable.io/blocks/hero/hero17)
|
||||
*
|
||||
* API:
|
||||
* - [Hero17 API](https://phoenixcoded.gitbook.io/saasable/ui-kit/development/components/hero/hero17#props-details)
|
||||
*/
|
||||
|
||||
export default function Hero17({ chip, headLine, captionLine, primaryBtn, videoSrc, videoThumbnail, listData }) {
|
||||
const theme = useTheme();
|
||||
const boxRadius = { xs: 24, sm: 32, md: 40 };
|
||||
|
||||
const containerRef = useRef(null);
|
||||
|
||||
const { scrollYProgress } = useScroll({
|
||||
target: containerRef,
|
||||
offset: ['start start', 'end start']
|
||||
});
|
||||
|
||||
const scale = useTransform(scrollYProgress, [0, 0.1, 0.2, 0.4, 0.6], [0.9, 0.92, 0.94, 0.96, 1]);
|
||||
|
||||
const videoRef = useRef(null);
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
|
||||
// Handle video play/pause based on intersection with the viewport
|
||||
useEffect(() => {
|
||||
const handleIntersection = (entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting) {
|
||||
if (videoRef.current && !isPlaying) {
|
||||
videoRef.current
|
||||
.play()
|
||||
.then(() => {
|
||||
setIsPlaying(true);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Autoplay was prevented:', error);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (videoRef.current && isPlaying) {
|
||||
videoRef.current.pause();
|
||||
setIsPlaying(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const observer = new IntersectionObserver(handleIntersection, options);
|
||||
const videoElement = videoRef.current;
|
||||
|
||||
if (videoElement) {
|
||||
observer.observe(videoElement);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (videoElement) {
|
||||
observer.unobserve(videoElement);
|
||||
}
|
||||
};
|
||||
}, [isPlaying]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
height: { xs: 592, sm: 738, md: 878 },
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: 1,
|
||||
zIndex: -1,
|
||||
borderBottomLeftRadius: boxRadius,
|
||||
borderBottomRightRadius: boxRadius,
|
||||
...getBackgroundDots(theme.vars.palette.grey[300], 2, 35),
|
||||
bgcolor: 'grey.100'
|
||||
}}
|
||||
/>
|
||||
<ContainerWrapper sx={{ py: SECTION_COMMON_PY }}>
|
||||
<Box ref={containerRef}>
|
||||
<Box sx={{ pb: { xs: 3, sm: 4, md: 5 } }}>
|
||||
<Stack sx={{ alignItems: 'center', gap: 1.5 }}>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.6 }}
|
||||
whileInView={{ opacity: 1, scale: [0.6, 1.15, 0.95, 1] }}
|
||||
animate={{
|
||||
boxShadow: [
|
||||
`0 0 0px ${withAlpha(theme.vars.palette.primary.dark, 0)}`,
|
||||
`0 0 20px ${withAlpha(theme.vars.palette.primary.main, 0.8)}`,
|
||||
`0 0 0px ${withAlpha(theme.vars.palette.primary.dark, 0)}`
|
||||
],
|
||||
borderRadius: '74px'
|
||||
}}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6, delay: 0.8, ease: 'linear' }}
|
||||
>
|
||||
<Chip
|
||||
variant="outlined"
|
||||
label={chip.label}
|
||||
slotProps={{
|
||||
label: {
|
||||
sx: { py: 0.5, px: 1.5, ...(typeof chip.label === 'string' && { typography: 'caption', color: 'text.secondary' }) }
|
||||
}
|
||||
}}
|
||||
sx={{ bgcolor: 'grey.100' }}
|
||||
/>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.6 }}
|
||||
whileInView={{ opacity: 1, scale: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6, delay: 0.2, ease: 'linear' }}
|
||||
>
|
||||
<Typography variant="h1" align="center" sx={{ maxWidth: 800 }}>
|
||||
{headLine}
|
||||
</Typography>
|
||||
</motion.div>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 1, delay: 0.2, ease: [0.215, 0.61, 0.355, 1] }}
|
||||
>
|
||||
<Box sx={{ pt: 0.5, pb: 0.75 }}>
|
||||
<Wave />
|
||||
</Box>
|
||||
</motion.div>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 1, delay: 0.3, ease: [0.215, 0.61, 0.355, 1] }}
|
||||
>
|
||||
<Typography variant="h6" align="center" sx={{ color: 'text.secondary', maxWidth: 650 }}>
|
||||
{captionLine}
|
||||
</Typography>
|
||||
</motion.div>
|
||||
</Stack>
|
||||
<Stack sx={{ alignItems: 'center', gap: 2, mt: { xs: 3, sm: 4, md: 5 } }}>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.3, ease: 'easeInOut' }}
|
||||
whileHover={{ scale: 1.06 }}
|
||||
>
|
||||
<ButtonAnimationWrapper>
|
||||
<Button
|
||||
color="primary"
|
||||
variant="contained"
|
||||
startIcon={<SvgIcon name="tabler-sparkles" size={16} stroke={3} color="background.default" />}
|
||||
{...primaryBtn}
|
||||
/>
|
||||
</ButtonAnimationWrapper>
|
||||
</motion.div>
|
||||
<Stack direction="row" sx={{ gap: 1, flexWrap: 'wrap', justifyContent: 'center' }}>
|
||||
{listData.map((item, index) => (
|
||||
<motion.div
|
||||
key={index}
|
||||
initial={{ opacity: 0, scale: 0.6 }}
|
||||
whileInView={{ opacity: 1, scale: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.9, delay: index * 0.08, ease: 'linear' }}
|
||||
>
|
||||
<Chip
|
||||
label={item.title}
|
||||
variant="outlined"
|
||||
icon={<GraphicsImage image={item.image} sx={{ width: 16, height: 16 }} />}
|
||||
slotProps={{ label: { sx: { py: 0.75, px: 1, typography: 'caption2' } } }}
|
||||
sx={{ height: 32, px: 1, bgcolor: 'grey.100' }}
|
||||
/>
|
||||
</motion.div>
|
||||
))}
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Box>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.6 }}
|
||||
whileInView={{ opacity: 1, scale: 0.9 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.9, delay: 0.3 }}
|
||||
style={{ scale }}
|
||||
>
|
||||
<GraphicsCard sx={{ border: '5px solid', borderColor: 'grey.300' }}>
|
||||
<video
|
||||
playsInline
|
||||
ref={videoRef}
|
||||
width="100%"
|
||||
height="100%"
|
||||
style={{ display: 'flex', objectFit: 'cover' }}
|
||||
preload="metadata"
|
||||
autoPlay={false}
|
||||
loop={true}
|
||||
muted={true}
|
||||
poster={videoThumbnail}
|
||||
>
|
||||
<source src={videoSrc} type="video/mp4" />
|
||||
</video>
|
||||
</GraphicsCard>
|
||||
</motion.div>
|
||||
</Box>
|
||||
</ContainerWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Hero17.propTypes = {
|
||||
chip: PropTypes.object,
|
||||
headLine: PropTypes.string,
|
||||
captionLine: PropTypes.string,
|
||||
primaryBtn: PropTypes.any,
|
||||
videoSrc: PropTypes.string,
|
||||
videoThumbnail: PropTypes.string,
|
||||
listData: PropTypes.array
|
||||
};
|
||||
Reference in New Issue
Block a user