feat: Web 1854 listed card (#5160)
* init * it's working with jack's card.tsx components * add nft details on cards for view my nfts * listed cards ready for review * remove unnecessary code * updated radius * first round charlie comments * respond all comments * init * fix * color * remove floor price when not on sell mode * remove floor when nft is listed * feat: Web 1858 disabled card 1155 when sell mode is on (#5169) * disabled states + tooltips * remove collection asset changes * popover offset changes * respond to padmini comment * respond to charlie
This commit is contained in:
parent
a95697daf8
commit
f1443671ef
@ -1,7 +1,7 @@
|
|||||||
import { Options, Placement } from '@popperjs/core'
|
import { Options, Placement } from '@popperjs/core'
|
||||||
import Portal from '@reach/portal'
|
import Portal from '@reach/portal'
|
||||||
import useInterval from 'lib/hooks/useInterval'
|
import useInterval from 'lib/hooks/useInterval'
|
||||||
import React, { useCallback, useMemo, useState } from 'react'
|
import React, { CSSProperties, useCallback, useMemo, useState } from 'react'
|
||||||
import { usePopper } from 'react-popper'
|
import { usePopper } from 'react-popper'
|
||||||
import styled from 'styled-components/macro'
|
import styled from 'styled-components/macro'
|
||||||
import { Z_INDEX } from 'theme/zIndex'
|
import { Z_INDEX } from 'theme/zIndex'
|
||||||
@ -76,9 +76,20 @@ export interface PopoverProps {
|
|||||||
show: boolean
|
show: boolean
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
placement?: Placement
|
placement?: Placement
|
||||||
|
offsetX?: number
|
||||||
|
offsetY?: number
|
||||||
|
style?: CSSProperties
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Popover({ content, show, children, placement = 'auto' }: PopoverProps) {
|
export default function Popover({
|
||||||
|
content,
|
||||||
|
show,
|
||||||
|
children,
|
||||||
|
placement = 'auto',
|
||||||
|
offsetX = 8,
|
||||||
|
offsetY = 8,
|
||||||
|
style,
|
||||||
|
}: PopoverProps) {
|
||||||
const [referenceElement, setReferenceElement] = useState<HTMLDivElement | null>(null)
|
const [referenceElement, setReferenceElement] = useState<HTMLDivElement | null>(null)
|
||||||
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null)
|
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null)
|
||||||
const [arrowElement, setArrowElement] = useState<HTMLDivElement | null>(null)
|
const [arrowElement, setArrowElement] = useState<HTMLDivElement | null>(null)
|
||||||
@ -88,12 +99,12 @@ export default function Popover({ content, show, children, placement = 'auto' }:
|
|||||||
placement,
|
placement,
|
||||||
strategy: 'fixed',
|
strategy: 'fixed',
|
||||||
modifiers: [
|
modifiers: [
|
||||||
{ name: 'offset', options: { offset: [8, 8] } },
|
{ name: 'offset', options: { offset: [offsetX, offsetY] } },
|
||||||
{ name: 'arrow', options: { element: arrowElement } },
|
{ name: 'arrow', options: { element: arrowElement } },
|
||||||
{ name: 'preventOverflow', options: { padding: 8 } },
|
{ name: 'preventOverflow', options: { padding: 8 } },
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
[arrowElement, placement]
|
[arrowElement, offsetX, offsetY, placement]
|
||||||
)
|
)
|
||||||
|
|
||||||
const { styles, update, attributes } = usePopper(referenceElement, popperElement, options)
|
const { styles, update, attributes } = usePopper(referenceElement, popperElement, options)
|
||||||
@ -105,7 +116,9 @@ export default function Popover({ content, show, children, placement = 'auto' }:
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ReferenceElement ref={setReferenceElement as any}>{children}</ReferenceElement>
|
<ReferenceElement style={style} ref={setReferenceElement as any}>
|
||||||
|
{children}
|
||||||
|
</ReferenceElement>
|
||||||
<Portal>
|
<Portal>
|
||||||
<PopoverContainer show={show} ref={setPopperElement as any} style={styles.popper} {...attributes.popper}>
|
<PopoverContainer show={show} ref={setPopperElement as any} style={styles.popper} {...attributes.popper}>
|
||||||
{content}
|
{content}
|
||||||
|
@ -42,9 +42,10 @@ export function MouseoverTooltip({ text, disableHover, children, ...rest }: Omit
|
|||||||
const [show, setShow] = useState(false)
|
const [show, setShow] = useState(false)
|
||||||
const open = useCallback(() => setShow(true), [setShow])
|
const open = useCallback(() => setShow(true), [setShow])
|
||||||
const close = useCallback(() => setShow(false), [setShow])
|
const close = useCallback(() => setShow(false), [setShow])
|
||||||
|
const noOp = () => null
|
||||||
return (
|
return (
|
||||||
<Tooltip {...rest} show={show} text={disableHover ? null : text}>
|
<Tooltip {...rest} show={show} text={disableHover ? null : text}>
|
||||||
<div onMouseEnter={open} onMouseLeave={close}>
|
<div onMouseEnter={disableHover ? noOp : open} onMouseLeave={disableHover ? noOp : close}>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
@ -163,10 +163,19 @@ interface CardProps {
|
|||||||
addAssetToBag: () => void
|
addAssetToBag: () => void
|
||||||
removeAssetFromBag: () => void
|
removeAssetFromBag: () => void
|
||||||
children: ReactNode
|
children: ReactNode
|
||||||
|
isDisabled?: boolean
|
||||||
onClick?: () => void
|
onClick?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const Container = ({ asset, selected, addAssetToBag, removeAssetFromBag, children, onClick }: CardProps) => {
|
const Container = ({
|
||||||
|
asset,
|
||||||
|
selected,
|
||||||
|
addAssetToBag,
|
||||||
|
removeAssetFromBag,
|
||||||
|
children,
|
||||||
|
isDisabled,
|
||||||
|
onClick,
|
||||||
|
}: CardProps) => {
|
||||||
const [hovered, toggleHovered] = useReducer((s) => !s, false)
|
const [hovered, toggleHovered] = useReducer((s) => !s, false)
|
||||||
const [href, setHref] = useState(baseHref(asset))
|
const [href, setHref] = useState(baseHref(asset))
|
||||||
|
|
||||||
@ -210,8 +219,9 @@ const Container = ({ asset, selected, addAssetToBag, removeAssetFromBag, childre
|
|||||||
onMouseEnter={() => toggleHovered()}
|
onMouseEnter={() => toggleHovered()}
|
||||||
onMouseLeave={() => toggleHovered()}
|
onMouseLeave={() => toggleHovered()}
|
||||||
transition="250"
|
transition="250"
|
||||||
cursor={asset.notForSale ? 'default' : 'pointer'}
|
opacity={isDisabled ? '0.5' : '1'}
|
||||||
onClick={onClick ?? handleAssetInBag}
|
cursor={isDisabled ? 'default' : 'pointer'}
|
||||||
|
onClick={isDisabled ? () => null : onClick ?? handleAssetInBag}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</Box>
|
</Box>
|
||||||
@ -535,8 +545,14 @@ const ProfileNftDetails = ({ asset, isSellMode }: ProfileNftDetailsProps) => {
|
|||||||
</TruncatedTextRow>
|
</TruncatedTextRow>
|
||||||
{asset.susFlag && <Suspicious />}
|
{asset.susFlag && <Suspicious />}
|
||||||
</Row>
|
</Row>
|
||||||
<TruncatedTextRow className={subhead} style={{ color: themeVars.colors.textSecondary }}>
|
<TruncatedTextRow
|
||||||
{!!asset.floorPrice && isSellMode && <span>{`${floorFormatter(asset.floorPrice)} ETH Floor`}</span>}
|
className={subhead}
|
||||||
|
style={{ color: !asset.notForSale ? themeVars.colors.textPrimary : themeVars.colors.textSecondary }}
|
||||||
|
>
|
||||||
|
{!asset.notForSale && <span>{`${floorFormatter(asset.floor_sell_order_price)} ETH`}</span>}
|
||||||
|
{asset.notForSale && isSellMode && !!asset.floorPrice && (
|
||||||
|
<span>{`${floorFormatter(asset.floorPrice)} ETH Floor`}</span>
|
||||||
|
)}
|
||||||
</TruncatedTextRow>
|
</TruncatedTextRow>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
|
import { Trans } from '@lingui/macro'
|
||||||
|
import { MouseoverTooltip } from 'components/Tooltip'
|
||||||
|
import { Box } from 'nft/components/Box'
|
||||||
import * as Card from 'nft/components/collection/Card'
|
import * as Card from 'nft/components/collection/Card'
|
||||||
import { AssetMediaType } from 'nft/components/collection/Card'
|
import { AssetMediaType } from 'nft/components/collection/Card'
|
||||||
|
import { bodySmall } from 'nft/css/common.css'
|
||||||
|
import { themeVars } from 'nft/css/sprinkles.css'
|
||||||
import { useBag, useIsMobile, useSellAsset } from 'nft/hooks'
|
import { useBag, useIsMobile, useSellAsset } from 'nft/hooks'
|
||||||
import { WalletAsset } from 'nft/types'
|
import { WalletAsset } from 'nft/types'
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
|
||||||
const NFT_DETAILS_HREF = (asset: WalletAsset) =>
|
const NFT_DETAILS_HREF = (asset: WalletAsset) =>
|
||||||
`/nfts/asset/${asset.asset_contract.address}/${asset.tokenId}?origin=profile`
|
`/nfts/asset/${asset.asset_contract.address}/${asset.tokenId}?origin=profile`
|
||||||
|
|
||||||
@ -15,6 +19,21 @@ interface ViewMyNftsAssetProps {
|
|||||||
setCurrentTokenPlayingMedia: (tokenId: string | undefined) => void
|
setCurrentTokenPlayingMedia: (tokenId: string | undefined) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getNftDisplayComponent = (
|
||||||
|
assetMediaType: AssetMediaType,
|
||||||
|
mediaShouldBePlaying: boolean,
|
||||||
|
setCurrentTokenPlayingMedia: (tokenId: string | undefined) => void
|
||||||
|
) => {
|
||||||
|
switch (assetMediaType) {
|
||||||
|
case AssetMediaType.Image:
|
||||||
|
return <Card.Image />
|
||||||
|
case AssetMediaType.Video:
|
||||||
|
return <Card.Video shouldPlay={mediaShouldBePlaying} setCurrentTokenPlayingMedia={setCurrentTokenPlayingMedia} />
|
||||||
|
case AssetMediaType.Audio:
|
||||||
|
return <Card.Audio shouldPlay={mediaShouldBePlaying} setCurrentTokenPlayingMedia={setCurrentTokenPlayingMedia} />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const ViewMyNftsAsset = ({
|
export const ViewMyNftsAsset = ({
|
||||||
asset,
|
asset,
|
||||||
isSellMode,
|
isSellMode,
|
||||||
@ -53,6 +72,10 @@ export const ViewMyNftsAsset = ({
|
|||||||
|
|
||||||
const assetMediaType = Card.useAssetMediaType(asset)
|
const assetMediaType = Card.useAssetMediaType(asset)
|
||||||
|
|
||||||
|
const isDisabled = asset.asset_contract.tokenType === 'ERC1155' || (isSellMode && asset.susFlag)
|
||||||
|
const disabledTooltipText =
|
||||||
|
asset.asset_contract.tokenType === 'ERC1155' ? 'ERC-1155 support coming soon' : 'Blocked from trading'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card.Container
|
<Card.Container
|
||||||
asset={asset}
|
asset={asset}
|
||||||
@ -60,16 +83,24 @@ export const ViewMyNftsAsset = ({
|
|||||||
addAssetToBag={() => handleSelect(false)}
|
addAssetToBag={() => handleSelect(false)}
|
||||||
removeAssetFromBag={() => handleSelect(true)}
|
removeAssetFromBag={() => handleSelect(true)}
|
||||||
onClick={onCardClick}
|
onClick={onCardClick}
|
||||||
|
isDisabled={isDisabled}
|
||||||
>
|
>
|
||||||
<Card.ImageContainer>
|
<MouseoverTooltip
|
||||||
{assetMediaType === AssetMediaType.Image ? (
|
text={
|
||||||
<Card.Image />
|
<Box as="span" className={bodySmall} style={{ color: themeVars.colors.textPrimary }}>
|
||||||
) : assetMediaType === AssetMediaType.Video ? (
|
<Trans>{disabledTooltipText}</Trans>{' '}
|
||||||
<Card.Video shouldPlay={mediaShouldBePlaying} setCurrentTokenPlayingMedia={setCurrentTokenPlayingMedia} />
|
</Box>
|
||||||
) : (
|
}
|
||||||
<Card.Audio shouldPlay={mediaShouldBePlaying} setCurrentTokenPlayingMedia={setCurrentTokenPlayingMedia} />
|
placement="bottom"
|
||||||
)}
|
offsetX={0}
|
||||||
</Card.ImageContainer>
|
offsetY={-100}
|
||||||
|
style={{ display: 'block' }}
|
||||||
|
disableHover={!isDisabled}
|
||||||
|
>
|
||||||
|
<Card.ImageContainer>
|
||||||
|
{getNftDisplayComponent(assetMediaType, mediaShouldBePlaying, setCurrentTokenPlayingMedia)}
|
||||||
|
</Card.ImageContainer>
|
||||||
|
</MouseoverTooltip>
|
||||||
<Card.DetailsContainer>
|
<Card.DetailsContainer>
|
||||||
<Card.ProfileNftDetails asset={asset} isSellMode={isSellMode} />
|
<Card.ProfileNftDetails asset={asset} isSellMode={isSellMode} />
|
||||||
</Card.DetailsContainer>
|
</Card.DetailsContainer>
|
||||||
|
Loading…
Reference in New Issue
Block a user