Mp 2713 asset selector (#275)

* added dumb asset selector

* fix table layouts borrow and farm

* 🍱 added basic overlay, esc btn

* finish asset selector

* Update tailwind configf to include button styles
This commit is contained in:
Bob van der Helm 2023-07-03 09:39:34 +02:00 committed by GitHub
parent 789a0d7b47
commit 39e745b210
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 467 additions and 40 deletions

View File

@ -80,7 +80,7 @@ describe('<Button />', () => {
})
it('should set correct values for progress indicator size', () => {
const sizeValues = { small: 10, medium: 12, large: 18 }
const sizeValues = { xs: 8, sm: 10, md: 12, lg: 18 }
Object.entries(sizeValues).forEach(([size, value]) => {
const { getByTestId } = render(

View File

@ -48,7 +48,7 @@ function Content(props: Props) {
if (props.type === 'active') {
return (
<Card
className='h-fit w-full bg-white/5'
className='mb-4 h-fit w-full bg-white/5'
title={props.type === 'active' ? 'Borrowings' : 'Available to borrow'}
>
<BorrowTable data={assets} />

View File

@ -0,0 +1,43 @@
import { useCallback, useEffect } from 'react'
import { Cross } from 'components/Icons'
import Button from 'components/Button'
import Text from 'components/Text'
interface Props {
enableKeyPress?: boolean
onClick: () => void
}
export default function EscButton(props: Props) {
const handleEscKey = useCallback(
(event: KeyboardEvent) => {
if (event.code === 'Escape') {
props.onClick()
}
},
[props],
)
useEffect(() => {
if (props.enableKeyPress) {
document.addEventListener('keydown', handleEscKey)
}
return () => {
document.removeEventListener('keydown', handleEscKey)
}
}, [props.onClick, props.enableKeyPress, handleEscKey])
return (
<Button
onClick={props.onClick}
leftIcon={<Cross />}
iconClassName='w-3'
color='tertiary'
className='h-3'
size='xs'
>
<Text size='2xs'>ESC</Text>
</Button>
)
}

View File

@ -31,21 +31,24 @@ export const buttonTransparentColorClasses = {
}
export const buttonRoundSizeClasses = {
small: 'h-[32px] w-[32px]',
medium: 'h-[40px] w-[40px]',
large: 'h-[56px] w-[56px]',
xs: 'h-5 w-5',
sm: 'h-8 w-8',
md: 'h-10 w-10',
lg: 'h-14 w-14',
}
export const buttonSizeClasses = {
small: 'text-sm',
medium: 'text-base',
large: 'text-lg',
xs: 'text-xs',
sm: 'text-sm',
md: 'text-base',
lg: 'text-lg',
}
export const buttonPaddingClasses = {
small: 'px-4 py-1.5 min-h-[32px]',
medium: 'px-4 py-2 min-h-[40px]',
large: 'px-4 py-2.5 min-h-[56px]',
xs: 'px-1.5 py-0.5 min-h-5',
sm: 'px-4 py-1.5 min-h-8',
md: 'px-4 py-2 min-h-10',
lg: 'px-4 py-2.5 min-h-14',
}
export const buttonVariantClasses = {
@ -53,3 +56,10 @@ export const buttonVariantClasses = {
transparent: 'rounded-sm bg-transparent p-0 transition duration-200 ease-in',
round: 'rounded-full p-0',
}
export const circularProgressSize = {
xs: 8,
sm: 10,
md: 12,
lg: 18,
}

View File

@ -11,6 +11,7 @@ import {
buttonSizeClasses,
buttonTransparentColorClasses,
buttonVariantClasses,
circularProgressSize,
focusClasses,
} from 'components/Button/constants'
import { glowElement } from 'components/Button/utils'
@ -24,7 +25,7 @@ interface Props {
disabled?: boolean
id?: string
showProgressIndicator?: boolean
size?: 'small' | 'medium' | 'large'
size?: 'xs' | 'sm' | 'md' | 'lg'
text?: string | ReactNode
variant?: 'solid' | 'transparent' | 'round'
onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void
@ -45,7 +46,7 @@ const Button = React.forwardRef(function Button(
disabled,
id = '',
showProgressIndicator,
size = 'small',
size = 'sm',
text,
variant = 'solid',
onClick,
@ -111,7 +112,7 @@ const Button = React.forwardRef(function Button(
tabIndex={tabIndex}
>
{showProgressIndicator ? (
<CircularProgress size={size === 'small' ? 10 : size === 'medium' ? 12 : 18} />
<CircularProgress size={circularProgressSize[size]} />
) : (
<>
{leftIcon && <span className={classNames(leftIconClassNames)}>{leftIcon}</span>}
@ -119,7 +120,7 @@ const Button = React.forwardRef(function Button(
{children && children}
{rightIcon && <span className={classNames(rightIconClassNames)}>{rightIcon}</span>}
{hasSubmenu && (
<span data-testid='button-submenu-indicator' className='ml-2 inline-block w-2.5'>
<span data-testid='button-submenu-indicator' className='ml-auto inline-block w-2.5'>
<ChevronDown />
</span>
)}

View File

@ -42,6 +42,14 @@ function Content(props: Props) {
if (!vaultsToDisplay.length) return null
if (props.type === 'deposited') {
return (
<Card className='mb-4 h-fit w-full bg-white/5' title={'Deposited'}>
<VaultTable data={vaultsToDisplay} />
</Card>
)
}
return <VaultTable data={vaultsToDisplay} />
}

View File

@ -0,0 +1,9 @@
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.52193 2.30302C7.67559 1.99173 7.75242 1.83609 7.85672 1.78636C7.94746 1.74309 8.05289 1.74309 8.14363 1.78636C8.24793 1.83609 8.32476 1.99173 8.47842 2.30302L9.9362 5.25634C9.98157 5.34824 10.0042 5.39419 10.0374 5.42986C10.0667 5.46145 10.1019 5.48705 10.141 5.50523C10.1852 5.52576 10.2359 5.53317 10.3373 5.548L13.5982 6.02462C13.9415 6.07481 14.1132 6.0999 14.1927 6.18377C14.2618 6.25674 14.2943 6.35701 14.2812 6.45666C14.266 6.5712 14.1417 6.69227 13.8931 6.9344L11.5345 9.23176C11.4609 9.30338 11.4242 9.33918 11.4004 9.38179C11.3794 9.41951 11.366 9.46096 11.3608 9.50382C11.3549 9.55223 11.3636 9.60281 11.3809 9.70397L11.9375 12.9489C11.9962 13.2911 12.0255 13.4623 11.9704 13.5638C11.9224 13.6522 11.8371 13.7141 11.7382 13.7325C11.6246 13.7535 11.4709 13.6727 11.1636 13.5111L8.24841 11.978C8.15758 11.9303 8.11217 11.9064 8.06432 11.897C8.02196 11.8887 7.97839 11.8887 7.93602 11.897C7.88818 11.9064 7.84276 11.9303 7.75193 11.978L4.83678 13.5111C4.52944 13.6727 4.37577 13.7535 4.26214 13.7325C4.16328 13.7141 4.07798 13.6522 4.02999 13.5638C3.97483 13.4623 4.00418 13.2911 4.06288 12.9489L4.61942 9.70397C4.63677 9.60281 4.64545 9.55223 4.63958 9.50382C4.63438 9.46096 4.6209 9.41951 4.5999 9.38179C4.57618 9.33918 4.53941 9.30337 4.46589 9.23176L2.1072 6.9344C1.8586 6.69227 1.73431 6.5712 1.71918 6.45666C1.70602 6.35701 1.73853 6.25674 1.80766 6.18377C1.88712 6.0999 2.05881 6.07481 2.40219 6.02462L5.66304 5.548C5.76445 5.53317 5.81515 5.52576 5.85931 5.50523C5.89841 5.48705 5.9336 5.46145 5.96295 5.42986C5.9961 5.39419 6.01878 5.34824 6.06415 5.25634L7.52193 2.30302Z" fill="url(#paint0_linear_1694_217638)"/>
<defs>
<linearGradient id="paint0_linear_1694_217638" x1="19.7824" y1="1.75391" x2="-3.19296" y2="1.7547" gradientUnits="userSpaceOnUse">
<stop stop-color="#BA08BD" stop-opacity="0.764896"/>
<stop offset="1" stop-color="#FFA0BB" stop-opacity="0.88641"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,3 @@
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.52193 2.30302C7.67559 1.99173 7.75242 1.83609 7.85672 1.78636C7.94746 1.74309 8.05289 1.74309 8.14363 1.78636C8.24793 1.83609 8.32476 1.99173 8.47842 2.30302L9.9362 5.25634C9.98157 5.34824 10.0042 5.39419 10.0374 5.42986C10.0667 5.46145 10.1019 5.48705 10.141 5.50523C10.1852 5.52576 10.2359 5.53317 10.3373 5.548L13.5982 6.02462C13.9415 6.07481 14.1132 6.0999 14.1927 6.18377C14.2618 6.25674 14.2943 6.35701 14.2812 6.45666C14.266 6.5712 14.1417 6.69227 13.8931 6.9344L11.5345 9.23176C11.4609 9.30338 11.4242 9.33918 11.4004 9.38179C11.3794 9.41951 11.366 9.46096 11.3608 9.50382C11.3549 9.55223 11.3636 9.60281 11.3809 9.70397L11.9375 12.9489C11.9962 13.2911 12.0255 13.4623 11.9704 13.5638C11.9224 13.6522 11.8371 13.7141 11.7382 13.7325C11.6246 13.7535 11.4709 13.6727 11.1636 13.5111L8.24841 11.978C8.15758 11.9303 8.11217 11.9064 8.06432 11.897C8.02196 11.8887 7.97839 11.8887 7.93602 11.897C7.88818 11.9064 7.84276 11.9303 7.75193 11.978L4.83678 13.5111C4.52944 13.6727 4.37577 13.7535 4.26214 13.7325C4.16328 13.7141 4.07798 13.6522 4.02999 13.5638C3.97483 13.4623 4.00418 13.2911 4.06288 12.9489L4.61942 9.70397C4.63677 9.60281 4.64545 9.55223 4.63958 9.50382C4.63438 9.46096 4.6209 9.41951 4.5999 9.38179C4.57618 9.33918 4.53941 9.30337 4.46589 9.23176L2.1072 6.9344C1.8586 6.69227 1.73431 6.5712 1.71918 6.45666C1.70602 6.35701 1.73853 6.25674 1.80766 6.18377C1.88712 6.0999 2.05881 6.07481 2.40219 6.02462L5.66304 5.548C5.76445 5.53317 5.81515 5.52576 5.85931 5.50523C5.89841 5.48705 5.9336 5.46145 5.96295 5.42986C5.9961 5.39419 6.01878 5.34824 6.06415 5.25634L7.52193 2.30302Z" stroke="currentColor" stroke-opacity="0.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,3 @@
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.3334 11.3333H2.66675M2.66675 11.3333L5.33341 8.66667M2.66675 11.3333L5.33341 14M2.66675 4.66667H13.3334M13.3334 4.66667L10.6667 2M13.3334 4.66667L10.6667 7.33333" stroke="currentColor" stroke-opacity="0.6" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 348 B

View File

@ -33,7 +33,10 @@ export { default as Shield } from 'components/Icons/Shield.svg'
export { default as SortAsc } from 'components/Icons/SortAsc.svg'
export { default as SortDesc } from 'components/Icons/SortDesc.svg'
export { default as SortNone } from 'components/Icons/SortNone.svg'
export { default as StarFilled } from 'components/Icons/StarFilled.svg'
export { default as StarOutlined } from 'components/Icons/StarOutlined.svg'
export { default as Subtract } from 'components/Icons/Subtract.svg'
export { default as SwapIcon } from 'components/Icons/SwapIcon.svg'
export { default as TrashBin } from 'components/Icons/TrashBin.svg'
export { default as Wallet } from 'components/Icons/Wallet.svg'
// @endindex

View File

@ -1,10 +1,9 @@
import classNames from 'classnames'
import { ReactNode, useEffect, useRef } from 'react'
import Button from 'components/Button'
import Card from 'components/Card'
import { Cross } from 'components/Icons'
import Text from 'components/Text'
import EscButton from './Button/EscButton'
interface Props {
header: string | ReactNode
@ -63,11 +62,7 @@ export default function Modal(props: Props) {
>
<div className={classNames('flex justify-between', props.headerClassName)}>
{props.header}
{!props.hideCloseBtn && (
<Button onClick={onClose} leftIcon={<Cross />} iconClassName='h-3 w-3' color='tertiary'>
<Text size='sm'>ESC</Text>
</Button>
)}
{!props.hideCloseBtn && <EscButton onClick={props.onClose} />}
</div>
<div className={classNames(props.contentClassName, 'flex-grow')}>
{props.children ? props.children : props.content}

View File

@ -1,15 +1,16 @@
import classNames from 'classnames'
import { ChangeEvent } from 'react'
import { ChangeEvent, forwardRef } from 'react'
import { Search } from 'components/Icons'
interface Props {
value: string
placeholder: string
autofocus?: boolean
onChange: (value: string) => void
}
export default function SearchBar(props: Props) {
const SearchBar = (props: Props) => {
function onChange(event: ChangeEvent<HTMLInputElement>) {
props.onChange(event.target.value)
}
@ -28,7 +29,10 @@ export default function SearchBar(props: Props) {
className='h-full w-full bg-transparent text-xs placeholder-white/30 outline-none'
placeholder={props.placeholder}
onChange={(event) => onChange(event)}
autoFocus={props.autofocus}
/>
</div>
)
}
export default forwardRef(SearchBar)

View File

@ -0,0 +1,22 @@
import AssetImage from 'components/AssetImage'
import Button from 'components/Button'
interface Props {
asset: Asset
onClick: () => void
}
export default function AssetButton(props: Props) {
return (
<Button
leftIcon={<AssetImage asset={props.asset} size={16} />}
text={props.asset.symbol}
color='tertiary'
variant='transparent'
className='w-full border border-white/20'
size='md'
hasSubmenu
{...props}
/>
)
}

View File

@ -0,0 +1,59 @@
import AssetImage from 'components/AssetImage'
import DisplayCurrency from 'components/DisplayCurrency'
import { StarFilled, StarOutlined } from 'components/Icons'
import Text from 'components/Text'
import { FAVORITE_ASSETS } from 'constants/localStore'
import { BNCoin } from 'types/classes/BNCoin'
import { BN } from 'utils/helpers'
interface Props {
asset: Asset
onSelectAsset: (asset: Asset) => void
}
export default function AssetItem(props: Props) {
const asset = props.asset
function handleToggleFavorite(event: React.MouseEvent<HTMLDivElement, MouseEvent>) {
event.stopPropagation()
const favoriteAssets: string[] = JSON.parse(localStorage.getItem(FAVORITE_ASSETS) || '[]')
if (favoriteAssets) {
if (favoriteAssets.includes(asset.denom)) {
localStorage.setItem(
FAVORITE_ASSETS,
JSON.stringify(favoriteAssets.filter((item: string) => item !== asset.denom)),
)
} else {
localStorage.setItem(FAVORITE_ASSETS, JSON.stringify([...favoriteAssets, asset.denom]))
}
window.dispatchEvent(new Event('storage'))
}
}
return (
<li className='border-b border-white/10 hover:bg-black/10'>
<button
onClick={() => props.onSelectAsset(asset)}
className='flex w-full items-center justify-between gap-2 p-4'
>
<div className='flex items-center gap-2'>
<div onClick={handleToggleFavorite}>
{asset.isFavorite ? <StarFilled width={16} /> : <StarOutlined width={16} />}
</div>
<AssetImage asset={asset} size={24} />
<Text size='sm' className='text-left'>
{asset.name}
</Text>
<div className='rounded-sm bg-white/20 px-[6px] py-[2px]'>
<Text size='xs'>{asset.symbol}</Text>
</div>
</div>
<DisplayCurrency
className='text-sm'
coin={
new BNCoin({ denom: asset.denom, amount: BN(1).shiftedBy(asset.decimals).toString() })
}
/>
</button>
</li>
)
}

View File

@ -0,0 +1,43 @@
import classNames from 'classnames'
import { ChevronDown } from 'components/Icons'
import Text from 'components/Text'
import AssetItem from 'components/Trade/TradeModule/AssetSelector/AssetItem'
interface Props {
type: 'buy' | 'sell'
assets: Asset[]
isOpen: boolean
toggleOpen: () => void
onChangeAsset: (asset: Asset) => void
}
export default function AssetList(props: Props) {
return (
<section>
<button
className='flex w-full items-center justify-between bg-black/20 p-4'
onClick={props.toggleOpen}
>
<Text>{props.type === 'buy' ? 'Buy asset' : 'Sell asset'}</Text>
<ChevronDown className={classNames(props.isOpen && '-rotate-180')} />
</button>
{props.isOpen &&
(props.assets.length === 0 ? (
<Text size='xs' className='p-4'>
No available assets found
</Text>
) : (
<ul>
{props.assets.map((asset) => (
<AssetItem
key={`${props.type}-${asset.symbol}`}
asset={asset}
onSelectAsset={props.onChangeAsset}
/>
))}
</ul>
))}
</section>
)
}

View File

@ -0,0 +1,86 @@
import { useCallback, useMemo, useRef } from 'react'
import EscButton from 'components/Button/EscButton'
import Divider from 'components/Divider'
import Overlay from 'components/Overlay'
import SearchBar from 'components/SearchBar'
import Text from 'components/Text'
import AssetList from 'components/Trade/TradeModule/AssetSelector/AssetList'
import useFilteredAssets from 'hooks/useFilteredAssets'
export type OverlayState = 'buy' | 'sell' | 'closed'
interface Props {
state: OverlayState
buyAsset: Asset
sellAsset: Asset
onChangeBuyAsset: (asset: Asset) => void
onChangeSellAsset: (asset: Asset) => void
onChangeState: (state: OverlayState) => void
}
export default function AssetOverlay(props: Props) {
const { assets, searchString, onChangeSearch } = useFilteredAssets()
const handleClose = useCallback(() => props.onChangeState('closed'), [props])
const handleToggle = useCallback(
() => props.onChangeState(props.state === 'buy' ? 'sell' : 'buy'),
[props],
)
const buyAssets = useMemo(
() => assets.filter((asset) => asset.denom !== props.sellAsset.denom),
[assets, props.sellAsset],
)
const sellAssets = useMemo(
() => assets.filter((asset) => asset.denom !== props.buyAsset.denom),
[assets, props.buyAsset],
)
function onChangeBuyAsset(asset: Asset) {
props.onChangeBuyAsset(asset)
props.onChangeState('sell')
onChangeSearch('')
}
function onChangeSellAsset(asset: Asset) {
props.onChangeSellAsset(asset)
onChangeSearch('')
}
return (
<Overlay className='w-full' show={props.state !== 'closed'} setShow={handleClose}>
<div className='flex justify-between p-4'>
<Text>Select asset</Text>
<EscButton onClick={handleClose} enableKeyPress />
</div>
<Divider />
<div className='p-4'>
<SearchBar
key={props.state}
value={searchString}
onChange={onChangeSearch}
placeholder='Search for e.g. "ETH" or "Ethereum"'
autofocus
/>
</div>
<Divider />
<AssetList
type='buy'
assets={buyAssets}
isOpen={props.state === 'buy'}
toggleOpen={handleToggle}
onChangeAsset={onChangeBuyAsset}
/>
<AssetList
type='sell'
assets={sellAssets}
isOpen={props.state === 'sell'}
toggleOpen={handleToggle}
onChangeAsset={onChangeSellAsset}
/>
</Overlay>
)
}

View File

@ -0,0 +1,67 @@
import { useCallback, useMemo, useState } from 'react'
import { SwapIcon } from 'components/Icons'
import Text from 'components/Text'
import { ASSETS } from 'constants/assets'
import AssetButton from 'components/Trade/TradeModule/AssetSelector/AssetButton'
import AssetOverlay, { OverlayState } from 'components/Trade/TradeModule/AssetSelector/AssetOverlay'
export default function AssetSelector() {
const [overlayState, setOverlayState] = useState<OverlayState>('closed')
const [buyAsset, setBuyAsset] = useState(ASSETS[0])
const [sellAsset, setSellAsset] = useState(ASSETS[1])
function handleSwapAssets() {
setBuyAsset(sellAsset)
setSellAsset(buyAsset)
}
const handleChangeBuyAsset = useCallback(
(asset: Asset) => {
setBuyAsset(asset)
setOverlayState('sell')
},
[setBuyAsset],
)
const handleChangeSellAsset = useCallback(
(asset: Asset) => {
setSellAsset(asset)
setOverlayState('closed')
},
[setSellAsset],
)
const handleChangeState = useCallback(
(state: OverlayState) => {
setOverlayState(state)
},
[setOverlayState],
)
const buyAssets = useMemo(
() => ASSETS.filter((asset) => asset.denom !== sellAsset.denom),
[sellAsset],
)
return (
<div className='grid-rows-auto relative grid grid-cols-[1fr_min-content_1fr] gap-y-2 bg-white/5 p-3'>
<Text size='sm'>Buy</Text>
<Text size='sm' className='col-start-3'>
Sell
</Text>
<AssetButton onClick={() => setOverlayState('buy')} asset={buyAsset} />
<button onClick={handleSwapAssets}>
<SwapIcon className='mx-2 w-4 place-self-center' />
</button>
<AssetButton onClick={() => setOverlayState('sell')} asset={sellAsset} />
<AssetOverlay
state={overlayState}
onChangeState={handleChangeState}
buyAsset={buyAsset}
sellAsset={sellAsset}
onChangeBuyAsset={handleChangeBuyAsset}
onChangeSellAsset={handleChangeSellAsset}
/>
</div>
)
}

View File

@ -1,9 +1,11 @@
import { Suspense } from 'react'
import { useParams } from 'react-router-dom'
import classNames from 'classnames'
import Card from 'components/Card'
import Loading from 'components/Loading'
import Text from 'components/Text'
import Divider from 'components/Divider'
import AssetSelector from './AssetSelector/AssetSelector'
function Content() {
const params = useParams()
@ -24,14 +26,15 @@ function Fallback() {
export default function TradeModule() {
return (
<Card
className='row-span-2 h-full w-full bg-white/5'
title='Trade Module'
contentClassName='px-4 py-6'
<div
className={classNames(
'relative isolate max-w-full overflow-hidden rounded-base',
'before:content-[" "] before:absolute before:inset-0 before:-z-1 before:rounded-base before:p-[1px] before:border-glas',
'row-span-2 h-full',
)}
>
<Suspense fallback={<Fallback />}>
<Content />
</Suspense>
</Card>
<AssetSelector />
<Divider />
</div>
)
}

View File

@ -1,2 +1,3 @@
export const DISPLAY_CURRENCY_KEY = 'displayCurrency'
export const ENABLE_ANIMATIONS_KEY = 'enableAnimations'
export const FAVORITE_ASSETS = 'favoriteAssets'

33
src/hooks/useAssets.ts Normal file
View File

@ -0,0 +1,33 @@
import { useCallback, useEffect, useState } from 'react'
import { ASSETS } from 'constants/assets'
import { FAVORITE_ASSETS } from 'constants/localStore'
export default function useAssets() {
const [assets, setAssets] = useState<Asset[]>(ASSETS)
const getFavoriteAssets = useCallback(() => {
const favoriteAssets = JSON.parse(localStorage.getItem(FAVORITE_ASSETS) || '[]')
const assets = ASSETS.map((asset) => ({
...asset,
isFavorite: favoriteAssets.includes(asset.denom),
})).sort((a, b) => {
if (a.isFavorite && !b.isFavorite) return -1
if (!a.isFavorite && b.isFavorite) return 1
return 0
})
setAssets(assets)
}, [])
useEffect(() => {
getFavoriteAssets()
window.addEventListener('storage', getFavoriteAssets)
return () => {
window.removeEventListener('storage', getFavoriteAssets)
}
}, [getFavoriteAssets])
return assets
}

View File

@ -0,0 +1,29 @@
import { useCallback, useMemo, useState } from 'react'
import useAssets from 'hooks/useAssets'
export default function useFilteredAssets() {
const [searchString, setSearchString] = useState('')
const allAssets = useAssets()
const assets = useMemo(
() =>
allAssets.filter(
(asset) =>
asset.denom.toLocaleLowerCase().includes(searchString.toLowerCase()) ||
asset.symbol.toLocaleLowerCase().includes(searchString.toLowerCase()) ||
asset.name.toLocaleLowerCase().includes(searchString.toLowerCase()),
),
[searchString, allAssets],
)
const onChangeSearch = useCallback(
(string: string) => {
setSearchString(string)
},
[setSearchString],
)
return { assets, searchString, onChangeSearch }
}

View File

@ -1,14 +1,14 @@
import { useState } from 'react'
import { useCallback, useState } from 'react'
export default function useToggle(
defaultValue?: boolean,
): [boolean, (isToggled?: boolean) => void] {
const [toggle, setToggle] = useState<boolean>(defaultValue ?? false)
function handleToggle(isToggled?: boolean) {
const handleToggle = useCallback((isToggled?: boolean) => {
if (isToggled !== undefined) return setToggle(isToggled)
return setToggle(!toggle)
}
return setToggle((isToggled) => !isToggled)
}, [])
return [toggle, handleToggle]
}

View File

@ -4,7 +4,7 @@ import TradingView from 'components/Trade/TradingView'
export default function TradePage() {
return (
<div className=' grid w-full grid-cols-[346px_auto] gap-4'>
<div className='grid h-full w-full grid-cols-[346px_auto] gap-4'>
<TradeModule />
<TradingView />
<OrderBook />

View File

@ -14,6 +14,7 @@ interface Asset {
isMarket: boolean
isDisplayCurrency?: boolean
isStable?: boolean
isFavorite?: boolean
}
interface OtherAsset extends Omit<Asset, 'symbol'> {

View File

@ -157,6 +157,10 @@ module.exports = {
},
minHeight: {
3: '12px',
5: '20px',
8: '32px',
10: '40px',
14: '56px',
},
maxWidth: {
content: '1024px',