add swipeable tabs

This commit is contained in:
saml33 2022-05-18 14:53:48 +10:00
parent a3e06cdee0
commit 924b9b279d
4 changed files with 335 additions and 296 deletions

View File

@ -14,11 +14,16 @@ import { useRouter } from 'next/router'
import { AreaChart, Area, XAxis, YAxis } from 'recharts'
import { useTheme } from 'next-themes'
const MarketsTable = ({ isPerpMarket }) => {
const MarketsTable = ({
isPerpMarket,
markets,
}: {
isPerpMarket?: boolean
markets: any[]
}) => {
const { t } = useTranslation('common')
const { width } = useViewport()
const isMobile = width ? width < breakpoints.md : false
const marketsInfo = useMangoStore((s) => s.marketsInfo)
const actions = useMangoStore((s) => s.actions)
const coingeckoPrices = useMangoStore((s) => s.coingeckoPrices.data)
const loadingCoingeckoPrices = useMangoStore((s) => s.coingeckoPrices.loading)
@ -31,290 +36,266 @@ const MarketsTable = ({ isPerpMarket }) => {
}
}, [coingeckoPrices])
const perpMarketsInfo = useMemo(
() =>
marketsInfo
.filter((mkt) => mkt?.name.includes('PERP'))
.sort((a, b) => b.volumeUsd24h - a.volumeUsd24h),
[marketsInfo]
)
const { items, requestSort, sortConfig } = useSortableData(markets)
const spotMarketsInfo = useMemo(
() =>
marketsInfo
.filter((mkt) => mkt?.name.includes('USDC'))
.sort((a, b) => b.volumeUsd24h - a.volumeUsd24h),
[marketsInfo]
)
const { items, requestSort, sortConfig } = useSortableData(
isPerpMarket ? perpMarketsInfo : spotMarketsInfo
)
return items.length > 0 ? (
!isMobile ? (
<Table>
<thead>
<TrHead>
<Th>
<LinkButton
className="flex items-center font-normal no-underline"
onClick={() => requestSort('name')}
>
<span className="text-left font-normal text-th-fgd-3">
{t('market')}
</span>
<ArrowSmDownIcon
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
sortConfig?.key === 'name'
? sortConfig.direction === 'ascending'
? 'rotate-180 transform'
: 'rotate-360 transform'
: null
}`}
/>
</LinkButton>
</Th>
<Th>
<LinkButton
className="flex items-center font-normal no-underline"
onClick={() => requestSort('last')}
>
<span className="text-left font-normal text-th-fgd-3">
{t('price')}
</span>
<ArrowSmDownIcon
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
sortConfig?.key === 'last'
? sortConfig.direction === 'ascending'
? 'rotate-180 transform'
: 'rotate-360 transform'
: null
}`}
/>
</LinkButton>
</Th>
<Th>
<LinkButton
className="flex items-center font-normal no-underline"
onClick={() => requestSort('change24h')}
>
<span className="text-left font-normal text-th-fgd-3">
{t('rolling-change')}
</span>
<ArrowSmDownIcon
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
sortConfig?.key === 'change24h'
? sortConfig.direction === 'ascending'
? 'rotate-180 transform'
: 'rotate-360 transform'
: null
}`}
/>
</LinkButton>
</Th>
<Th>
<LinkButton
className="flex items-center font-normal no-underline"
onClick={() => requestSort('volumeUsd24h')}
>
<span className="text-left font-normal text-th-fgd-3">
{t('daily-volume')}
</span>
<ArrowSmDownIcon
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
sortConfig?.key === 'volumeUsd24h'
? sortConfig.direction === 'ascending'
? 'rotate-180 transform'
: 'rotate-360 transform'
: null
}`}
/>
</LinkButton>
</Th>
{isPerpMarket ? (
<>
<Th>
<LinkButton
className="flex items-center font-normal no-underline"
onClick={() => requestSort('funding1h')}
>
<span className="text-left font-normal text-th-fgd-3">
{t('average-funding')}
</span>
<ArrowSmDownIcon
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
sortConfig?.key === 'funding1h'
? sortConfig.direction === 'ascending'
? 'rotate-180 transform'
: 'rotate-360 transform'
: null
}`}
/>
</LinkButton>
</Th>
<Th>
<LinkButton
className="flex items-center no-underline"
onClick={() => requestSort('openInterestUsd')}
>
<span className="text-left font-normal text-th-fgd-3">
{t('open-interest')}
</span>
<ArrowSmDownIcon
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
sortConfig?.key === 'openInterestUsd'
? sortConfig.direction === 'ascending'
? 'rotate-180 transform'
: 'rotate-360 transform'
: null
}`}
/>
</LinkButton>
</Th>
</>
) : null}
<Th>
<span className="flex justify-end">{t('favorite')}</span>
</Th>
</TrHead>
</thead>
<tbody>
{items.map((market) => {
const {
baseSymbol,
change24h,
funding1h,
last,
name,
openInterest,
openInterestUsd,
volumeUsd24h,
} = market
const fundingApr = funding1h
? (funding1h * 24 * 365).toFixed(2)
: '-'
const coingeckoData = coingeckoPrices.find(
(asset) => asset.symbol === baseSymbol
)
const chartData = coingeckoData ? coingeckoData.prices : undefined
const chartColor =
change24h >= 0
? theme === 'Mango'
? '#AFD803'
: '#5EBF4D'
: theme === 'Mango'
? '#F84638'
: '#CC2929'
return (
<TrBody key={name} className="hover:bg-th-bkg-3">
<Td>
<Link href={`/?name=${name}`} shallow={true}>
<a className="hover:cursor-pointer">
<div className="flex h-full items-center text-th-fgd-2 hover:text-th-primary">
<img
alt=""
width="20"
height="20"
src={`/assets/icons/${baseSymbol.toLowerCase()}.svg`}
className={`mr-2.5`}
/>
<span className="default-transition">{name}</span>
</div>
</a>
</Link>
</Td>
<Td className="flex items-center">
<div className="w-20">
{last ? (
formatUsdValue(last)
) : (
<span className="text-th-fgd-4">{t('unavailable')}</span>
)}
</div>
<div className="pl-6">
{!loadingCoingeckoPrices ? (
chartData !== undefined ? (
<PriceChart
color={chartColor}
data={chartData}
height={40}
width={104}
/>
) : (
t('unavailable')
)
) : (
<div className="h-10 w-[104px] animate-pulse rounded bg-th-bkg-3" />
)}
</div>
</Td>
<Td>
<span
className={change24h >= 0 ? 'text-th-green' : 'text-th-red'}
>
{change24h || change24h === 0 ? (
`${(change24h * 100).toFixed(2)}%`
) : (
<span className="text-th-fgd-4">{t('unavailable')}</span>
)}
return !isMobile ? (
<Table>
<thead>
<TrHead>
<Th>
<LinkButton
className="flex items-center font-normal no-underline"
onClick={() => requestSort('name')}
>
<span className="text-left font-normal text-th-fgd-3">
{t('market')}
</span>
<ArrowSmDownIcon
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
sortConfig?.key === 'name'
? sortConfig.direction === 'ascending'
? 'rotate-180 transform'
: 'rotate-360 transform'
: null
}`}
/>
</LinkButton>
</Th>
<Th>
<LinkButton
className="flex items-center font-normal no-underline"
onClick={() => requestSort('last')}
>
<span className="text-left font-normal text-th-fgd-3">
{t('price')}
</span>
<ArrowSmDownIcon
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
sortConfig?.key === 'last'
? sortConfig.direction === 'ascending'
? 'rotate-180 transform'
: 'rotate-360 transform'
: null
}`}
/>
</LinkButton>
</Th>
<Th>
<LinkButton
className="flex items-center font-normal no-underline"
onClick={() => requestSort('change24h')}
>
<span className="text-left font-normal text-th-fgd-3">
{t('rolling-change')}
</span>
<ArrowSmDownIcon
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
sortConfig?.key === 'change24h'
? sortConfig.direction === 'ascending'
? 'rotate-180 transform'
: 'rotate-360 transform'
: null
}`}
/>
</LinkButton>
</Th>
<Th>
<LinkButton
className="flex items-center font-normal no-underline"
onClick={() => requestSort('volumeUsd24h')}
>
<span className="text-left font-normal text-th-fgd-3">
{t('daily-volume')}
</span>
<ArrowSmDownIcon
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
sortConfig?.key === 'volumeUsd24h'
? sortConfig.direction === 'ascending'
? 'rotate-180 transform'
: 'rotate-360 transform'
: null
}`}
/>
</LinkButton>
</Th>
{isPerpMarket ? (
<>
<Th>
<LinkButton
className="flex items-center font-normal no-underline"
onClick={() => requestSort('funding1h')}
>
<span className="text-left font-normal text-th-fgd-3">
{t('average-funding')}
</span>
</Td>
<Td>
{volumeUsd24h ? (
usdFormatter(volumeUsd24h, 0)
<ArrowSmDownIcon
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
sortConfig?.key === 'funding1h'
? sortConfig.direction === 'ascending'
? 'rotate-180 transform'
: 'rotate-360 transform'
: null
}`}
/>
</LinkButton>
</Th>
<Th>
<LinkButton
className="flex items-center no-underline"
onClick={() => requestSort('openInterestUsd')}
>
<span className="text-left font-normal text-th-fgd-3">
{t('open-interest')}
</span>
<ArrowSmDownIcon
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
sortConfig?.key === 'openInterestUsd'
? sortConfig.direction === 'ascending'
? 'rotate-180 transform'
: 'rotate-360 transform'
: null
}`}
/>
</LinkButton>
</Th>
</>
) : null}
<Th>
<span className="flex justify-end">{t('favorite')}</span>
</Th>
</TrHead>
</thead>
<tbody>
{items.map((market) => {
const {
baseSymbol,
change24h,
funding1h,
last,
name,
openInterest,
openInterestUsd,
volumeUsd24h,
} = market
const fundingApr = funding1h ? (funding1h * 24 * 365).toFixed(2) : '-'
const coingeckoData = coingeckoPrices.find(
(asset) => asset.symbol === baseSymbol
)
const chartData = coingeckoData ? coingeckoData.prices : undefined
const chartColor =
change24h >= 0
? theme === 'Mango'
? '#AFD803'
: '#5EBF4D'
: theme === 'Mango'
? '#F84638'
: '#CC2929'
return (
<TrBody key={name} className="hover:bg-th-bkg-3">
<Td>
<Link href={`/?name=${name}`} shallow={true}>
<a className="hover:cursor-pointer">
<div className="flex h-full items-center text-th-fgd-2 hover:text-th-primary">
<img
alt=""
width="20"
height="20"
src={`/assets/icons/${baseSymbol.toLowerCase()}.svg`}
className={`mr-2.5`}
/>
<span className="default-transition">{name}</span>
</div>
</a>
</Link>
</Td>
<Td className="flex items-center">
<div className="w-20">
{last ? (
formatUsdValue(last)
) : (
<span className="text-th-fgd-4">{t('unavailable')}</span>
)}
</Td>
{isPerpMarket ? (
<>
<Td>
{funding1h ? (
<>
<span>{`${funding1h.toFixed(4)}%`}</span>{' '}
<span className="text-xs text-th-fgd-3">{`(${fundingApr}% APR)`}</span>
</>
) : (
<span className="text-th-fgd-4">
{t('unavailable')}
</span>
)}
</Td>
<Td>
{openInterestUsd ? (
<>
<span>{usdFormatter(openInterestUsd, 0)}</span>{' '}
{openInterest ? (
<div className="text-xs text-th-fgd-4">
{openInterest.toLocaleString(undefined, {
maximumFractionDigits:
perpContractPrecision[baseSymbol],
})}{' '}
{baseSymbol}
</div>
) : null}
</>
) : (
<span className="text-th-fgd-4">
{t('unavailable')}
</span>
)}
</Td>
</>
) : null}
<Td>
<div className="flex justify-end">
<FavoriteMarketButton market={market} />
</div>
</Td>
</TrBody>
)
})}
</tbody>
</Table>
) : (
items.map((market) => {
</div>
<div className="pl-6">
{!loadingCoingeckoPrices ? (
chartData !== undefined ? (
<PriceChart
color={chartColor}
data={chartData}
height={40}
width={104}
/>
) : (
t('unavailable')
)
) : (
<div className="h-10 w-[104px] animate-pulse rounded bg-th-bkg-3" />
)}
</div>
</Td>
<Td>
<span
className={change24h >= 0 ? 'text-th-green' : 'text-th-red'}
>
{change24h || change24h === 0 ? (
`${(change24h * 100).toFixed(2)}%`
) : (
<span className="text-th-fgd-4">{t('unavailable')}</span>
)}
</span>
</Td>
<Td>
{volumeUsd24h ? (
usdFormatter(volumeUsd24h, 0)
) : (
<span className="text-th-fgd-4">{t('unavailable')}</span>
)}
</Td>
{isPerpMarket ? (
<>
<Td>
{funding1h ? (
<>
<span>{`${funding1h.toFixed(4)}%`}</span>{' '}
<span className="text-xs text-th-fgd-3">{`(${fundingApr}% APR)`}</span>
</>
) : (
<span className="text-th-fgd-4">{t('unavailable')}</span>
)}
</Td>
<Td>
{openInterestUsd ? (
<>
<span>{usdFormatter(openInterestUsd, 0)}</span>{' '}
{openInterest ? (
<div className="text-xs text-th-fgd-4">
{openInterest.toLocaleString(undefined, {
maximumFractionDigits:
perpContractPrecision[baseSymbol],
})}{' '}
{baseSymbol}
</div>
) : null}
</>
) : (
<span className="text-th-fgd-4">{t('unavailable')}</span>
)}
</Td>
</>
) : null}
<Td>
<div className="flex justify-end">
<FavoriteMarketButton market={market} />
</div>
</Td>
</TrBody>
)
})}
</tbody>
</Table>
) : (
<>
{items.map((market) => {
const { baseSymbol, change24h, funding1h, last, name } = market
const fundingApr = funding1h ? (funding1h * 24 * 365).toFixed(2) : '-'
const coingeckoData = coingeckoPrices.find(
@ -332,7 +313,7 @@ const MarketsTable = ({ isPerpMarket }) => {
return (
<Link href={`/?name=${name}`} shallow={true} key={name}>
<a
className="mb-2.5 block w-full rounded-lg bg-th-bkg-3 p-4 pb-2.5"
className="mb-2 block w-full rounded-lg bg-th-bkg-3 p-4 pb-2.5"
onClick={() =>
router.push(`/?name=${name}`, undefined, {
shallow: true,
@ -405,13 +386,11 @@ const MarketsTable = ({ isPerpMarket }) => {
</a>
</Link>
)
})
)
) : null
})}
</>
)
}
export default MarketsTable as any
const PriceChart = ({ data, width, height, color }) => (
<AreaChart width={width} height={height} data={data}>
<defs>
@ -431,3 +410,29 @@ const PriceChart = ({ data, width, height, color }) => (
<YAxis domain={['dataMin', 'dataMax']} dataKey="1" hide />
</AreaChart>
)
export const SpotMarketsTable = () => {
const marketsInfo = useMangoStore((s) => s.marketsInfo)
const spotMarketsInfo = useMemo(
() =>
marketsInfo
.filter((mkt) => mkt?.name.includes('USDC'))
.sort((a, b) => b.volumeUsd24h - a.volumeUsd24h),
[marketsInfo]
)
return <MarketsTable markets={spotMarketsInfo} />
}
export const PerpMarketsTable = () => {
const marketsInfo = useMangoStore((s) => s.marketsInfo)
const perpMarketsInfo = useMemo(
() =>
marketsInfo
.filter((mkt) => mkt?.name.includes('PERP'))
.sort((a, b) => b.volumeUsd24h - a.volumeUsd24h),
[marketsInfo]
)
return <MarketsTable isPerpMarket markets={perpMarketsInfo} />
}

View File

@ -33,7 +33,7 @@ const BottomBar = () => {
return (
<>
<div className="default-transition grid grid-cols-5 grid-rows-1 bg-th-bkg-3 py-2.5">
<div className="default-transition grid grid-cols-5 grid-rows-1 bg-th-bkg-4 py-2.5">
<Link
href={{
pathname: '/markets',

View File

@ -6,6 +6,7 @@ import useMangoStore from '../../stores/useMangoStore'
import { getWeights, PerpMarket } from '@blockworks-foundation/mango-client'
import { CandlesIcon } from '../icons'
import SwipeableTabs from './SwipeableTabs'
import Swipeable from './Swipeable'
import AdvancedTradeForm from '../trade_form/AdvancedTradeForm'
import Orderbook from '../Orderbook'
import MarketBalances from '../MarketBalances'
@ -14,7 +15,6 @@ import MarketPosition from '../MarketPosition'
import OpenOrdersTable from '../OpenOrdersTable'
import RecentMarketTrades from '../RecentMarketTrades'
import FloatingElement from '../FloatingElement'
import Swipeable from './Swipeable'
import { useTranslation } from 'next-i18next'
import { useWallet } from '@solana/wallet-adapter-react'
import SwitchMarketDropdown from 'components/SwitchMarketDropdown'

View File

@ -3,8 +3,12 @@ import PageBodyContainer from '../components/PageBodyContainer'
import TopBar from '../components/TopBar'
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
import { useTranslation } from 'next-i18next'
import MarketsTable from '../components/MarketsTable'
import { PerpMarketsTable, SpotMarketsTable } from '../components/MarketsTable'
import Tabs from '../components/Tabs'
import SwipeableTabs from '../components/mobile/SwipeableTabs'
import Swipeable from '../components/mobile/Swipeable'
import { useViewport } from '../hooks/useViewport'
import { breakpoints } from '../components/TradePageGrid'
const TABS = ['futures', 'spot']
@ -19,12 +23,19 @@ export async function getStaticProps({ locale }) {
export default function Markets() {
const [activeTab, setActiveTab] = useState('futures')
const [viewIndex, setViewIndex] = useState(0)
const { t } = useTranslation(['common'])
const { width } = useViewport()
const isMobile = width ? width < breakpoints.sm : false
const handleTabChange = (tabName) => {
setActiveTab(tabName)
}
const handleChangeViewIndex = (index) => {
setViewIndex(index)
}
return (
<div className={`bg-th-bkg-1 text-th-fgd-1 transition-all`}>
<TopBar />
@ -34,13 +45,36 @@ export default function Markets() {
</div>
<div className="md:rounded-lg md:bg-th-bkg-2 md:p-6">
<div className="mb-0 sm:mb-6">
<Tabs
activeTab={activeTab}
onChange={handleTabChange}
tabs={TABS}
/>
{!isMobile ? (
<Tabs
activeTab={activeTab}
onChange={handleTabChange}
tabs={TABS}
/>
) : (
<SwipeableTabs
onChange={handleChangeViewIndex}
tabs={TABS}
tabIndex={viewIndex}
/>
)}
</div>
<MarketsTable isPerpMarket={activeTab === 'futures'} />
{!isMobile ? (
activeTab === 'futures' ? (
<PerpMarketsTable />
) : (
<SpotMarketsTable />
)
) : (
<Swipeable index={viewIndex} onChangeIndex={handleChangeViewIndex}>
<div className="px-1">
<PerpMarketsTable />
</div>
<div className="px-1">
<SpotMarketsTable />
</div>
</Swipeable>
)}
</div>
</PageBodyContainer>
</div>