Commit ff0209a7 authored by Charles Bachmeier's avatar Charles Bachmeier Committed by GitHub

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: default avatarCharles Bachmeier <charlie@genie.xyz>
parent 924e8313
...@@ -47,7 +47,7 @@ export const DataPage = ({ asset }: { asset: GenieAsset }) => { ...@@ -47,7 +47,7 @@ export const DataPage = ({ asset }: { asset: GenieAsset }) => {
<DataPageHeader /> <DataPageHeader />
<ContentContainer> <ContentContainer>
<LeftColumn> <LeftColumn>
{!!asset.traits?.length && <DataPageTraits traits={asset.traits} />} {!!asset.traits?.length && <DataPageTraits asset={asset} />}
<DataPageDescription /> <DataPageDescription />
</LeftColumn> </LeftColumn>
<DataPageTable /> <DataPageTable />
......
...@@ -4,7 +4,7 @@ import { render } from 'test-utils/render' ...@@ -4,7 +4,7 @@ import { render } from 'test-utils/render'
import { DataPageTraits } from './DataPageTraits' import { DataPageTraits } from './DataPageTraits'
it('data page trait component does not load with asset with no traits', () => { 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() expect(asFragment()).toMatchSnapshot()
}) })
......
...@@ -3,7 +3,7 @@ import Column from 'components/Column' ...@@ -3,7 +3,7 @@ import Column from 'components/Column'
import { ScrollBarStyles } from 'components/Common' import { ScrollBarStyles } from 'components/Common'
import Row from 'components/Row' import Row from 'components/Row'
import { useSubscribeScrollState } from 'nft/hooks' import { useSubscribeScrollState } from 'nft/hooks'
import { Trait } from 'nft/types' import { GenieAsset } from 'nft/types'
import { useMemo } from 'react' import { useMemo } from 'react'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
import { ThemedText } from 'theme' import { ThemedText } from 'theme'
...@@ -57,13 +57,15 @@ const Scrim = styled.div<{ isBottom?: boolean }>` ...@@ -57,13 +57,15 @@ const Scrim = styled.div<{ isBottom?: boolean }>`
display: flex; display: flex;
` `
const TraitsContent = ({ traits }: { traits?: Trait[] }) => { const TraitsContent = ({ asset }: { asset: GenieAsset }) => {
const { userCanScroll, scrollRef, scrollProgress, scrollHandler } = useSubscribeScrollState() const { userCanScroll, scrollRef, scrollProgress, scrollHandler } = useSubscribeScrollState()
// This is needed to prevent rerenders when handling scrolls // This is needed to prevent rerenders when handling scrolls
const traitRows = useMemo(() => { const traitRows = useMemo(() => {
return traits?.map((trait) => <TraitRow trait={trait} key={trait.trait_type + ':' + trait.trait_value} />) return asset.traits?.map((trait) => (
}, [traits]) <TraitRow collectionAddress={asset.address} trait={trait} key={trait.trait_type + ':' + trait.trait_value} />
))
}, [asset.address, asset.traits])
return ( return (
<Column> <Column>
...@@ -96,7 +98,7 @@ enum TraitTabsKeys { ...@@ -96,7 +98,7 @@ enum TraitTabsKeys {
Traits = 'traits', Traits = 'traits',
} }
export const DataPageTraits = ({ traits }: { traits: Trait[] }) => { export const DataPageTraits = ({ asset }: { asset: GenieAsset }) => {
const TraitTabs: Map<string, Tab> = useMemo( const TraitTabs: Map<string, Tab> = useMemo(
() => () =>
new Map([ new Map([
...@@ -105,12 +107,12 @@ export const DataPageTraits = ({ traits }: { traits: Trait[] }) => { ...@@ -105,12 +107,12 @@ export const DataPageTraits = ({ traits }: { traits: Trait[] }) => {
{ {
title: <Trans>Traits</Trans>, title: <Trans>Traits</Trans>,
key: TraitTabsKeys.Traits, key: TraitTabsKeys.Traits,
content: <TraitsContent traits={traits} />, content: <TraitsContent asset={asset} />,
count: traits?.length, count: asset.traits?.length,
}, },
], ],
]), ]),
[traits] [asset]
) )
return <TabbedComponent tabs={TraitTabs} /> return <TabbedComponent tabs={TraitTabs} />
} }
import { Trans } from '@lingui/macro'
import Row from 'components/Row' import Row from 'components/Row'
import { Trait } from 'nft/types' import { Trait } from 'nft/types'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
...@@ -13,6 +14,7 @@ const RarityBar = styled.div<{ $color?: string }>` ...@@ -13,6 +14,7 @@ const RarityBar = styled.div<{ $color?: string }>`
interface RarityValue { interface RarityValue {
threshold: number threshold: number
color: string color: string
caption: React.ReactNode
} }
enum RarityLevel { enum RarityLevel {
...@@ -27,26 +29,31 @@ const RarityLevels: { [key in RarityLevel]: RarityValue } = { ...@@ -27,26 +29,31 @@ const RarityLevels: { [key in RarityLevel]: RarityValue } = {
[RarityLevel.VeryCommon]: { [RarityLevel.VeryCommon]: {
threshold: 0.8, threshold: 0.8,
color: colors.gray500, color: colors.gray500,
caption: <Trans>Very common</Trans>,
}, },
[RarityLevel.Common]: { [RarityLevel.Common]: {
threshold: 0.6, threshold: 0.6,
color: colors.green300, color: colors.green300,
caption: <Trans>Common</Trans>,
}, },
[RarityLevel.Rare]: { [RarityLevel.Rare]: {
threshold: 0.4, threshold: 0.4,
color: colors.blueVibrant, color: colors.blueVibrant,
caption: <Trans>Rare</Trans>,
}, },
[RarityLevel.VeryRare]: { [RarityLevel.VeryRare]: {
threshold: 0.2, threshold: 0.2,
color: colors.purpleVibrant, color: colors.purpleVibrant,
caption: <Trans>Very rare</Trans>,
}, },
[RarityLevel.ExtremelyRare]: { [RarityLevel.ExtremelyRare]: {
threshold: 0, threshold: 0,
color: colors.magentaVibrant, color: colors.magentaVibrant,
caption: <Trans>Extremely rare</Trans>,
}, },
} }
function getRarityLevel(rarity: number) { export function getRarityLevel(rarity: number) {
switch (true) { switch (true) {
case rarity > RarityLevels[RarityLevel.VeryCommon].threshold: case rarity > RarityLevels[RarityLevel.VeryCommon].threshold:
return RarityLevels[RarityLevel.VeryCommon] return RarityLevels[RarityLevel.VeryCommon]
......
...@@ -2,21 +2,45 @@ import Column from 'components/Column' ...@@ -2,21 +2,45 @@ import Column from 'components/Column'
import Row from 'components/Row' import Row from 'components/Row'
import { Trait } from 'nft/types' import { Trait } from 'nft/types'
import { formatEth } from 'nft/utils' import { formatEth } from 'nft/utils'
import qs from 'qs'
import { Link } from 'react-router-dom'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
import { ThemedText } from 'theme' 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; font-size: 10px;
line-height: 16px; line-height: 16px;
font-weight: 600; font-weight: 600;
color: ${({ theme }) => theme.textSecondary}; color: ${({ theme, $color }) => ($color ? $color : theme.textSecondary)};
`
const SubheaderTinyHidden = styled(SubheaderTiny)`
opacity: 0;
` `
const TraitValue = styled(Column)` 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; gap: 4px;
flex: 3; flex: ${({ $flex }) => $flex ?? 3};
align-items: ${({ $alignItems }) => $alignItems};
` `
const TraitRowValue = styled(ThemedText.BodySmall)<{ $flex?: number; $justifyContent?: string }>` const TraitRowValue = styled(ThemedText.BodySmall)<{ $flex?: number; $justifyContent?: string }>`
...@@ -27,21 +51,31 @@ const TraitRowValue = styled(ThemedText.BodySmall)<{ $flex?: number; $justifyCon ...@@ -27,21 +51,31 @@ const TraitRowValue = styled(ThemedText.BodySmall)<{ $flex?: number; $justifyCon
justify-content: ${({ $justifyContent }) => $justifyContent}; justify-content: ${({ $justifyContent }) => $justifyContent};
` `
export const TraitRow = ({ trait }: { trait: Trait }) => { export const TraitRow = ({ trait, collectionAddress }: { trait: Trait; collectionAddress: string }) => {
// TODO: Replace with actual rarity, count, and floor price when BE supports // 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 // rarity eventually should be number of items with this trait / total number of items, smaller rarity means more rare
const randomRarity = Math.random() const randomRarity = Math.random()
const rarityLevel = getRarityLevel(randomRarity)
const params = qs.stringify(
{ traits: [`("${trait.trait_type}","${trait.trait_value}")`] },
{
arrayFormat: 'comma',
}
)
return ( return (
<Row padding="12px 18px 12px 0px"> <TraitRowLink to={`/nfts/collection/${collectionAddress}?${params}`}>
<TraitValue> <TraitRowContainer>
<SubheaderTiny>{trait.trait_type}</SubheaderTiny> <TraitColumnValue>
<ThemedText.BodyPrimary lineHeight="20px">{trait.trait_value}</ThemedText.BodyPrimary> <SubheaderTiny>{trait.trait_type}</SubheaderTiny>
</TraitValue> <ThemedText.BodyPrimary lineHeight="20px">{trait.trait_value}</ThemedText.BodyPrimary>
<TraitRowValue $flex={2}>{formatEth(randomRarity * 1000)} ETH</TraitRowValue> </TraitColumnValue>
<TraitRowValue>{Math.round(randomRarity * 10000)}</TraitRowValue> <TraitRowValue $flex={2}>{formatEth(randomRarity * 1000)} ETH</TraitRowValue>
<TraitRowValue $flex={1.5} $justifyContent="flex-end"> <TraitRowValue>{Math.round(randomRarity * 10000)}</TraitRowValue>
<RarityGraph trait={trait} rarity={randomRarity} /> <TraitColumnValue $flex={1.5} $alignItems="flex-end">
</TraitRowValue> <SubheaderTinyHidden $color={rarityLevel.color}>{rarityLevel.caption}</SubheaderTinyHidden>
</Row> <RarityGraph trait={trait} rarity={randomRarity} />
</TraitColumnValue>
</TraitRowContainer>
</TraitRowLink>
) )
} }
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