render links to token pages on server for indexing
This commit is contained in:
parent
df079a5994
commit
27013aa7f8
|
@ -1,10 +1,6 @@
|
|||
'use client'
|
||||
|
||||
import { useMemo, useState } from 'react'
|
||||
import { TokenPageWithData } from '../../../contentful/tokenPage'
|
||||
import { MangoTokenData } from '../../types/mango'
|
||||
import CategorySwitcher from './CategorySwitcher'
|
||||
import TableViewToggle from './TableViewToggle'
|
||||
import TokenTable from './TokenTable'
|
||||
import { TokenCategoryPage } from '../../../contentful/tokenCategoryPage'
|
||||
import { sortTokens } from './ExploreTokens'
|
||||
|
@ -22,12 +18,12 @@ const Category = ({
|
|||
tokensForCategory: TokenPageWithData[]
|
||||
mangoTokensData: MangoTokenData[]
|
||||
}) => {
|
||||
const [showTableView, setShowTableView] = useState(true)
|
||||
// const [showTableView, setShowTableView] = useState(true)
|
||||
const { category, slug } = categoryPageData
|
||||
|
||||
const sortedTokens = useMemo(() => {
|
||||
return sortTokens(tokensForCategory)
|
||||
}, [tokensForCategory])
|
||||
// const sortedTokens = useMemo(() => {
|
||||
// return sortTokens(tokensForCategory)
|
||||
// }, [tokensForCategory])
|
||||
|
||||
const backgroundImageUrl = `/images/categories/${slug}.webp`
|
||||
|
||||
|
@ -42,21 +38,20 @@ const Category = ({
|
|||
className={`px-6 lg:px-20 ${MAX_CONTENT_WIDTH} mx-auto py-10 md:py-16`}
|
||||
>
|
||||
<div className="mb-4 flex flex-col-reverse sm:flex-row sm:items-center sm:justify-between">
|
||||
<p>{`${sortedTokens?.length} ${category} ${
|
||||
sortedTokens?.length === 1 ? 'token' : 'tokens'
|
||||
<p>{`${tokensForCategory?.length} ${category} ${
|
||||
tokensForCategory?.length === 1 ? 'token' : 'tokens'
|
||||
} listed on Mango`}</p>
|
||||
<div className="flex space-x-2 mb-6 sm:mb-0">
|
||||
<CategorySwitcher categories={categoryPages} />
|
||||
<TableViewToggle
|
||||
{/* <TableViewToggle
|
||||
showTableView={showTableView}
|
||||
setShowTableView={setShowTableView}
|
||||
/>
|
||||
/> */}
|
||||
</div>
|
||||
</div>
|
||||
<TokenTable
|
||||
tokens={sortedTokens}
|
||||
tokens={sortTokens(tokensForCategory)}
|
||||
mangoTokensData={mangoTokensData}
|
||||
showTableView={showTableView}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
'use client'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import Select from '../forms/Select'
|
||||
import { AppRouterInstance } from 'next/dist/shared/lib/app-router-context.shared-runtime'
|
||||
|
|
|
@ -1,51 +1,9 @@
|
|||
'use client'
|
||||
|
||||
import { TokenPageWithData } from '../../../contentful/tokenPage'
|
||||
import { MangoTokenData } from '../../types/mango'
|
||||
import { MagnifyingGlassIcon } from '@heroicons/react/20/solid'
|
||||
import { ChangeEvent, useMemo, useState } from 'react'
|
||||
import Input from '../forms/Input'
|
||||
import TokenTable from './TokenTable'
|
||||
import TableViewToggle from './TableViewToggle'
|
||||
import { MAX_CONTENT_WIDTH } from '../../utils/constants'
|
||||
import PageHeader from './PageHeader'
|
||||
|
||||
const generateSearchTerm = (item: TokenPageWithData, searchValue: string) => {
|
||||
const normalizedSearchValue = searchValue.toLowerCase()
|
||||
const nameValue = item.tokenName.toLowerCase()
|
||||
const symbolValue = item.symbol.toLowerCase()
|
||||
|
||||
const isMatchingName = nameValue.includes(normalizedSearchValue)
|
||||
const isMatchingSymbol = symbolValue.includes(normalizedSearchValue)
|
||||
const matchingNamePercent = isMatchingName
|
||||
? normalizedSearchValue.length / item.tokenName.length
|
||||
: 0
|
||||
const matchingSymbolPercent = isMatchingSymbol
|
||||
? normalizedSearchValue.length / item.symbol.length
|
||||
: 0
|
||||
|
||||
const matchingPercent = Math.max(matchingNamePercent, matchingSymbolPercent)
|
||||
|
||||
const matchingIdx =
|
||||
matchingPercent === matchingNamePercent
|
||||
? nameValue.indexOf(normalizedSearchValue)
|
||||
: symbolValue.indexOf(normalizedSearchValue)
|
||||
|
||||
return {
|
||||
token: item,
|
||||
matchingIdx,
|
||||
matchingPercent,
|
||||
}
|
||||
}
|
||||
|
||||
const startSearch = (items: TokenPageWithData[], searchValue: string) => {
|
||||
return items
|
||||
.map((item) => generateSearchTerm(item, searchValue))
|
||||
.filter((item) => item.matchingIdx >= 0)
|
||||
.sort((i1, i2) => i1.matchingIdx - i2.matchingIdx)
|
||||
.sort((i1, i2) => i2.matchingPercent - i1.matchingPercent)
|
||||
.map((item) => item.token)
|
||||
}
|
||||
import TokensFilter from './TokensFilter'
|
||||
|
||||
export const sortTokens = (tokens: TokenPageWithData[]) => {
|
||||
return tokens.sort((a, b) => {
|
||||
|
@ -71,17 +29,6 @@ const ExploreTokens = ({
|
|||
tokens: TokenPageWithData[]
|
||||
mangoTokensData: MangoTokenData[]
|
||||
}) => {
|
||||
const [showTableView, setShowTableView] = useState(true)
|
||||
const [searchString, setSearchString] = useState('')
|
||||
|
||||
const filteredTokens = useMemo(() => {
|
||||
return searchString ? startSearch(tokens, searchString) : sortTokens(tokens)
|
||||
}, [searchString, tokens])
|
||||
|
||||
const handleUpdateSearch = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
setSearchString(e.target.value)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHeader title="Explore listed tokens" />
|
||||
|
@ -91,25 +38,12 @@ const ExploreTokens = ({
|
|||
<div className="mb-4 flex flex-col-reverse sm:flex-row sm:items-center sm:justify-between">
|
||||
<p>{`${tokens?.length} tokens listed on Mango`}</p>
|
||||
<div className="flex space-x-2 mb-6 sm:mb-0">
|
||||
<div className="relative w-full lg:mb-0 sm:w-44">
|
||||
<Input
|
||||
heightClass="h-10 pl-8"
|
||||
type="text"
|
||||
value={searchString}
|
||||
onChange={handleUpdateSearch}
|
||||
/>
|
||||
<MagnifyingGlassIcon className="absolute left-2 top-3 h-4 w-4 text-th-fgd-3" />
|
||||
</div>
|
||||
<TableViewToggle
|
||||
showTableView={showTableView}
|
||||
setShowTableView={setShowTableView}
|
||||
/>
|
||||
<TokensFilter tokens={tokens} />
|
||||
</div>
|
||||
</div>
|
||||
<TokenTable
|
||||
tokens={filteredTokens}
|
||||
tokens={sortTokens(tokens)}
|
||||
mangoTokensData={mangoTokensData}
|
||||
showTableView={showTableView}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
|
|
|
@ -1,33 +1,20 @@
|
|||
'use client'
|
||||
|
||||
import Image from 'next/image'
|
||||
import { CUSTOM_TOKEN_ICONS } from '../../utils/constants'
|
||||
import {
|
||||
SortableColumnHeader,
|
||||
Table,
|
||||
Td,
|
||||
Th,
|
||||
TrBody,
|
||||
TrHead,
|
||||
} from '../shared/TableElements'
|
||||
import TokenCard from './TokenCard'
|
||||
import { Table, Td, Th, TrBody, TrHead } from '../shared/TableElements'
|
||||
import {
|
||||
ChevronRightIcon,
|
||||
QuestionMarkCircleIcon,
|
||||
} from '@heroicons/react/20/solid'
|
||||
import { formatNumericValue, numberCompacter } from '../../utils/numbers'
|
||||
import SimpleAreaChart from '../shared/SimpleAreaChart'
|
||||
import { useSortableData } from '../../hooks/useSortableData'
|
||||
import { TokenPageWithData } from '../../../contentful/tokenPage'
|
||||
import { useCallback } from 'react'
|
||||
import { MangoTokenData } from '../../types/mango'
|
||||
import { useViewport } from '../../hooks/useViewport'
|
||||
import { AppRouterInstance } from 'next/dist/shared/lib/app-router-context.shared-runtime'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { BirdeyePriceHistoryData } from '../../types/birdeye'
|
||||
import SheenLoader from '../shared/SheenLoader'
|
||||
import NoResults from './NoResults'
|
||||
import Solana from '../icons/Solana'
|
||||
import Link from 'next/link'
|
||||
import TokenCard from './TokenCard'
|
||||
|
||||
export type FormattedTableData = {
|
||||
change: number | undefined
|
||||
|
@ -46,22 +33,13 @@ export type FormattedTableData = {
|
|||
ethCircSupply: number | undefined
|
||||
}
|
||||
|
||||
const goToTokenPage = (slug: string, router: AppRouterInstance) => {
|
||||
router.push(`/explore/tokens/${slug}`)
|
||||
}
|
||||
|
||||
const TokenTable = ({
|
||||
tokens,
|
||||
mangoTokensData,
|
||||
showTableView,
|
||||
}: {
|
||||
tokens: TokenPageWithData[]
|
||||
mangoTokensData: MangoTokenData[]
|
||||
showTableView: boolean
|
||||
}) => {
|
||||
const router = useRouter()
|
||||
const { isDesktop, width } = useViewport()
|
||||
|
||||
const formattedTableData = useCallback(() => {
|
||||
const formatted: FormattedTableData[] = []
|
||||
for (const token of tokens) {
|
||||
|
@ -135,189 +113,141 @@ const TokenTable = ({
|
|||
return formatted
|
||||
}, [tokens, mangoTokensData])
|
||||
|
||||
const {
|
||||
items: tableData,
|
||||
requestSort,
|
||||
sortConfig,
|
||||
} = useSortableData(formattedTableData())
|
||||
return formattedTableData().length ? (
|
||||
<>
|
||||
<div className="hidden lg:block">
|
||||
<Table>
|
||||
<thead>
|
||||
<TrHead>
|
||||
<Th className="text-left">Token</Th>
|
||||
<Th className="text-right">Price</Th>
|
||||
<Th className="text-right">24h chart</Th>
|
||||
<Th className="text-right">24h Change</Th>
|
||||
<Th className="text-right">24h Volume</Th>
|
||||
<Th className="text-right">FDV</Th>
|
||||
<Th />
|
||||
</TrHead>
|
||||
</thead>
|
||||
<tbody>
|
||||
{formattedTableData().map((token) => {
|
||||
const {
|
||||
tokenName,
|
||||
change,
|
||||
chartData,
|
||||
price,
|
||||
volume,
|
||||
fdv,
|
||||
mangoSymbol,
|
||||
logoURI,
|
||||
symbol,
|
||||
slug,
|
||||
ethCircSupply,
|
||||
ethMint,
|
||||
} = token
|
||||
const hasCustomIcon = mangoSymbol
|
||||
? CUSTOM_TOKEN_ICONS[mangoSymbol.toLowerCase()]
|
||||
: false
|
||||
const logoPath = hasCustomIcon
|
||||
? `/icons/tokens/${mangoSymbol?.toLowerCase()}.svg`
|
||||
: logoURI
|
||||
|
||||
return tableData.length ? (
|
||||
!width ? (
|
||||
<SheenLoader className="flex flex-1">
|
||||
<div className={`h-96 w-full rounded-lg bg-th-bkg-2`} />
|
||||
</SheenLoader>
|
||||
) : isDesktop && showTableView ? (
|
||||
<Table>
|
||||
<thead>
|
||||
<TrHead>
|
||||
<Th className="text-left">
|
||||
<SortableColumnHeader
|
||||
sortKey="tokenName"
|
||||
sort={() => requestSort('tokenName')}
|
||||
sortConfig={sortConfig}
|
||||
title="Token"
|
||||
/>
|
||||
</Th>
|
||||
<Th>
|
||||
<div className="flex justify-end">
|
||||
<SortableColumnHeader
|
||||
sortKey="price"
|
||||
sort={() => requestSort('price')}
|
||||
sortConfig={sortConfig}
|
||||
title="Price"
|
||||
/>
|
||||
</div>
|
||||
</Th>
|
||||
<Th className="text-right">24h chart</Th>
|
||||
<Th>
|
||||
<div className="flex justify-end">
|
||||
<SortableColumnHeader
|
||||
sortKey="change"
|
||||
sort={() => requestSort('change')}
|
||||
sortConfig={sortConfig}
|
||||
title="24h Change"
|
||||
/>
|
||||
</div>
|
||||
</Th>
|
||||
<Th>
|
||||
<div className="flex justify-end">
|
||||
<SortableColumnHeader
|
||||
sortKey="volume"
|
||||
sort={() => requestSort('volume')}
|
||||
sortConfig={sortConfig}
|
||||
title="24h Volume"
|
||||
/>
|
||||
</div>
|
||||
</Th>
|
||||
<Th>
|
||||
<div className="flex justify-end">
|
||||
<SortableColumnHeader
|
||||
sortKey="fdv"
|
||||
sort={() => requestSort('fdv')}
|
||||
sortConfig={sortConfig}
|
||||
title="FDV"
|
||||
/>
|
||||
</div>
|
||||
</Th>
|
||||
<Th />
|
||||
</TrHead>
|
||||
</thead>
|
||||
<tbody>
|
||||
{tableData.map((token) => {
|
||||
const {
|
||||
tokenName,
|
||||
change,
|
||||
chartData,
|
||||
price,
|
||||
volume,
|
||||
fdv,
|
||||
mangoSymbol,
|
||||
logoURI,
|
||||
symbol,
|
||||
slug,
|
||||
ethCircSupply,
|
||||
ethMint,
|
||||
} = token
|
||||
const hasCustomIcon = mangoSymbol
|
||||
? CUSTOM_TOKEN_ICONS[mangoSymbol.toLowerCase()]
|
||||
: false
|
||||
const logoPath = hasCustomIcon
|
||||
? `/icons/tokens/${mangoSymbol?.toLowerCase()}.svg`
|
||||
: logoURI
|
||||
|
||||
return (
|
||||
<TrBody
|
||||
key={slug}
|
||||
className="default-transition md:hover:cursor-pointer md:hover:bg-th-bkg-2"
|
||||
onClick={() => goToTokenPage(slug, router)}
|
||||
>
|
||||
<Td>
|
||||
<div className="flex items-center space-x-3">
|
||||
{logoPath ? (
|
||||
<Image src={logoPath} alt="Logo" height={32} width={32} />
|
||||
) : (
|
||||
<QuestionMarkCircleIcon className="h-8 w-8 text-th-fgd-4" />
|
||||
)}
|
||||
<div>
|
||||
<p>{tokenName}</p>
|
||||
<p className="text-sm text-th-fgd-4">
|
||||
{symbol || mangoSymbol}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<p className="text-right">
|
||||
{price ? `$${formatNumericValue(price)}` : '–'}
|
||||
</p>
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex justify-end">
|
||||
{chartData && chartData.length ? (
|
||||
<div className="h-9 w-20">
|
||||
<SimpleAreaChart
|
||||
color={
|
||||
chartData[0].value <=
|
||||
chartData[chartData.length - 1].value
|
||||
? 'var(--up)'
|
||||
: 'var(--down)'
|
||||
}
|
||||
data={chartData}
|
||||
name={tokenName}
|
||||
xKey="unixTime"
|
||||
yKey="value"
|
||||
return (
|
||||
<TrBody className="relative hover:bg-th-bkg-2" key={slug}>
|
||||
<Td>
|
||||
<div className="flex items-center space-x-3">
|
||||
{logoPath ? (
|
||||
<Image
|
||||
src={logoPath}
|
||||
alt="Logo"
|
||||
height={32}
|
||||
width={32}
|
||||
/>
|
||||
) : (
|
||||
<QuestionMarkCircleIcon className="h-8 w-8 text-th-fgd-4" />
|
||||
)}
|
||||
<div>
|
||||
<p>{tokenName}</p>
|
||||
<p className="text-sm text-th-fgd-4">
|
||||
{symbol || mangoSymbol}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-th-fgd-4">Unavailable</p>
|
||||
)}
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<p
|
||||
className={`text-right ${
|
||||
!change
|
||||
? 'text-th-fgd-3'
|
||||
: change > 0
|
||||
? 'text-th-up'
|
||||
: change < 0
|
||||
? 'text-th-down'
|
||||
: 'text-th-fgd-3'
|
||||
}`}
|
||||
>
|
||||
{change ? `${change.toFixed(2)}%` : '–'}
|
||||
</p>
|
||||
</Td>
|
||||
<Td>
|
||||
<p className="text-right">
|
||||
{volume ? `$${numberCompacter.format(volume)}` : '–'}
|
||||
</p>
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex items-center justify-end">
|
||||
{ethMint && !ethCircSupply ? (
|
||||
<Solana className="h-3.5 w-3.5 mr-1.5" />
|
||||
) : null}
|
||||
<p>{fdv ? `$${numberCompacter.format(fdv)}` : '–'}</p>
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex items-center justify-end">
|
||||
<ChevronRightIcon className="h-5 w-5 text-th-fgd-4" />
|
||||
</div>
|
||||
</Td>
|
||||
</TrBody>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</Table>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{tableData.map((data) => (
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<p className="text-right">
|
||||
{price ? `$${formatNumericValue(price)}` : '–'}
|
||||
</p>
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex justify-end">
|
||||
{chartData && chartData.length ? (
|
||||
<div className="h-9 w-20">
|
||||
<SimpleAreaChart
|
||||
color={
|
||||
chartData[0].value <=
|
||||
chartData[chartData.length - 1].value
|
||||
? 'var(--up)'
|
||||
: 'var(--down)'
|
||||
}
|
||||
data={chartData}
|
||||
name={tokenName}
|
||||
xKey="unixTime"
|
||||
yKey="value"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-th-fgd-4">Unavailable</p>
|
||||
)}
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<p
|
||||
className={`text-right ${
|
||||
!change
|
||||
? 'text-th-fgd-3'
|
||||
: change > 0
|
||||
? 'text-th-up'
|
||||
: change < 0
|
||||
? 'text-th-down'
|
||||
: 'text-th-fgd-3'
|
||||
}`}
|
||||
>
|
||||
{change ? `${change.toFixed(2)}%` : '–'}
|
||||
</p>
|
||||
</Td>
|
||||
<Td>
|
||||
<p className="text-right">
|
||||
{volume ? `$${numberCompacter.format(volume)}` : '–'}
|
||||
</p>
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex items-center justify-end">
|
||||
{ethMint && !ethCircSupply ? (
|
||||
<Solana className="h-3.5 w-3.5 mr-1.5" />
|
||||
) : null}
|
||||
<p>{fdv ? `$${numberCompacter.format(fdv)}` : '–'}</p>
|
||||
</div>
|
||||
</Td>
|
||||
<Td className="absolute w-full z-20 h-[69px] left-0 pt-0 pb-0 last:pr-2">
|
||||
<Link
|
||||
className="h-[69px] flex items-center justify-end"
|
||||
href={`/explore/tokens/${slug}`}
|
||||
>
|
||||
<ChevronRightIcon className="h-6 w-6 text-th-fgd-4" />
|
||||
</Link>
|
||||
</Td>
|
||||
</TrBody>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</Table>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 lg:hidden">
|
||||
{formattedTableData().map((data) => (
|
||||
<TokenCard token={data} key={data.slug} />
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
</>
|
||||
) : (
|
||||
<NoResults message="No tokens found..." />
|
||||
)
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
'use client'
|
||||
import { ChangeEvent, useMemo, useState } from 'react'
|
||||
import { TokenPageWithData } from '../../../contentful/tokenPage'
|
||||
import Input from '../forms/Input'
|
||||
import {
|
||||
ChevronRightIcon,
|
||||
MagnifyingGlassIcon,
|
||||
} from '@heroicons/react/20/solid'
|
||||
import Link from 'next/link'
|
||||
|
||||
const generateSearchTerm = (item: TokenPageWithData, searchValue: string) => {
|
||||
const normalizedSearchValue = searchValue.toLowerCase()
|
||||
const nameValue = item.tokenName.toLowerCase()
|
||||
const symbolValue = item.symbol.toLowerCase()
|
||||
|
||||
const isMatchingName = nameValue.includes(normalizedSearchValue)
|
||||
const isMatchingSymbol = symbolValue.includes(normalizedSearchValue)
|
||||
const matchingNamePercent = isMatchingName
|
||||
? normalizedSearchValue.length / item.tokenName.length
|
||||
: 0
|
||||
const matchingSymbolPercent = isMatchingSymbol
|
||||
? normalizedSearchValue.length / item.symbol.length
|
||||
: 0
|
||||
|
||||
const matchingPercent = Math.max(matchingNamePercent, matchingSymbolPercent)
|
||||
|
||||
const matchingIdx =
|
||||
matchingPercent === matchingNamePercent
|
||||
? nameValue.indexOf(normalizedSearchValue)
|
||||
: symbolValue.indexOf(normalizedSearchValue)
|
||||
|
||||
return {
|
||||
token: item,
|
||||
matchingIdx,
|
||||
matchingPercent,
|
||||
}
|
||||
}
|
||||
|
||||
const startSearch = (items: TokenPageWithData[], searchValue: string) => {
|
||||
return items
|
||||
.map((item) => generateSearchTerm(item, searchValue))
|
||||
.filter((item) => item.matchingIdx >= 0)
|
||||
.sort((i1, i2) => i1.matchingIdx - i2.matchingIdx)
|
||||
.sort((i1, i2) => i2.matchingPercent - i1.matchingPercent)
|
||||
.map((item) => item.token)
|
||||
}
|
||||
|
||||
export const sortTokens = (tokens: TokenPageWithData[]) => {
|
||||
return tokens.sort((a, b) => {
|
||||
const aValue = a?.birdeyeData?.v24hUSD
|
||||
const bValue = b?.birdeyeData?.v24hUSD
|
||||
|
||||
if (aValue === undefined && bValue === undefined) {
|
||||
return 0
|
||||
} else if (aValue === undefined) {
|
||||
return 1
|
||||
} else if (bValue === undefined) {
|
||||
return -1
|
||||
} else {
|
||||
return bValue - aValue
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const TokensFilter = ({ tokens }: { tokens: TokenPageWithData[] }) => {
|
||||
const [searchString, setSearchString] = useState('')
|
||||
|
||||
const filteredTokens = useMemo(() => {
|
||||
return searchString ? startSearch(tokens, searchString) : []
|
||||
}, [searchString, tokens])
|
||||
|
||||
const handleUpdateSearch = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
setSearchString(e.target.value)
|
||||
}
|
||||
return (
|
||||
<div className="relative w-full lg:mb-0 sm:w-44">
|
||||
<Input
|
||||
heightClass="h-10 pl-8"
|
||||
type="text"
|
||||
value={searchString}
|
||||
onChange={handleUpdateSearch}
|
||||
/>
|
||||
<MagnifyingGlassIcon className="absolute left-2 top-3 h-4 w-4 text-th-fgd-3" />
|
||||
{filteredTokens.length && searchString.length > 1 ? (
|
||||
<div className="absolute z-20 top-12 bg-th-bkg-2 rounded-lg p-4 space-y-3 w-full">
|
||||
{filteredTokens.map((token) => (
|
||||
<Link
|
||||
className="flex items-center justify-between text-th-fgd-2 md:hover:text-th-active"
|
||||
key={token.slug}
|
||||
href={`/explore/tokens/${token.slug}`}
|
||||
>
|
||||
<div className="flex-col flex">
|
||||
<span className="text-sm">{token.symbol}</span>
|
||||
<span className="text-xs text-th-fgd-4">{token.tokenName}</span>
|
||||
</div>
|
||||
<ChevronRightIcon className="h-5 w-5 text-th-fgd-4" />
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TokensFilter
|
|
@ -53,7 +53,7 @@ const Select = ({
|
|||
</div>
|
||||
</Listbox.Button>
|
||||
<Listbox.Options
|
||||
className={`thin-scroll absolute left-0 z-20 mt-1 max-h-60 w-full origin-top-left overflow-auto rounded-md bg-th-bkg-2 p-2 outline-none ${dropdownPanelClassName}`}
|
||||
className={`thin-scroll absolute left-0 z-30 mt-1 max-h-60 w-full origin-top-left overflow-auto rounded-md bg-th-bkg-2 p-2 outline-none ${dropdownPanelClassName}`}
|
||||
>
|
||||
{children}
|
||||
</Listbox.Options>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
'use client'
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { useMemo } from 'react'
|
||||
import { Area, AreaChart, ResponsiveContainer, XAxis, YAxis } from 'recharts'
|
||||
|
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue