267 lines
8.5 KiB
TypeScript
267 lines
8.5 KiB
TypeScript
import type { BoxProps } from '@mui/material/Box';
|
|
import type { UseClientRectReturn } from 'minimal-shared/hooks';
|
|
|
|
import { useRef, useState } from 'react';
|
|
import { varAlpha } from 'minimal-shared/utils';
|
|
import { useClientRect } from 'minimal-shared/hooks';
|
|
import { m, useSpring, useScroll, useTransform, useMotionValueEvent } from 'framer-motion';
|
|
|
|
import Box from '@mui/material/Box';
|
|
import Stack from '@mui/material/Stack';
|
|
import SvgIcon from '@mui/material/SvgIcon';
|
|
import Container from '@mui/material/Container';
|
|
import Typography from '@mui/material/Typography';
|
|
import { styled, useTheme } from '@mui/material/styles';
|
|
|
|
import { CONFIG } from 'src/global-config';
|
|
import { primaryColorPresets } from 'src/theme/with-settings';
|
|
|
|
import { Iconify } from 'src/components/iconify';
|
|
import { varFade, MotionViewport } from 'src/components/animate';
|
|
|
|
import { SectionTitle } from './components/section-title';
|
|
import { FloatLine, FloatPlusIcon } from './components/svg-elements';
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
const renderLines = () => (
|
|
<>
|
|
<FloatPlusIcon sx={{ top: 72, left: 72 }} />
|
|
<FloatLine sx={{ top: 80, left: 0 }} />
|
|
<FloatLine vertical sx={{ top: 0, left: 80 }} />
|
|
</>
|
|
);
|
|
|
|
export function HomeHighlightFeatures({ sx, ...other }: BoxProps) {
|
|
const containerRoot = useClientRect<HTMLDivElement>();
|
|
|
|
return (
|
|
<Box
|
|
component="section"
|
|
sx={[
|
|
{
|
|
position: 'relative',
|
|
pt: { xs: 10, md: 20 },
|
|
},
|
|
...(Array.isArray(sx) ? sx : [sx]),
|
|
]}
|
|
{...other}
|
|
>
|
|
<MotionViewport>
|
|
{renderLines()}
|
|
|
|
<Container>
|
|
<Stack
|
|
ref={containerRoot.elementRef}
|
|
spacing={5}
|
|
sx={[
|
|
{
|
|
textAlign: { xs: 'center', md: 'left' },
|
|
alignItems: { xs: 'center', md: 'flex-start' },
|
|
},
|
|
]}
|
|
>
|
|
<SectionTitle caption="App Features" title="Highlight" txtGradient="features" />
|
|
|
|
<SvgIcon
|
|
component={m.svg}
|
|
variants={varFade('inDown', { distance: 24 })}
|
|
sx={[{ width: 28, height: 28, color: 'grey.500' }]}
|
|
>
|
|
<path
|
|
d="M13.9999 6.75956L7.74031 0.5H20.2594L13.9999 6.75956Z"
|
|
fill="currentColor"
|
|
opacity={0.12}
|
|
/>
|
|
<path
|
|
d="M13.9998 23.8264L2.14021 11.9668H25.8593L13.9998 23.8264Z"
|
|
fill="currentColor"
|
|
opacity={0.24}
|
|
/>
|
|
</SvgIcon>
|
|
</Stack>
|
|
</Container>
|
|
</MotionViewport>
|
|
|
|
<ScrollableContent containerRoot={containerRoot} />
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
const ITEMS = [
|
|
{
|
|
title: 'Dark mode',
|
|
subtitle: 'A dark theme that feels easier on the eyes.',
|
|
icon: 'solar:cloudy-moon-bold-duotone',
|
|
imgUrl: [`${CONFIG.assetsDir}/assets/images/home/highlight-darkmode.webp`],
|
|
},
|
|
{
|
|
title: 'Color presets',
|
|
subtitle: 'Express your own style with just one click.',
|
|
icon: 'solar:pallete-2-bold-duotone',
|
|
imgUrl: [
|
|
`${CONFIG.assetsDir}/assets/images/home/highlight-presets-1.webp`,
|
|
`${CONFIG.assetsDir}/assets/images/home/highlight-presets-2.webp`,
|
|
`${CONFIG.assetsDir}/assets/images/home/highlight-presets-3.webp`,
|
|
`${CONFIG.assetsDir}/assets/images/home/highlight-presets-4.webp`,
|
|
`${CONFIG.assetsDir}/assets/images/home/highlight-presets-5.webp`,
|
|
],
|
|
},
|
|
{
|
|
title: 'Right-to-left',
|
|
subtitle: 'Support languages such as Arabic, Persian, and Hebrew.',
|
|
icon: 'solar:align-right-bold-duotone',
|
|
imgUrl: [`${CONFIG.assetsDir}/assets/images/home/highlight-rtl.webp`],
|
|
},
|
|
] as const;
|
|
|
|
type ScrollContentProps = {
|
|
containerRoot: UseClientRectReturn;
|
|
};
|
|
|
|
function ScrollableContent({ containerRoot }: ScrollContentProps) {
|
|
const theme = useTheme();
|
|
const isRtl = theme.direction === 'rtl';
|
|
|
|
const containerRef = useRef(null);
|
|
const containeRect = useClientRect(containerRef);
|
|
|
|
const scrollRef = useRef(null);
|
|
const scrollRect = useClientRect(scrollRef);
|
|
|
|
const { scrollYProgress } = useScroll({ target: containerRef });
|
|
|
|
const [startScroll, setStartScroll] = useState(false);
|
|
|
|
const physics = { damping: 16, mass: 0.12, stiffness: 80 };
|
|
|
|
const scrollRange = (-scrollRect.scrollWidth + containeRect.width / 2) * (isRtl ? -1 : 1);
|
|
|
|
const x = useSpring(useTransform(scrollYProgress, [0, 1], [0, scrollRange]), physics);
|
|
|
|
const background = useTransform(
|
|
scrollYProgress,
|
|
[0, 0.12, 0.28, 0.48, 0.58, 0.62, 0.72, 0.92],
|
|
[
|
|
`transparent`,
|
|
`linear-gradient(180deg, ${theme.palette.primary.light}, ${theme.palette.primary.dark})`,
|
|
`linear-gradient(180deg, ${primaryColorPresets.preset1.light}, ${primaryColorPresets.preset1.dark})`,
|
|
`linear-gradient(180deg, ${primaryColorPresets.preset2.light}, ${primaryColorPresets.preset2.dark})`,
|
|
`linear-gradient(180deg, ${primaryColorPresets.preset3.light}, ${primaryColorPresets.preset3.dark})`,
|
|
`linear-gradient(180deg, ${primaryColorPresets.preset4.light}, ${primaryColorPresets.preset4.dark})`,
|
|
`linear-gradient(180deg, ${primaryColorPresets.preset5.light}, ${primaryColorPresets.preset5.dark})`,
|
|
`linear-gradient(180deg, ${theme.palette.background.neutral}, ${theme.palette.background.neutral})`,
|
|
]
|
|
);
|
|
|
|
useMotionValueEvent(scrollYProgress, 'change', (latest) => {
|
|
if (latest !== 0 && latest !== 1) {
|
|
setStartScroll(true);
|
|
} else {
|
|
setStartScroll(false);
|
|
}
|
|
});
|
|
|
|
return (
|
|
<ScrollRoot ref={containerRef} sx={{ height: scrollRect.scrollWidth, minHeight: '100vh' }}>
|
|
<ScrollContainer style={{ background }} data-scrolling={startScroll}>
|
|
<ScrollContent
|
|
ref={scrollRef}
|
|
style={{ x }}
|
|
layout
|
|
sx={{ ml: `${containerRoot.left}px` }}
|
|
transition={{ ease: 'linear', duration: 0.25 }}
|
|
>
|
|
{ITEMS.map((item) => (
|
|
<Item key={item.title} item={item} />
|
|
))}
|
|
</ScrollContent>
|
|
</ScrollContainer>
|
|
</ScrollRoot>
|
|
);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
const ScrollRoot = styled(m.div)(({ theme }) => ({
|
|
zIndex: 9,
|
|
position: 'relative',
|
|
paddingTop: theme.spacing(5),
|
|
[theme.breakpoints.up('md')]: { paddingTop: theme.spacing(8) },
|
|
}));
|
|
|
|
const ScrollContainer = styled(m.div)(({ theme }) => ({
|
|
top: 0,
|
|
height: '100vh',
|
|
display: 'flex',
|
|
position: 'sticky',
|
|
overflow: 'hidden',
|
|
flexDirection: 'column',
|
|
alignItems: 'flex-start',
|
|
transition: theme.transitions.create(['background-color']),
|
|
'&[data-scrolling="true"]': { justifyContent: 'center' },
|
|
}));
|
|
|
|
const ScrollContent = styled(m.div)(({ theme }) => ({
|
|
display: 'flex',
|
|
gap: theme.spacing(5),
|
|
paddingLeft: theme.spacing(3),
|
|
transition: theme.transitions.create(['margin-left', 'margin-top']),
|
|
[theme.breakpoints.up('md')]: { gap: theme.spacing(8), paddingLeft: theme.spacing(0) },
|
|
}));
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
type ItemProps = BoxProps & {
|
|
item: (typeof ITEMS)[number];
|
|
};
|
|
|
|
function Item({ item, sx, ...other }: ItemProps) {
|
|
return (
|
|
<Box sx={[{ flexShrink: 0 }, ...(Array.isArray(sx) ? sx : [sx])]} {...other}>
|
|
<Box sx={{ mb: 6, gap: 2, display: 'flex' }}>
|
|
<Iconify width={28} icon={item.icon} sx={{ mt: '10px' }} />
|
|
<Stack spacing={2}>
|
|
<Typography variant="h3">{item.title}</Typography>
|
|
<Typography sx={{ color: 'text.secondary' }}>{item.subtitle}</Typography>
|
|
</Stack>
|
|
</Box>
|
|
|
|
<Box sx={{ display: 'flex', gap: { xs: 5, md: 8 } }}>
|
|
{item.imgUrl.map((url) => (
|
|
<Box
|
|
key={url}
|
|
sx={[
|
|
(theme) => ({
|
|
borderRadius: 2,
|
|
overflow: 'hidden',
|
|
boxShadow: `-40px 40px 80px 0px ${varAlpha(theme.vars.palette.grey['500Channel'], 0.16)}`,
|
|
...theme.applyStyles('dark', {
|
|
boxShadow: `-40px 40px 80px 0px ${varAlpha(theme.vars.palette.common.blackChannel, 0.16)}`,
|
|
}),
|
|
}),
|
|
]}
|
|
>
|
|
<Box
|
|
component="img"
|
|
alt={url}
|
|
src={url}
|
|
sx={{
|
|
width: {
|
|
xs: 480,
|
|
sm: 640,
|
|
md: 800,
|
|
lg: 1140,
|
|
xl: 1280,
|
|
},
|
|
}}
|
|
/>
|
|
</Box>
|
|
))}
|
|
</Box>
|
|
</Box>
|
|
);
|
|
}
|