Commit 6e282a6d authored by Jack Short's avatar Jack Short Committed by GitHub

style: updating explore table (#5043)

* style: updating explore table

* pr comments

* addressing pr comments

* moved loading table to its own component

* removing clsx

* updating key for row

* updating spacing
parent d3a2e14d
......@@ -23,7 +23,7 @@ const BannerContainer = styled.div`
height: 100%;
gap: 14px;
margin-top: 4px;
margin-bottom: 30px;
margin-bottom: 6px;
}
`
......
......@@ -38,6 +38,7 @@ export const address = style([
])
export const verifiedBadge = sprinkles({
marginLeft: '4',
display: 'inline-block',
paddingTop: '4',
height: '28',
......
import { formatEther } from '@ethersproject/units'
import { SquareArrowDownIcon, SquareArrowUpIcon, VerifiedIcon } from 'nft/components/icons'
import { Denomination } from 'nft/types'
import { volumeFormatter } from 'nft/utils'
import styled from 'styled-components/macro'
import { ThemedText } from 'theme'
import { ethNumberStandardFormatter, formatWeiToDecimal } from '../../../utils/currency'
import { putCommas } from '../../../utils/putCommas'
import { formatChange } from '../../../utils/toSignificant'
import { Box } from '../../Box'
import { Column, Row } from '../../Flex'
import { VerifiedIcon } from '../../icons'
import * as styles from './Cells.css'
const CollectionNameContainer = styled.div`
display: flex;
padding: 14px 0px 14px 8px;
align-items: center;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
`
const CollectionName = styled.div`
margin-left: 8px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
`
const TruncatedSubHeader = styled(ThemedText.SubHeader)`
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
`
const RoundedImage = styled.div<{ src?: string }>`
height: 36px;
width: 36px;
border-radius: 36px;
background: ${({ src, theme }) => (src ? `url(${src})` : theme.backgroundModule)};
background-size: cover;
background-position: center;
flex-shrink: 0;
`
const ChangeCellContainer = styled.div<{ change: number }>`
display: flex;
color: ${({ theme, change }) => (change >= 0 ? theme.accentSuccess : theme.accentFailure)};
justify-content: end;
align-items: center;
gap: 2px;
`
const EthContainer = styled.div`
display: flex;
justify-content: end;
`
interface CellProps {
value: {
logo?: string
......@@ -19,48 +69,87 @@ interface CellProps {
export const CollectionTitleCell = ({ value }: CellProps) => {
return (
<Row as="span" style={{ marginLeft: '52px' }}>
<img className={styles.logo} src={value.logo} alt={`${value.name} logo`} height={44} width={44} />
<span className={styles.title}>{value.name}</span>
<CollectionNameContainer>
<RoundedImage src={value.logo} />
<CollectionName>
<TruncatedSubHeader>{value.name}</TruncatedSubHeader>
</CollectionName>
{value.isVerified && (
<span className={styles.verifiedBadge}>
<VerifiedIcon />
</span>
)}
</Row>
</CollectionNameContainer>
)
}
export const WithCommaCell = ({ value }: CellProps) => <span>{value.value ? putCommas(value.value) : '-'}</span>
export const EthCell = ({ value }: { value: number }) => (
<Row justifyContent="flex-end" color="textPrimary">
{value ? <>{formatWeiToDecimal(value.toString(), true)} ETH</> : '-'}
</Row>
export const DiscreteNumberCell = ({ value }: CellProps) => (
<span>{value.value ? volumeFormatter(value.value) : '-'}</span>
)
export const VolumeCell = ({ value }: CellProps) => (
<Row justifyContent="flex-end" color="textPrimary">
{value.value ? <>{ethNumberStandardFormatter(value.value.toString())} ETH</> : '-'}
</Row>
)
const getDenominatedValue = (denomination: Denomination, inWei: boolean, value?: number, usdPrice?: number) => {
if (denomination === Denomination.ETH) return value
if (usdPrice && value) return usdPrice * (inWei ? parseFloat(formatEther(value)) : value)
export const EthWithDayChange = ({ value }: CellProps) => (
<Column gap="4">
<VolumeCell value={{ value: value.value }} />
{value.change ? (
<Box
as="span"
color={value.change > 0 ? 'green' : 'accentFailure'}
fontWeight="normal"
fontSize="12"
position="relative"
>
{value.change > 0 && '+'}
{formatChange(value.change)}%
</Box>
) : null}
</Column>
return undefined
}
export const EthCell = ({
value,
denomination,
usdPrice,
}: {
value?: number
denomination: Denomination
usdPrice?: number
}) => {
const denominatedValue = getDenominatedValue(denomination, true, value, usdPrice)
const formattedValue = denominatedValue
? denomination === Denomination.ETH
? formatWeiToDecimal(denominatedValue.toString(), true) + ' ETH'
: ethNumberStandardFormatter(denominatedValue, true, false, true)
: '-'
return (
<EthContainer>
<ThemedText.BodyPrimary>{value ? formattedValue : '-'}</ThemedText.BodyPrimary>
</EthContainer>
)
}
export const VolumeCell = ({
value,
denomination,
usdPrice,
}: {
value?: number
denomination: Denomination
usdPrice?: number
}) => {
const denominatedValue = getDenominatedValue(denomination, false, value, usdPrice)
const formattedValue = denominatedValue
? denomination === Denomination.ETH
? ethNumberStandardFormatter(denominatedValue.toString(), false, false, true) + ' ETH'
: ethNumberStandardFormatter(denominatedValue, true, false, true)
: '-'
return (
<EthContainer>
<ThemedText.BodyPrimary>{value ? formattedValue : '-'}</ThemedText.BodyPrimary>
</EthContainer>
)
}
export const ChangeCell = ({ change }: { change?: number }) => (
<ChangeCellContainer change={change ?? 0}>
{!change || change > 0 ? (
<SquareArrowUpIcon width="20px" height="20px" />
) : (
<SquareArrowDownIcon width="20px" height="20px" />
)}
<ThemedText.BodyPrimary color="currentColor">{change ? Math.abs(Math.round(change)) : 0}%</ThemedText.BodyPrimary>
</ChangeCellContainer>
)
export const WeiWithDayChange = ({ value }: CellProps) => (
......@@ -82,21 +171,3 @@ export const WeiWithDayChange = ({ value }: CellProps) => (
) : null}
</Column>
)
export const CommaWithDayChange = ({ value }: CellProps) => (
<Column gap="4">
<WithCommaCell value={value} />
{value.change ? (
<Box
as="span"
color={value.change > 0 ? 'green' : 'accentFailure'}
fontWeight="normal"
fontSize="12"
position="relative"
>
{value.change > 0 && '+'}
{formatChange(value.change)}%
</Box>
) : null}
</Column>
)
import { CellProps, Column } from 'react-table'
import { BigNumber } from '@ethersproject/bignumber'
import { useMemo } from 'react'
import { CellProps, Column, Row } from 'react-table'
import { CollectionTableColumn } from '../../types'
import {
CollectionTitleCell,
CommaWithDayChange,
EthWithDayChange,
WeiWithDayChange,
WithCommaCell,
} from './Cells/Cells'
import { ChangeCell, CollectionTitleCell, DiscreteNumberCell, EthCell, VolumeCell } from './Cells/Cells'
import { Table } from './Table'
export enum ColumnHeaders {
Volume = 'Volume',
VolumeChange = 'Volume change',
Floor = 'Floor',
FloorChange = 'Floor change',
Sales = 'Sales',
Items = 'Items',
Owners = 'Owners',
}
const columns: Column<CollectionTableColumn>[] = [
const compareFloats = (a: number, b: number): 1 | -1 => {
return Math.round(a * 100000) >= Math.round(b * 100000) ? 1 : -1
}
const CollectionTable = ({ data }: { data: CollectionTableColumn[] }) => {
const floorSort = useMemo(() => {
return (rowA: Row<CollectionTableColumn>, rowB: Row<CollectionTableColumn>) => {
const aFloor = BigNumber.from(rowA.original.floor.value)
const bFloor = BigNumber.from(rowB.original.floor.value)
return aFloor.gte(bFloor) ? 1 : -1
}
}, [])
const floorChangeSort = useMemo(() => {
return (rowA: Row<CollectionTableColumn>, rowB: Row<CollectionTableColumn>) => {
return compareFloats(rowA.original.floor.change, rowB.original.floor.change)
}
}, [])
const volumeSort = useMemo(() => {
return (rowA: Row<CollectionTableColumn>, rowB: Row<CollectionTableColumn>) => {
return compareFloats(rowA.original.volume.value, rowB.original.volume.value)
}
}, [])
const volumeChangeSort = useMemo(() => {
return (rowA: Row<CollectionTableColumn>, rowB: Row<CollectionTableColumn>) => {
return compareFloats(rowA.original.volume.change, rowB.original.volume.change)
}
}, [])
const columns: Column<CollectionTableColumn>[] = useMemo(
() => [
{
Header: 'Collection',
Header: 'Collection name',
accessor: 'collection',
Cell: CollectionTitleCell,
disableSortBy: true,
},
{
id: ColumnHeaders.Volume,
Header: ColumnHeaders.Volume,
accessor: ({ volume }) => volume.value,
sortDescFirst: true,
Cell: function EthDayChanget(cell: CellProps<CollectionTableColumn>) {
return <EthWithDayChange value={cell.row.original.volume} />
id: ColumnHeaders.Floor,
Header: ColumnHeaders.Floor,
accessor: ({ floor }) => floor.value,
sortType: floorSort,
Cell: function ethCell(cell: CellProps<CollectionTableColumn>) {
return (
<EthCell
value={cell.row.original.floor.value}
denomination={cell.row.original.denomination}
usdPrice={cell.row.original.usdPrice}
/>
)
},
},
{
id: ColumnHeaders.Floor,
Header: ColumnHeaders.Floor,
id: ColumnHeaders.FloorChange,
Header: ColumnHeaders.FloorChange,
accessor: ({ floor }) => floor.value,
sortDescFirst: true,
Cell: function weiDayChange(cell: CellProps<CollectionTableColumn>) {
return <WeiWithDayChange value={cell.row.original.floor} />
sortType: floorChangeSort,
Cell: function changeCell(cell: CellProps<CollectionTableColumn>) {
return <ChangeCell change={cell.row.original.floor.change} />
},
},
{
id: ColumnHeaders.Sales,
Header: ColumnHeaders.Sales,
accessor: 'sales',
id: ColumnHeaders.Volume,
Header: ColumnHeaders.Volume,
accessor: ({ volume }) => volume.value,
sortDescFirst: true,
Cell: function withCommaCell(cell: CellProps<CollectionTableColumn>) {
return <WithCommaCell value={{ value: cell.row.original.sales }} />
sortType: volumeSort,
Cell: function volumeCell(cell: CellProps<CollectionTableColumn>) {
return (
<VolumeCell
value={cell.row.original.volume.value}
denomination={cell.row.original.denomination}
usdPrice={cell.row.original.usdPrice}
/>
)
},
},
{
id: ColumnHeaders.VolumeChange,
Header: ColumnHeaders.VolumeChange,
accessor: ({ volume }) => volume.value,
sortDescFirst: true,
sortType: volumeChangeSort,
Cell: function changeCell(cell: CellProps<CollectionTableColumn>) {
return <ChangeCell change={cell.row.original.volume.change} />
},
},
{
......@@ -56,25 +112,38 @@ const columns: Column<CollectionTableColumn>[] = [
Header: ColumnHeaders.Items,
accessor: 'totalSupply',
sortDescFirst: true,
Cell: function withCommaCell(cell: CellProps<CollectionTableColumn>) {
return <WithCommaCell value={{ value: cell.row.original.totalSupply }} />
Cell: function discreteNumberCell(cell: CellProps<CollectionTableColumn>) {
return <DiscreteNumberCell value={{ value: cell.row.original.totalSupply }} />
},
},
{
Header: ColumnHeaders.Owners,
accessor: ({ owners }) => owners.value,
sortDescFirst: true,
Cell: function commaDayChange(cell: CellProps<CollectionTableColumn>) {
return <CommaWithDayChange value={cell.row.original.owners} />
Cell: function discreteNumberCell(cell: CellProps<CollectionTableColumn>) {
return <DiscreteNumberCell value={cell.row.original.owners} />
},
},
]
const CollectionTable = ({ data }: { data: CollectionTableColumn[] }) => {
],
[floorChangeSort, floorSort, volumeChangeSort, volumeSort]
)
return (
<>
<Table
hiddenColumns={[ColumnHeaders.Volume, ColumnHeaders.Owners, ColumnHeaders.Items, ColumnHeaders.Sales]}
smallHiddenColumns={[
ColumnHeaders.Items,
ColumnHeaders.FloorChange,
ColumnHeaders.Volume,
ColumnHeaders.VolumeChange,
ColumnHeaders.Owners,
]}
mediumHiddenColumns={[
ColumnHeaders.Items,
ColumnHeaders.FloorChange,
ColumnHeaders.VolumeChange,
ColumnHeaders.Owners,
]}
largeHiddenColumns={[ColumnHeaders.Items, ColumnHeaders.Owners]}
{...{ data, columns }}
/>
</>
......
......@@ -8,7 +8,7 @@ export const section = style([
paddingRight: { sm: '16', xl: '0' },
}),
{
maxWidth: '1000px',
maxWidth: '1200px',
margin: '0 auto',
display: 'flex',
flexDirection: 'row',
......@@ -155,6 +155,7 @@ export const table = style([
borderSpacing: '0px 40px',
},
sprinkles({
background: 'backgroundSurface',
width: 'full',
borderRadius: '12',
borderStyle: 'none',
......@@ -178,15 +179,12 @@ export const th = style([
},
},
sprinkles({
color: { default: 'textSecondary', hover: 'textPrimary' },
cursor: 'pointer',
color: { default: 'textSecondary' },
paddingTop: '12',
paddingBottom: '12',
}),
])
export const tr = sprinkles({ cursor: 'pointer' })
export const rank = sprinkles({
color: 'textSecondary',
position: 'absolute',
......@@ -198,7 +196,6 @@ export const rank = sprinkles({
export const td = style([
body,
{
verticalAlign: 'middle',
selectors: {
'&:nth-last-child(1)': {
paddingRight: '20px',
......@@ -207,15 +204,32 @@ export const td = style([
},
sprinkles({
maxWidth: '160',
paddingTop: '10',
paddingBottom: '10',
paddingY: '8',
textAlign: 'right',
position: 'relative',
}),
])
export const loadingTd = style([
body,
{
selectors: {
'&:nth-last-child(1)': {
paddingRight: '20px',
},
},
},
sprinkles({
maxWidth: '160',
paddingY: '8',
textAlign: 'right',
position: 'relative',
}),
])
export const trendingOptions = sprinkles({
marginBottom: '32',
marginTop: '36',
marginBottom: '20',
height: '44',
borderRadius: '12',
borderWidth: '2px',
......
import { useWeb3React } from '@web3-react/core'
import { ElementName, Event, EventName } from 'analytics/constants'
import { TraceEvent } from 'analytics/TraceEvent'
import clsx from 'clsx'
import { LoadingBubble } from 'components/Tokens/loading'
import { useWindowSize } from 'hooks/useWindowSize'
import { useEffect } from 'react'
import { useNavigate } from 'react-router-dom'
import { Column, IdType, useSortBy, useTable } from 'react-table'
import { isMobile } from 'utils/userAgent'
import { Column, ColumnInstance, HeaderGroup, IdType, useSortBy, useTable } from 'react-table'
import styled, { useTheme } from 'styled-components/macro'
import { ThemedText } from 'theme'
import { Box } from '../../components/Box'
import { CollectionTableColumn } from '../../types'
......@@ -13,24 +13,91 @@ import { ArrowRightIcon } from '../icons'
import { ColumnHeaders } from './CollectionTable'
import * as styles from './Explore.css'
const RankCellContainer = styled.div`
display: flex;
align-items: center;
padding-left: 24px;
gap: 12px;
`
const StyledRow = styled.tr`
cursor: pointer;
:hover {
background: ${({ theme }) => theme.stateOverlayHover};
}
:active {
background: ${({ theme }) => theme.stateOverlayPressed};
}
`
const StyledLoadingRow = styled.tr`
height: 80px;
`
const StyledHeader = styled.th<{ isFirstHeader: boolean }>`
${({ isFirstHeader }) => !isFirstHeader && `cursor: pointer;`}
:hover {
${({ theme, isFirstHeader }) => !isFirstHeader && `opacity: ${theme.opacity.hover};`}
}
:active {
${({ theme, isFirstHeader }) => !isFirstHeader && `opacity: ${theme.opacity.click};`}
}
`
const StyledLoadingHolder = styled.div`
display: flex;
width: 100%;
justify-content: end;
align-items: center;
`
const StyledCollectionNameHolder = styled.div`
display: flex;
margin-left: 24px;
gap: 8px;
align-items: center;
`
const StyledImageHolder = styled(LoadingBubble)`
width: 36px;
height: 36px;
border-radius: 36px;
`
const StyledRankHolder = styled(LoadingBubble)`
width: 8px;
height: 16px;
margin-right: 12px;
`
const DEFAULT_TRENDING_TABLE_QUERY_AMOUNT = 10
interface TableProps<D extends Record<string, unknown>> {
columns: Column<CollectionTableColumn>[]
data: CollectionTableColumn[]
hiddenColumns: IdType<D>[]
smallHiddenColumns: IdType<D>[]
mediumHiddenColumns: IdType<D>[]
largeHiddenColumns: IdType<D>[]
classNames?: {
td: string
}
}
export function Table<D extends Record<string, unknown>>({
columns,
data,
hiddenColumns,
smallHiddenColumns,
mediumHiddenColumns,
largeHiddenColumns,
classNames,
...props
}: TableProps<D>) {
const { chainId } = useWeb3React()
const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow, setHiddenColumns } = useTable(
const theme = useTheme()
const { width } = useWindowSize()
const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow, setHiddenColumns, visibleColumns } =
useTable(
{
columns,
data,
......@@ -50,12 +117,22 @@ export function Table<D extends Record<string, unknown>>({
const navigate = useNavigate()
useEffect(() => {
if (hiddenColumns && isMobile) {
setHiddenColumns(hiddenColumns)
if (!width) return
if (width < theme.breakpoint.sm) {
setHiddenColumns(smallHiddenColumns)
} else if (width < theme.breakpoint.md) {
setHiddenColumns(mediumHiddenColumns)
} else if (width < theme.breakpoint.lg) {
setHiddenColumns(largeHiddenColumns)
} else {
setHiddenColumns([])
}
}, [hiddenColumns, setHiddenColumns])
}, [width, setHiddenColumns, columns, smallHiddenColumns, mediumHiddenColumns, largeHiddenColumns, theme.breakpoint])
if (data.length === 0) {
return <LoadingTable headerGroups={headerGroups} visibleColumns={visibleColumns} {...getTableProps()} />
}
return (
<table {...getTableProps()} className={styles.table}>
......@@ -64,13 +141,14 @@ export function Table<D extends Record<string, unknown>>({
<tr {...headerGroup.getHeaderGroupProps()} key={headerGroup.id}>
{headerGroup.headers.map((column, index) => {
return (
<th
<StyledHeader
className={styles.th}
{...column.getHeaderProps(column.getSortByToggleProps())}
style={{
textAlign: index === 0 ? 'left' : 'right',
paddingLeft: index === 0 ? '52px' : 0,
}}
isFirstHeader={index === 0}
key={index}
>
<Box as="span" color="accentAction" position="relative">
......@@ -87,7 +165,7 @@ export function Table<D extends Record<string, unknown>>({
<Box as="span" paddingLeft={column.isSorted ? '18' : '0'}>
{column.render('Header')}
</Box>
</th>
</StyledHeader>
)
})}
</tr>
......@@ -98,31 +176,100 @@ export function Table<D extends Record<string, unknown>>({
prepareRow(row)
return (
<TraceEvent
events={[Event.onClick]}
name={EventName.NFT_TRENDING_ROW_SELECTED}
properties={{ collection_address: row.original.collection.address, chain_id: chainId }}
element={ElementName.NFT_TRENDING_ROW}
key={i}
>
<tr
className={styles.tr}
<StyledRow
{...row.getRowProps()}
key={i}
key={row.id}
onClick={() => navigate(`/nfts/collection/${row.original.collection.address}`)}
>
{row.cells.map((cell, cellIndex) => {
return (
<td className={clsx(styles.td, classNames?.td)} {...cell.getCellProps()} key={cellIndex}>
{cellIndex === 0 ? <span className={styles.rank}>{i + 1}</span> : null}
{cellIndex === 0 ? (
<RankCellContainer>
<ThemedText.BodySecondary fontSize="14px" lineHeight="20px">
{i + 1}
</ThemedText.BodySecondary>
{cell.render('Cell')}
</RankCellContainer>
) : (
cell.render('Cell')
)}
</td>
)
})}
</StyledRow>
)
})}
</tbody>
</table>
)
}
interface LoadingTableProps {
headerGroups: HeaderGroup<CollectionTableColumn>[]
visibleColumns: ColumnInstance<CollectionTableColumn>[]
}
function LoadingTable({ headerGroups, visibleColumns, ...props }: LoadingTableProps) {
return (
<table {...props} className={styles.table}>
<thead className={styles.thead}>
{headerGroups.map((headerGroup) => (
<tr {...headerGroup.getHeaderGroupProps()} key={headerGroup.id}>
{headerGroup.headers.map((column, index) => {
return (
<StyledHeader
className={styles.th}
{...column.getHeaderProps(column.getSortByToggleProps())}
style={{
textAlign: index === 0 ? 'left' : 'right',
paddingLeft: index === 0 ? '52px' : 0,
}}
isFirstHeader={index === 0}
key={index}
>
<Box as="span" color="accentAction" position="relative">
{column.isSorted ? (
column.isSortedDesc ? (
<ArrowRightIcon style={{ transform: 'rotate(90deg)', position: 'absolute' }} />
) : (
<ArrowRightIcon style={{ transform: 'rotate(-90deg)', position: 'absolute' }} />
)
) : (
''
)}
</Box>
<Box as="span" paddingLeft={column.isSorted ? '18' : '0'}>
{column.render('Header')}
</Box>
</StyledHeader>
)
})}
</tr>
</TraceEvent>
))}
</thead>
<tbody {...props}>
{[...Array(DEFAULT_TRENDING_TABLE_QUERY_AMOUNT)].map((_, index) => (
<StyledLoadingRow key={index}>
{[...Array(visibleColumns.length)].map((_, cellIndex) => {
return (
<td className={styles.loadingTd} key={cellIndex}>
{cellIndex === 0 ? (
<StyledCollectionNameHolder>
<StyledRankHolder />
<StyledImageHolder />
<LoadingBubble />
</StyledCollectionNameHolder>
) : (
<StyledLoadingHolder>
<LoadingBubble />
</StyledLoadingHolder>
)}
</td>
)
})}
</StyledLoadingRow>
))}
</tbody>
</table>
)
......
import clsx from 'clsx'
import ms from 'ms.macro'
import { CollectionTableColumn, Denomination, TimePeriod, VolumeType } from 'nft/types'
import { fetchPrice } from 'nft/utils'
import { useMemo, useState } from 'react'
import { useQuery } from 'react-query'
import styled from 'styled-components/macro'
import { ThemedText } from 'theme'
import { Box } from '../../components/Box'
import { Column, Row } from '../../components/Flex'
import { headlineMedium } from '../../css/common.css'
import { fetchTrendingCollections } from '../../queries'
import { CollectionTableColumn, TimePeriod, VolumeType } from '../../types'
import CollectionTable from './CollectionTable'
import * as styles from './Explore.css'
const timeOptions: { label: string; value: TimePeriod }[] = [
{ label: '24 hour', value: TimePeriod.OneDay },
{ label: '7 day', value: TimePeriod.SevenDays },
{ label: '30 day', value: TimePeriod.ThirtyDays },
{ label: 'All time', value: TimePeriod.AllTime },
{ label: '1D', value: TimePeriod.OneDay },
{ label: '1W', value: TimePeriod.SevenDays },
{ label: '1M', value: TimePeriod.ThirtyDays },
{ label: 'All', value: TimePeriod.AllTime },
]
const ExploreContainer = styled.div`
display: flex;
flex-direction: column;
width: 100%;
max-width: 1200px;
`
const StyledHeader = styled.div`
color: ${({ theme }) => theme.textPrimary};
font-size: 36px;
line-height: 44px;
weight: 500;
@media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.sm}px`}) {
font-size: 20px;
line-height: 28px;
}
`
const FiltersRow = styled.div`
display: flex;
justify-content: space-between;
margin-top: 36px;
margin-bottom: 20px;
@media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.sm}px`}) {
margin-bottom: 16px;
margin-top: 16px;
}
`
const Filter = styled.div`
display: flex;
outline: 1px solid ${({ theme }) => theme.backgroundOutline};
border-radius: 16px;
padding: 4px;
`
const Selector = styled.div<{ active: boolean }>`
padding: 8px 12px;
border-radius: 12px;
background: ${({ active, theme }) => (active ? theme.backgroundInteractive : 'none')};
cursor: pointer;
:hover {
opacity: ${({ theme }) => theme.opacity.hover};
}
:active {
opacity: ${({ theme }) => theme.opacity.click};
}
`
const StyledSelectorText = styled(ThemedText.SubHeader)<{ active: boolean }>`
color: ${({ theme, active }) => (active ? theme.textPrimary : theme.textSecondary)};
`
const TrendingCollections = () => {
const [timePeriod, setTimePeriod] = useState<TimePeriod>(TimePeriod.OneDay)
const [isEthToggled, setEthToggled] = useState(true)
const { isSuccess, data } = useQuery(
['trendingCollections', timePeriod],
......@@ -33,6 +90,13 @@ const TrendingCollections = () => {
}
)
const { data: usdPrice } = useQuery(['fetchPrice', {}], () => fetchPrice(), {
refetchOnReconnect: false,
refetchOnWindowFocus: false,
refetchOnMount: false,
refetchInterval: ms`1m`,
})
const trendingCollections = useMemo(() => {
if (isSuccess && data) {
return data.map((d) => ({
......@@ -58,39 +122,46 @@ const TrendingCollections = () => {
},
sales: d.sales,
totalSupply: d.totalSupply,
denomination: isEthToggled ? Denomination.ETH : Denomination.USD,
usdPrice,
}))
} else return [] as CollectionTableColumn[]
}, [data, isSuccess])
}, [data, isSuccess, isEthToggled, usdPrice])
return (
<Box width="full" className={styles.section}>
<Column width="full">
<Row>
<Box as="h2" className={headlineMedium} marginTop="88">
Trending Collections
</Box>
</Row>
<Row>
<Box className={styles.trendingOptions}>
<ExploreContainer>
<StyledHeader>Trending NFT collections</StyledHeader>
<FiltersRow>
<Filter>
{timeOptions.map((timeOption) => {
return (
<span
className={clsx(
styles.trendingOption,
timeOption.value === timePeriod && styles.trendingOptionActive
)}
<Selector
key={timeOption.value}
active={timeOption.value === timePeriod}
onClick={() => setTimePeriod(timeOption.value)}
>
<StyledSelectorText lineHeight="20px" active={timeOption.value === timePeriod}>
{timeOption.label}
</span>
</StyledSelectorText>
</Selector>
)
})}
</Box>
</Row>
<Row paddingBottom="52">{data ? <CollectionTable data={trendingCollections} /> : <p>Loading</p>}</Row>
</Column>
</Box>
</Filter>
<Filter onClick={() => setEthToggled(!isEthToggled)}>
<Selector active={isEthToggled}>
<StyledSelectorText lineHeight="20px" active={isEthToggled}>
ETH
</StyledSelectorText>
</Selector>
<Selector active={!isEthToggled}>
<StyledSelectorText lineHeight="20px" active={!isEthToggled}>
USD
</StyledSelectorText>
</Selector>
</Filter>
</FiltersRow>
<CollectionTable data={trendingCollections} />
</ExploreContainer>
)
}
......
......@@ -10,6 +10,14 @@ const ExploreContainer = styled.div`
align-items: center;
width: 100%;
padding: 16px;
@media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.md}px`}) {
gap: 16px;
}
@media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.sm}px`}) {
gap: 0px;
}
`
const NftExplore = () => {
......
......@@ -52,6 +52,11 @@ export interface TrendingCollection {
sales: number
}
export enum Denomination {
ETH = 'ETH',
USD = 'USD',
}
export interface CollectionTableColumn {
collection: {
name: string
......@@ -74,4 +79,6 @@ export interface CollectionTableColumn {
}
sales: number
totalSupply: number
denomination: Denomination
usdPrice?: number
}
......@@ -44,7 +44,8 @@ export const numberToWei = (amount: number) => {
export const ethNumberStandardFormatter = (
amount: string | number | undefined,
includeDollarSign = false,
removeZeroes = false
removeZeroes = false,
roundToNearestWholeNumber = false
): string => {
if (!amount) return '-'
......@@ -53,8 +54,14 @@ export const ethNumberStandardFormatter = (
if (amountInDecimals === 0) return '-'
if (amountInDecimals < 0.0001) return `< ${conditionalDollarSign}0.00001`
if (amountInDecimals < 1) return `${conditionalDollarSign}${amountInDecimals.toFixed(3)}`
const formattedPrice = (removeZeroes ? parseFloat(amountInDecimals.toFixed(2)) : amountInDecimals.toFixed(2))
if (amountInDecimals < 1) return `${conditionalDollarSign}${parseFloat(amountInDecimals.toFixed(3))}`
const formattedPrice = (
removeZeroes
? parseFloat(amountInDecimals.toFixed(2))
: roundToNearestWholeNumber
? Math.round(amountInDecimals)
: amountInDecimals.toFixed(2)
)
.toString()
.replace(/\B(?=(\d{3})+(?!\d))/g, ',')
return conditionalDollarSign + formattedPrice
......@@ -62,5 +69,5 @@ export const ethNumberStandardFormatter = (
export const formatWeiToDecimal = (amount: string, removeZeroes = false) => {
if (!amount) return '-'
return ethNumberStandardFormatter(formatEther(amount), false, removeZeroes)
return ethNumberStandardFormatter(formatEther(amount), false, removeZeroes, false)
}
......@@ -8,6 +8,7 @@ export * from './fetchPrice'
export * from './isAudio'
export * from './isVideo'
export * from './listNfts'
export * from './numbers'
export * from './putCommas'
export * from './rarity'
export * from './roundAndPluralize'
......
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