Skip to content

Commit

Permalink
feat: scroll based animation in VerticalStack
Browse files Browse the repository at this point in the history
  • Loading branch information
DiogoSoaress committed Nov 19, 2024
1 parent 42bc163 commit 485e423
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 42 deletions.
3 changes: 2 additions & 1 deletion src/components/Wallet/VerticalSlide/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import useScrollProgress from '@/hooks/useScrollProgress'

const icons = [<RecoveryIcon key="recovery" />, <ScanIcon key="scan" />, <MultipleKeysIcon key="multiple-keys" />]

const selectIndex = (scrollYProgress: number) => {
// TODO: move to utils
export const selectIndex = (scrollYProgress: number) => {
if (scrollYProgress >= 0 && scrollYProgress <= 0.4) {
return 0
} else if (scrollYProgress > 0.4 && scrollYProgress <= 0.6) {
Expand Down
53 changes: 53 additions & 0 deletions src/components/Wallet/VerticalStack/Table.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import type { BaseBlock } from '@/components/Home/types'
import { useEffect, useState, type ReactElement, type RefObject } from 'react'
import css from './styles.module.css'
import { Grid, Typography } from '@mui/material'
import useScrollProgress from '@/hooks/useScrollProgress'
import { selectIndex } from '@/components/Wallet/VerticalSlide/Table'

export const GridItem = ({
image,
title,
text,
isSelected,
}: Partial<BaseBlock> & { isSelected: boolean }): ReactElement => (
<Grid item xs={12} className={`${css.card} ${isSelected ? css.selected : ''}`}>
{image ? <img {...image} /> : null}

<Typography variant="h5">{title}</Typography>

{text && (
<Typography color="primary.light" component="div">
{text}
</Typography>
)}
</Grid>
)

const Table = ({ items = [], sectionRef }: { items: BaseBlock['items']; sectionRef: RefObject<HTMLDivElement> }) => {
const [selectedIndex, setSelectedIndex] = useState(0)
const { scrollYProgress } = useScrollProgress(sectionRef)

useEffect(() => {
const handleScroll = () => {
const newIndex = selectIndex(scrollYProgress.get())
setSelectedIndex(newIndex)
}

const unsubscribe = scrollYProgress.on('change', handleScroll)

return () => {
unsubscribe()
}
}, [scrollYProgress, items.length])

return (
<div className={css.cardWrapper}>
{items.map((item, index) => (
<GridItem key={index} isSelected={index === selectedIndex} {...item} />
))}
</div>
)
}

export default Table
71 changes: 31 additions & 40 deletions src/components/Wallet/VerticalStack/index.tsx
Original file line number Diff line number Diff line change
@@ -1,51 +1,42 @@
import type { DetailedHTMLProps, ReactElement, SourceHTMLAttributes } from 'react'
import dynamic from 'next/dynamic'
import { useRef, type DetailedHTMLProps, type SourceHTMLAttributes } from 'react'
import { Container, Grid, Typography } from '@mui/material'
import type { BaseBlock } from '@/components/Home/types'
import layoutCss from '@/components/common/styles.module.css'
import css from './styles.module.css'

const Table = dynamic(() => import('./Table'))

type VideoEmbed = {
sources: Array<DetailedHTMLProps<SourceHTMLAttributes<HTMLSourceElement>, HTMLSourceElement>>
}

export const GridItem = ({ image, title, text }: Partial<BaseBlock>): ReactElement => (
<Grid item xs={12} className={css.card}>
{image ? <img {...image} /> : null}

<Typography variant="h5">{title}</Typography>

{text && (
<Typography color="primary.light" component="div">
{text}
</Typography>
)}
</Grid>
)

const VerticalStack = ({ title, video, items = [] }: BaseBlock & { video: VideoEmbed }) => (
<Container className={layoutCss.containerShort}>
<Grid container spacing="40px" justifyContent="space-between">
<Grid item md={6} className={css.titleWrapper}>
{video && (
<video autoPlay muted playsInline loop className={css.video}>
{video.sources.map((s, i) => (
<source key={i} {...s} />
))}
</video>
)}

<Typography variant="h2">{title}</Typography>
</Grid>

<Grid item xs={12} md={5}>
<div className={css.cardWrapper}>
{items.map((item, index) => (
<GridItem key={index} {...item} />
))}
</div>
</Grid>
</Grid>
</Container>
)
const VerticalStack = ({ title, video, items = [] }: BaseBlock & { video: VideoEmbed }) => {
const sectionRef = useRef<HTMLDivElement>(null)

return (
<Container className={`${layoutCss.containerShort} ${css.sectionContainer}`} ref={sectionRef}>
<div className={css.stickyContainer}>
<Grid container spacing="40px" justifyContent="space-between">
<Grid item md={6} className={css.titleWrapper}>
{video && (
<video autoPlay muted playsInline loop className={css.video}>
{video.sources.map((s, i) => (
<source key={i} {...s} />
))}
</video>
)}

<Typography variant="h2">{title}</Typography>
</Grid>

<Grid item xs={12} md={6}>
<Table items={items} sectionRef={sectionRef} />
</Grid>
</Grid>
</div>
</Container>
)
}

export default VerticalStack
32 changes: 31 additions & 1 deletion src/components/Wallet/VerticalStack/styles.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,45 @@
flex-direction: column;
gap: 16px;
align-items: flex-start;
padding: 48px 40px;
padding: 32px;
text-align: left;
position: relative;
}

.video {
width: 144px;
}

.card.selected::before {
content: '';
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 4px;
background-color: var(--mui-palette-primary-main);
}

.selected {
color: var(--mui-palette-primary-main);
}

@media (min-width: 900px) {
.sectionContainer {
height: 240vh;
}

.stickyContainer {
height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-start;
position: sticky;
top: 0;
overflow: hidden;
}

.titleWrapper {
display: flex;
flex-direction: column;
Expand Down

0 comments on commit 485e423

Please sign in to comment.