Commit 04bd4900 authored by Greg Bugyis's avatar Greg Bugyis Committed by GitHub

feat: adds banner section (carousel only) for NFT Explore (#4383)

* NFT Explore: Banner section and carousel

* Fixes from PR feedback

* PR feedback and slight refactor of Carousel Progress indicators

* Only render current collection, and simplify fullWidth class

* Add colors to sprinkles and drop zIndex on bannerContent

* Simplify component structure

* Separate out CarouselIndicator and other cleanup

* Restore CarouselProgress component

* Position carousel progress over bg overlay
Co-authored-by: default avatargbugyis <greg@bugyis.com>
parent 81f277b3
import { style } from '@vanilla-extract/css'
import { breakpoints, sprinkles } from 'nft/css/sprinkles.css'
export const section = style([
sprinkles({
paddingLeft: { mobile: '16', desktopL: '0' },
paddingRight: { mobile: '16', desktopL: '0' },
}),
{
maxWidth: '1000px',
margin: '0 auto',
display: 'flex',
flexDirection: 'row',
flexWrap: 'nowrap',
position: 'relative',
},
])
export const bannerWrap = style([
sprinkles({
position: 'relative',
overflow: 'hidden',
height: '386',
}),
{
backgroundPosition: 'center',
backgroundSize: 'cover',
},
])
export const bannerOverlay = style([
{
opacity: '0.7',
height: '386px',
},
sprinkles({
position: 'absolute',
zIndex: '0',
width: 'full',
backgroundColor: 'grey900',
left: '0',
top: '0',
}),
])
export const collectionName = style([
sprinkles({
textAlign: 'left',
overflow: 'hidden',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
display: 'inline-block',
color: 'explicitWhite',
}),
{
maxWidth: 'calc(100% - 80px)',
},
])
export const collectionDetails = style([
sprinkles({
width: 'full',
}),
{
'@media': {
[`screen and (min-width: ${breakpoints.tabletL}px)`]: {
width: '40%',
},
},
},
])
export const volumeRank = style([
sprinkles({
paddingTop: '8',
paddingBottom: '8',
paddingRight: '16',
paddingLeft: '16',
color: 'blue400',
background: 'accentActionSoft',
}),
{
borderRadius: '64px',
maxWidth: '172px',
},
])
export const exploreCollection = style([
{
width: '176px',
},
sprinkles({
color: 'explicitWhite',
marginTop: '36',
borderRadius: '12',
padding: '12',
paddingRight: '16',
paddingLeft: '16',
}),
])
export const carouselIndicator = sprinkles({
width: '36',
height: '4',
marginRight: '6',
borderRadius: 'round',
display: 'inline-block',
})
import clsx from 'clsx'
import { Box } from 'nft/components/Box'
import { Center, Column, Row } from 'nft/components/Flex'
import { VerifiedIcon } from 'nft/components/icons'
import { bodySmall, buttonMedium, header1 } from 'nft/css/common.css'
import { vars } from 'nft/css/sprinkles.css'
import { fetchTrendingCollections } from 'nft/queries'
import { TimePeriod, TrendingCollection } from 'nft/types'
import { formatEthPrice } from 'nft/utils/currency'
import { putCommas } from 'nft/utils/putCommas'
import { formatChange, toSignificant } from 'nft/utils/toSignificant'
import { useEffect, useState } from 'react'
import { useQuery } from 'react-query'
import { Link } from 'react-router-dom'
import * as styles from './Banner.css'
const Banner = () => {
/* Sets initially displayed collection to random number between 0 and 4 */
const [current, setCurrent] = useState(Math.floor(Math.random() * 5))
const [hovered, setHover] = useState(false)
const { data: collections } = useQuery(
['trendingCollections'],
() => {
return fetchTrendingCollections({ volumeType: 'eth', timePeriod: TimePeriod.OneDay, size: 5 })
},
{
refetchOnReconnect: false,
refetchOnWindowFocus: false,
refetchOnMount: false,
}
)
useEffect(() => {
/* Rotate through Top 5 Collections on 15 second interval */
let stale = false
if (hovered || stale) return
const interval = setInterval(async () => {
if (collections) {
const nextCollectionIndex = (current + 1) % collections.length
setCurrent(nextCollectionIndex)
}
}, 15_000)
return () => {
stale = true
clearInterval(interval)
}
}, [current, collections, hovered])
return (
<Box onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)} cursor="pointer" width="full">
{collections && collections[current] ? (
<Link to={`/nfts/collection/${collections[current].address}`} style={{ textDecoration: 'none' }}>
<Box style={{ height: '386px' }}>
<div
className={styles.bannerWrap}
style={{ backgroundImage: `url(${collections[current].bannerImageUrl})` }}
>
<Box className={styles.bannerOverlay} width="full" />
<CollectionDetails collection={collections[current]} hovered={hovered} rank={current + 1} />
<CarouselProgress length={collections.length} currentIndex={current} setCurrent={setCurrent} />
</div>
</Box>
</Link>
) : (
<>
{/* TODO: Improve Loading State */}
<p>Loading</p>
</>
)}
</Box>
)
}
export default Banner
/* Collection Details: displays collection stats within Banner */
const CollectionDetails = ({
collection,
rank,
hovered,
}: {
collection: TrendingCollection
rank: number
hovered: boolean
}) => (
<Box as="section" className={styles.section} paddingTop="40">
<Column className={styles.collectionDetails} paddingTop="24">
<div className={styles.volumeRank}>#{rank} volume in 24hr</div>
<Row>
<Box as="span" marginTop="16" className={clsx(header1, styles.collectionName)}>
{collection.name}
</Box>
{collection.isVerified && (
<Box as="span" marginTop="24">
<VerifiedIcon height="32" width="32" />
</Box>
)}
</Row>
<Row className={bodySmall} marginTop="12" color="explicitWhite">
<Box>
<Box as="span" color="darkGray" marginRight="4">
Floor:
</Box>
{collection.floor ? formatEthPrice(collection.floor.toString()) : '--'} ETH
</Box>
<Box>
{collection.floorChange ? (
<Box as="span" color={collection.floorChange > 0 ? 'green200' : 'error'} marginLeft="4">
{collection.floorChange > 0 && '+'}
{formatChange(collection.floorChange)}%
</Box>
) : null}
</Box>
<Box marginLeft="24" color="explicitWhite">
<Box as="span" color="darkGray" marginRight="4">
Volume:
</Box>
{collection.volume ? putCommas(+toSignificant(collection.volume.toString())) : '--'} ETH
</Box>
<Box>
{collection.volumeChange ? (
<Box as="span" color={collection.volumeChange > 0 ? 'green200' : 'error'} marginLeft="4">
{collection.volumeChange > 0 && '+'}
{formatChange(collection.volumeChange)}%
</Box>
) : null}
</Box>
</Row>
<Link
className={clsx(buttonMedium, styles.exploreCollection)}
to={`/nfts/collection/${collection.address}`}
style={{ textDecoration: 'none', backgroundColor: `${hovered ? vars.color.blue400 : vars.color.grey700}` }}
>
Explore collection
</Link>
</Column>
</Box>
)
/* Carousel Progress indicators */
const CarouselProgress = ({
length,
currentIndex,
setCurrent,
}: {
length: number
currentIndex: number
setCurrent: React.Dispatch<React.SetStateAction<number>>
}) => (
<Center marginTop="16">
{Array(length)
.fill(null)
.map((value, carouselIndex) => (
<Box
cursor="pointer"
paddingTop="16"
paddingBottom="16"
position="relative"
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
setCurrent(carouselIndex)
}}
key={carouselIndex}
>
<Box
as="span"
className={styles.carouselIndicator}
display="inline-block"
backgroundColor={currentIndex === carouselIndex ? 'explicitWhite' : 'accentTextLightTertiary'}
/>
</Box>
))}
</Center>
)
...@@ -20,14 +20,6 @@ export const column = sprinkles({ ...@@ -20,14 +20,6 @@ export const column = sprinkles({
alignItems: 'center', alignItems: 'center',
}) })
export const section = style([
sprinkles({
paddingLeft: { mobile: '16', desktopL: '0' },
paddingRight: { mobile: '16', desktopL: '0' },
}),
{ maxWidth: '1000px', margin: '0 auto' },
])
// TYPOGRAPHY // TYPOGRAPHY
export const header1 = sprinkles({ fontWeight: 'normal', fontSize: '36' }) export const header1 = sprinkles({ fontWeight: 'normal', fontSize: '36' })
export const header2 = sprinkles({ fontWeight: 'normal', fontSize: '28' }) export const header2 = sprinkles({ fontWeight: 'normal', fontSize: '28' })
......
...@@ -159,6 +159,8 @@ export const vars = createGlobalTheme(':root', { ...@@ -159,6 +159,8 @@ export const vars = createGlobalTheme(':root', {
grey200: '#B7BED4', grey200: '#B7BED4',
grey100: '#DDE3F7', grey100: '#DDE3F7',
grey50: '#EDEFF7', grey50: '#EDEFF7',
accentActionSoft: 'rgba(76, 130, 251, 0.24)',
accentTextLightTertiary: 'rgba(255, 255, 255, 0.12)',
lightGrayOverlay: '#99A1BD14', lightGrayOverlay: '#99A1BD14',
}, },
border: { border: {
......
import Banner from 'nft/components/explore/Banner'
const NftExplore = () => {
return (
<>
<Banner />
</>
)
}
export default NftExplore
...@@ -43,6 +43,7 @@ import Tokens from './Tokens' ...@@ -43,6 +43,7 @@ import Tokens from './Tokens'
const TokenDetails = lazy(() => import('./TokenDetails')) const TokenDetails = lazy(() => import('./TokenDetails'))
const Vote = lazy(() => import('./Vote')) const Vote = lazy(() => import('./Vote'))
const NftExplore = lazy(() => import('nft/pages/explore'))
const Collection = lazy(() => import('nft/pages/collection')) const Collection = lazy(() => import('nft/pages/collection'))
const Sell = lazy(() => import('nft/pages/sell/sell')) const Sell = lazy(() => import('nft/pages/sell/sell'))
const Asset = lazy(() => import('nft/pages/asset/Asset')) const Asset = lazy(() => import('nft/pages/asset/Asset'))
...@@ -215,6 +216,7 @@ export default function App() { ...@@ -215,6 +216,7 @@ export default function App() {
{nftFlag === NftVariant.Enabled && ( {nftFlag === NftVariant.Enabled && (
<> <>
<Route path="/nfts/collection/:contractAddress" element={<Collection />} /> <Route path="/nfts/collection/:contractAddress" element={<Collection />} />
<Route path="/nfts" element={<NftExplore />} />
<Route path="/nft/sell" element={<Sell />} /> <Route path="/nft/sell" element={<Sell />} />
<Route path="/nft/asset/:contractAddress/:tokenId" element={<Asset />} /> <Route path="/nft/asset/:contractAddress/:tokenId" element={<Asset />} />
</> </>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment