Commit ef6d1f20 authored by Charles Bachmeier's avatar Charles Bachmeier Committed by GitHub

feat: [DetailsV2] Show data page header when nft scrolled out of view (#6585)

* show data page header when nft scrolled out of view

* add new snapshot test

* useRef for observer

* add comment

---------
Co-authored-by: default avatarCharles Bachmeier <charlie@genie.xyz>
parent 10b156ff
......@@ -3,7 +3,14 @@ import { render } from 'test-utils/render'
import { DataPage } from './DataPage'
it('placeholder containers load', () => {
const { asFragment } = render(<DataPage asset={TEST_NFT_ASSET} />)
it('data page loads with header showing', () => {
const { asFragment } = render(<DataPage asset={TEST_NFT_ASSET} showDataHeader={true} />)
expect(asFragment()).toMatchSnapshot()
})
// The header is hidden via opacity: 0 to maintain its spacing, so it still exists in the DOM
// Therefore we can not check for its non-existence and instead rely on comparing the full generated snapshots
it('data page loads without header showing', () => {
const { asFragment } = render(<DataPage asset={TEST_NFT_ASSET} showDataHeader={false} />)
expect(asFragment()).toMatchSnapshot()
})
......@@ -35,6 +35,20 @@ const DataPageContainer = styled(Column)`
margin: 0 auto;
`
const HeaderContainer = styled.div<{ showDataHeader?: boolean }>`
position: sticky;
top: ${({ theme }) => `${theme.navHeight}px`};
padding-top: 16px;
backdrop-filter: blur(12px);
z-index: 1;
transition: ${({ theme }) => `opacity ${theme.transition.duration.fast}`};
opacity: ${({ showDataHeader }) => (showDataHeader ? '1' : '0')};
@media screen and (max-width: ${BREAKPOINTS.md}px) {
display: none;
}
`
const ContentContainer = styled(Row)`
gap: 24px;
padding-bottom: 45px;
......@@ -50,11 +64,13 @@ const LeftColumn = styled(Column)`
align-self: flex-start;
`
export const DataPage = ({ asset }: { asset: GenieAsset }) => {
export const DataPage = ({ asset, showDataHeader }: { asset: GenieAsset; showDataHeader: boolean }) => {
return (
<DataPagePaddingContainer>
<DataPageContainer>
<DataPageHeader asset={asset} />
<HeaderContainer showDataHeader={showDataHeader}>
<DataPageHeader asset={asset} />
</HeaderContainer>
<ContentContainer>
<LeftColumn>
{!!asset.traits?.length && <DataPageTraits asset={asset} />}
......
......@@ -10,9 +10,6 @@ import { BREAKPOINTS, ThemedText } from 'theme'
const HeaderContainer = styled(Row)`
gap: 24px;
@media screen and (max-width: ${BREAKPOINTS.md}px) {
display: none;
}
`
const AssetImage = styled.img`
......
......@@ -3,10 +3,26 @@ import { render } from 'test-utils/render'
import { LandingPage } from './LandingPage'
beforeEach(() => {
// IntersectionObserver isn't available in test environment
const mockIntersectionObserver = jest.fn()
mockIntersectionObserver.mockReturnValue({
observe: () => null,
unobserve: () => null,
disconnect: () => null,
})
window.IntersectionObserver = mockIntersectionObserver
})
describe('LandingPage', () => {
const mockSetShowDataHeader = jest.fn()
it('renders it correctly', () => {
const { asFragment } = render(
<LandingPage asset={TEST_NFT_ASSET} collection={TEST_NFT_COLLECTION_INFO_FOR_ASSET} />
<LandingPage
asset={TEST_NFT_ASSET}
collection={TEST_NFT_COLLECTION_INFO_FOR_ASSET}
setShowDataHeader={mockSetShowDataHeader}
/>
)
expect(asFragment()).toMatchSnapshot()
})
......
......@@ -2,6 +2,7 @@ import Column, { ColumnCenter } from 'components/Column'
import Row from 'components/Row'
import { VerifiedIcon } from 'nft/components/icons'
import { CollectionInfoForAsset, GenieAsset } from 'nft/types'
import { useEffect, useRef } from 'react'
import styled from 'styled-components/macro'
import { BREAKPOINTS } from 'theme'
......@@ -102,12 +103,36 @@ const MediaContainer = styled.div`
interface LandingPageProps {
asset: GenieAsset
collection: CollectionInfoForAsset
setShowDataHeader: (showDataHeader: boolean) => void
}
export const LandingPage = ({ asset, collection }: LandingPageProps) => {
export const LandingPage = ({ asset, collection, setShowDataHeader }: LandingPageProps) => {
const intersectionRef = useRef<HTMLDivElement>(null)
const observableRef = useRef(
new IntersectionObserver((entries) => {
if (!entries[0].isIntersecting) {
setShowDataHeader(true)
} else {
setShowDataHeader(false)
}
})
)
// Checks if the intersectionRef is in the viewport
// If it is not in the viewport, the data page header becomes visible
useEffect(() => {
const cachedRef = intersectionRef.current
const observer = observableRef.current
if (cachedRef && observer) {
observer.observe(cachedRef)
return () => observer.unobserve(cachedRef)
}
return
}, [intersectionRef, observableRef, setShowDataHeader])
return (
<LandingPageContainer>
<MediaContainer>
<MediaContainer ref={intersectionRef}>
<MediaRenderer asset={asset} />
</MediaContainer>
<InfoContainer>
......
import { CollectionInfoForAsset, GenieAsset } from 'nft/types'
import { useState } from 'react'
import styled from 'styled-components/macro'
import { Z_INDEX } from 'theme/zIndex'
......@@ -27,12 +28,13 @@ const DetailsContentContainer = styled.div`
`
export const NftDetails = ({ asset, collection }: NftDetailsProps) => {
const [showDataHeader, setShowDataHeader] = useState(false)
return (
<>
{asset.imageUrl && <DetailsBackground backgroundImage={asset.imageUrl} />}
<DetailsContentContainer>
<LandingPage asset={asset} collection={collection} />
<DataPage asset={asset} />
<LandingPage asset={asset} collection={collection} setShowDataHeader={setShowDataHeader} />
<DataPage asset={asset} showDataHeader={showDataHeader} />
</DetailsContentContainer>
</>
)
......
......@@ -257,12 +257,6 @@ exports[`Header loads with asset with a sell order 1`] = `
border-radius: 16px;
}
@media screen and (max-width:768px) {
.c2 {
display: none;
}
}
@media screen and (max-width:1024px) {
.c3 {
display: none;
......@@ -561,12 +555,6 @@ exports[`Header loads with asset with no sell orders 1`] = `
border-radius: 16px;
}
@media screen and (max-width:768px) {
.c2 {
display: none;
}
}
@media screen and (max-width:1024px) {
.c3 {
display: none;
......
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