feat: [ListV2] Changes to Multiple Market handling and row hovering (#5953)

* add overlay to row hover

* add remove icon on hover

* working expand and collapse icons

* fixed margin

* don't show collpase on nonhovered rows

* display mutliple markets

* remove market

* 0 margin divider

* hide on mobile

* better mobile check

* margin adjustment and hover border radius

* address comments

---------

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
This commit is contained in:
Charles Bachmeier 2023-02-09 11:58:59 -08:00 committed by GitHub
parent 12df4b3981
commit 0208ccd7d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 133 additions and 83 deletions

@ -74,10 +74,6 @@ const ListingHeader = styled(Row)`
const GridWrapper = styled.div`
margin-top: 24px;
margin-bottom: 48px;
@media screen and (min-width: ${SMALL_MEDIA_BREAKPOINT}) {
margin-left: 40px;
}
`
const MobileListButtonWrapper = styled.div`

@ -3,12 +3,13 @@ import { t } from '@lingui/macro'
import Column from 'components/Column'
import Row from 'components/Row'
import { MouseoverTooltip } from 'components/Tooltip'
import { RowsCollpsedIcon, RowsExpandedIcon } from 'nft/components/icons'
import { useSellAsset } from 'nft/hooks'
import { ListingMarket, ListingWarning, WalletAsset } from 'nft/types'
import { LOOKS_RARE_CREATOR_BASIS_POINTS } from 'nft/utils'
import { formatEth, formatUsdPrice } from 'nft/utils/currency'
import { fetchPrice } from 'nft/utils/fetchPrice'
import { Dispatch, useEffect, useMemo, useState } from 'react'
import React, { Dispatch, DispatchWithoutAction, useEffect, useMemo, useReducer, useState } from 'react'
import styled from 'styled-components/macro'
import { BREAKPOINTS, ThemedText } from 'theme'
@ -28,20 +29,45 @@ const PastPriceInfo = styled(Column)`
`
const RemoveMarketplaceWrap = styled(RemoveIconWrap)`
top: 11px;
top: 8px;
left: 16px;
z-index: 3;
`
const MarketIconsWrapper = styled(Row)`
position: relative;
margin-right: 12px;
width: 44px;
justify-content: flex-end;
@media screen and (max-width: ${BREAKPOINTS.sm}px) {
display: none;
}
`
const MarketIconWrapper = styled(Column)`
position: relative;
cursor: pointer;
margin-right: 16px;
`
const MarketIcon = styled.img`
width: 28px;
height: 28px;
const MarketIcon = styled.img<{ index: number }>`
width: 20px;
height: 20px;
border-radius: 4px;
object-fit: cover;
z-index: ${({ index }) => 2 - index};
margin-left: ${({ index }) => `${index === 0 ? 0 : -8}px`};
outline: 1px solid ${({ theme }) => theme.backgroundInteractive};
`
const ExpandMarketIconWrapper = styled.div`
cursor: pointer;
margin-left: 4px;
height: 28px;
@media screen and (max-width: ${BREAKPOINTS.sm}px) {
display: none;
}
`
const FeeColumnWrapper = styled(Column)`
@ -84,6 +110,8 @@ interface MarketplaceRowProps {
asset: WalletAsset
showMarketplaceLogo: boolean
expandMarketplaceRows?: boolean
rowHovered?: boolean
toggleExpandMarketplaceRows: DispatchWithoutAction
}
export const MarketplaceRow = ({
@ -95,14 +123,16 @@ export const MarketplaceRow = ({
asset,
showMarketplaceLogo,
expandMarketplaceRows,
toggleExpandMarketplaceRows,
rowHovered,
}: MarketplaceRowProps) => {
const [listPrice, setListPrice] = useState<number>()
const [globalOverride, setGlobalOverride] = useState(false)
const showGlobalPrice = globalPriceMethod === SetPriceMethod.SAME_PRICE && !globalOverride && globalPrice
const setAssetListPrice = useSellAsset((state) => state.setAssetListPrice)
const removeAssetMarketplace = useSellAsset((state) => state.removeAssetMarketplace)
const [hovered, setHovered] = useState(false)
const handleHover = () => setHovered(!hovered)
const [marketIconHovered, toggleMarketIconHovered] = useReducer((s) => !s, false)
const [marketRowHovered, toggleMarketRowHovered] = useReducer((s) => !s, false)
const price = showGlobalPrice ? globalPrice : listPrice
@ -186,7 +216,7 @@ export const MarketplaceRow = ({
}
return (
<Row>
<Row onMouseEnter={toggleMarketRowHovered} onMouseLeave={toggleMarketRowHovered}>
<PastPriceInfo>
<ThemedText.BodySmall color="textSecondary" lineHeight="20px">
{asset.floorPrice ? `${asset.floorPrice.toFixed(3)} ETH` : '-'}
@ -198,22 +228,25 @@ export const MarketplaceRow = ({
</ThemedText.BodySmall>
</PastPriceInfo>
<Row flex="2">
{showMarketplaceLogo && (
<MarketIconWrapper
onMouseEnter={handleHover}
onMouseLeave={handleHover}
onClick={(e) => {
e.stopPropagation()
removeAssetMarketplace(asset, selectedMarkets[0])
removeMarket && removeMarket()
}}
>
<MarketIcon alt={selectedMarkets[0].name} src={selectedMarkets[0].icon} />
<RemoveMarketplaceWrap hovered={hovered}>
<img width="32px" src="/nft/svgs/minusCircle.svg" alt="Remove item" />
</RemoveMarketplaceWrap>
</MarketIconWrapper>
<Row flex="3">
{(expandMarketplaceRows || selectedMarkets.length > 1) && (
<MarketIconsWrapper onMouseEnter={toggleMarketIconHovered} onMouseLeave={toggleMarketIconHovered}>
{selectedMarkets.map((market, index) => (
<MarketIconWrapper
key={market.name + asset.collection?.address + asset.tokenId}
onClick={(e) => {
e.stopPropagation()
removeAssetMarketplace(asset, selectedMarkets[0])
removeMarket && removeMarket()
}}
>
<MarketIcon alt={selectedMarkets[0].name} src={market.icon} index={index} />
<RemoveMarketplaceWrap hovered={marketIconHovered && (expandMarketplaceRows ?? false)}>
<img width="20px" src="/nft/svgs/minusCircle.svg" alt="Remove item" />
</RemoveMarketplaceWrap>
</MarketIconWrapper>
))}
</MarketIconsWrapper>
)}
{globalPriceMethod === SetPriceMethod.SAME_PRICE && !globalOverride ? (
<PriceTextInput
@ -224,7 +257,6 @@ export const MarketplaceRow = ({
globalOverride={globalOverride}
warning={warning}
asset={asset}
shrink={expandMarketplaceRows}
/>
) : (
<PriceTextInput
@ -235,9 +267,13 @@ export const MarketplaceRow = ({
globalOverride={globalOverride}
warning={warning}
asset={asset}
shrink={expandMarketplaceRows}
/>
)}
{rowHovered && ((expandMarketplaceRows && marketRowHovered) || selectedMarkets.length > 1) && (
<ExpandMarketIconWrapper onClick={toggleExpandMarketplaceRows}>
{expandMarketplaceRows ? <RowsExpandedIcon /> : <RowsCollpsedIcon />}
</ExpandMarketIconWrapper>
)}
</Row>
<FeeColumnWrapper>

@ -1,19 +1,42 @@
import Column from 'components/Column'
import Row from 'components/Row'
import { RowsCollpsedIcon, RowsExpandedIcon, VerifiedIcon } from 'nft/components/icons'
import { VerifiedIcon } from 'nft/components/icons'
import { useSellAsset } from 'nft/hooks'
import { ListingMarket, WalletAsset } from 'nft/types'
import { Dispatch, useEffect, useState } from 'react'
import styled, { css } from 'styled-components/macro'
import { Dispatch, useEffect, useReducer, useState } from 'react'
import { Trash2 } from 'react-feather'
import styled, { css, useTheme } from 'styled-components/macro'
import { BREAKPOINTS, ThemedText } from 'theme'
import { opacify } from 'theme/utils'
import { MarketplaceRow } from './MarketplaceRow'
import { SetPriceMethod } from './NFTListingsGrid'
import { RemoveIconWrap } from './shared'
const NFTListRowWrapper = styled(Row)`
margin: 24px 0px;
padding: 24px 0px;
align-items: center;
border-radius: 8px;
&:hover {
background: ${({ theme }) => opacify(24, theme.backgroundOutline)};
}
`
const RemoveIconContainer = styled.div`
width: 48px;
height: 44px;
padding-left: 12px;
align-self: flex-start;
align-items: center;
display: flex;
@media screen and (max-width: ${BREAKPOINTS.sm}px) {
display: none;
}
&:hover {
opacity: ${({ theme }) => theme.opacity.hover};
}
`
const NFTInfoWrapper = styled(Row)`
@ -23,27 +46,11 @@ const NFTInfoWrapper = styled(Row)`
margin-bottom: auto;
`
const ExpandMarketIconWrapper = styled.div`
cursor: pointer;
margin-right: 8px;
`
const NFTImageWrapper = styled.div`
position: relative;
cursor: pointer;
height: 48px;
margin-right: 8px;
`
const NFTImage = styled.img`
width: 48px;
height: 48px;
border-radius: 8px;
`
const RemoveIcon = styled.img`
width: 32px;
height: 32px;
margin-right: 8px;
`
const HideTextOverflow = css`
@ -74,6 +81,7 @@ const CollectionName = styled(ThemedText.BodySmall)`
const MarketPlaceRowWrapper = styled(Column)`
gap: 24px;
flex: 1.5;
margin-right: 12px;
@media screen and (min-width: ${BREAKPOINTS.md}px) {
flex: 2;
@ -103,37 +111,41 @@ export const NFTListRow = ({
setGlobalPrice,
selectedMarkets,
}: NFTListRowProps) => {
const [expandMarketplaceRows, setExpandMarketplaceRows] = useState(false)
const [expandMarketplaceRows, toggleExpandMarketplaceRows] = useReducer((s) => !s, false)
const removeAsset = useSellAsset((state) => state.removeSellAsset)
const [localMarkets, setLocalMarkets] = useState([])
const [hovered, setHovered] = useState(false)
const handleHover = () => setHovered(!hovered)
const [localMarkets, setLocalMarkets] = useState<ListingMarket[]>([])
const [hovered, toggleHovered] = useReducer((s) => !s, false)
const theme = useTheme()
useEffect(() => {
setLocalMarkets(JSON.parse(JSON.stringify(selectedMarkets)))
selectedMarkets.length < 2 && setExpandMarketplaceRows(false)
}, [selectedMarkets])
selectedMarkets.length < 2 && expandMarketplaceRows && toggleExpandMarketplaceRows()
}, [expandMarketplaceRows, selectedMarkets])
return (
<NFTListRowWrapper>
<NFTInfoWrapper>
{localMarkets.length > 1 && (
<ExpandMarketIconWrapper onClick={() => setExpandMarketplaceRows(!expandMarketplaceRows)}>
{expandMarketplaceRows ? <RowsExpandedIcon /> : <RowsCollpsedIcon />}
</ExpandMarketIconWrapper>
<NFTListRowWrapper
onMouseEnter={() => {
!hovered && toggleHovered()
}}
onMouseLeave={() => {
hovered && toggleHovered()
}}
>
<RemoveIconContainer>
{hovered && (
<Trash2
size={20}
color={theme.textSecondary}
cursor="pointer"
onClick={() => {
removeAsset(asset)
}}
/>
)}
<NFTImageWrapper
onMouseEnter={handleHover}
onMouseLeave={handleHover}
onClick={() => {
removeAsset(asset)
}}
>
<RemoveIconWrap hovered={hovered}>
<RemoveIcon src="/nft/svgs/minusCircle.svg" alt="Remove item" />
</RemoveIconWrap>
<NFTImage alt={asset.name} src={asset.imageUrl || '/nft/svgs/image-placeholder.svg'} />
</NFTImageWrapper>
</RemoveIconContainer>
<NFTInfoWrapper>
<NFTImage alt={asset.name} src={asset.imageUrl || '/nft/svgs/image-placeholder.svg'} />
<TokenInfoWrapper>
<TokenName>{asset.name ? asset.name : `#${asset.tokenId}`}</TokenName>
<CollectionName>
@ -154,8 +166,10 @@ export const NFTListRow = ({
removeMarket={() => localMarkets.splice(index, 1)}
asset={asset}
showMarketplaceLogo={true}
key={index}
key={asset.name + market.name}
expandMarketplaceRows={expandMarketplaceRows}
rowHovered={hovered}
toggleExpandMarketplaceRows={toggleExpandMarketplaceRows}
/>
)
})
@ -167,6 +181,8 @@ export const NFTListRow = ({
selectedMarkets={localMarkets}
asset={asset}
showMarketplaceLogo={false}
rowHovered={hovered}
toggleExpandMarketplaceRows={toggleExpandMarketplaceRows}
/>
)}
</MarketPlaceRowWrapper>

@ -26,6 +26,10 @@ const TableHeader = styled.div`
font-size: 14px;
font-weight: normal;
line-height: 20px;
@media screen and (min-width: ${BREAKPOINTS.sm}px) {
margin-left: 48px;
}
`
const NFTHeader = styled.div`
@ -34,10 +38,7 @@ const NFTHeader = styled.div`
const PriceHeaders = styled(Row)`
flex: 1.5;
@media screen and (min-width: ${BREAKPOINTS.md}px) {
flex: 2;
}
margin-right: 12px;
@media screen and (min-width: ${BREAKPOINTS.md}px) {
flex: 3;
@ -54,7 +55,7 @@ const PriceInfoHeader = styled.div`
`
const DropdownAndHeaderWrapper = styled(Row)`
flex: 2;
flex: 3;
gap: 4px;
`
@ -125,6 +126,7 @@ const RowDivider = styled.hr`
border-radius: 20px;
border-width: 0.5px;
border-style: solid;
margin: 0;
border-color: ${({ theme }) => theme.backgroundInteractive};
`