feat: [DetailsV2] Add Trait component content (#6460)
* 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 * correct padding --------- Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
This commit is contained in:
parent
97312bb174
commit
6bc7cfc996
@ -47,7 +47,7 @@ export const DataPage = ({ asset }: { asset: GenieAsset }) => {
|
||||
<DataPageHeader />
|
||||
<ContentContainer>
|
||||
<LeftColumn>
|
||||
<DataPageTraits asset={asset} />
|
||||
{!!asset.traits?.length && <DataPageTraits traits={asset.traits} />}
|
||||
<DataPageDescription />
|
||||
</LeftColumn>
|
||||
<DataPageTable />
|
||||
|
12
src/nft/components/details/detailsV2/DataPageTraits.test.tsx
Normal file
12
src/nft/components/details/detailsV2/DataPageTraits.test.tsx
Normal file
@ -0,0 +1,12 @@
|
||||
import { TEST_NFT_ASSET } from 'test-utils/nft/fixtures'
|
||||
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 ?? []} />)
|
||||
expect(asFragment()).toMatchSnapshot()
|
||||
})
|
||||
|
||||
// TODO(NFT-1189): add test for trait component with asset with traits when rarity is not randomly generated
|
||||
// while rarities are randomly generated, snapshots will never match
|
@ -1,23 +1,102 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { GenieAsset } from 'nft/types'
|
||||
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 { useMemo } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ThemedText } from 'theme'
|
||||
import { opacify } from 'theme/utils'
|
||||
|
||||
import { Tab, TabbedComponent } from './TabbedComponent'
|
||||
import { TraitRow } from './TraitRow'
|
||||
|
||||
const TraitsContentContainer = styled.div`
|
||||
height: 492px;
|
||||
const TraitsHeaderContainer = styled(Row)`
|
||||
padding-right: 12px;
|
||||
`
|
||||
|
||||
const TraitsContent = () => {
|
||||
return <TraitsContentContainer>Traits Content</TraitsContentContainer>
|
||||
const TraitsHeader = styled(ThemedText.SubHeaderSmall)<{ $flex?: number; $justifyContent?: string }>`
|
||||
display: flex;
|
||||
line-height: 20px;
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
flex: ${({ $flex }) => $flex ?? 1};
|
||||
justify-content: ${({ $justifyContent }) => $justifyContent};
|
||||
`
|
||||
|
||||
const TraitRowContainer = styled.div`
|
||||
position: relative;
|
||||
`
|
||||
|
||||
const TraitRowScrollableContainer = styled.div`
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
max-height: 412px;
|
||||
width: calc(100% + 6px);
|
||||
|
||||
${ScrollBarStyles}
|
||||
`
|
||||
|
||||
// Scrim that fades out the top and bottom of the scrollable container, isBottom changes the direction and placement of the fade
|
||||
const Scrim = styled.div<{ isBottom?: boolean }>`
|
||||
position: absolute;
|
||||
height: 88px;
|
||||
left: 0px;
|
||||
right: 6px;
|
||||
|
||||
${({ isBottom }) =>
|
||||
isBottom
|
||||
? 'bottom: 0px'
|
||||
: `
|
||||
top: 0px;
|
||||
transform: matrix(1, 0, 0, -1, 0, 0);
|
||||
`};
|
||||
|
||||
background: ${({ theme }) =>
|
||||
`linear-gradient(180deg, ${opacify(0, theme.backgroundSurface)} 0%, ${theme.backgroundSurface} 100%)`};
|
||||
display: flex;
|
||||
`
|
||||
|
||||
const TraitsContent = ({ traits }: { traits?: Trait[] }) => {
|
||||
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 (
|
||||
<Column>
|
||||
<TraitsHeaderContainer>
|
||||
<TraitsHeader $flex={3}>
|
||||
<Trans>Trait</Trans>
|
||||
</TraitsHeader>
|
||||
<TraitsHeader $flex={2}>
|
||||
<Trans>Floor price</Trans>
|
||||
</TraitsHeader>
|
||||
<TraitsHeader>
|
||||
<Trans>Quantity</Trans>
|
||||
</TraitsHeader>
|
||||
<TraitsHeader $flex={1.5} $justifyContent="flex-end">
|
||||
<Trans>Rarity</Trans>
|
||||
</TraitsHeader>
|
||||
</TraitsHeaderContainer>
|
||||
<TraitRowContainer>
|
||||
{scrollProgress > 0 && <Scrim />}
|
||||
<TraitRowScrollableContainer ref={scrollRef} onScroll={scrollHandler}>
|
||||
{traitRows}
|
||||
</TraitRowScrollableContainer>
|
||||
{userCanScroll && scrollProgress !== 100 && <Scrim isBottom={true} />}
|
||||
</TraitRowContainer>
|
||||
</Column>
|
||||
)
|
||||
}
|
||||
|
||||
enum TraitTabsKeys {
|
||||
Traits = 'traits',
|
||||
}
|
||||
|
||||
export const DataPageTraits = ({ asset }: { asset: GenieAsset }) => {
|
||||
export const DataPageTraits = ({ traits }: { traits: Trait[] }) => {
|
||||
const TraitTabs: Map<string, Tab> = useMemo(
|
||||
() =>
|
||||
new Map([
|
||||
@ -26,12 +105,12 @@ export const DataPageTraits = ({ asset }: { asset: GenieAsset }) => {
|
||||
{
|
||||
title: <Trans>Traits</Trans>,
|
||||
key: TraitTabsKeys.Traits,
|
||||
content: <TraitsContent />,
|
||||
count: asset.traits?.length,
|
||||
content: <TraitsContent traits={traits} />,
|
||||
count: traits?.length,
|
||||
},
|
||||
],
|
||||
]),
|
||||
[asset.traits?.length]
|
||||
[traits]
|
||||
)
|
||||
return <TabbedComponent tabs={TraitTabs} />
|
||||
}
|
||||
|
78
src/nft/components/details/detailsV2/RarityGraph.tsx
Normal file
78
src/nft/components/details/detailsV2/RarityGraph.tsx
Normal file
@ -0,0 +1,78 @@
|
||||
import Row from 'components/Row'
|
||||
import { Trait } from 'nft/types'
|
||||
import styled from 'styled-components/macro'
|
||||
import { colors } from 'theme/colors'
|
||||
|
||||
const RarityBar = styled.div<{ $color?: string }>`
|
||||
background: ${({ $color, theme }) => $color ?? theme.backgroundOutline};
|
||||
width: 2px;
|
||||
height: 10px;
|
||||
border-radius: 2px;
|
||||
`
|
||||
|
||||
interface RarityValue {
|
||||
threshold: number
|
||||
color: string
|
||||
}
|
||||
|
||||
enum RarityLevel {
|
||||
VeryCommon = 'Very Common',
|
||||
Common = 'Common',
|
||||
Rare = 'Rare',
|
||||
VeryRare = 'Very Rare',
|
||||
ExtremelyRare = 'Extremely Rare',
|
||||
}
|
||||
|
||||
const RarityLevels: { [key in RarityLevel]: RarityValue } = {
|
||||
[RarityLevel.VeryCommon]: {
|
||||
threshold: 0.8,
|
||||
color: colors.gray500,
|
||||
},
|
||||
[RarityLevel.Common]: {
|
||||
threshold: 0.6,
|
||||
color: colors.green300,
|
||||
},
|
||||
[RarityLevel.Rare]: {
|
||||
threshold: 0.4,
|
||||
color: colors.blueVibrant,
|
||||
},
|
||||
[RarityLevel.VeryRare]: {
|
||||
threshold: 0.2,
|
||||
color: colors.purpleVibrant,
|
||||
},
|
||||
[RarityLevel.ExtremelyRare]: {
|
||||
threshold: 0,
|
||||
color: colors.magentaVibrant,
|
||||
},
|
||||
}
|
||||
|
||||
function getRarityLevel(rarity: number) {
|
||||
switch (true) {
|
||||
case rarity > RarityLevels[RarityLevel.VeryCommon].threshold:
|
||||
return RarityLevels[RarityLevel.VeryCommon]
|
||||
case rarity > RarityLevels[RarityLevel.Common].threshold:
|
||||
return RarityLevels[RarityLevel.Common]
|
||||
case rarity > RarityLevels[RarityLevel.Rare].threshold:
|
||||
return RarityLevels[RarityLevel.Rare]
|
||||
case rarity > RarityLevels[RarityLevel.VeryRare].threshold:
|
||||
return RarityLevels[RarityLevel.VeryRare]
|
||||
case rarity >= RarityLevels[RarityLevel.ExtremelyRare].threshold:
|
||||
return RarityLevels[RarityLevel.ExtremelyRare]
|
||||
default:
|
||||
return RarityLevels[RarityLevel.VeryCommon]
|
||||
}
|
||||
}
|
||||
|
||||
export const RarityGraph = ({ trait, rarity }: { trait: Trait; rarity: number }) => {
|
||||
const rarityLevel = getRarityLevel(rarity)
|
||||
return (
|
||||
<Row gap="1.68px" justify="flex-end">
|
||||
{Array.from({ length: 20 }).map((_, index) => (
|
||||
<RarityBar
|
||||
key={trait.trait_value + '_bar_' + index}
|
||||
$color={index * 0.05 <= 1 - rarity ? rarityLevel?.color : undefined}
|
||||
/>
|
||||
))}
|
||||
</Row>
|
||||
)
|
||||
}
|
47
src/nft/components/details/detailsV2/TraitRow.tsx
Normal file
47
src/nft/components/details/detailsV2/TraitRow.tsx
Normal file
@ -0,0 +1,47 @@
|
||||
import Column from 'components/Column'
|
||||
import Row from 'components/Row'
|
||||
import { Trait } from 'nft/types'
|
||||
import { formatEth } from 'nft/utils'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ThemedText } from 'theme'
|
||||
|
||||
import { RarityGraph } from './RarityGraph'
|
||||
|
||||
const SubheaderTiny = styled.div`
|
||||
font-size: 10px;
|
||||
line-height: 16px;
|
||||
font-weight: 600;
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
`
|
||||
|
||||
const TraitValue = styled(Column)`
|
||||
gap: 4px;
|
||||
flex: 3;
|
||||
`
|
||||
|
||||
const TraitRowValue = styled(ThemedText.BodySmall)<{ $flex?: number; $justifyContent?: string }>`
|
||||
display: flex;
|
||||
line-height: 20px;
|
||||
padding-top: 20px;
|
||||
flex: ${({ $flex }) => $flex ?? 1};
|
||||
justify-content: ${({ $justifyContent }) => $justifyContent};
|
||||
`
|
||||
|
||||
export const TraitRow = ({ trait }: { trait: Trait }) => {
|
||||
// TODO: 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()
|
||||
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>
|
||||
)
|
||||
}
|
@ -61,7 +61,7 @@ exports[`placeholder containers load 1`] = `
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.c17 {
|
||||
.c15 {
|
||||
height: 568px;
|
||||
}
|
||||
|
||||
@ -82,32 +82,26 @@ exports[`placeholder containers load 1`] = `
|
||||
}
|
||||
|
||||
.c10 {
|
||||
color: #0D111C;
|
||||
line-height: 24px;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.c13 {
|
||||
color: #0D111C;
|
||||
line-height: 24px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.c13:hover {
|
||||
.c10:hover {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.c14 {
|
||||
.c12 {
|
||||
color: #98A1C0;
|
||||
line-height: 24px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.c14:hover {
|
||||
.c12:hover {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.c16 {
|
||||
.c14 {
|
||||
background: #D2D9EE;
|
||||
border-radius: 4px;
|
||||
padding: 2px 4px;
|
||||
@ -115,7 +109,7 @@ exports[`placeholder containers load 1`] = `
|
||||
line-height: 12px;
|
||||
}
|
||||
|
||||
.c15 {
|
||||
.c13 {
|
||||
height: 252px;
|
||||
}
|
||||
|
||||
@ -134,10 +128,6 @@ exports[`placeholder containers load 1`] = `
|
||||
padding-left: 0px;
|
||||
}
|
||||
|
||||
.c12 {
|
||||
height: 492px;
|
||||
}
|
||||
|
||||
.c1 {
|
||||
padding: 24px 64px;
|
||||
height: 100vh;
|
||||
@ -202,28 +192,6 @@ exports[`placeholder containers load 1`] = `
|
||||
>
|
||||
<div
|
||||
class="c9 c10 css-rjqmed"
|
||||
>
|
||||
<div
|
||||
class="c3 c11"
|
||||
>
|
||||
Traits
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c12"
|
||||
>
|
||||
Traits Content
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c7"
|
||||
>
|
||||
<div
|
||||
class="c3 c4 c8"
|
||||
>
|
||||
<div
|
||||
class="c9 c13 css-rjqmed"
|
||||
>
|
||||
<div
|
||||
class="c3 c11"
|
||||
@ -232,7 +200,7 @@ exports[`placeholder containers load 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c9 c14 css-rjqmed"
|
||||
class="c9 c12 css-rjqmed"
|
||||
>
|
||||
<div
|
||||
class="c3 c11"
|
||||
@ -242,7 +210,7 @@ exports[`placeholder containers load 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c15"
|
||||
class="c13"
|
||||
>
|
||||
Description Content
|
||||
</div>
|
||||
@ -255,7 +223,7 @@ exports[`placeholder containers load 1`] = `
|
||||
class="c3 c4 c8"
|
||||
>
|
||||
<div
|
||||
class="c9 c13 css-rjqmed"
|
||||
class="c9 c10 css-rjqmed"
|
||||
>
|
||||
<div
|
||||
class="c3 c11"
|
||||
@ -264,28 +232,28 @@ exports[`placeholder containers load 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c9 c14 css-rjqmed"
|
||||
class="c9 c12 css-rjqmed"
|
||||
>
|
||||
<div
|
||||
class="c3 c11"
|
||||
>
|
||||
Offers
|
||||
<div
|
||||
class="c16 css-f8aq60"
|
||||
class="c14 css-f8aq60"
|
||||
>
|
||||
10+
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c9 c14 css-rjqmed"
|
||||
class="c9 c12 css-rjqmed"
|
||||
>
|
||||
<div
|
||||
class="c3 c11"
|
||||
>
|
||||
Listings
|
||||
<div
|
||||
class="c16 css-f8aq60"
|
||||
class="c14 css-f8aq60"
|
||||
>
|
||||
10+
|
||||
</div>
|
||||
@ -293,7 +261,7 @@ exports[`placeholder containers load 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c17"
|
||||
class="c15"
|
||||
>
|
||||
Activity Content
|
||||
</div>
|
||||
|
@ -0,0 +1,230 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`data page trait component does not load with asset with no traits 1`] = `
|
||||
<DocumentFragment>
|
||||
.c1 {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.c2 {
|
||||
width: 100%;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
padding: 0;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
-webkit-box-pack: start;
|
||||
-webkit-justify-content: flex-start;
|
||||
-ms-flex-pack: start;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.c6 {
|
||||
width: 100%;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
padding: 0;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
-webkit-box-pack: start;
|
||||
-webkit-justify-content: flex-start;
|
||||
-ms-flex-pack: start;
|
||||
justify-content: flex-start;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.c4 {
|
||||
color: #0D111C;
|
||||
}
|
||||
|
||||
.c9 {
|
||||
color: #7780A0;
|
||||
}
|
||||
|
||||
.c7 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-direction: column;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
-webkit-box-pack: start;
|
||||
-webkit-justify-content: flex-start;
|
||||
-ms-flex-pack: start;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.c0 {
|
||||
background: #FFFFFF;
|
||||
border-radius: 16px;
|
||||
padding: 16px 20px;
|
||||
width: 100%;
|
||||
-webkit-align-self: flex-start;
|
||||
-ms-flex-item-align: start;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.c3 {
|
||||
gap: 32px;
|
||||
margin-bottom: 12px;
|
||||
width: 100;
|
||||
}
|
||||
|
||||
.c5 {
|
||||
color: #0D111C;
|
||||
line-height: 24px;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.c8 {
|
||||
padding-right: 12px;
|
||||
}
|
||||
|
||||
.c10 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
line-height: 20px;
|
||||
color: #7780A0;
|
||||
-webkit-flex: 3;
|
||||
-ms-flex: 3;
|
||||
flex: 3;
|
||||
}
|
||||
|
||||
.c11 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
line-height: 20px;
|
||||
color: #7780A0;
|
||||
-webkit-flex: 2;
|
||||
-ms-flex: 2;
|
||||
flex: 2;
|
||||
}
|
||||
|
||||
.c12 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
line-height: 20px;
|
||||
color: #7780A0;
|
||||
-webkit-flex: 1;
|
||||
-ms-flex: 1;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.c13 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
line-height: 20px;
|
||||
color: #7780A0;
|
||||
-webkit-flex: 1.5;
|
||||
-ms-flex: 1.5;
|
||||
flex: 1.5;
|
||||
-webkit-box-pack: end;
|
||||
-webkit-justify-content: flex-end;
|
||||
-ms-flex-pack: end;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.c14 {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.c15 {
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
max-height: 412px;
|
||||
width: calc(100% + 6px);
|
||||
-webkit-scrollbar-width: thin;
|
||||
-moz-scrollbar-width: thin;
|
||||
-ms-scrollbar-width: thin;
|
||||
scrollbar-width: thin;
|
||||
-webkit-scrollbar-color: #D2D9EE transparent;
|
||||
-moz-scrollbar-color: #D2D9EE transparent;
|
||||
-ms-scrollbar-color: #D2D9EE transparent;
|
||||
scrollbar-color: #D2D9EE transparent;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.c15::-webkit-scrollbar {
|
||||
background: transparent;
|
||||
width: 4px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.c15::-webkit-scrollbar-thumb {
|
||||
background: #D2D9EE;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
<div
|
||||
class="c0"
|
||||
>
|
||||
<div
|
||||
class="c1 c2 c3"
|
||||
>
|
||||
<div
|
||||
class="c4 c5 css-rjqmed"
|
||||
>
|
||||
<div
|
||||
class="c1 c6"
|
||||
>
|
||||
Traits
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c7"
|
||||
>
|
||||
<div
|
||||
class="c1 c2 c8"
|
||||
>
|
||||
<div
|
||||
class="c9 c10 css-1aekuku"
|
||||
>
|
||||
Trait
|
||||
</div>
|
||||
<div
|
||||
class="c9 c11 css-1aekuku"
|
||||
>
|
||||
Floor price
|
||||
</div>
|
||||
<div
|
||||
class="c9 c12 css-1aekuku"
|
||||
>
|
||||
Quantity
|
||||
</div>
|
||||
<div
|
||||
class="c9 c13 css-1aekuku"
|
||||
>
|
||||
Rarity
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c14"
|
||||
>
|
||||
<div
|
||||
class="c15"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
@ -11,6 +11,7 @@ export * from './useProfilePageState'
|
||||
export * from './useSelectAsset'
|
||||
export * from './useSellAsset'
|
||||
export * from './useSendTransaction'
|
||||
export * from './useSubscribeScrollState'
|
||||
export * from './useSweep'
|
||||
export * from './useTransactionResponse'
|
||||
export * from './useWalletBalance'
|
||||
|
22
src/nft/hooks/useSubscribeScrollState.ts
Normal file
22
src/nft/hooks/useSubscribeScrollState.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { useState } from 'react'
|
||||
|
||||
// TODO(NFT-1190): update Bag component to use this hook
|
||||
export function useSubscribeScrollState() {
|
||||
const [userCanScroll, setUserCanScroll] = useState(false)
|
||||
const [scrollProgress, setScrollProgress] = useState(0)
|
||||
const scrollRef = (node: HTMLDivElement) => {
|
||||
if (node !== null) {
|
||||
const canScroll = node.scrollHeight > node.clientHeight
|
||||
canScroll !== userCanScroll && setUserCanScroll(canScroll)
|
||||
}
|
||||
}
|
||||
|
||||
const scrollHandler = (event: React.UIEvent<HTMLDivElement>) => {
|
||||
const scrollTop = event.currentTarget.scrollTop
|
||||
const containerHeight = event.currentTarget.clientHeight
|
||||
const scrollHeight = event.currentTarget.scrollHeight
|
||||
|
||||
setScrollProgress(scrollTop ? ((scrollTop + containerHeight) / scrollHeight) * 100 : 0)
|
||||
}
|
||||
return { scrollRef, scrollHandler, scrollProgress, userCanScroll }
|
||||
}
|
@ -87,6 +87,7 @@ export const colors = {
|
||||
magentaVibrant: '#FC72FF',
|
||||
purple300: '#8440F2',
|
||||
purple900: '#1C0337',
|
||||
purpleVibrant: '#6100FF',
|
||||
// TODO: add all other vibrant variations
|
||||
networkEthereum: '#627EEA',
|
||||
networkOptimism: '#FF0420',
|
||||
|
Loading…
Reference in New Issue
Block a user