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 />
|
<DataPageHeader />
|
||||||
<ContentContainer>
|
<ContentContainer>
|
||||||
<LeftColumn>
|
<LeftColumn>
|
||||||
<DataPageTraits asset={asset} />
|
{!!asset.traits?.length && <DataPageTraits traits={asset.traits} />}
|
||||||
<DataPageDescription />
|
<DataPageDescription />
|
||||||
</LeftColumn>
|
</LeftColumn>
|
||||||
<DataPageTable />
|
<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 { 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 { useMemo } from 'react'
|
||||||
import styled from 'styled-components/macro'
|
import styled from 'styled-components/macro'
|
||||||
|
import { ThemedText } from 'theme'
|
||||||
|
import { opacify } from 'theme/utils'
|
||||||
|
|
||||||
import { Tab, TabbedComponent } from './TabbedComponent'
|
import { Tab, TabbedComponent } from './TabbedComponent'
|
||||||
|
import { TraitRow } from './TraitRow'
|
||||||
|
|
||||||
const TraitsContentContainer = styled.div`
|
const TraitsHeaderContainer = styled(Row)`
|
||||||
height: 492px;
|
padding-right: 12px;
|
||||||
`
|
`
|
||||||
|
|
||||||
const TraitsContent = () => {
|
const TraitsHeader = styled(ThemedText.SubHeaderSmall)<{ $flex?: number; $justifyContent?: string }>`
|
||||||
return <TraitsContentContainer>Traits Content</TraitsContentContainer>
|
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 {
|
enum TraitTabsKeys {
|
||||||
Traits = 'traits',
|
Traits = 'traits',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DataPageTraits = ({ asset }: { asset: GenieAsset }) => {
|
export const DataPageTraits = ({ traits }: { traits: Trait[] }) => {
|
||||||
const TraitTabs: Map<string, Tab> = useMemo(
|
const TraitTabs: Map<string, Tab> = useMemo(
|
||||||
() =>
|
() =>
|
||||||
new Map([
|
new Map([
|
||||||
@ -26,12 +105,12 @@ export const DataPageTraits = ({ asset }: { asset: GenieAsset }) => {
|
|||||||
{
|
{
|
||||||
title: <Trans>Traits</Trans>,
|
title: <Trans>Traits</Trans>,
|
||||||
key: TraitTabsKeys.Traits,
|
key: TraitTabsKeys.Traits,
|
||||||
content: <TraitsContent />,
|
content: <TraitsContent traits={traits} />,
|
||||||
count: asset.traits?.length,
|
count: traits?.length,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
]),
|
]),
|
||||||
[asset.traits?.length]
|
[traits]
|
||||||
)
|
)
|
||||||
return <TabbedComponent tabs={TraitTabs} />
|
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;
|
justify-content: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.c17 {
|
.c15 {
|
||||||
height: 568px;
|
height: 568px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,32 +82,26 @@ exports[`placeholder containers load 1`] = `
|
|||||||
}
|
}
|
||||||
|
|
||||||
.c10 {
|
.c10 {
|
||||||
color: #0D111C;
|
|
||||||
line-height: 24px;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c13 {
|
|
||||||
color: #0D111C;
|
color: #0D111C;
|
||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.c13:hover {
|
.c10:hover {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.c14 {
|
.c12 {
|
||||||
color: #98A1C0;
|
color: #98A1C0;
|
||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.c14:hover {
|
.c12:hover {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.c16 {
|
.c14 {
|
||||||
background: #D2D9EE;
|
background: #D2D9EE;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
padding: 2px 4px;
|
padding: 2px 4px;
|
||||||
@ -115,7 +109,7 @@ exports[`placeholder containers load 1`] = `
|
|||||||
line-height: 12px;
|
line-height: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.c15 {
|
.c13 {
|
||||||
height: 252px;
|
height: 252px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,10 +128,6 @@ exports[`placeholder containers load 1`] = `
|
|||||||
padding-left: 0px;
|
padding-left: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.c12 {
|
|
||||||
height: 492px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c1 {
|
.c1 {
|
||||||
padding: 24px 64px;
|
padding: 24px 64px;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
@ -202,28 +192,6 @@ exports[`placeholder containers load 1`] = `
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="c9 c10 css-rjqmed"
|
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
|
<div
|
||||||
class="c3 c11"
|
class="c3 c11"
|
||||||
@ -232,7 +200,7 @@ exports[`placeholder containers load 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="c9 c14 css-rjqmed"
|
class="c9 c12 css-rjqmed"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="c3 c11"
|
class="c3 c11"
|
||||||
@ -242,7 +210,7 @@ exports[`placeholder containers load 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="c15"
|
class="c13"
|
||||||
>
|
>
|
||||||
Description Content
|
Description Content
|
||||||
</div>
|
</div>
|
||||||
@ -255,7 +223,7 @@ exports[`placeholder containers load 1`] = `
|
|||||||
class="c3 c4 c8"
|
class="c3 c4 c8"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="c9 c13 css-rjqmed"
|
class="c9 c10 css-rjqmed"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="c3 c11"
|
class="c3 c11"
|
||||||
@ -264,28 +232,28 @@ exports[`placeholder containers load 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="c9 c14 css-rjqmed"
|
class="c9 c12 css-rjqmed"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="c3 c11"
|
class="c3 c11"
|
||||||
>
|
>
|
||||||
Offers
|
Offers
|
||||||
<div
|
<div
|
||||||
class="c16 css-f8aq60"
|
class="c14 css-f8aq60"
|
||||||
>
|
>
|
||||||
10+
|
10+
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="c9 c14 css-rjqmed"
|
class="c9 c12 css-rjqmed"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="c3 c11"
|
class="c3 c11"
|
||||||
>
|
>
|
||||||
Listings
|
Listings
|
||||||
<div
|
<div
|
||||||
class="c16 css-f8aq60"
|
class="c14 css-f8aq60"
|
||||||
>
|
>
|
||||||
10+
|
10+
|
||||||
</div>
|
</div>
|
||||||
@ -293,7 +261,7 @@ exports[`placeholder containers load 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="c17"
|
class="c15"
|
||||||
>
|
>
|
||||||
Activity Content
|
Activity Content
|
||||||
</div>
|
</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 './useSelectAsset'
|
||||||
export * from './useSellAsset'
|
export * from './useSellAsset'
|
||||||
export * from './useSendTransaction'
|
export * from './useSendTransaction'
|
||||||
|
export * from './useSubscribeScrollState'
|
||||||
export * from './useSweep'
|
export * from './useSweep'
|
||||||
export * from './useTransactionResponse'
|
export * from './useTransactionResponse'
|
||||||
export * from './useWalletBalance'
|
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',
|
magentaVibrant: '#FC72FF',
|
||||||
purple300: '#8440F2',
|
purple300: '#8440F2',
|
||||||
purple900: '#1C0337',
|
purple900: '#1C0337',
|
||||||
|
purpleVibrant: '#6100FF',
|
||||||
// TODO: add all other vibrant variations
|
// TODO: add all other vibrant variations
|
||||||
networkEthereum: '#627EEA',
|
networkEthereum: '#627EEA',
|
||||||
networkOptimism: '#FF0420',
|
networkOptimism: '#FF0420',
|
||||||
|
Loading…
Reference in New Issue
Block a user