Commit ec6c843d authored by Jordan Frankfurt's avatar Jordan Frankfurt Committed by GitHub

fix: collection screen for mobile (#5287)

* fix: collection screen for mobile

* margin to match collection item container

* fix loading skeleton

* pr feedback + some minor code cleanup
parent 7a6bb369
...@@ -29,6 +29,3 @@ export const assetList = style([ ...@@ -29,6 +29,3 @@ export const assetList = style([
}, },
}, },
]) ])
//Using negative margin and overflowing the width but 2*16px so that the edges of this area always properly clip the softer, wider shadow on the cards
export const actionBarContainer = style([{ marginLeft: '-16px', width: 'calc(100% + 32px)' }])
...@@ -48,7 +48,7 @@ import { applyFiltersFromURL, syncLocalFiltersWithURL } from 'nft/utils/urlParam ...@@ -48,7 +48,7 @@ import { applyFiltersFromURL, syncLocalFiltersWithURL } from 'nft/utils/urlParam
import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import InfiniteScroll from 'react-infinite-scroll-component' import InfiniteScroll from 'react-infinite-scroll-component'
import { useLocation } from 'react-router-dom' import { useLocation } from 'react-router-dom'
import styled from 'styled-components/macro' import styled, { css } from 'styled-components/macro'
import { ThemedText } from 'theme' import { ThemedText } from 'theme'
import { CollectionAssetLoading } from './CollectionAssetLoading' import { CollectionAssetLoading } from './CollectionAssetLoading'
...@@ -64,11 +64,26 @@ interface CollectionNftsProps { ...@@ -64,11 +64,26 @@ interface CollectionNftsProps {
const rarityStatusCache = new Map<string, boolean>() const rarityStatusCache = new Map<string, boolean>()
const InfiniteScrollWrapperCss = css`
margin: 0 16px;
@media screen and (min-width: ${({ theme }) => theme.breakpoint.sm}px) {
margin: 0 20px;
}
@media screen and (min-width: ${({ theme }) => theme.breakpoint.md}px) {
margin: 0 26px;
}
@media screen and (min-width: ${({ theme }) => theme.breakpoint.lg}px) {
margin: 0 48px;
}
`
const ActionsContainer = styled.div` const ActionsContainer = styled.div`
display: flex; display: flex;
flex: 1 1 auto;
gap: 10px; gap: 10px;
width: 100%;
justify-content: space-between; justify-content: space-between;
${InfiniteScrollWrapperCss}
` `
const ActionsSubContainer = styled.div` const ActionsSubContainer = styled.div`
...@@ -93,7 +108,7 @@ export const SortDropdownContainer = styled.div<{ isFiltersExpanded: boolean }>` ...@@ -93,7 +108,7 @@ export const SortDropdownContainer = styled.div<{ isFiltersExpanded: boolean }>`
const EmptyCollectionWrapper = styled.div` const EmptyCollectionWrapper = styled.div`
display: block; display: block;
textalign: center; text-align: center;
` `
const ViewFullCollection = styled.span` const ViewFullCollection = styled.span`
...@@ -111,6 +126,10 @@ const ClearAllButton = styled.button` ...@@ -111,6 +126,10 @@ const ClearAllButton = styled.button`
background: none; background: none;
` `
const InfiniteScrollWrapper = styled.div`
${InfiniteScrollWrapperCss}
`
const SweepButton = styled.div<{ toggled: boolean; disabled?: boolean }>` const SweepButton = styled.div<{ toggled: boolean; disabled?: boolean }>`
display: flex; display: flex;
gap: 8px; gap: 8px;
...@@ -148,7 +167,7 @@ const MarketNameWrapper = styled(Row)` ...@@ -148,7 +167,7 @@ const MarketNameWrapper = styled(Row)`
gap: 8px; gap: 8px;
` `
const loadingAssets = () => ( const LoadingAssets = () => (
<> <>
{Array.from(Array(ASSET_PAGE_SIZE), (_, index) => ( {Array.from(Array(ASSET_PAGE_SIZE), (_, index) => (
<CollectionAssetLoading key={index} /> <CollectionAssetLoading key={index} />
...@@ -158,7 +177,7 @@ const loadingAssets = () => ( ...@@ -158,7 +177,7 @@ const loadingAssets = () => (
export const CollectionNftsLoading = () => ( export const CollectionNftsLoading = () => (
<Box width="full" className={styles.assetList}> <Box width="full" className={styles.assetList}>
{loadingAssets()} <LoadingAssets />
</Box> </Box>
) )
...@@ -278,6 +297,7 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie ...@@ -278,6 +297,7 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie
} }
const { assets: collectionNfts, loadNext, hasNext, isLoadingNext } = useLazyLoadAssetsQuery(assetQueryParams) const { assets: collectionNfts, loadNext, hasNext, isLoadingNext } = useLazyLoadAssetsQuery(assetQueryParams)
const handleNextPageLoad = useCallback(() => loadNext(ASSET_PAGE_SIZE), [loadNext])
const getPoolPosition = useCallback( const getPoolPosition = useCallback(
(asset: GenieAsset) => { (asset: GenieAsset) => {
...@@ -449,6 +469,15 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie ...@@ -449,6 +469,15 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie
} }
}, [collectionStats, priceRangeLow, priceRangeHigh, setPriceRangeHigh, setPriceRangeLow]) }, [collectionStats, priceRangeLow, priceRangeHigh, setPriceRangeHigh, setPriceRangeLow])
const handleSweepClick = useCallback(() => {
if (hasErc1155s) return
if (!sweepIsOpen) {
scrollToTop()
if (!bagExpanded && !isMobile) toggleBag()
}
setSweepOpen(!sweepIsOpen)
}, [bagExpanded, hasErc1155s, isMobile, sweepIsOpen, toggleBag])
return ( return (
<> <>
<AnimatedBox <AnimatedBox
...@@ -458,8 +487,8 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie ...@@ -458,8 +487,8 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie
width="full" width="full"
zIndex="3" zIndex="3"
marginBottom={{ sm: '8', md: '20' }} marginBottom={{ sm: '8', md: '20' }}
padding="16" paddingTop="16"
className={styles.actionBarContainer} paddingBottom="16"
> >
<ActionsContainer> <ActionsContainer>
<ActionsSubContainer> <ActionsSubContainer>
...@@ -482,26 +511,19 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie ...@@ -482,26 +511,19 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie
</SortDropdownContainer> </SortDropdownContainer>
<CollectionSearch /> <CollectionSearch />
</ActionsSubContainer> </ActionsSubContainer>
{!hasErc1155s ? ( {!hasErc1155s && (
<SweepButton <SweepButton
toggled={sweepIsOpen} toggled={sweepIsOpen}
disabled={hasErc1155s} disabled={hasErc1155s}
className={buttonTextMedium} className={buttonTextMedium}
onClick={() => { onClick={handleSweepClick}
if (hasErc1155s) return
if (!sweepIsOpen) {
scrollToTop()
if (!bagExpanded && !isMobile) toggleBag()
}
setSweepOpen(!sweepIsOpen)
}}
> >
<SweepIcon viewBox="0 0 24 24" width="20px" height="20px" /> <SweepIcon viewBox="0 0 24 24" width="20px" height="20px" />
<SweepText fontWeight={600} color="currentColor" lineHeight="20px"> <SweepText fontWeight={600} color="currentColor" lineHeight="20px">
Sweep Sweep
</SweepText> </SweepText>
</SweepButton> </SweepButton>
) : null} )}
</ActionsContainer> </ActionsContainer>
{sweepIsOpen && ( {sweepIsOpen && (
<Sweep contractAddress={contractAddress} minPrice={debouncedMinPrice} maxPrice={debouncedMaxPrice} /> <Sweep contractAddress={contractAddress} minPrice={debouncedMinPrice} maxPrice={debouncedMaxPrice} />
...@@ -564,35 +586,37 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie ...@@ -564,35 +586,37 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie
) : null} ) : null}
</Row> </Row>
</AnimatedBox> </AnimatedBox>
<InfiniteScroll <InfiniteScrollWrapper>
next={() => loadNext(ASSET_PAGE_SIZE)} <InfiniteScroll
hasMore={hasNext} next={handleNextPageLoad}
loader={hasNext && hasNfts ? loadingAssets() : null} hasMore={hasNext}
dataLength={collectionAssets?.length ?? 0} loader={Boolean(hasNext && hasNfts) && <LoadingAssets />}
style={{ overflow: 'unset' }} dataLength={collectionAssets?.length ?? 0}
className={hasNfts || isLoadingNext ? styles.assetList : undefined} style={{ overflow: 'unset' }}
> className={hasNfts || isLoadingNext ? styles.assetList : undefined}
{hasNfts ? ( >
assets {hasNfts ? (
) : collectionAssets?.length === 0 ? ( assets
<Center width="full" color="textSecondary" textAlign="center" style={{ height: '60vh' }}> ) : collectionAssets?.length === 0 ? (
<EmptyCollectionWrapper> <Center width="full" color="textSecondary" textAlign="center" style={{ height: '60vh' }}>
<p className={headlineMedium}>No NFTS found</p> <EmptyCollectionWrapper>
<Box <p className={headlineMedium}>No NFTS found</p>
onClick={reset} <Box
type="button" onClick={reset}
className={clsx(bodySmall, buttonTextMedium)} type="button"
color="blue" className={clsx(bodySmall, buttonTextMedium)}
cursor="pointer" color="blue"
> cursor="pointer"
<ViewFullCollection>View full collection</ViewFullCollection> >
</Box> <ViewFullCollection>View full collection</ViewFullCollection>
</EmptyCollectionWrapper> </Box>
</Center> </EmptyCollectionWrapper>
) : ( </Center>
<CollectionNftsLoading /> ) : (
)} <CollectionNftsLoading />
</InfiniteScroll> )}
</InfiniteScroll>
</InfiniteScrollWrapper>
</> </>
) )
} }
...@@ -2,8 +2,7 @@ import Column from 'components/Column' ...@@ -2,8 +2,7 @@ import Column from 'components/Column'
import Row from 'components/Row' import Row from 'components/Row'
import { Box } from 'nft/components/Box' import { Box } from 'nft/components/Box'
import { useIsMobile } from 'nft/hooks' import { useIsMobile } from 'nft/hooks'
import { CollectionBannerLoading } from 'nft/pages/collection' import { BannerWrapper, CollectionBannerLoading } from 'nft/pages/collection'
import { COLLECTION_BANNER_HEIGHT } from 'nft/pages/collection'
import { ScreenBreakpointsPaddings } from 'nft/pages/collection/index.css' import { ScreenBreakpointsPaddings } from 'nft/pages/collection/index.css'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
...@@ -32,9 +31,9 @@ export const CollectionPageSkeleton = () => { ...@@ -32,9 +31,9 @@ export const CollectionPageSkeleton = () => {
const isMobile = useIsMobile() const isMobile = useIsMobile()
return ( return (
<StyledColumn> <StyledColumn>
<Box width="full" height={`${COLLECTION_BANNER_HEIGHT}`}> <BannerWrapper width="full">
<CollectionBannerLoading /> <CollectionBannerLoading />
</Box> </BannerWrapper>
<CollectionDescriptionSection> <CollectionDescriptionSection>
<CollectionStatsLoading isMobile={isMobile} /> <CollectionStatsLoading isMobile={isMobile} />
<StyledRow>{ActivitySwitcherLoading}</StyledRow> <StyledRow>{ActivitySwitcherLoading}</StyledRow>
......
...@@ -3,7 +3,6 @@ import { getDeltaArrow } from 'components/Tokens/TokenDetails/PriceChart' ...@@ -3,7 +3,6 @@ import { getDeltaArrow } from 'components/Tokens/TokenDetails/PriceChart'
import { Box, BoxProps } from 'nft/components/Box' import { Box, BoxProps } from 'nft/components/Box'
import { Column, Row } from 'nft/components/Flex' import { Column, Row } from 'nft/components/Flex'
import { Marquee } from 'nft/components/layout/Marquee' import { Marquee } from 'nft/components/layout/Marquee'
import { headlineMedium } from 'nft/css/common.css'
import { themeVars } from 'nft/css/sprinkles.css' import { themeVars } from 'nft/css/sprinkles.css'
import { useIsCollectionLoading } from 'nft/hooks/useIsCollectionLoading' import { useIsCollectionLoading } from 'nft/hooks/useIsCollectionLoading'
import { GenieCollection, TokenType } from 'nft/types' import { GenieCollection, TokenType } from 'nft/types'
...@@ -11,6 +10,7 @@ import { floorFormatter, quantityFormatter, roundWholePercentage, volumeFormatte ...@@ -11,6 +10,7 @@ import { floorFormatter, quantityFormatter, roundWholePercentage, volumeFormatte
import { ReactNode, useEffect, useReducer, useRef, useState } from 'react' import { ReactNode, useEffect, useReducer, useRef, useState } from 'react'
import ReactMarkdown from 'react-markdown' import ReactMarkdown from 'react-markdown'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
import { ThemedText } from 'theme'
import { DiscordIcon, EllipsisIcon, ExternalIcon, InstagramIcon, TwitterIcon, VerifiedIcon, XMarkIcon } from '../icons' import { DiscordIcon, EllipsisIcon, ExternalIcon, InstagramIcon, TwitterIcon, VerifiedIcon, XMarkIcon } from '../icons'
import * as styles from './CollectionStats.css' import * as styles from './CollectionStats.css'
...@@ -134,14 +134,14 @@ const CollectionName = ({ ...@@ -134,14 +134,14 @@ const CollectionName = ({
toggleCollectionSocials: () => void toggleCollectionSocials: () => void
}) => { }) => {
const isCollectionStatsLoading = useIsCollectionLoading((state) => state.isCollectionStatsLoading) const isCollectionStatsLoading = useIsCollectionLoading((state) => state.isCollectionStatsLoading)
const nameClass = isCollectionStatsLoading ? styles.nameTextLoading : clsx(headlineMedium, styles.nameText) const nameClass = isCollectionStatsLoading ? styles.nameTextLoading : styles.nameText
return ( return (
<Row justifyContent="space-between"> <Row justifyContent="space-between">
<Row minWidth="0"> <Row minWidth="0">
<Box marginRight={!isVerified ? '12' : '0'} className={nameClass}> <ThemedText.HeadlineSmall marginRight={!isVerified ? '12' : '0'} className={nameClass}>
{name} {name}
</Box> </ThemedText.HeadlineSmall>
{isVerified && <VerifiedIcon style={{ width: '32px', height: '32px' }} />} {isVerified && <VerifiedIcon style={{ width: '32px', height: '32px' }} />}
<Row <Row
display={{ sm: 'none', md: 'flex' }} display={{ sm: 'none', md: 'flex' }}
...@@ -246,7 +246,7 @@ const CollectionDescription = ({ description }: { description: string }) => { ...@@ -246,7 +246,7 @@ const CollectionDescription = ({ description }: { description: string }) => {
const StatsItem = ({ children, label, shouldHide }: { children: ReactNode; label: string; shouldHide: boolean }) => { const StatsItem = ({ children, label, shouldHide }: { children: ReactNode; label: string; shouldHide: boolean }) => {
return ( return (
<Box display={shouldHide ? 'none' : 'flex'} flexDirection="column" alignItems="baseline" gap="2" height="min"> <Box display={shouldHide ? 'none' : 'flex'} flexDirection="column" alignItems="baseline" gap="2" height="min">
<span className={styles.statsValue}>{children}</span> <ThemedText.SubHeader className={styles.statsValue}>{children}</ThemedText.SubHeader>
<Box as="span" className={styles.statsLabel}> <Box as="span" className={styles.statsLabel}>
{label} {label}
</Box> </Box>
......
...@@ -2,11 +2,6 @@ import { Trace } from '@uniswap/analytics' ...@@ -2,11 +2,6 @@ import { Trace } from '@uniswap/analytics'
import { PageName } from '@uniswap/analytics-events' import { PageName } from '@uniswap/analytics-events'
import { useWeb3React } from '@web3-react/core' import { useWeb3React } from '@web3-react/core'
import { OpacityHoverState } from 'components/Common' import { OpacityHoverState } from 'components/Common'
import {
MAX_WIDTH_MEDIA_BREAKPOINT,
MOBILE_MEDIA_BREAKPOINT,
SMALL_MEDIA_BREAKPOINT,
} from 'components/Tokens/constants'
import { useLoadAssetsQuery } from 'graphql/data/nft/Asset' import { useLoadAssetsQuery } from 'graphql/data/nft/Asset'
import { useCollectionQuery, useLoadCollectionQuery } from 'graphql/data/nft/Collection' import { useCollectionQuery, useLoadCollectionQuery } from 'graphql/data/nft/Collection'
import { MobileHoverBag } from 'nft/components/bag/MobileHoverBag' import { MobileHoverBag } from 'nft/components/bag/MobileHoverBag'
...@@ -28,7 +23,13 @@ import { TRANSITION_DURATIONS } from 'theme/styles' ...@@ -28,7 +23,13 @@ import { TRANSITION_DURATIONS } from 'theme/styles'
const FILTER_WIDTH = 332 const FILTER_WIDTH = 332
const BAG_WIDTH = 324 const BAG_WIDTH = 324
export const COLLECTION_BANNER_HEIGHT = 288
export const BannerWrapper = styled(Box)`
height: 100px;
@media screen and (min-width: ${({ theme }) => theme.breakpoint.sm}px) {
height: 288px;
}
`
export const CollectionBannerLoading = () => <Box height="full" width="full" className={styles.loadingBanner} /> export const CollectionBannerLoading = () => <Box height="full" width="full" className={styles.loadingBanner} />
...@@ -45,25 +46,6 @@ const MobileFilterHeader = styled(Row)` ...@@ -45,25 +46,6 @@ const MobileFilterHeader = styled(Row)`
// As a result it needs 16px padding on either side. These paddings are offset by 16px to account for this. Please see CollectionNFTs.css.ts for the additional sizing context. // As a result it needs 16px padding on either side. These paddings are offset by 16px to account for this. Please see CollectionNFTs.css.ts for the additional sizing context.
// See breakpoint values in ScreenBreakpointsPaddings above - they must match // See breakpoint values in ScreenBreakpointsPaddings above - they must match
const CollectionDisplaySection = styled(Row)` const CollectionDisplaySection = styled(Row)`
@media screen and (min-width: ${MAX_WIDTH_MEDIA_BREAKPOINT}) {
padding-left: 48px;
padding-right: 48px;
}
@media screen and (max-width: ${MAX_WIDTH_MEDIA_BREAKPOINT}) {
padding-left: 26px;
padding-right: 26px;
}
@media screen and (max-width: ${SMALL_MEDIA_BREAKPOINT}) {
padding-left: 20px;
padding-right: 20px;
}
@media screen and (max-width: ${MOBILE_MEDIA_BREAKPOINT}) {
padding-left: 16px;
padding-right: 16px;
}
align-items: flex-start; align-items: flex-start;
position: relative; position: relative;
` `
...@@ -134,7 +116,7 @@ const Collection = () => { ...@@ -134,7 +116,7 @@ const Collection = () => {
<Column width="full"> <Column width="full">
{contractAddress ? ( {contractAddress ? (
<> <>
<Box width="full" height={`${COLLECTION_BANNER_HEIGHT}`}> <BannerWrapper width="full">
<Box <Box
as={collectionStats?.bannerImageUrl ? 'img' : 'div'} as={collectionStats?.bannerImageUrl ? 'img' : 'div'}
height="full" height="full"
...@@ -147,7 +129,7 @@ const Collection = () => { ...@@ -147,7 +129,7 @@ const Collection = () => {
className={styles.bannerImage} className={styles.bannerImage}
background="none" background="none"
/> />
</Box> </BannerWrapper>
<CollectionDescriptionSection> <CollectionDescriptionSection>
{collectionStats && ( {collectionStats && (
<CollectionStats stats={collectionStats || ({} as GenieCollection)} isMobile={isMobile} /> <CollectionStats stats={collectionStats || ({} as GenieCollection)} isMobile={isMobile} />
......
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