Commit 77747f9f authored by Jack Short's avatar Jack Short Committed by GitHub

style: updating bag (#5539)

* style: updating bag

* desktop bag

* initial setup

* bag animations

* mobile stylings

* removing vanilla extract components

* background position

* design comments

* screen sizing

* closing filters and bag on appropriate screen sizes

* move screen size to main hooks

* prettier

* bag margins

* footer outline

* correct scrolling indicator width

* collection stats truncation

* merged with main

* collection stats on screen
parent 0622ff30
...@@ -3,7 +3,7 @@ import { breakpoints, sprinkles } from 'nft/css/sprinkles.css' ...@@ -3,7 +3,7 @@ import { breakpoints, sprinkles } from 'nft/css/sprinkles.css'
export const assetsContainer = style([ export const assetsContainer = style([
sprinkles({ sprinkles({
paddingX: '16', paddingX: '20',
maxHeight: 'full', maxHeight: 'full',
overflowY: 'scroll', overflowY: 'scroll',
}), }),
......
...@@ -30,6 +30,7 @@ import { combineBuyItemsWithTxRoute } from 'nft/utils/txRoute/combineItemsWithTx ...@@ -30,6 +30,7 @@ import { combineBuyItemsWithTxRoute } from 'nft/utils/txRoute/combineItemsWithTx
import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useQuery, useQueryClient } from 'react-query' import { useQuery, useQueryClient } from 'react-query'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
import { Z_INDEX } from 'theme/zIndex'
import shallow from 'zustand/shallow' import shallow from 'zustand/shallow'
import * as styles from './Bag.css' import * as styles from './Bag.css'
...@@ -38,22 +39,53 @@ import { BagHeader } from './BagHeader' ...@@ -38,22 +39,53 @@ import { BagHeader } from './BagHeader'
import EmptyState from './EmptyContent' import EmptyState from './EmptyContent'
import { ProfileBagContent } from './profile/ProfileBagContent' import { ProfileBagContent } from './profile/ProfileBagContent'
export const BAG_WIDTH = 320
export const XXXL_BAG_WIDTH = 360
interface SeparatorProps { interface SeparatorProps {
top?: boolean top?: boolean
show?: boolean show?: boolean
} }
const BagContainer = styled.div<{ raiseZIndex: boolean }>`
position: fixed;
display: flex;
flex-direction: column;
top: 88px;
right: 20px;
width: ${BAG_WIDTH}px;
height: calc(100vh - 108px);
background: ${({ theme }) => theme.backgroundSurface};
border: 1px solid ${({ theme }) => theme.backgroundOutline};
border-radius: 16px;
box-shadow: ${({ theme }) => theme.shallowShadow};
z-index: ${({ raiseZIndex }) => (raiseZIndex ? Z_INDEX.modalOverTooltip : 3)};
@media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.sm}px`}) {
right: 0px;
top: 0px;
width: 100%;
height: 100%;
border-radius: 0px;
border: none;
}
@media only screen and (min-width: ${({ theme }) => `${theme.breakpoint.xxxl}px`}) {
width: ${XXXL_BAG_WIDTH}px;
}
`
const DetailsPageBackground = styled.div` const DetailsPageBackground = styled.div`
position: fixed; position: fixed;
background: rgba(0, 0, 0, 0.7); background: rgba(0, 0, 0, 0.7);
top: 72px; top: 0px;
width: 100%; width: 100%;
height: 100%; height: 100%;
` `
const ScrollingIndicator = ({ top, show }: SeparatorProps) => ( const ScrollingIndicator = ({ top, show }: SeparatorProps) => (
<Box <Box
marginX="16" marginX="24"
borderWidth="1px" borderWidth="1px"
borderStyle="solid" borderStyle="solid"
borderColor="transparent" borderColor="transparent"
...@@ -112,7 +144,7 @@ const Bag = () => { ...@@ -112,7 +144,7 @@ const Bag = () => {
const itemsInBag = useMemo(() => recalculateBagUsingPooledAssets(uncheckedItemsInBag), [uncheckedItemsInBag]) const itemsInBag = useMemo(() => recalculateBagUsingPooledAssets(uncheckedItemsInBag), [uncheckedItemsInBag])
const [isOpen, setModalIsOpen] = useState(false) const [isModalOpen, setModalIsOpen] = useState(false)
const [userCanScroll, setUserCanScroll] = useState(false) const [userCanScroll, setUserCanScroll] = useState(false)
const [scrollProgress, setScrollProgress] = useState(0) const [scrollProgress, setScrollProgress] = useState(0)
const scrollRef = (node: HTMLDivElement) => { const scrollRef = (node: HTMLDivElement) => {
...@@ -229,8 +261,8 @@ const Bag = () => { ...@@ -229,8 +261,8 @@ const Bag = () => {
}, []) }, [])
useEffect(() => { useEffect(() => {
if (bagIsLocked && !isOpen) setModalIsOpen(true) if (bagIsLocked && !isModalOpen) setModalIsOpen(true)
}, [bagIsLocked, isOpen]) }, [bagIsLocked, isModalOpen])
useEffect(() => { useEffect(() => {
if (transactionStateRef.current === TxStateType.Confirming) setBagStatus(BagStatus.PROCESSING_TRANSACTION) if (transactionStateRef.current === TxStateType.Confirming) setBagStatus(BagStatus.PROCESSING_TRANSACTION)
...@@ -281,60 +313,57 @@ const Bag = () => { ...@@ -281,60 +313,57 @@ const Bag = () => {
return ( return (
<Portal> <Portal>
{!(isProfilePage && profilePageState === ProfilePageStateType.LISTING) ? ( <BagContainer data-testid="nft-bag" raiseZIndex={isMobile || isModalOpen}>
<Column {!(isProfilePage && profilePageState === ProfilePageStateType.LISTING) ? (
data-testid="nft-bag" <>
zIndex={isMobile || isOpen ? 'modalOverTooltip' : '3'} <BagHeader
className={styles.bagContainer} numberOfAssets={isProfilePage ? sellAssets.length : itemsInBag.length}
> closeBag={handleCloseBag}
<BagHeader resetFlow={isProfilePage ? resetSellAssets : reset}
numberOfAssets={isProfilePage ? sellAssets.length : itemsInBag.length} isProfilePage={isProfilePage}
closeBag={handleCloseBag}
resetFlow={isProfilePage ? resetSellAssets : reset}
isProfilePage={isProfilePage}
/>
{shouldRenderEmptyState && <EmptyState />}
<ScrollingIndicator top show={userCanScroll && scrollProgress > 0} />
<Column ref={scrollRef} className={styles.assetsContainer} onScroll={scrollHandler} gap="12">
{isProfilePage ? <ProfileBagContent /> : <BagContent />}
</Column>
{hasAssetsToShow && !isProfilePage && (
<BagFooter
totalEthPrice={totalEthPrice}
totalUsdPrice={totalUsdPrice}
bagStatus={bagStatus}
fetchAssets={fetchAssets}
eventProperties={eventProperties}
/> />
)} {shouldRenderEmptyState && <EmptyState />}
{isSellingAssets && isProfilePage && ( <ScrollingIndicator top show={userCanScroll && scrollProgress > 0} />
<Box <Column ref={scrollRef} className={styles.assetsContainer} onScroll={scrollHandler} gap="12">
marginTop="32" {isProfilePage ? <ProfileBagContent /> : <BagContent />}
marginX="28" </Column>
paddingY="10" {hasAssetsToShow && !isProfilePage && (
className={`${buttonTextMedium} ${commonButtonStyles}`} <BagFooter
backgroundColor="accentAction" totalEthPrice={totalEthPrice}
color="white" totalUsdPrice={totalUsdPrice}
textAlign="center" bagStatus={bagStatus}
onClick={() => { fetchAssets={fetchAssets}
isMobile && toggleBag() eventProperties={eventProperties}
setProfilePageState(ProfilePageStateType.LISTING) />
}} )}
> {isSellingAssets && isProfilePage && (
Continue <Box
</Box> marginTop="32"
)} marginX="28"
</Column> marginBottom="16"
) : ( paddingY="10"
<Column zIndex={isMobile || isOpen ? 'modalOverTooltip' : '3'} className={styles.bagContainer}> className={`${buttonTextMedium} ${commonButtonStyles}`}
backgroundColor="accentAction"
color="white"
textAlign="center"
onClick={() => {
isMobile && toggleBag()
setProfilePageState(ProfilePageStateType.LISTING)
}}
>
Continue
</Box>
)}
</>
) : (
<ListingModal /> <ListingModal />
</Column> )}
)} </BagContainer>
{isDetailsPage ? ( {isDetailsPage ? (
<DetailsPageBackground onClick={toggleBag} /> <DetailsPageBackground onClick={toggleBag} />
) : ( ) : (
isOpen && <Overlay onClick={() => (!bagIsLocked ? setModalIsOpen(false) : undefined)} /> isModalOpen && <Overlay onClick={() => (!bagIsLocked ? setModalIsOpen(false) : undefined)} />
)} )}
</Portal> </Portal>
) )
......
...@@ -96,7 +96,7 @@ export const BagContent = () => { ...@@ -96,7 +96,7 @@ export const BagContent = () => {
/> />
))} ))}
</Column> </Column>
<Column gap="8"> <Column>
{unchangedAssets {unchangedAssets
.slice(0) .slice(0)
.reverse() .reverse()
......
...@@ -2,10 +2,6 @@ import { style } from '@vanilla-extract/css' ...@@ -2,10 +2,6 @@ import { style } from '@vanilla-extract/css'
import { body } from 'nft/css/common.css' import { body } from 'nft/css/common.css'
import { sprinkles } from 'nft/css/sprinkles.css' import { sprinkles } from 'nft/css/sprinkles.css'
export const footerContainer = sprinkles({
paddingX: '16',
})
export const footer = style([ export const footer = style([
sprinkles({ sprinkles({
background: 'backgroundModule', background: 'backgroundModule',
......
...@@ -22,13 +22,17 @@ import { switchChain } from 'utils/switchChain' ...@@ -22,13 +22,17 @@ import { switchChain } from 'utils/switchChain'
import * as styles from './BagFooter.css' import * as styles from './BagFooter.css'
const FooterContainer = styled.div`
padding: 0px 12px;
`
const Footer = styled.div` const Footer = styled.div`
border-top: 1px solid ${({ theme }) => theme.backgroundOutline}; border-top: 1px solid ${({ theme }) => theme.backgroundOutline};
color: ${({ theme }) => theme.textPrimary}; color: ${({ theme }) => theme.textPrimary};
display: flex; display: flex;
flex-direction: column; flex-direction: column;
margin-bottom: 8px; margin: 0px 16px 8px;
padding: 12px 16px; padding: 12px 0px;
border-bottom-left-radius: 12px; border-bottom-left-radius: 12px;
border-bottom-right-radius: 12px; border-bottom-right-radius: 12px;
` `
...@@ -149,9 +153,9 @@ export const BagFooter = ({ ...@@ -149,9 +153,9 @@ export const BagFooter = ({
const isPending = PENDING_BAG_STATUSES.includes(bagStatus) const isPending = PENDING_BAG_STATUSES.includes(bagStatus)
return ( return (
<Column className={styles.footerContainer}> <FooterContainer>
<Footer> <Footer>
<Column gap="4" paddingTop="8" paddingBottom="20"> <Column gap="4" paddingTop="8" paddingBottom={warningText ? '8' : '20'}>
<Row justifyContent="space-between"> <Row justifyContent="space-between">
<Box> <Box>
<ThemedText.HeadlineSmall>Total</ThemedText.HeadlineSmall> <ThemedText.HeadlineSmall>Total</ThemedText.HeadlineSmall>
...@@ -180,6 +184,6 @@ export const BagFooter = ({ ...@@ -180,6 +184,6 @@ export const BagFooter = ({
</ActionButton> </ActionButton>
</TraceEvent> </TraceEvent>
</Footer> </Footer>
</Column> </FooterContainer>
) )
} }
...@@ -52,7 +52,7 @@ const Wrapper = styled.div` ...@@ -52,7 +52,7 @@ const Wrapper = styled.div`
flex-direction: row; flex-direction: row;
gap: 8px; gap: 8px;
justify-content: flex-start; justify-content: flex-start;
margin: 16px 20px; margin: 16px 28px;
text-align: center; text-align: center;
` `
interface BagHeaderProps { interface BagHeaderProps {
...@@ -82,7 +82,9 @@ export const BagHeader = ({ numberOfAssets, closeBag, resetFlow, isProfilePage } ...@@ -82,7 +82,9 @@ export const BagHeader = ({ numberOfAssets, closeBag, resetFlow, isProfilePage }
{numberOfAssets > 0 && ( {numberOfAssets > 0 && (
<> <>
<CounterDot sizing={sizing}>{numberOfAssets}</CounterDot> <CounterDot sizing={sizing}>{numberOfAssets}</CounterDot>
<ClearButton onClick={resetFlow}>Clear all</ClearButton> <ClearButton onClick={resetFlow}>
<Trans>Clear all</Trans>
</ClearButton>
</> </>
)} )}
<IconWrapper onClick={closeBag}> <IconWrapper onClick={closeBag}>
......
...@@ -13,6 +13,7 @@ import { ...@@ -13,6 +13,7 @@ import {
useLoadSweepAssetsQuery, useLoadSweepAssetsQuery,
} from 'graphql/data/nft/Asset' } from 'graphql/data/nft/Asset'
import useDebounce from 'hooks/useDebounce' import useDebounce from 'hooks/useDebounce'
import { useScreenSize } from 'hooks/useScreenSize'
import { AnimatedBox, Box } from 'nft/components/Box' import { AnimatedBox, Box } from 'nft/components/Box'
import { CollectionSearch, FilterButton } from 'nft/components/collection' import { CollectionSearch, FilterButton } from 'nft/components/collection'
import { CollectionAsset } from 'nft/components/collection/CollectionAsset' import { CollectionAsset } from 'nft/components/collection/CollectionAsset'
...@@ -378,6 +379,7 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie ...@@ -378,6 +379,7 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie
const [isFiltersExpanded, setFiltersExpanded] = useFiltersExpanded() const [isFiltersExpanded, setFiltersExpanded] = useFiltersExpanded()
const oldStateRef = useRef<CollectionFilters | null>(null) const oldStateRef = useRef<CollectionFilters | null>(null)
const isMobile = useIsMobile() const isMobile = useIsMobile()
const screenSize = useScreenSize()
useEffect(() => { useEffect(() => {
setIsCollectionNftsLoading(isLoadingNext) setIsCollectionNftsLoading(isLoadingNext)
...@@ -514,7 +516,10 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie ...@@ -514,7 +516,10 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie
isMobile={isMobile} isMobile={isMobile}
isFiltersExpanded={isFiltersExpanded} isFiltersExpanded={isFiltersExpanded}
collectionCount={collectionAssets?.[0]?.totalCount ?? 0} collectionCount={collectionAssets?.[0]?.totalCount ?? 0}
onClick={() => setFiltersExpanded(!isFiltersExpanded)} onClick={() => {
if (bagExpanded && !screenSize['xl']) toggleBag()
setFiltersExpanded(!isFiltersExpanded)
}}
/> />
</TraceEvent> </TraceEvent>
<SortDropdownContainer isFiltersExpanded={isFiltersExpanded}> <SortDropdownContainer isFiltersExpanded={isFiltersExpanded}>
......
import Column from 'components/Column' import Column from 'components/Column'
import Row from 'components/Row' import Row from 'components/Row'
import { useIsMobile } from 'nft/hooks' import { BAG_WIDTH, XXXL_BAG_WIDTH } from 'nft/components/bag/Bag'
import { useBag, useIsMobile } from 'nft/hooks'
import { BannerWrapper, CollectionBannerLoading } from 'nft/pages/collection' import { BannerWrapper, CollectionBannerLoading } 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'
...@@ -13,8 +14,13 @@ const CollectionDescriptionSection = styled(Column)` ...@@ -13,8 +14,13 @@ const CollectionDescriptionSection = styled(Column)`
${ScreenBreakpointsPaddings} ${ScreenBreakpointsPaddings}
` `
const StyledColumn = styled(Column)` const StyledColumn = styled(Column)<{ isBagExpanded: boolean }>`
width: 100%; width: ${({ isBagExpanded }) => (isBagExpanded ? `calc(100% - ${BAG_WIDTH + 16}px)` : '100%')};
align-self: start;
@media only screen and (min-width: ${({ theme }) => `${theme.breakpoint.xxxl}px`}) {
width: ${({ isBagExpanded }) => (isBagExpanded ? `calc(100% - ${XXXL_BAG_WIDTH + 16}px)` : '100%')};
}
` `
const StyledRow = styled(Row)` const StyledRow = styled(Row)`
...@@ -23,10 +29,12 @@ const StyledRow = styled(Row)` ...@@ -23,10 +29,12 @@ const StyledRow = styled(Row)`
` `
export const CollectionPageSkeleton = () => { export const CollectionPageSkeleton = () => {
const isBagExpanded = useBag((s) => s.bagExpanded)
const isMobile = useIsMobile() const isMobile = useIsMobile()
return ( return (
<StyledColumn> <StyledColumn isBagExpanded={isBagExpanded}>
<BannerWrapper width="full"> <BannerWrapper>
<CollectionBannerLoading /> <CollectionBannerLoading />
</BannerWrapper> </BannerWrapper>
<CollectionDescriptionSection> <CollectionDescriptionSection>
......
import { getDeltaArrow } from 'components/Tokens/TokenDetails/PriceChart' import { getDeltaArrow } from 'components/Tokens/TokenDetails/PriceChart'
import { useScreenSize } from 'hooks/useScreenSize'
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 { body, bodySmall, headlineMedium, headlineSmall } from 'nft/css/common.css' import { body, bodySmall, headlineMedium, headlineSmall } from 'nft/css/common.css'
import { loadingAsset } from 'nft/css/loading.css' import { loadingAsset } from 'nft/css/loading.css'
import { themeVars } from 'nft/css/sprinkles.css' import { themeVars } from 'nft/css/sprinkles.css'
import { useIsMobile } from 'nft/hooks' import { useBag, useIsMobile } from 'nft/hooks'
import { useIsCollectionLoading } from 'nft/hooks/useIsCollectionLoading' import { useIsCollectionLoading } from 'nft/hooks/useIsCollectionLoading'
import { GenieCollection, TokenType } from 'nft/types' import { GenieCollection, TokenType } from 'nft/types'
import { floorFormatter, quantityFormatter, roundWholePercentage, volumeFormatter } from 'nft/utils/numbers' import { floorFormatter, quantityFormatter, roundWholePercentage, volumeFormatter } from 'nft/utils/numbers'
...@@ -353,6 +354,10 @@ const StatsRow = ({ stats, isMobile, ...props }: { stats: GenieCollection; isMob ...@@ -353,6 +354,10 @@ const StatsRow = ({ stats, isMobile, ...props }: { stats: GenieCollection; isMob
const floorChangeStr = Math.round(Math.abs(stats?.stats?.one_day_floor_change ?? 0)) const floorChangeStr = Math.round(Math.abs(stats?.stats?.one_day_floor_change ?? 0))
const arrow = stats?.stats?.one_day_floor_change ? getDeltaArrow(stats.stats.one_day_floor_change) : undefined const arrow = stats?.stats?.one_day_floor_change ? getDeltaArrow(stats.stats.one_day_floor_change) : undefined
const isBagExpanded = useBag((state) => state.bagExpanded)
const isScreenSize = useScreenSize()
const isSmallContainer = isMobile || (!isScreenSize['lg'] && isBagExpanded)
return ( return (
<Row gap={{ sm: '24', md: '36', lg: '48', xl: '60' }} {...props}> <Row gap={{ sm: '24', md: '36', lg: '48', xl: '60' }} {...props}>
{isCollectionStatsLoading ? ( {isCollectionStatsLoading ? (
...@@ -383,13 +388,13 @@ const StatsRow = ({ stats, isMobile, ...props }: { stats: GenieCollection; isMob ...@@ -383,13 +388,13 @@ const StatsRow = ({ stats, isMobile, ...props }: { stats: GenieCollection; isMob
</StatsItem> </StatsItem>
) : null} ) : null}
{Boolean(uniqueOwnersPercentage && stats.standard !== TokenType.ERC1155) ? ( {Boolean(uniqueOwnersPercentage && stats.standard !== TokenType.ERC1155) ? (
<StatsItem label="Unique owners" shouldHide={isMobile ?? false}> <StatsItem label="Unique owners" shouldHide={isSmallContainer ?? false}>
{uniqueOwnersPercentage}% {uniqueOwnersPercentage}%
</StatsItem> </StatsItem>
) : null} ) : null}
{stats.stats?.total_listings && stats.standard !== TokenType.ERC1155 ? ( {stats.stats?.total_listings && stats.standard !== TokenType.ERC1155 ? (
<StatsItem label="Listed" shouldHide={isMobile ?? false}> <StatsItem label="Listed" shouldHide={isSmallContainer ?? false}>
{listedPercentageStr}% {listedPercentageStr}%
</StatsItem> </StatsItem>
) : null} ) : null}
...@@ -463,7 +468,7 @@ export const CollectionStats = ({ stats, isMobile }: { stats: GenieCollection; i ...@@ -463,7 +468,7 @@ export const CollectionStats = ({ stats, isMobile }: { stats: GenieCollection; i
{(stats.description || isCollectionStatsLoading) && !isMobile && ( {(stats.description || isCollectionStatsLoading) && !isMobile && (
<CollectionDescription description={stats.description ?? ''} /> <CollectionDescription description={stats.description ?? ''} />
)} )}
<StatsRow display={{ sm: 'none', md: 'flex' }} stats={stats} marginTop="20" /> <StatsRow display={{ sm: 'none', md: 'flex' }} overflow="hidden" stats={stats} marginTop="20" />
</Box> </Box>
{(stats.description || isCollectionStatsLoading) && isMobile && ( {(stats.description || isCollectionStatsLoading) && isMobile && (
<CollectionDescription description={stats.description ?? ''} /> <CollectionDescription description={stats.description ?? ''} />
......
...@@ -6,8 +6,6 @@ import { css } from 'styled-components/macro' ...@@ -6,8 +6,6 @@ import { css } from 'styled-components/macro'
import { sprinkles } from '../../css/sprinkles.css' import { sprinkles } from '../../css/sprinkles.css'
export const bannerImage = style({ objectFit: 'cover' })
export const baseActivitySwitcherToggle = style([ export const baseActivitySwitcherToggle = style([
buttonTextMedium, buttonTextMedium,
sprinkles({ sprinkles({
......
import { Trace } from '@uniswap/analytics' 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 Column from 'components/Column'
import { OpacityHoverState } from 'components/Common' import { OpacityHoverState } from 'components/Common'
import Row from 'components/Row'
import { LoadingBubble } from 'components/Tokens/loading'
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 { useScreenSize } from 'hooks/useScreenSize'
import { BAG_WIDTH, XXXL_BAG_WIDTH } from 'nft/components/bag/Bag'
import { MobileHoverBag } from 'nft/components/bag/MobileHoverBag' import { MobileHoverBag } from 'nft/components/bag/MobileHoverBag'
import { AnimatedBox, Box } from 'nft/components/Box'
import { Activity, ActivitySwitcher, CollectionNfts, CollectionStats, Filters } from 'nft/components/collection' import { Activity, ActivitySwitcher, CollectionNfts, CollectionStats, Filters } from 'nft/components/collection'
import { CollectionNftsAndMenuLoading } from 'nft/components/collection/CollectionNfts' import { CollectionNftsAndMenuLoading } from 'nft/components/collection/CollectionNfts'
import { CollectionPageSkeleton } from 'nft/components/collection/CollectionPageSkeleton' import { CollectionPageSkeleton } from 'nft/components/collection/CollectionPageSkeleton'
import { Column, Row } from 'nft/components/Flex'
import { BagCloseIcon } from 'nft/components/icons' import { BagCloseIcon } from 'nft/components/icons'
import { useBag, useCollectionFilters, useFiltersExpanded, useIsMobile } from 'nft/hooks' import { useBag, useCollectionFilters, useFiltersExpanded, useIsMobile } from 'nft/hooks'
import * as styles from 'nft/pages/collection/index.css' import * as styles from 'nft/pages/collection/index.css'
import { GenieCollection } from 'nft/types' import { GenieCollection } from 'nft/types'
import { Suspense, useEffect } from 'react' import { Suspense, useEffect } from 'react'
import { useLocation, useNavigate, useParams } from 'react-router-dom' import { useLocation, useNavigate, useParams } from 'react-router-dom'
import { easings, useSpring } from 'react-spring' import { animated, easings, useSpring } from 'react-spring'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
import { ThemedText } from 'theme' import { ThemedText } from 'theme'
import { TRANSITION_DURATIONS } from 'theme/styles' import { TRANSITION_DURATIONS } from 'theme/styles'
import { Z_INDEX } from 'theme/zIndex'
const FILTER_WIDTH = 332 const FILTER_WIDTH = 332
const BAG_WIDTH = 324
export const BannerWrapper = styled(Box)` export const CollectionBannerLoading = styled(LoadingBubble)`
width: 100%;
height: 100%;
border-radius: 0px;
@media screen and (min-width: ${({ theme }) => theme.breakpoint.sm}px) {
border-radius: 16px;
}
`
const CollectionContainer = styled(Column)`
width: 100%;
align-self: start;
will-change: width;
`
const AnimatedCollectionContainer = animated(CollectionContainer)
const CollectionAssetsContainer = styled.div<{ hideUnderneath: boolean }>`
position: ${({ hideUnderneath }) => (hideUnderneath ? 'fixed' : 'static')};
`
const AnimatedCollectionAssetsContainer = animated(CollectionAssetsContainer)
export const BannerWrapper = styled.div`
height: 100px; height: 100px;
max-width: 100%;
@media screen and (min-width: ${({ theme }) => theme.breakpoint.sm}px) { @media screen and (min-width: ${({ theme }) => theme.breakpoint.sm}px) {
margin-top: 16px;
margin-left: 20px;
margin-right: 20px;
height: 288px; height: 288px;
} }
` `
export const CollectionBannerLoading = () => <Box height="full" width="full" className={styles.loadingBanner} /> const Banner = styled.div<{ src: string }>`
height: 100%;
width: 100%;
background-image: url(${({ src }) => src});
background-position-y: center;
background-size: cover;
@media screen and (min-width: ${({ theme }) => theme.breakpoint.sm}px) {
border-radius: 16px;
}
`
const CollectionDescriptionSection = styled(Column)` const CollectionDescriptionSection = styled(Column)`
${styles.ScreenBreakpointsPaddings} ${styles.ScreenBreakpointsPaddings}
` `
const FiltersContainer = styled.div<{ isMobile: boolean; isFiltersExpanded: boolean }>`
position: ${({ isMobile }) => (isMobile ? 'fixed' : 'sticky')};
top: 0px;
left: 0px;
width: ${({ isMobile }) => (isMobile ? '100%' : '0px')};
height: ${({ isMobile, isFiltersExpanded }) => (isMobile && isFiltersExpanded ? '100%' : undefined)};
background: ${({ theme, isMobile }) => (isMobile ? theme.backgroundBackdrop : undefined)};
z-index: ${Z_INDEX.modalBackdrop};
overflow-y: ${({ isMobile }) => (isMobile ? 'scroll' : undefined)};
@media screen and (min-width: ${({ theme }) => theme.breakpoint.sm}px) {
top: 72px;
}
`
const MobileFilterHeader = styled(Row)` const MobileFilterHeader = styled(Row)`
padding: 20px 16px; padding: 20px 16px;
justify-content: space-between; justify-content: space-between;
...@@ -74,19 +131,21 @@ const Collection = () => { ...@@ -74,19 +131,21 @@ const Collection = () => {
const isBagExpanded = useBag((state) => state.bagExpanded) const isBagExpanded = useBag((state) => state.bagExpanded)
const setBagExpanded = useBag((state) => state.setBagExpanded) const setBagExpanded = useBag((state) => state.setBagExpanded)
const { chainId } = useWeb3React() const { chainId } = useWeb3React()
const screenSize = useScreenSize()
const collectionStats = useCollectionQuery(contractAddress as string) const collectionStats = useCollectionQuery(contractAddress as string)
const { gridX, gridWidthOffset } = useSpring({ const { CollectionContainerWidthChange } = useSpring({
gridX: isFiltersExpanded && !isMobile ? FILTER_WIDTH : 0, CollectionContainerWidthChange:
gridWidthOffset: isBagExpanded && !isMobile ? (screenSize['xxxl'] ? XXXL_BAG_WIDTH : BAG_WIDTH) + 16 : 0,
isFiltersExpanded && !isMobile config: {
? isBagExpanded duration: TRANSITION_DURATIONS.medium,
? BAG_WIDTH + FILTER_WIDTH easing: easings.easeOutSine,
: FILTER_WIDTH },
: isBagExpanded && !isMobile })
? BAG_WIDTH
: 0, const { gridWidthOffset } = useSpring({
gridWidthOffset: isFiltersExpanded && !isMobile ? FILTER_WIDTH : 0,
config: { config: {
duration: TRANSITION_DURATIONS.medium, duration: TRANSITION_DURATIONS.medium,
easing: easings.easeOutSine, easing: easings.easeOutSine,
...@@ -101,6 +160,10 @@ const Collection = () => { ...@@ -101,6 +160,10 @@ const Collection = () => {
setMarketCount(marketCount) setMarketCount(marketCount)
}, [collectionStats?.marketplaceCount, setMarketCount]) }, [collectionStats?.marketplaceCount, setMarketCount])
useEffect(() => {
if (isBagExpanded && isFiltersExpanded && !screenSize['xl']) setFiltersExpanded(false)
}, [isBagExpanded, isFiltersExpanded, screenSize, setFiltersExpanded])
useEffect(() => { useEffect(() => {
setBagExpanded({ bagExpanded: false, manualClose: false }) setBagExpanded({ bagExpanded: false, manualClose: false })
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
...@@ -119,21 +182,18 @@ const Collection = () => { ...@@ -119,21 +182,18 @@ const Collection = () => {
properties={{ collection_address: contractAddress, chain_id: chainId, is_activity_view: isActivityToggled }} properties={{ collection_address: contractAddress, chain_id: chainId, is_activity_view: isActivityToggled }}
shouldLogImpression shouldLogImpression
> >
<Column width="full"> <AnimatedCollectionContainer
style={{
width: CollectionContainerWidthChange.to((x) => `calc(100% - ${x as number}px)`),
}}
>
{contractAddress ? ( {contractAddress ? (
<> <>
<BannerWrapper width="full"> <BannerWrapper>
<Box <Banner
as={collectionStats?.bannerImageUrl ? 'img' : 'div'}
height="full"
width="full"
src={ src={
collectionStats?.bannerImageUrl collectionStats?.bannerImageUrl ? `${collectionStats.bannerImageUrl}?w=${window.innerWidth}` : ''
? `${collectionStats.bannerImageUrl}?w=${window.innerWidth}`
: undefined
} }
className={styles.bannerImage}
background="none"
/> />
</BannerWrapper> </BannerWrapper>
<CollectionDescriptionSection> <CollectionDescriptionSection>
...@@ -150,16 +210,7 @@ const Collection = () => { ...@@ -150,16 +210,7 @@ const Collection = () => {
/> />
</CollectionDescriptionSection> </CollectionDescriptionSection>
<CollectionDisplaySection> <CollectionDisplaySection>
<Box <FiltersContainer isMobile={isMobile} isFiltersExpanded={isFiltersExpanded}>
position={isMobile ? 'fixed' : 'sticky'}
top={{ sm: '0', md: '72' }}
left="0"
width={isMobile ? 'full' : '0'}
height={isMobile && isFiltersExpanded ? 'full' : undefined}
background={isMobile ? 'backgroundBackdrop' : undefined}
zIndex="modalBackdrop"
overflowY={isMobile ? 'scroll' : undefined}
>
{isFiltersExpanded && ( {isFiltersExpanded && (
<> <>
{isMobile && ( {isMobile && (
...@@ -173,13 +224,12 @@ const Collection = () => { ...@@ -173,13 +224,12 @@ const Collection = () => {
<Filters traitsByGroup={collectionStats?.traits ?? {}} /> <Filters traitsByGroup={collectionStats?.traits ?? {}} />
</> </>
)} )}
</Box> </FiltersContainer>
{/* @ts-ignore: https://github.com/microsoft/TypeScript/issues/34933 */} <AnimatedCollectionAssetsContainer
<AnimatedBox hideUnderneath={isMobile && (isFiltersExpanded || isBagExpanded)}
position={isMobile && (isFiltersExpanded || isBagExpanded) ? 'fixed' : 'static'}
style={{ style={{
transform: gridX.to((x) => `translate(${x as number}px)`), transform: gridWidthOffset.to((x) => `translate(${x as number}px)`),
width: gridWidthOffset.to((x) => `calc(100% - ${x as number}px)`), width: gridWidthOffset.to((x) => `calc(100% - ${x as number}px)`),
}} }}
> >
...@@ -202,14 +252,13 @@ const Collection = () => { ...@@ -202,14 +252,13 @@ const Collection = () => {
/> />
</Suspense> </Suspense>
)} )}
</AnimatedBox> </AnimatedCollectionAssetsContainer>
</CollectionDisplaySection> </CollectionDisplaySection>
</> </>
) : ( ) : (
// TODO: Put no collection asset page here
<div className={styles.noCollectionAssets}>No collection assets exist at this address</div> <div className={styles.noCollectionAssets}>No collection assets exist at this address</div>
)} )}
</Column> </AnimatedCollectionContainer>
</Trace> </Trace>
<MobileHoverBag /> <MobileHoverBag />
</> </>
......
...@@ -10,7 +10,6 @@ import { Box } from 'nft/components/Box' ...@@ -10,7 +10,6 @@ import { Box } from 'nft/components/Box'
import { CollectionPageSkeleton } from 'nft/components/collection/CollectionPageSkeleton' import { CollectionPageSkeleton } from 'nft/components/collection/CollectionPageSkeleton'
import { AssetDetailsLoading } from 'nft/components/details/AssetDetailsLoading' import { AssetDetailsLoading } from 'nft/components/details/AssetDetailsLoading'
import { ProfilePageLoadingSkeleton } from 'nft/components/profile/view/ProfilePageLoadingSkeleton' import { ProfilePageLoadingSkeleton } from 'nft/components/profile/view/ProfilePageLoadingSkeleton'
import { useBag } from 'nft/hooks'
import { lazy, Suspense, useEffect, useState } from 'react' import { lazy, Suspense, useEffect, useState } from 'react'
import { Navigate, Route, Routes, useLocation } from 'react-router-dom' import { Navigate, Route, Routes, useLocation } from 'react-router-dom'
import { useIsDarkMode } from 'state/user/hooks' import { useIsDarkMode } from 'state/user/hooks'
...@@ -201,9 +200,7 @@ export default function App() { ...@@ -201,9 +200,7 @@ export default function App() {
return () => window.removeEventListener('scroll', scrollListener) return () => window.removeEventListener('scroll', scrollListener)
}, []) }, [])
const isBagExpanded = useBag((state) => state.bagExpanded) const isHeaderTransparent = !scrolledState
const isHeaderTransparent = !scrolledState && !isBagExpanded
const landingPageFlag = useLandingPageFlag() const landingPageFlag = useLandingPageFlag()
......
...@@ -11,4 +11,5 @@ export enum Z_INDEX { ...@@ -11,4 +11,5 @@ export enum Z_INDEX {
modal = 1060, modal = 1060,
popover = 1070, popover = 1070,
tooltip = 1080, tooltip = 1080,
modalOverTooltip = 1090,
} }
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