mango-ui-v3/components/TradeNavMenu.tsx

257 lines
7.5 KiB
TypeScript

import {
Fragment,
FunctionComponent,
useEffect,
useMemo,
useRef,
useState,
} from 'react'
import { Popover, Transition } from '@headlessui/react'
import { useTranslation } from 'next-i18next'
import { StarIcon } from '@heroicons/react/outline'
import {
ChevronDownIcon,
StarIcon as FilledStarIcon,
} from '@heroicons/react/solid'
import useLocalStorageState from '../hooks/useLocalStorageState'
import MarketNavItem from './MarketNavItem'
import useMangoStore from '../stores/useMangoStore'
const initialMenuCategories = [
{ name: 'Futures', desc: 'perp-desc' },
{ name: 'Spot', desc: 'spot-desc' },
]
export const FAVORITE_MARKETS_KEY = 'favoriteMarkets-0.1'
const TradeNavMenu = () => {
const [favoriteMarkets] = useLocalStorageState(FAVORITE_MARKETS_KEY, [])
const [activeMenuCategory, setActiveMenuCategory] = useState('Futures')
const [menuCategories, setMenuCategories] = useState(initialMenuCategories)
const buttonRef = useRef<HTMLButtonElement>(null)
const { t } = useTranslation('common')
const marketsInfo = useMangoStore((s) => s.marketsInfo)
const perpMarketsInfo = useMemo(
() =>
marketsInfo
.filter((mkt) => mkt?.name.includes('PERP'))
.sort((a, b) => b.volumeUsd24h - a.volumeUsd24h),
[marketsInfo]
)
const spotMarketsInfo = useMemo(
() =>
marketsInfo
.filter((mkt) => mkt?.name.includes('USDC'))
.sort((a, b) => b.volumeUsd24h - a.volumeUsd24h),
[marketsInfo]
)
const markets = useMemo(
() =>
activeMenuCategory === 'Futures'
? perpMarketsInfo
: activeMenuCategory === 'Spot'
? spotMarketsInfo
: marketsInfo.filter((mkt) => favoriteMarkets.includes(mkt.name)),
[activeMenuCategory, marketsInfo]
)
const handleMenuCategoryChange = (categoryName) => {
setActiveMenuCategory(categoryName)
}
const toggleMenu = () => {
buttonRef?.current?.click()
if (favoriteMarkets.length > 0) {
setActiveMenuCategory('Favorites')
} else {
setActiveMenuCategory('Futures')
}
}
const onHoverMenu = (open, action) => {
if (
(!open && action === 'onMouseEnter') ||
(open && action === 'onMouseLeave')
) {
toggleMenu()
}
}
const handleClickOutside = (event) => {
if (buttonRef.current && !buttonRef.current.contains(event.target)) {
event.stopPropagation()
}
}
useEffect(() => {
document.addEventListener('mousedown', handleClickOutside)
return () => {
document.removeEventListener('mousedown', handleClickOutside)
}
})
useEffect(() => {
if (favoriteMarkets.length > 0 && menuCategories.length === 2) {
const newCategories = [{ name: 'Favorites', desc: '' }, ...menuCategories]
setMenuCategories(newCategories)
}
if (favoriteMarkets.length === 0 && menuCategories.length === 3) {
setMenuCategories(
menuCategories.filter((cat) => cat.name !== 'Favorites')
)
if (activeMenuCategory === 'Favorites') {
setActiveMenuCategory('Futures')
}
}
}, [favoriteMarkets])
return (
<Popover>
{({ open }) => (
<div
onMouseEnter={() => onHoverMenu(open, 'onMouseEnter')}
onMouseLeave={() => onHoverMenu(open, 'onMouseLeave')}
className="relative z-50 flex flex-col"
>
<Popover.Button
className={`-mr-3 rounded-none px-3 focus:bg-th-bkg-3 focus:outline-none ${
open && 'bg-th-bkg-3'
}`}
ref={buttonRef}
>
<div
className={`flex h-14 items-center rounded-none font-bold hover:text-th-primary`}
>
<span>{t('trade')}</span>
<ChevronDownIcon
className={`default-transition ml-0.5 h-5 w-5 ${
open ? 'rotate-180 transform' : 'rotate-360 transform'
}`}
/>
</div>
</Popover.Button>
<Transition
appear={true}
show={open}
as={Fragment}
enter="transition-all ease-in duration-100"
enterFrom="opacity-0 transform scale-90"
enterTo="opacity-100 transform scale-100"
leave="transition ease-out duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Popover.Panel className="absolute top-14 grid min-h-[235px] w-[760px] grid-cols-3 grid-rows-1">
<div className="col-span-1 rounded-bl-lg bg-th-bkg-4">
<MenuCategories
activeCategory={activeMenuCategory}
categories={menuCategories}
onChange={handleMenuCategoryChange}
/>
</div>
<div className="col-span-2 rounded-br-lg bg-th-bkg-3 p-4">
<div className="grid grid-flow-row grid-cols-2 gap-x-6">
{markets.map((mkt) => (
<MarketNavItem
buttonRef={buttonRef}
market={mkt}
key={mkt.name}
/>
))}
</div>
</div>
</Popover.Panel>
</Transition>
</div>
)}
</Popover>
)
}
export default TradeNavMenu
interface MenuCategoriesProps {
activeCategory: string
onChange: (x) => void
categories: Array<any>
}
const MenuCategories: FunctionComponent<MenuCategoriesProps> = ({
activeCategory,
onChange,
categories,
}) => {
const { t } = useTranslation('common')
return (
<div className={`relative`}>
<div
className={`default-transition absolute top-0 left-0 z-10 w-0.5 bg-th-primary`}
style={{
transform: `translateY(${
categories.findIndex((cat) => cat.name === activeCategory) * 100
}%)`,
height: `${100 / categories.length}%`,
}}
/>
{categories.map((cat) => {
return (
<button
key={cat.name}
onClick={() => onChange(cat.name)}
onMouseEnter={() => onChange(cat.name)}
className={`default-transition relative flex h-14 w-full cursor-pointer flex-col justify-center whitespace-nowrap rounded-none px-4 font-bold hover:bg-th-bkg-3 ${
activeCategory === cat.name
? `bg-th-bkg-3 text-th-primary`
: `text-th-fgd-2 hover:text-th-primary`
}
`}
>
{t(cat.name.toLowerCase().replace(' ', '-'))}
<div className="text-xs font-normal text-th-fgd-4">
{t(cat.desc)}
</div>
</button>
)
})}
</div>
)
}
export const FavoriteMarketButton = ({ market }) => {
const [favoriteMarkets, setFavoriteMarkets] = useLocalStorageState(
FAVORITE_MARKETS_KEY,
[]
)
const addToFavorites = (mkt) => {
const newFavorites: any = [...favoriteMarkets, mkt]
setFavoriteMarkets(newFavorites)
}
const removeFromFavorites = (mkt) => {
setFavoriteMarkets(favoriteMarkets.filter((m) => m.name !== mkt))
}
return favoriteMarkets.find((mkt) => mkt === market.name) ? (
<button
className="default-transition flex items-center justify-center text-th-primary hover:text-th-fgd-3"
onClick={() => removeFromFavorites(market.name)}
>
<FilledStarIcon className="h-5 w-5" />
</button>
) : (
<button
className="default-transition flex items-center justify-center text-th-fgd-4 hover:text-th-primary"
onClick={() => addToFavorites(market.name)}
>
<StarIcon className="h-5 w-5" />
</button>
)
}