Commit 155bf2e8 authored by Jack Short's avatar Jack Short Committed by GitHub

style: updated collection cards (#5047)

parent 6e282a6d
......@@ -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([
......
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,31 +586,33 @@ const Ranking = ({ rarity, provider, rarityVerified, rarityLogo }: RankingProps)
const { asset } = useCardContext()
return (
<MouseoverTooltip
text={
<Row>
<Box display="flex" marginRight="4">
<img src={rarityLogo} alt="cardLogo" width={16} />
</Box>
<Box width="full" className={bodySmall}>
{rarityVerified
? `Verified by ${asset.collectionName}`
: `Ranking by ${rarity.primaryProvider === 'Genie' ? fallbackProvider : rarity.primaryProvider}`}
<RankingContainer>
<MouseoverTooltip
text={
<Row>
<Box display="flex" marginRight="4">
<img src={rarityLogo} alt="cardLogo" width={16} />
</Box>
<Box width="full" className={bodySmall}>
{rarityVerified
? `Verified by ${asset.collectionName}`
: `Ranking by ${rarity.primaryProvider === 'Genie' ? fallbackProvider : rarity.primaryProvider}`}
</Box>
</Row>
}
placement="top"
>
<Box className={styles.rarityInfo}>
<Box paddingTop="2" paddingBottom="2" display="flex">
{putCommas(provider.rank)}
</Box>
</Row>
}
placement="top"
>
<Box className={styles.rarityInfo}>
<Box paddingTop="2" paddingBottom="2" display="flex">
{putCommas(provider.rank)}
</Box>
<Box display="flex" height="16">
{rarityVerified ? <RarityVerifiedIcon /> : null}
<Box display="flex" height="16">
{rarityVerified ? <RarityVerifiedIcon /> : null}
</Box>
</Box>
</Box>
</MouseoverTooltip>
</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,24 +73,45 @@ export const CollectionAsset = ({
}, [asset])
return (
<Card.Container asset={asset} selected={isSelected}>
{assetMediaType === AssetMediaType.Image ? (
<Card.Image uniformHeight={uniformHeight} setUniformHeight={setUniformHeight} />
) : assetMediaType === AssetMediaType.Video ? (
<Card.Video
uniformHeight={uniformHeight}
setUniformHeight={setUniformHeight}
shouldPlay={mediaShouldBePlaying}
setCurrentTokenPlayingMedia={setCurrentTokenPlayingMedia}
/>
) : (
<Card.Audio
uniformHeight={uniformHeight}
setUniformHeight={setUniformHeight}
shouldPlay={mediaShouldBePlaying}
setCurrentTokenPlayingMedia={setCurrentTokenPlayingMedia}
/>
)}
<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 ? (
<Card.Video
uniformHeight={uniformHeight}
setUniformHeight={setUniformHeight}
shouldPlay={mediaShouldBePlaying}
setCurrentTokenPlayingMedia={setCurrentTokenPlayingMedia}
/>
) : (
<Card.Audio
uniformHeight={uniformHeight}
setUniformHeight={setUniformHeight}
shouldPlay={mediaShouldBePlaying}
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
......
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