style: updated collection cards (#5047)

This commit is contained in:
Jack Short 2022-11-01 19:15:50 -04:00 committed by GitHub
parent 6e282a6d13
commit 155bf2e873
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 232 additions and 188 deletions

@ -7,6 +7,8 @@ export const card = style([
overflow: 'hidden',
borderStyle: 'solid',
borderWidth: '1px',
paddingBottom: '12',
boxShadow: 'shallow',
}),
{
boxSizing: 'border-box',
@ -20,9 +22,6 @@ export const card = style([
},
},
},
':hover': {
boxShadow: themeVars.shadows.deep,
},
},
])
@ -34,8 +33,13 @@ export const notSelectedCard = style([
card,
sprinkles({
backgroundColor: 'backgroundSurface',
borderColor: 'transparent',
borderColor: 'backgroundOutline',
}),
{
':hover': {
backgroundColor: themeVars.colors.stateOverlayHover,
},
},
])
export const cardImageHover = style({
@ -45,9 +49,15 @@ export const cardImageHover = style({
export const selectedCard = style([
card,
sprinkles({
background: 'lightGrayOverlay',
borderColor: 'backgroundOutline',
background: 'backgroundSurface',
borderColor: 'accentAction',
borderWidth: '3px',
}),
{
':hover': {
backgroundColor: themeVars.colors.stateOverlayHover,
},
},
])
export const button = style([

@ -1,18 +1,17 @@
import clsx from 'clsx'
import Column from 'components/Column'
import { MouseoverTooltip } from 'components/Tooltip'
import { Box } from 'nft/components/Box'
import { Row } from 'nft/components/Flex'
import {
ChevronRightIcon,
MinusIconLarge,
PauseButtonIcon,
PlayButtonIcon,
PlusIconLarge,
PoolIcon,
RarityVerifiedIcon,
SuspiciousIcon20,
} from 'nft/components/icons'
import { body, bodySmall, subheadSmall } from 'nft/css/common.css'
import { body, bodySmall } from 'nft/css/common.css'
import { themeVars } from 'nft/css/sprinkles.css'
import { useIsMobile } from 'nft/hooks'
import { GenieAsset, Rarity, UniformHeight, UniformHeights } from 'nft/types'
@ -28,6 +27,9 @@ import {
useRef,
useState,
} from 'react'
import { AlertTriangle } from 'react-feather'
import styled from 'styled-components/macro'
import { ThemedText } from 'theme'
import * as styles from './Card.css'
@ -38,6 +40,8 @@ export interface CardContextProps {
selected: boolean
href: string
setHref: (href: string) => void
addAssetToBag: () => void
removeAssetFromBag: () => void
}
const CardContext = createContext<CardContextProps | undefined>(undefined)
@ -50,14 +54,85 @@ const useCardContext = () => {
const baseHref = (asset: GenieAsset) => `/#/nfts/asset/${asset.address}/${asset.tokenId}?origin=collection`
const DetailsLinkContainer = styled.a`
display: flex;
flex-shrink: 0;
text-decoration: none;
color: ${({ theme }) => theme.textSecondary};
font-size: 14px;
line-height: 20px;
weight: 400;
:hover {
color: ${({ theme }) => theme.accentAction};
}
`
const SuspiciousIcon = styled(AlertTriangle)`
width: 16px;
height: 16px;
color: ${({ theme }) => theme.accentFailure};
`
const Erc1155ControlsRow = styled.div`
position: absolute;
display: flex;
width: 100%;
bottom: 12px;
z-index: 2;
justify-content: center;
`
const Erc1155ControlsContainer = styled.div`
display: flex;
border: 1px solid ${({ theme }) => theme.backgroundOutline};
border-radius: 12px 12px 12px 12px;
overflow: hidden;
`
const Erc1155ControlsDisplay = styled(ThemedText.HeadlineSmall)`
display: flex;
padding: 6px 8px;
width: 60px;
background: ${({ theme }) => theme.backgroundBackdrop};
justify-content: center;
cursor: default;
`
const Erc1155ControlsInput = styled.div`
display: flex;
justify-content: center;
align-items: center;
width: 40px;
background: ${({ theme }) => theme.backgroundInteractive};
color: ${({ theme }) => theme.textPrimary};
:hover {
color: ${({ theme }) => theme.accentAction};
}
`
const RankingContainer = styled.div`
position: absolute;
top: 12px;
left: 12px;
z-index: 2;
`
const StyledImageContainer = styled.div`
position: relative;
`
/* -------- ASSET CARD -------- */
interface CardProps {
asset: GenieAsset
selected: boolean
addAssetToBag: () => void
removeAssetFromBag: () => void
children: ReactNode
}
const Container = ({ asset, selected, children }: CardProps) => {
const Container = ({ asset, selected, addAssetToBag, removeAssetFromBag, children }: CardProps) => {
const [hovered, toggleHovered] = useReducer((s) => !s, false)
const [href, setHref] = useState(baseHref(asset))
@ -69,8 +144,10 @@ const Container = ({ asset, selected, children }: CardProps) => {
toggleHovered,
href,
setHref,
addAssetToBag,
removeAssetFromBag,
}),
[asset, hovered, selected, href]
[asset, hovered, selected, href, addAssetToBag, removeAssetFromBag]
)
const assetRef = useRef<HTMLDivElement>(null)
@ -82,16 +159,21 @@ const Container = ({ asset, selected, children }: CardProps) => {
return (
<CardContext.Provider value={providerValue}>
<Box
as="a"
href={href ? href : baseHref(asset)}
position={'relative'}
ref={assetRef}
borderRadius={'20'}
className={styles.notSelectedCard}
className={selected ? styles.selectedCard : styles.notSelectedCard}
draggable={false}
onMouseEnter={() => toggleHovered()}
onMouseLeave={() => toggleHovered()}
transition="250"
cursor={asset.notForSale ? 'default' : 'pointer'}
onClick={(e: MouseEvent) => {
if (!asset.notForSale) {
e.preventDefault()
!selected ? addAssetToBag() : removeAssetFromBag()
}
}}
>
{children}
</Box>
@ -99,6 +181,10 @@ const Container = ({ asset, selected, children }: CardProps) => {
)
}
const ImageContainer = ({ children }: { children: ReactNode }) => (
<StyledImageContainer>{children}</StyledImageContainer>
)
/* -------- CARD IMAGE -------- */
interface ImageProps {
uniformHeight: UniformHeight
@ -365,10 +451,14 @@ const InfoContainer = ({ children }: { children: ReactNode }) => {
)
}
const PrimaryRow = ({ children }: { children: ReactNode }) => <Row justifyContent="space-between">{children}</Row>
const PrimaryRow = ({ children }: { children: ReactNode }) => (
<Row gap="8" justifyContent="space-between">
{children}
</Row>
)
const PrimaryDetails = ({ children }: { children: ReactNode }) => (
<Row overflow="hidden" whiteSpace="nowrap">
<Row justifyItems="center" overflow="hidden" whiteSpace="nowrap">
{children}
</Row>
)
@ -425,100 +515,35 @@ const TertiaryInfo = ({ children }: { children: ReactNode }) => {
)
}
interface ButtonProps {
children: ReactNode
quantity: number
selectedChildren: ReactNode
onClick: (e: MouseEvent) => void
onSelectedClick: (e: MouseEvent) => void
interface Erc1155ControlsInterface {
quantity: string
}
const Button = ({ children, quantity, selectedChildren, onClick, onSelectedClick }: ButtonProps) => {
const [buttonHovered, toggleButtonHovered] = useReducer((s) => !s, false)
const { asset, selected, setHref } = useCardContext()
const buttonRef = useRef<HTMLDivElement>(null)
const isMobile = useIsMobile()
useLayoutEffect(() => {
if (buttonHovered && buttonRef.current?.matches(':hover') === false) toggleButtonHovered()
}, [buttonHovered])
const Erc1155Controls = ({ quantity }: Erc1155ControlsInterface) => {
const { addAssetToBag, removeAssetFromBag } = useCardContext()
return (
<>
{!selected || asset.tokenType !== 'ERC1155' ? (
<Box
as="button"
ref={buttonRef}
color={
buttonHovered || isMobile
? 'explicitWhite'
: selected
? 'accentFailure'
: asset.notForSale
? 'textTertiary'
: 'accentAction'
}
background={
buttonHovered || isMobile
? asset.notForSale
? 'backgroundInteractive'
: selected
? 'accentFailure'
: 'accentAction'
: asset.notForSale
? 'backgroundModule'
: selected
? 'accentFailureSoft'
: 'accentActionSoft'
}
className={clsx(styles.button, subheadSmall)}
onClick={(e) =>
selected
? onSelectedClick(e)
: asset.notForSale
? () => {
return true
}
: onClick(e)
}
onMouseEnter={() => {
!asset.notForSale && setHref('')
!buttonHovered && toggleButtonHovered()
<Erc1155ControlsRow>
<Erc1155ControlsContainer>
<Erc1155ControlsInput
onClick={(e: MouseEvent) => {
e.stopPropagation()
removeAssetFromBag()
}}
onMouseLeave={() => {
!asset.notForSale && setHref(baseHref(asset))
buttonHovered && toggleButtonHovered()
>
<MinusIconLarge width="24px" height="24px" />
</Erc1155ControlsInput>
<Erc1155ControlsDisplay>{quantity}</Erc1155ControlsDisplay>
<Erc1155ControlsInput
onClick={(e: MouseEvent) => {
e.stopPropagation()
addAssetToBag()
}}
transition="250"
>
{selected
? selectedChildren
: asset.notForSale
? buttonHovered || isMobile
? 'See details'
: 'Not for sale'
: children}
</Box>
) : (
<Row className={styles.erc1155ButtonRow}>
<Column
as="button"
className={styles.erc1155MinusButton}
onClick={(e: MouseEvent<Element, globalThis.MouseEvent>) => onSelectedClick(e)}
>
<MinusIconLarge width="32" height="32" />
</Column>
<Box className={`${styles.erc1155QuantityText} ${subheadSmall}`}>{quantity.toString()}</Box>
<Column
as="button"
className={styles.erc1155PlusButton}
onClick={(e: MouseEvent<Element, globalThis.MouseEvent>) => onClick(e)}
>
<PlusIconLarge width="32" height="32" />
</Column>
</Row>
)}
</>
<PlusIconLarge width="24px" height="24px" />
</Erc1155ControlsInput>
</Erc1155ControlsContainer>
</Erc1155ControlsRow>
)
}
@ -533,6 +558,22 @@ const MarketplaceIcon = ({ marketplace }: { marketplace: string }) => {
)
}
const DetailsLink = () => {
const { asset } = useCardContext()
return (
<DetailsLinkContainer
href={baseHref(asset)}
onClick={(e: MouseEvent) => {
e.stopPropagation()
}}
>
Details
<ChevronRightIcon width="20px" height="20px" />
</DetailsLinkContainer>
)
}
/* -------- RANKING CARD -------- */
interface RankingProps {
rarity: Rarity
@ -545,6 +586,7 @@ const Ranking = ({ rarity, provider, rarityVerified, rarityLogo }: RankingProps)
const { asset } = useCardContext()
return (
<RankingContainer>
<MouseoverTooltip
text={
<Row>
@ -570,6 +612,7 @@ const Ranking = ({ rarity, provider, rarityVerified, rarityLogo }: RankingProps)
</Box>
</Box>
</MouseoverTooltip>
</RankingContainer>
)
}
const SUSPICIOUS_TEXT = 'Blocked on OpenSea'
@ -577,8 +620,8 @@ const SUSPICIOUS_TEXT = 'Blocked on OpenSea'
const Suspicious = () => {
return (
<MouseoverTooltip text={<Box className={bodySmall}>{SUSPICIOUS_TEXT}</Box>} placement="top">
<Box display="flex" flexShrink="0" marginLeft="2">
<SuspiciousIcon20 width="20" height="20" />
<Box display="flex" flexShrink="0" marginLeft="4">
<SuspiciousIcon />
</Box>
</MouseoverTooltip>
)
@ -632,7 +675,7 @@ const NoContentContainer = ({ uniformHeight }: NoContentContainerProps) => (
width="full"
style={{
paddingTop: '100%',
background: `linear-gradient(270deg, ${themeVars.colors.backgroundOutline} 0%, ${themeVars.colors.backgroundSurface} 100%)`,
background: `linear-gradient(90deg, ${themeVars.colors.backgroundSurface} 0%, ${themeVars.colors.backgroundInteractive} 95.83%)`,
}}
>
<Box
@ -656,10 +699,12 @@ const NoContentContainer = ({ uniformHeight }: NoContentContainerProps) => (
export {
Audio,
Button,
Container,
DetailsContainer,
DetailsLink,
Erc1155Controls,
Image,
ImageContainer,
InfoContainer,
MarketplaceIcon,
Pool,

@ -2,7 +2,7 @@ import { BigNumber } from '@ethersproject/bignumber'
import { useBag } from 'nft/hooks'
import { GenieAsset, Markets, UniformHeight } from 'nft/types'
import { formatWeiToDecimal, isAudio, isVideo, rarityProviderLogo } from 'nft/utils'
import { MouseEvent, useMemo } from 'react'
import { useMemo } from 'react'
import * as Card from './Card'
@ -73,7 +73,27 @@ export const CollectionAsset = ({
}, [asset])
return (
<Card.Container asset={asset} selected={isSelected}>
<Card.Container
asset={asset}
selected={isSelected}
addAssetToBag={() => {
addAssetsToBag([asset])
!bagExpanded && !isMobile && toggleBag()
}}
removeAssetFromBag={() => {
removeAssetsFromBag([asset])
}}
>
<Card.ImageContainer>
{asset.tokenType === 'ERC1155' && quantity > 0 && <Card.Erc1155Controls quantity={quantity.toString()} />}
{asset.rarity && provider && provider.rank && (
<Card.Ranking
rarity={asset.rarity}
provider={provider}
rarityVerified={!!rarityVerified}
rarityLogo={rarityLogo}
/>
)}
{assetMediaType === AssetMediaType.Image ? (
<Card.Image uniformHeight={uniformHeight} setUniformHeight={setUniformHeight} />
) : assetMediaType === AssetMediaType.Video ? (
@ -91,6 +111,7 @@ export const CollectionAsset = ({
setCurrentTokenPlayingMedia={setCurrentTokenPlayingMedia}
/>
)}
</Card.ImageContainer>
<Card.DetailsContainer>
<Card.InfoContainer>
<Card.PrimaryRow>
@ -98,14 +119,7 @@ export const CollectionAsset = ({
<Card.PrimaryInfo>{asset.name ? asset.name : `#${asset.tokenId}`}</Card.PrimaryInfo>
{asset.susFlag && <Card.Suspicious />}
</Card.PrimaryDetails>
{asset.rarity && provider && provider.rank && (
<Card.Ranking
rarity={asset.rarity}
provider={provider}
rarityVerified={!!rarityVerified}
rarityLogo={rarityLogo}
/>
)}
<Card.DetailsLink />
</Card.PrimaryRow>
<Card.SecondaryRow>
<Card.SecondaryDetails>
@ -119,21 +133,6 @@ export const CollectionAsset = ({
)}
</Card.SecondaryRow>
</Card.InfoContainer>
<Card.Button
quantity={quantity}
selectedChildren={'Remove'}
onClick={(e: MouseEvent) => {
e.preventDefault()
addAssetsToBag([asset])
!bagExpanded && !isMobile && toggleBag()
}}
onSelectedClick={(e: MouseEvent) => {
e.preventDefault()
removeAssetsFromBag([asset])
}}
>
{'Buy now'}
</Card.Button>
</Card.DetailsContainer>
</Card.Container>
)

@ -14,14 +14,9 @@ export const CollectionAssetLoading = () => {
</Box>
<Row justifyContent="space-between" marginTop="12" paddingLeft="12" paddingRight="12">
<Box as="div" className={loadingAsset} height="12" width="120"></Box>
<Box as="div" className={loadingAsset} width="36" height="12"></Box>
</Row>
<Row justifyContent="space-between" marginTop="12" paddingLeft="12" paddingRight="12">
<Box as="div" className={loadingAsset} height="16" width="80"></Box>
<Box as="div" className={loadingAsset} width="16" height="16" borderRadius="4"></Box>
</Row>
<Row marginTop="12" paddingLeft="12" paddingRight="12">
<Box as="div" className={loadingAsset} width="full" height="32"></Box>
</Row>
</Box>
)

@ -1239,20 +1239,15 @@ export const SuspiciousIcon20 = (props: SVGProps) => (
)
export const MinusIconLarge = (props: SVGProps) => (
<svg fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
d="M8.72879 16.7601H23.2734C23.8862 16.7601 24.4085 16.2478 24.4085 15.615C24.4085 14.9922 23.8862 14.4699 23.2734 14.4699H8.72879C8.13616 14.4699 7.59375 14.9922 7.59375 15.615C7.59375 16.2478 8.13616 16.7601 8.72879 16.7601Z"
fill="currentColor"
/>
<svg {...props} fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 12H19" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
)
export const PlusIconLarge = (props: SVGProps) => (
<svg fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
d="M8.72712 16.75H14.8544V22.8772C14.8544 23.5 15.3666 24.0223 15.9994 24.0223C16.6323 24.0223 17.1445 23.5 17.1445 22.8772V16.75H23.2718C23.8945 16.75 24.4169 16.2377 24.4169 15.6049C24.4169 14.9721 23.8945 14.4598 23.2718 14.4598H17.1445V8.33259C17.1445 7.70982 16.6323 7.1875 15.9994 7.1875C15.3666 7.1875 14.8544 7.70982 14.8544 8.33259V14.4598H8.72712C8.10435 14.4598 7.58203 14.9721 7.58203 15.6049C7.58203 16.2377 8.10435 16.75 8.72712 16.75Z"
fill="currentColor"
/>
<svg {...props} fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 5V19" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
<path d="M5 12H19" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
)

@ -265,7 +265,7 @@ const flexAlignment = [
const overflow = ['hidden', 'inherit', 'scroll', 'visible', 'auto'] as const
const borderWidth = ['0px', '0.5px', '1px', '1.5px', '2px', '4px']
const borderWidth = ['0px', '0.5px', '1px', '1.5px', '2px', '3px', '4px']
const borderStyle = ['none', 'solid'] as const