feat: [DetailsV2] add link and hover state to trait component (#6472)

* hide trait container when asset has no traits

* add traits header row

* trait value rows and scroll behaviour

* row with placeholder values

* add random filler values and proper scrollbar styles

* working rarity graph

* bar border radius

* move rarity graph to its own file

* always show scrim

* working scrim and move traitrow to its own file

* cleanup

* remove padding

* move scrollbar right

* add snapshot tests

* add comment about randomly generated rarities

* cleanup

* only pass traits

* justify

* not important

* cleanup scrim styles

* remove comment

* add scroll state hook

* lint

* update test

* object over map

* remove spaces

* justify content

* add ticket

* add comments

* update snapshot

* add link and hover state to trait component

* correct padding

* respond to comments

* add component and use css for vis

---------

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
This commit is contained in:
Charles Bachmeier 2023-05-01 14:52:49 -04:00 committed by GitHub
parent 924e83139b
commit ff0209a78f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 72 additions and 29 deletions

@ -47,7 +47,7 @@ export const DataPage = ({ asset }: { asset: GenieAsset }) => {
<DataPageHeader />
<ContentContainer>
<LeftColumn>
{!!asset.traits?.length && <DataPageTraits traits={asset.traits} />}
{!!asset.traits?.length && <DataPageTraits asset={asset} />}
<DataPageDescription />
</LeftColumn>
<DataPageTable />

@ -4,7 +4,7 @@ import { render } from 'test-utils/render'
import { DataPageTraits } from './DataPageTraits'
it('data page trait component does not load with asset with no traits', () => {
const { asFragment } = render(<DataPageTraits traits={TEST_NFT_ASSET.traits ?? []} />)
const { asFragment } = render(<DataPageTraits asset={TEST_NFT_ASSET} />)
expect(asFragment()).toMatchSnapshot()
})

@ -3,7 +3,7 @@ import Column from 'components/Column'
import { ScrollBarStyles } from 'components/Common'
import Row from 'components/Row'
import { useSubscribeScrollState } from 'nft/hooks'
import { Trait } from 'nft/types'
import { GenieAsset } from 'nft/types'
import { useMemo } from 'react'
import styled from 'styled-components/macro'
import { ThemedText } from 'theme'
@ -57,13 +57,15 @@ const Scrim = styled.div<{ isBottom?: boolean }>`
display: flex;
`
const TraitsContent = ({ traits }: { traits?: Trait[] }) => {
const TraitsContent = ({ asset }: { asset: GenieAsset }) => {
const { userCanScroll, scrollRef, scrollProgress, scrollHandler } = useSubscribeScrollState()
// This is needed to prevent rerenders when handling scrolls
const traitRows = useMemo(() => {
return traits?.map((trait) => <TraitRow trait={trait} key={trait.trait_type + ':' + trait.trait_value} />)
}, [traits])
return asset.traits?.map((trait) => (
<TraitRow collectionAddress={asset.address} trait={trait} key={trait.trait_type + ':' + trait.trait_value} />
))
}, [asset.address, asset.traits])
return (
<Column>
@ -96,7 +98,7 @@ enum TraitTabsKeys {
Traits = 'traits',
}
export const DataPageTraits = ({ traits }: { traits: Trait[] }) => {
export const DataPageTraits = ({ asset }: { asset: GenieAsset }) => {
const TraitTabs: Map<string, Tab> = useMemo(
() =>
new Map([
@ -105,12 +107,12 @@ export const DataPageTraits = ({ traits }: { traits: Trait[] }) => {
{
title: <Trans>Traits</Trans>,
key: TraitTabsKeys.Traits,
content: <TraitsContent traits={traits} />,
count: traits?.length,
content: <TraitsContent asset={asset} />,
count: asset.traits?.length,
},
],
]),
[traits]
[asset]
)
return <TabbedComponent tabs={TraitTabs} />
}

@ -1,3 +1,4 @@
import { Trans } from '@lingui/macro'
import Row from 'components/Row'
import { Trait } from 'nft/types'
import styled from 'styled-components/macro'
@ -13,6 +14,7 @@ const RarityBar = styled.div<{ $color?: string }>`
interface RarityValue {
threshold: number
color: string
caption: React.ReactNode
}
enum RarityLevel {
@ -27,26 +29,31 @@ const RarityLevels: { [key in RarityLevel]: RarityValue } = {
[RarityLevel.VeryCommon]: {
threshold: 0.8,
color: colors.gray500,
caption: <Trans>Very common</Trans>,
},
[RarityLevel.Common]: {
threshold: 0.6,
color: colors.green300,
caption: <Trans>Common</Trans>,
},
[RarityLevel.Rare]: {
threshold: 0.4,
color: colors.blueVibrant,
caption: <Trans>Rare</Trans>,
},
[RarityLevel.VeryRare]: {
threshold: 0.2,
color: colors.purpleVibrant,
caption: <Trans>Very rare</Trans>,
},
[RarityLevel.ExtremelyRare]: {
threshold: 0,
color: colors.magentaVibrant,
caption: <Trans>Extremely rare</Trans>,
},
}
function getRarityLevel(rarity: number) {
export function getRarityLevel(rarity: number) {
switch (true) {
case rarity > RarityLevels[RarityLevel.VeryCommon].threshold:
return RarityLevels[RarityLevel.VeryCommon]

@ -2,21 +2,45 @@ import Column from 'components/Column'
import Row from 'components/Row'
import { Trait } from 'nft/types'
import { formatEth } from 'nft/utils'
import qs from 'qs'
import { Link } from 'react-router-dom'
import styled from 'styled-components/macro'
import { ThemedText } from 'theme'
import { RarityGraph } from './RarityGraph'
import { getRarityLevel, RarityGraph } from './RarityGraph'
const SubheaderTiny = styled.div`
const TraitRowLink = styled(Link)`
text-decoration: none;
`
const SubheaderTiny = styled.div<{ $color?: string }>`
font-size: 10px;
line-height: 16px;
font-weight: 600;
color: ${({ theme }) => theme.textSecondary};
color: ${({ theme, $color }) => ($color ? $color : theme.textSecondary)};
`
const TraitValue = styled(Column)`
const SubheaderTinyHidden = styled(SubheaderTiny)`
opacity: 0;
`
const TraitRowContainer = styled(Row)`
padding: 12px 18px 12px 0px;
border-radius: 12px;
cursor: pointer;
text-decoration: none;
&:hover {
background: ${({ theme }) => theme.hoverDefault};
${SubheaderTinyHidden} {
opacity: 1;
}
}
`
const TraitColumnValue = styled(Column)<{ $flex?: number; $alignItems?: string }>`
gap: 4px;
flex: 3;
flex: ${({ $flex }) => $flex ?? 3};
align-items: ${({ $alignItems }) => $alignItems};
`
const TraitRowValue = styled(ThemedText.BodySmall)<{ $flex?: number; $justifyContent?: string }>`
@ -27,21 +51,31 @@ const TraitRowValue = styled(ThemedText.BodySmall)<{ $flex?: number; $justifyCon
justify-content: ${({ $justifyContent }) => $justifyContent};
`
export const TraitRow = ({ trait }: { trait: Trait }) => {
// TODO: Replace with actual rarity, count, and floor price when BE supports
export const TraitRow = ({ trait, collectionAddress }: { trait: Trait; collectionAddress: string }) => {
// TODO(NFT-1189): Replace with actual rarity, count, and floor price when BE supports
// rarity eventually should be number of items with this trait / total number of items, smaller rarity means more rare
const randomRarity = Math.random()
const rarityLevel = getRarityLevel(randomRarity)
const params = qs.stringify(
{ traits: [`("${trait.trait_type}","${trait.trait_value}")`] },
{
arrayFormat: 'comma',
}
)
return (
<Row padding="12px 18px 12px 0px">
<TraitValue>
<SubheaderTiny>{trait.trait_type}</SubheaderTiny>
<ThemedText.BodyPrimary lineHeight="20px">{trait.trait_value}</ThemedText.BodyPrimary>
</TraitValue>
<TraitRowValue $flex={2}>{formatEth(randomRarity * 1000)} ETH</TraitRowValue>
<TraitRowValue>{Math.round(randomRarity * 10000)}</TraitRowValue>
<TraitRowValue $flex={1.5} $justifyContent="flex-end">
<RarityGraph trait={trait} rarity={randomRarity} />
</TraitRowValue>
</Row>
<TraitRowLink to={`/nfts/collection/${collectionAddress}?${params}`}>
<TraitRowContainer>
<TraitColumnValue>
<SubheaderTiny>{trait.trait_type}</SubheaderTiny>
<ThemedText.BodyPrimary lineHeight="20px">{trait.trait_value}</ThemedText.BodyPrimary>
</TraitColumnValue>
<TraitRowValue $flex={2}>{formatEth(randomRarity * 1000)} ETH</TraitRowValue>
<TraitRowValue>{Math.round(randomRarity * 10000)}</TraitRowValue>
<TraitColumnValue $flex={1.5} $alignItems="flex-end">
<SubheaderTinyHidden $color={rarityLevel.color}>{rarityLevel.caption}</SubheaderTinyHidden>
<RarityGraph trait={trait} rarity={randomRarity} />
</TraitColumnValue>
</TraitRowContainer>
</TraitRowLink>
)
}