feat: add tooltips when adding and removing from nft bag (#5222)
* init * make sure tooltips dont appear during sweeps * fixes comments from charlie * style: example tooltip (#5238) * refactor: remove unused CollectionProfile (#5229) * ex * centering * fix: updating bag to not remove nfts on click (#5224) * updating bag to not remove nfts on click * chore: bump redux-multicall (#5211) * chore: bump redux-multicall * fix: updgrade multicall Co-authored-by: Zach Pomerantz <zzmp@uniswap.org> * refactor: remove unused nft utils (#5239) * fix: remove Select All and Sell buttons in Profile (#5228) * refactor: remove isSellMode in ProfilePage * yes * fix(token-details): remove balance summary links to current page (#5223) * refactor: rm remaining unused nft code (#5243) * chore: enable jsx-curly-brace-presence and autofix (#5242) * fix: limit max volume change value to 9999% (#5227) * fix web-2246 * chore: change to > * chore: use single component * fix: approve button font size (#5187) * fix: approve button font size Co-authored-by: 0xsaranonearth <saran.s@pillow.fund> * fix: don't include accentActiveSoft background on navicon active state (#5240) don't include accentActiveSoft background on navicon active state * feat: render blurred collection cover photo in the header (#5233) * initial commit * feat: blurred header * chore: replace with helper * chore: cleanup * chore: different extension * chore: layout tweaks * chore: tweaks * chore: prevent weird text selection on double click * chore: wip for linear gradient/plain color light mode * feat: linear-gradient when image missing * chore: clean up post merge * feat: different opacity for dark/light mode * chore: fix paddings * refactor: remove unused nft css (#5241) * refactor: remove unused nft css * unused * unused * refactor: remove unused isSellMode, setIsSellMode in useSellAsset (#5236) * refactor: remove unused isSellMode, setIsSellMode in useSellAsset * rm * fix: reverting navbar change (#5237) * reverting mobile navbar changes Co-authored-by: vignesh mohankumar <vignesh@vigneshmohankumar.com> Co-authored-by: aballerr <alex.ball@uniswap.org> Co-authored-by: Zach Pomerantz <zzmp@uniswap.org> Co-authored-by: Jordan Frankfurt <jordanwfrankfurt@gmail.com> Co-authored-by: Mike Grabowski <grabbou@gmail.com> Co-authored-by: S A R A N <44068102+saranonearth@users.noreply.github.com> Co-authored-by: 0xsaranonearth <saran.s@pillow.fund> Co-authored-by: Lynn Yu <lynn.yu@uniswap.org> Co-authored-by: Jack Short <john.short.tj@gmail.com> Co-authored-by: vignesh mohankumar <vignesh@vigneshmohankumar.com> Co-authored-by: aballerr <alex.ball@uniswap.org> Co-authored-by: Zach Pomerantz <zzmp@uniswap.org> Co-authored-by: Jordan Frankfurt <jordanwfrankfurt@gmail.com> Co-authored-by: Mike Grabowski <grabbou@gmail.com> Co-authored-by: S A R A N <44068102+saranonearth@users.noreply.github.com> Co-authored-by: 0xsaranonearth <saran.s@pillow.fund>
This commit is contained in:
parent
d38854749b
commit
f391f1c719
@ -74,10 +74,12 @@ const Arrow = styled.div`
|
||||
export interface PopoverProps {
|
||||
content: React.ReactNode
|
||||
show: boolean
|
||||
children: React.ReactNode
|
||||
children?: React.ReactNode
|
||||
placement?: Placement
|
||||
offsetX?: number
|
||||
offsetY?: number
|
||||
hideArrow?: boolean
|
||||
showInline?: boolean
|
||||
style?: CSSProperties
|
||||
}
|
||||
|
||||
@ -88,6 +90,8 @@ export default function Popover({
|
||||
placement = 'auto',
|
||||
offsetX = 8,
|
||||
offsetY = 8,
|
||||
hideArrow = false,
|
||||
showInline = false,
|
||||
style,
|
||||
}: PopoverProps) {
|
||||
const [referenceElement, setReferenceElement] = useState<HTMLDivElement | null>(null)
|
||||
@ -114,7 +118,9 @@ export default function Popover({
|
||||
}, [update])
|
||||
useInterval(updateCallback, show ? 100 : null)
|
||||
|
||||
return (
|
||||
return showInline ? (
|
||||
<PopoverContainer show={show}>{content}</PopoverContainer>
|
||||
) : (
|
||||
<>
|
||||
<ReferenceElement style={style} ref={setReferenceElement as any}>
|
||||
{children}
|
||||
@ -122,12 +128,14 @@ export default function Popover({
|
||||
<Portal>
|
||||
<PopoverContainer show={show} ref={setPopperElement as any} style={styles.popper} {...attributes.popper}>
|
||||
{content}
|
||||
{!hideArrow && (
|
||||
<Arrow
|
||||
className={`arrow-${attributes.popper?.['data-popper-placement'] ?? ''}`}
|
||||
ref={setArrowElement as any}
|
||||
style={styles.arrow}
|
||||
{...attributes.arrow}
|
||||
/>
|
||||
)}
|
||||
</PopoverContainer>
|
||||
</Portal>
|
||||
</>
|
||||
|
@ -1,10 +1,15 @@
|
||||
import { BigNumber } from '@ethersproject/bignumber'
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { sendAnalyticsEvent, useTrace } from '@uniswap/analytics'
|
||||
import { EventName, PageName } from '@uniswap/analytics-events'
|
||||
import Tooltip from 'components/Tooltip'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { bodySmall } from 'nft/css/common.css'
|
||||
import { useBag } from 'nft/hooks'
|
||||
import { GenieAsset, Markets } from 'nft/types'
|
||||
import { GenieAsset, Markets, TokenType } from 'nft/types'
|
||||
import { formatWeiToDecimal, rarityProviderLogo } from 'nft/utils'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import { useAssetMediaType, useNotForSale } from './Card'
|
||||
import { AssetMediaType } from './Card'
|
||||
@ -18,6 +23,18 @@ interface CollectionAssetProps {
|
||||
rarityVerified?: boolean
|
||||
}
|
||||
|
||||
const TOOLTIP_TIMEOUT = 2000
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
position: absolute;
|
||||
bottom: 12px;
|
||||
left: 0px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
z-index: 2;
|
||||
`
|
||||
|
||||
export const CollectionAsset = ({
|
||||
asset,
|
||||
isMobile,
|
||||
@ -28,22 +45,27 @@ export const CollectionAsset = ({
|
||||
const bagManuallyClosed = useBag((state) => state.bagManuallyClosed)
|
||||
const addAssetsToBag = useBag((state) => state.addAssetsToBag)
|
||||
const removeAssetsFromBag = useBag((state) => state.removeAssetsFromBag)
|
||||
const usedSweep = useBag((state) => state.usedSweep)
|
||||
const itemsInBag = useBag((state) => state.itemsInBag)
|
||||
const bagExpanded = useBag((state) => state.bagExpanded)
|
||||
const setBagExpanded = useBag((state) => state.setBagExpanded)
|
||||
const trace = useTrace({ page: PageName.NFT_COLLECTION_PAGE })
|
||||
|
||||
const { quantity, isSelected } = useMemo(() => {
|
||||
return {
|
||||
quantity: itemsInBag.filter(
|
||||
(x) => x.asset.tokenType === 'ERC1155' && x.asset.tokenId === asset.tokenId && x.asset.address === asset.address
|
||||
).length,
|
||||
isSelected: itemsInBag.some(
|
||||
const { erc1155TokenQuantity, isSelected } = useMemo(() => {
|
||||
const matchingItems = itemsInBag.filter(
|
||||
(item) => asset.tokenId === item.asset.tokenId && asset.address === item.asset.address
|
||||
),
|
||||
)
|
||||
const erc1155TokenQuantity = matchingItems.filter((x) => x.asset.tokenType === TokenType.ERC1155).length
|
||||
const isSelected = matchingItems.length > 0
|
||||
return {
|
||||
erc1155TokenQuantity,
|
||||
isSelected,
|
||||
}
|
||||
}, [asset, itemsInBag])
|
||||
|
||||
const [showTooltip, setShowTooltip] = useState(false)
|
||||
const isSelectedRef = useRef(isSelected)
|
||||
|
||||
const notForSale = useNotForSale(asset)
|
||||
const assetMediaType = useAssetMediaType(asset)
|
||||
|
||||
@ -71,6 +93,22 @@ export const CollectionAsset = ({
|
||||
}
|
||||
}, [addAssetsToBag, asset, bagExpanded, bagManuallyClosed, isMobile, setBagExpanded, trace])
|
||||
|
||||
useEffect(() => {
|
||||
if (isSelected !== isSelectedRef.current && !usedSweep) {
|
||||
setShowTooltip(true)
|
||||
isSelectedRef.current = isSelected
|
||||
const tooltipTimer = setTimeout(() => {
|
||||
setShowTooltip(false)
|
||||
}, TOOLTIP_TIMEOUT)
|
||||
|
||||
return () => {
|
||||
clearTimeout(tooltipTimer)
|
||||
}
|
||||
}
|
||||
isSelectedRef.current = isSelected
|
||||
return undefined
|
||||
}, [isSelected, isSelectedRef, usedSweep])
|
||||
|
||||
const handleRemoveAssetFromBag = useCallback(() => {
|
||||
removeAssetsFromBag([asset])
|
||||
}, [asset, removeAssetsFromBag])
|
||||
@ -83,7 +121,25 @@ export const CollectionAsset = ({
|
||||
removeAssetFromBag={handleRemoveAssetFromBag}
|
||||
>
|
||||
<Card.ImageContainer>
|
||||
{asset.tokenType === 'ERC1155' && quantity > 0 && <Card.Erc1155Controls quantity={quantity.toString()} />}
|
||||
<StyledContainer>
|
||||
<Tooltip
|
||||
text={
|
||||
<Box as="span" className={bodySmall} color="textPrimary">
|
||||
{isSelected ? <Trans>Added to bag</Trans> : <Trans>Removed from bag</Trans>}
|
||||
</Box>
|
||||
}
|
||||
show={showTooltip}
|
||||
style={{ display: 'block' }}
|
||||
offsetX={0}
|
||||
offsetY={0}
|
||||
hideArrow={true}
|
||||
placement="bottom"
|
||||
showInline
|
||||
/>
|
||||
</StyledContainer>
|
||||
{asset.tokenType === TokenType.ERC1155 && erc1155TokenQuantity > 0 && (
|
||||
<Card.Erc1155Controls quantity={erc1155TokenQuantity.toString()} />
|
||||
)}
|
||||
{asset.rarity && provider && (
|
||||
<Card.Ranking
|
||||
rarity={asset.rarity}
|
||||
@ -116,7 +172,7 @@ export const CollectionAsset = ({
|
||||
</Card.SecondaryInfo>
|
||||
{(asset.marketplace === Markets.NFTX || asset.marketplace === Markets.NFT20) && <Card.Pool />}
|
||||
</Card.SecondaryDetails>
|
||||
{asset.tokenType !== 'ERC1155' && asset.marketplace && (
|
||||
{asset.tokenType !== TokenType.ERC1155 && asset.marketplace && (
|
||||
<Card.MarketplaceIcon marketplace={asset.marketplace} />
|
||||
)}
|
||||
</Card.SecondaryRow>
|
||||
|
@ -256,7 +256,7 @@ export const Sweep = ({ contractAddress, minPrice, maxPrice }: SweepProps) => {
|
||||
if (sweepItemsInBag.length < value) {
|
||||
addAssetsToBag(sortedAssets.slice(sweepItemsInBag.length, value), true)
|
||||
} else {
|
||||
removeAssetsFromBag(sweepItemsInBag.slice(value, sweepItemsInBag.length))
|
||||
removeAssetsFromBag(sweepItemsInBag.slice(value, sweepItemsInBag.length), true)
|
||||
}
|
||||
setSweepAmount(value < 1 ? '' : value.toString())
|
||||
} else {
|
||||
@ -290,7 +290,7 @@ export const Sweep = ({ contractAddress, minPrice, maxPrice }: SweepProps) => {
|
||||
}
|
||||
|
||||
if (wishAssets.length > 0) {
|
||||
removeAssetsFromBag(wishAssets)
|
||||
removeAssetsFromBag(wishAssets, true)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { MouseoverTooltip } from 'components/Tooltip'
|
||||
import Tooltip from 'components/Tooltip'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import * as Card from 'nft/components/collection/Card'
|
||||
import { AssetMediaType } from 'nft/components/collection/Card'
|
||||
@ -7,7 +8,9 @@ import { bodySmall } from 'nft/css/common.css'
|
||||
import { themeVars } from 'nft/css/sprinkles.css'
|
||||
import { useBag, useIsMobile, useSellAsset } from 'nft/hooks'
|
||||
import { TokenType, WalletAsset } from 'nft/types'
|
||||
import { useMemo } from 'react'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
|
||||
const TOOLTIP_TIMEOUT = 2000
|
||||
|
||||
interface ViewMyNftsAssetProps {
|
||||
asset: WalletAsset
|
||||
@ -50,6 +53,9 @@ export const ViewMyNftsAsset = ({
|
||||
)
|
||||
}, [asset, sellAssets])
|
||||
|
||||
const [showTooltip, setShowTooltip] = useState(false)
|
||||
const isSelectedRef = useRef(isSelected)
|
||||
|
||||
const onCardClick = () => handleSelect(isSelected)
|
||||
|
||||
const handleSelect = (removeAsset: boolean) => {
|
||||
@ -64,25 +70,26 @@ export const ViewMyNftsAsset = ({
|
||||
toggleCart()
|
||||
}
|
||||
|
||||
const assetMediaType = Card.useAssetMediaType(asset)
|
||||
useEffect(() => {
|
||||
if (isSelected !== isSelectedRef.current) {
|
||||
setShowTooltip(true)
|
||||
isSelectedRef.current = isSelected
|
||||
const tooltipTimer = setTimeout(() => {
|
||||
setShowTooltip(false)
|
||||
}, TOOLTIP_TIMEOUT)
|
||||
|
||||
return () => {
|
||||
clearTimeout(tooltipTimer)
|
||||
}
|
||||
}
|
||||
isSelectedRef.current = isSelected
|
||||
return undefined
|
||||
}, [isSelected, isSelectedRef])
|
||||
|
||||
const assetMediaType = Card.useAssetMediaType(asset)
|
||||
const isDisabled = asset.asset_contract.tokenType === TokenType.ERC1155 || asset.susFlag
|
||||
const disabledTooltipText =
|
||||
asset.asset_contract.tokenType === TokenType.ERC1155 ? 'ERC-1155 support coming soon' : 'Blocked from trading'
|
||||
|
||||
return (
|
||||
<MouseoverTooltip
|
||||
text={
|
||||
<Box as="span" className={bodySmall} style={{ color: themeVars.colors.textPrimary }}>
|
||||
<Trans>{disabledTooltipText}</Trans>{' '}
|
||||
</Box>
|
||||
}
|
||||
placement="bottom"
|
||||
offsetX={0}
|
||||
offsetY={-180}
|
||||
style={{ display: 'block' }}
|
||||
disableHover={!isDisabled}
|
||||
>
|
||||
<Card.Container
|
||||
asset={asset}
|
||||
selected={isSelected}
|
||||
@ -90,14 +97,44 @@ export const ViewMyNftsAsset = ({
|
||||
removeAssetFromBag={() => handleSelect(true)}
|
||||
onClick={onCardClick}
|
||||
isDisabled={isDisabled}
|
||||
>
|
||||
<MouseoverTooltip
|
||||
text={
|
||||
<Box as="span" className={bodySmall} style={{ color: themeVars.colors.textPrimary }}>
|
||||
{asset.asset_contract.tokenType === TokenType.ERC1155 ? (
|
||||
<Trans>ERC-1155 support coming soon</Trans>
|
||||
) : (
|
||||
<Trans>Blocked from trading</Trans>
|
||||
)}
|
||||
</Box>
|
||||
}
|
||||
placement="bottom"
|
||||
offsetX={0}
|
||||
offsetY={-100}
|
||||
style={{ display: 'block' }}
|
||||
disableHover={!isDisabled}
|
||||
>
|
||||
<Tooltip
|
||||
text={
|
||||
<Box as="span" className={bodySmall} color="textPrimary">
|
||||
{isSelected ? <Trans>Selected</Trans> : <Trans>Deselected</Trans>}
|
||||
</Box>
|
||||
}
|
||||
show={showTooltip}
|
||||
style={{ display: 'block' }}
|
||||
offsetX={0}
|
||||
offsetY={-52}
|
||||
hideArrow={true}
|
||||
placement="bottom"
|
||||
>
|
||||
<Card.ImageContainer>
|
||||
{getNftDisplayComponent(assetMediaType, mediaShouldBePlaying, setCurrentTokenPlayingMedia)}
|
||||
</Card.ImageContainer>
|
||||
</Tooltip>
|
||||
</MouseoverTooltip>
|
||||
<Card.DetailsContainer>
|
||||
<Card.ProfileNftDetails asset={asset} hideDetails={hideDetails} />
|
||||
</Card.DetailsContainer>
|
||||
</Card.Container>
|
||||
</MouseoverTooltip>
|
||||
)
|
||||
}
|
||||
|
@ -16,13 +16,14 @@ interface BagState {
|
||||
totalUsdPrice: number | undefined
|
||||
setTotalUsdPrice: (totalUsdPrice: number | undefined) => void
|
||||
addAssetsToBag: (asset: UpdatedGenieAsset[], fromSweep?: boolean) => void
|
||||
removeAssetsFromBag: (assets: UpdatedGenieAsset[]) => void
|
||||
removeAssetsFromBag: (assets: UpdatedGenieAsset[], fromSweep?: boolean) => void
|
||||
markAssetAsReviewed: (asset: UpdatedGenieAsset, toKeep: boolean) => void
|
||||
lockSweepItems: (contractAddress: string) => void
|
||||
didOpenUnavailableAssets: boolean
|
||||
setDidOpenUnavailableAssets: (didOpen: boolean) => void
|
||||
bagExpanded: boolean
|
||||
toggleBag: () => void
|
||||
usedSweep: boolean
|
||||
isLocked: boolean
|
||||
setLocked: (isLocked: boolean) => void
|
||||
reset: () => void
|
||||
@ -59,6 +60,7 @@ export const useBag = create<BagState>()(
|
||||
setBagExpanded: ({ bagExpanded, manualClose }) =>
|
||||
set(({ bagManuallyClosed }) => ({ bagExpanded, bagManuallyClosed: manualClose || bagManuallyClosed })),
|
||||
toggleBag: () => set(({ bagExpanded }) => ({ bagExpanded: !bagExpanded })),
|
||||
usedSweep: false,
|
||||
isLocked: false,
|
||||
setLocked: (_isLocked) =>
|
||||
set(() => ({
|
||||
@ -107,14 +109,16 @@ export const useBag = create<BagState>()(
|
||||
return {
|
||||
itemsInBag: items,
|
||||
bagStatus: BagStatus.ADDING_TO_BAG,
|
||||
usedSweep: fromSweep,
|
||||
}
|
||||
else
|
||||
return {
|
||||
itemsInBag: [...itemsInBagCopy, ...items],
|
||||
bagStatus: BagStatus.ADDING_TO_BAG,
|
||||
usedSweep: fromSweep,
|
||||
}
|
||||
}),
|
||||
removeAssetsFromBag: (assets) => {
|
||||
removeAssetsFromBag: (assets, fromSweep = false) => {
|
||||
set(({ bagManuallyClosed, itemsInBag }) => {
|
||||
if (get().isLocked) return { itemsInBag: get().itemsInBag }
|
||||
if (itemsInBag.length === 0) return { itemsInBag: [] }
|
||||
@ -126,7 +130,11 @@ export const useBag = create<BagState>()(
|
||||
: asset.tokenId === item.asset.tokenId && asset.address === item.asset.address
|
||||
)
|
||||
)
|
||||
return { bagManuallyClosed: itemsCopy.length === 0 ? false : bagManuallyClosed, itemsInBag: itemsCopy }
|
||||
return {
|
||||
bagManuallyClosed: itemsCopy.length === 0 ? false : bagManuallyClosed,
|
||||
itemsInBag: itemsCopy,
|
||||
usedSweep: fromSweep,
|
||||
}
|
||||
})
|
||||
},
|
||||
lockSweepItems: (contractAddress) =>
|
||||
|
Loading…
Reference in New Issue
Block a user