Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
I
interface
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
LuckySwap
interface
Commits
629fe2c1
Unverified
Commit
629fe2c1
authored
May 15, 2023
by
Jack Short
Committed by
GitHub
May 15, 2023
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: [DetailsV2] trait bubbles (#6552)
parent
d73763ce
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
402 additions
and
10 deletions
+402
-10
InfoChips.tsx
src/nft/components/details/detailsV2/InfoChips.tsx
+201
-0
LandingPage.tsx
src/nft/components/details/detailsV2/LandingPage.tsx
+2
-0
TraitRow.tsx
src/nft/components/details/detailsV2/TraitRow.tsx
+3
-9
LandingPage.test.tsx.snap
...details/detailsV2/__snapshots__/LandingPage.test.tsx.snap
+183
-0
asset.tsx
src/nft/utils/asset.tsx
+13
-1
No files found.
src/nft/components/details/detailsV2/InfoChips.tsx
0 → 100644
View file @
629fe2c1
import
{
Trans
}
from
'
@lingui/macro
'
import
{
useWeb3React
}
from
'
@web3-react/core
'
import
Column
from
'
components/Column
'
import
Row
from
'
components/Row
'
import
{
Unicon
}
from
'
components/Unicon
'
import
useENSAvatar
from
'
hooks/useENSAvatar
'
import
useENSName
from
'
hooks/useENSName
'
import
{
useIsMobile
}
from
'
nft/hooks
'
import
{
GenieAsset
}
from
'
nft/types
'
import
{
getLinkForTrait
}
from
'
nft/utils
'
import
{
ReactNode
,
useReducer
}
from
'
react
'
import
{
ChevronDown
,
DollarSign
}
from
'
react-feather
'
import
{
Link
}
from
'
react-router-dom
'
import
styled
from
'
styled-components/macro
'
import
{
BREAKPOINTS
,
ClickableStyle
,
EllipsisStyle
,
ExternalLink
,
LinkStyle
,
ThemedText
}
from
'
theme
'
import
{
isAddress
,
shortenAddress
}
from
'
utils
'
import
{
ExplorerDataType
}
from
'
utils/getExplorerLink
'
import
{
getExplorerLink
}
from
'
utils/getExplorerLink
'
const
StyledBubble
=
styled
(
Row
)
`
background-color:
${({
theme
})
=>
theme
.
backgroundSurface
}
;
padding: 10px 12px 10px 8px;
border-radius: 20px;
max-width: 144px;
@media screen and (min-width:
${
BREAKPOINTS
.
sm
}
px) {
max-width: 169px;
}
`
const
StyledLabelMedium
=
styled
.
div
`
font-weight: 600;
font-size: 16px;
line-height: 20px;
color:
${({
theme
})
=>
theme
.
textPrimary
}
;
${
EllipsisStyle
}
`
const
StyledIcon
=
styled
(
Row
)
`
width: 24px;
height: 24px;
flex-shrink: 0;
color:
${({
theme
})
=>
theme
.
accentAction
}
;
border-radius: 100%;
overflow: hidden;
justify-content: center;
align-items: center;
`
const
StyledLink
=
styled
(
Link
)
`
${
ClickableStyle
}
${
LinkStyle
}
`
const
ConditionalLinkWrapper
=
({
isExternal
,
href
,
children
,
}:
{
isExternal
?:
boolean
href
:
string
children
:
ReactNode
})
=>
{
return
isExternal
?
(
<
ExternalLink
href=
{
href
}
>
{
children
}
</
ExternalLink
>
)
:
(
<
StyledLink
to=
{
href
}
>
{
children
}
</
StyledLink
>
)
}
const
InfoBubble
=
({
title
,
info
,
icon
,
href
,
isExternal
,
}:
{
title
:
ReactNode
info
:
string
icon
:
ReactNode
href
:
string
isExternal
?:
boolean
})
=>
{
return
(
<
Column
gap=
"sm"
>
<
ThemedText
.
Caption
color=
"textSecondary"
>
{
title
}
</
ThemedText
.
Caption
>
<
ConditionalLinkWrapper
isExternal=
{
isExternal
}
href=
{
href
}
>
<
StyledBubble
gap=
"sm"
>
<
StyledIcon
>
{
icon
}
</
StyledIcon
>
<
StyledLabelMedium
>
{
info
}
</
StyledLabelMedium
>
</
StyledBubble
>
</
ConditionalLinkWrapper
>
</
Column
>
)
}
const
InfoChipDropdown
=
styled
.
button
`
padding: 10px;
background-color:
${({
theme
})
=>
theme
.
backgroundSurface
}
;
color:
${({
theme
})
=>
theme
.
textSecondary
}
;
border-radius: 100%;
border: none;
cursor: pointer;
`
const
InfoChipDropdownContainer
=
styled
(
Column
)
`
height: 100%;
margin-top: auto;
@media screen and (min-width:
${
BREAKPOINTS
.
sm
}
px) {
display: none;
}
`
const
Break
=
styled
(
Column
)
`
flex-basis: 100%;
@media screen and (min-width:
${
BREAKPOINTS
.
sm
}
px) {
display: none;
}
`
const
InfoChipsContainer
=
styled
(
Row
)
`
gap: 4px;
width: 100%;
flex-wrap: wrap;
@media screen and (min-width:
${
BREAKPOINTS
.
sm
}
px) {
gap: 12px;
flex-wrap: nowrap;
}
`
const
StyledChevron
=
styled
(
ChevronDown
)
<
{
isOpen
:
boolean
}
>
`
transform:
${({
isOpen
})
=>
(
isOpen
?
'
rotate(180deg)
'
:
'
rotate(0deg)
'
)}
;
will-change: transform;
transition: transform
${({
theme
})
=>
theme
.
transition
.
duration
.
medium
}
;
`
export
const
InfoChips
=
({
asset
}:
{
asset
:
GenieAsset
})
=>
{
const
{
chainId
}
=
useWeb3React
()
const
isMobile
=
useIsMobile
()
const
[
showExtraInfoChips
,
toggleShowExtraInfoChips
]
=
useReducer
((
s
)
=>
!
s
,
false
)
const
shouldShowExtraInfoChips
=
!
isMobile
||
showExtraInfoChips
const
topTrait
=
asset
?.
traits
?.[
0
]
const
traitCollectionAddress
=
topTrait
&&
getLinkForTrait
(
topTrait
,
asset
.
address
)
const
isChecksummedAddress
=
isAddress
(
asset
.
ownerAddress
)
const
checksummedAddress
=
isChecksummedAddress
?
isChecksummedAddress
:
undefined
const
{
ENSName
}
=
useENSName
(
checksummedAddress
)
const
{
avatar
}
=
useENSAvatar
(
checksummedAddress
)
const
shortenedAddress
=
asset
.
ownerAddress
?
shortenAddress
(
asset
.
ownerAddress
)
:
''
const
addressToDisplay
=
ENSName
??
shortenedAddress
const
avatarToDisplay
=
avatar
?
(
<
img
src=
{
avatar
}
width=
{
24
}
height=
{
24
}
/>
)
:
(
<
Unicon
size=
{
24
}
address=
{
asset
.
ownerAddress
??
''
}
/>
)
return
(
<
Column
gap=
"sm"
>
<
InfoChipsContainer
justify=
"center"
>
<
InfoBubble
title=
{
<
Trans
>
Owner
</
Trans
>
}
info=
{
addressToDisplay
}
icon=
{
avatarToDisplay
}
href=
{
getExplorerLink
(
chainId
??
1
,
asset
.
ownerAddress
??
''
,
ExplorerDataType
.
ADDRESS
)
}
isExternal=
{
true
}
/>
{
traitCollectionAddress
&&
(
<>
<
InfoBubble
title=
{
<
Trans
>
Trait Floor
</
Trans
>
}
info=
"5.3 ETH"
icon=
{
<
DollarSign
size=
{
20
}
/>
}
href=
{
traitCollectionAddress
}
/>
<
InfoChipDropdownContainer
>
<
InfoChipDropdown
onClick=
{
toggleShowExtraInfoChips
}
>
<
StyledChevron
isOpen=
{
showExtraInfoChips
}
size=
{
20
}
display=
"block"
/>
</
InfoChipDropdown
>
</
InfoChipDropdownContainer
>
{
shouldShowExtraInfoChips
&&
(
<>
<
Break
/>
<
InfoBubble
title=
{
<
Trans
>
Top Trait
</
Trans
>
}
info=
{
topTrait
.
trait_value
}
icon=
""
href=
{
traitCollectionAddress
}
/>
</>
)
}
</>
)
}
</
InfoChipsContainer
>
</
Column
>
)
}
src/nft/components/details/detailsV2/LandingPage.tsx
View file @
629fe2c1
...
...
@@ -5,6 +5,7 @@ import { CollectionInfoForAsset, GenieAsset } from 'nft/types'
import
styled
from
'
styled-components/macro
'
import
{
BREAKPOINTS
}
from
'
theme
'
import
{
InfoChips
}
from
'
./InfoChips
'
import
{
MediaRenderer
}
from
'
./MediaRenderer
'
const
MAX_WIDTH
=
560
...
...
@@ -117,6 +118,7 @@ export const LandingPage = ({ asset, collection }: LandingPageProps) => {
</
Row
>
<
StyledHeadlineText
>
{
asset
.
name
??
`${asset.collectionName} #${asset.tokenId}`
}
</
StyledHeadlineText
>
</
InfoDetailsContainer
>
<
InfoChips
asset=
{
asset
}
/>
</
InfoContainer
>
</
LandingPageContainer
>
)
...
...
src/nft/components/details/detailsV2/TraitRow.tsx
View file @
629fe2c1
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
{
formatEth
,
getLinkForTrait
}
from
'
nft/utils
'
import
{
Link
}
from
'
react-router-dom
'
import
styled
from
'
styled-components/macro
'
import
{
BREAKPOINTS
,
ThemedText
}
from
'
theme
'
...
...
@@ -60,14 +59,9 @@ export const TraitRow = ({ trait, collectionAddress }: { trait: Trait; collectio
// 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
(
<
TraitRowLink
to=
{
`/nfts/collection/${collectionAddress}?${params}`
}
>
<
TraitRowLink
to=
{
getLinkForTrait
(
trait
,
collectionAddress
)
}
>
<
TraitRowContainer
>
<
TraitColumnValue
>
<
SubheaderTiny
>
{
trait
.
trait_type
}
</
SubheaderTiny
>
...
...
src/nft/components/details/detailsV2/__snapshots__/LandingPage.test.tsx.snap
View file @
629fe2c1
...
...
@@ -26,6 +26,81 @@ exports[`LandingPage renders it correctly 1`] = `
gap: 4px;
}
.c16 {
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: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
}
.c20 {
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;
}
.c22 {
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;
}
.c18 {
color: #7780A0;
}
.c19 {
-webkit-text-decoration: none;
text-decoration: none;
cursor: pointer;
-webkit-transition-duration: 125ms;
transition-duration: 125ms;
color: #FB118E;
stroke: #FB118E;
font-weight: 500;
}
.c19:hover {
opacity: 0.6;
}
.c19:active {
opacity: 0.4;
}
.c5 {
display: -webkit-box;
display: -webkit-flex;
...
...
@@ -40,6 +115,21 @@ exports[`LandingPage renders it correctly 1`] = `
justify-content: flex-start;
}
.c15 {
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;
gap: 8px;
}
.c6 {
width: 100%;
-webkit-align-items: center;
...
...
@@ -48,6 +138,50 @@ exports[`LandingPage renders it correctly 1`] = `
align-items: center;
}
.c21 {
background-color: #FFFFFF;
padding: 10px 12px 10px 8px;
border-radius: 20px;
max-width: 144px;
}
.c24 {
font-weight: 600;
font-size: 16px;
line-height: 20px;
color: #0D111C;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.c23 {
width: 24px;
height: 24px;
-webkit-flex-shrink: 0;
-ms-flex-negative: 0;
flex-shrink: 0;
color: #FB118E;
border-radius: 100%;
overflow: hidden;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.c17 {
gap: 4px;
width: 100%;
-webkit-flex-wrap: wrap;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
}
.c2 {
position: relative;
object-fit: contain;
...
...
@@ -128,6 +262,21 @@ exports[`LandingPage renders it correctly 1`] = `
filter: drop-shadow(0px 12px 20px rgba(0,0,0,0.1));
}
@media screen and (min-width:640px) {
.c21 {
max-width: 169px;
}
}
@media screen and (min-width:640px) {
.c17 {
gap: 12px;
-webkit-flex-wrap: nowrap;
-ms-flex-wrap: nowrap;
flex-wrap: nowrap;
}
}
@media screen and (min-width:1280px) {
.c4 {
-webkit-filter: blur(50px);
...
...
@@ -259,6 +408,40 @@ exports[`LandingPage renders it correctly 1`] = `
Azuki #3318
</div>
</div>
<div
class="c15"
>
<div
class="c9 c16 c17"
>
<div
class="c15"
>
<div
class="c18 css-4u0e4f"
>
Owner
</div>
<a
class="c19"
href="https://etherscan.io/address/"
rel="noopener noreferrer"
target="_blank"
>
<div
class="c9 c20 c21"
>
<div
class="c9 c22 c23"
/>
<div
class="c24"
/>
</div>
</a>
</div>
</div>
</div>
</div>
</div>
</DocumentFragment>
...
...
src/nft/utils/asset.tsx
View file @
629fe2c1
...
...
@@ -13,7 +13,8 @@ import {
SquareSudoSwapMarketplaceIcon
,
SquareZoraMarketplaceIcon
,
}
from
'
nft/components/icons
'
import
{
DetailsOrigin
,
GenieAsset
,
Listing
,
Markets
,
UpdatedGenieAsset
,
WalletAsset
}
from
'
nft/types
'
import
{
DetailsOrigin
,
GenieAsset
,
Listing
,
Markets
,
Trait
,
UpdatedGenieAsset
,
WalletAsset
}
from
'
nft/types
'
import
qs
from
'
qs
'
import
{
v4
as
uuidv4
}
from
'
uuid
'
export
function
getRarityStatus
(
...
...
@@ -125,3 +126,14 @@ export const generateTweetForList = (assets: WalletAsset[]): string => {
.
join
(
'
,
'
)}
\n\nMarketplaces:
${
assets
[
0
].
marketplaces
?.
map
((
market
)
=>
market
.
name
).
join
(
'
,
'
)}
`
return `
https
:
//twitter.com/intent/tweet?text=${encodeURIComponent(tweetText)}`
}
export function getLinkForTrait(trait: Trait, collectionAddress: string): string {
const params = qs.stringify(
{ traits: [`
(
"
${trait.trait_type}
"
,
"
${trait.trait_value}
"
)
`] },
{
arrayFormat: 'comma',
}
)
return `
/
nfts
/
collection
/
$
{
collectionAddress
}?
$
{
params
}
`
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment