Skip to content
This repository has been archived by the owner on Oct 10, 2023. It is now read-only.

Redesign AssetSelect #2426

Merged
merged 4 commits into from
Oct 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/renderer/components/swap/Swap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ import { LedgerConfirmationModal, WalletPasswordConfirmationModal } from '../mod
import { TxModal } from '../modal/tx'
import { SwapAssets } from '../modal/tx/extra'
import { LoadingView } from '../shared/loading'
import { AssetSelect2 } from '../uielements/assets/assetSelect/AssetSelect2'
import { FlatButton, ViewTxButton } from '../uielements/button'
import { WalletTypeLabel } from '../uielements/common/Common.styles'
import { Fees, UIFeesRD } from '../uielements/fees'
Expand Down Expand Up @@ -1599,7 +1600,7 @@ export const Swap = ({
() => <></>,
(asset) => (
<Styled.AssetSelectContainer>
<Styled.TargetAssetSelect
<AssetSelect2
onSelect={setTargetAsset}
asset={asset}
assets={selectableTargetAssets}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { useState } from 'react'

import { ComponentMeta } from '@storybook/react'
import {
Asset,
AssetBCH,
AssetBNB,
AssetBTC,
AssetDOGE,
AssetETH,
AssetLTC,
AssetRuneNative
} from '@xchainjs/xchain-util'

import { Network } from '../../../../../shared/api/types'
import { AssetBUSDBD1 } from '../../../../const'
import * as AT from '../../../../storybook/argTypes'
import { AssetSelect2 as Component } from './AssetSelect2'

const assets = [AssetBTC, AssetBNB, AssetRuneNative, AssetETH, AssetLTC, AssetBCH, AssetDOGE, AssetBUSDBD1]

type Args = {
network: Network
dialogHeadline: string
onSelect: (asset: Asset) => void
}

const Template = ({ network, onSelect, dialogHeadline }: Args) => {
const [asset, setAsset] = useState<Asset>(AssetBNB)
return (
<Component
asset={asset}
assets={assets}
onSelect={(asset) => {
onSelect(asset)
setAsset(asset)
}}
dialogHeadline={dialogHeadline}
network={network}
/>
)
}
export const Default = Template.bind({})

const meta: ComponentMeta<typeof Template> = {
component: Template,
title: 'Components/Assets/AssetSelect2',
argTypes: {
network: AT.network,
onSelect: {
action: 'onSelect'
}
},
args: { network: 'mainnet', dialogHeadline: 'Change asset' },
decorators: [
(Story) => (
<div className="flex min-h-full w-full flex-col items-center justify-center bg-white">
<h1 className="uppercase text-gray2">Random headline</h1>
<p className="uppercase text-gray1">Some random text</p>
<Story />
</div>
)
]
}

export default meta
186 changes: 186 additions & 0 deletions src/renderer/components/uielements/assets/assetSelect/AssetSelect2.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import React, { useCallback, useState, useMemo, useRef } from 'react'

import { Dialog } from '@headlessui/react'
import { ArchiveBoxXMarkIcon, ChevronDownIcon, XMarkIcon } from '@heroicons/react/24/outline'
import { Asset, assetToString } from '@xchainjs/xchain-util'
import * as A from 'fp-ts/lib/Array'
import * as FP from 'fp-ts/lib/function'
import * as NEA from 'fp-ts/lib/NonEmptyArray'
import * as O from 'fp-ts/lib/Option'
import { useIntl } from 'react-intl'

import { Network } from '../../../../../shared/api/types'
import { emptyString } from '../../../../helpers/stringHelper'
import { BaseButton } from '../../button'
import { InputSearch } from '../../input'
import { AssetData } from '../assetData'

export type Props = {
asset: Asset
assets: Asset[]
onSelect: (_: Asset) => void
className?: string
showAssetName?: boolean
dialogHeadline?: string
disabled?: boolean
network: Network
}

export const AssetSelect2: React.FC<Props> = (props): JSX.Element => {
const {
asset,
assets = [],
onSelect = (_: Asset) => {},
className = '',
dialogHeadline = emptyString,
showAssetName = true,
disabled = false,
network
} = props

const [openMenu, setOpenMenu] = useState<boolean>(false)

const [searchValue, setSearchValue] = useState<string>(emptyString)

const clearSearchValue = useCallback(() => {
setSearchValue(emptyString)
}, [])

const intl = useIntl()

const handleDropdownButtonClicked = (e: React.MouseEvent) => {
e.stopPropagation()
setOpenMenu(!openMenu)
}

const handleChangeAsset = useCallback(
async (asset: Asset) => {
onSelect(asset)
setOpenMenu(false)
clearSearchValue()
},
[clearSearchValue, onSelect]
)

const filteredAssets = useMemo(
() =>
FP.pipe(
assets,
A.filter((asset) =>
// filter assets depending on search input
searchValue ? assetToString(asset).toLowerCase().includes(searchValue.toLowerCase()) : true
)
),
[assets, searchValue]
)
const renderAssets = useMemo(
() =>
FP.pipe(
filteredAssets,
NEA.fromArray,
O.fold(
() => (
<div className="flex h-full w-full flex-col items-center justify-center px-20px py-50px">
<h2 className="mb-10px text-[14px] uppercase text-gray1 dark:text-gray1d">
{intl.formatMessage({ id: 'common.noResult' })}
</h2>
<ArchiveBoxXMarkIcon className="h-[75px] w-[75px] text-gray0 dark:text-gray0d" />
</div>
),
(assets) => (
<div className="w-full overflow-y-auto">
{FP.pipe(
assets,
NEA.map((asset) => (
<BaseButton
key={assetToString(asset)}
onClick={() => handleChangeAsset(asset)}
className="w-full !justify-start hover:bg-gray0 hover:dark:bg-gray0d">
<AssetData asset={asset} network={network} className="" />
</BaseButton>
))
)}
</div>
)
)
),
[filteredAssets, handleChangeAsset, intl, network]
)

const searchHandler = useCallback(({ target }: React.ChangeEvent<HTMLInputElement>) => {
const { value } = target
setSearchValue(value.replace(/\s/g, ''))
}, [])

const onCloseMenu = useCallback(() => {
setOpenMenu(false)
clearSearchValue()
}, [clearSearchValue])

// Ref to `InputSearch` - needed for intial focus in dialog
// @see https://headlessui.com/react/dialog#managing-initial-focus
const inputSearchRef = useRef(null)

const renderMenu = useMemo(
() => (
<Dialog as="div" className="relative z-10" initialFocus={inputSearchRef} open={openMenu} onClose={onCloseMenu}>
{/* backdrop */}
<div className="fixed inset-0 bg-bg0/80 dark:bg-bg0d/80" aria-hidden="true" />

{/* container to center the panel */}
<div className="fixed inset-0 flex items-center justify-center p-4">
{/* dialog panel */}
<Dialog.Panel
className="relative mx-auto flex h-[50%] min-h-[350px] max-w-[250px]
flex-col items-center rounded-[10px]
bg-bg0 px-20px
pb-20px pt-30px shadow-lg dark:bg-bg0d
">
<BaseButton
className="absolute right-[15px] top-10px !p-0 text-gray1 hover:text-gray2 dark:text-gray1d hover:dark:text-gray2d"
onClick={() => setOpenMenu(false)}>
<XMarkIcon className="h-20px w-20px text-inherit" />
</BaseButton>
{dialogHeadline && (
<h1 className="!my-5px text-center font-mainSemiBold text-[17px] uppercase text-text2 dark:text-text2d">
{dialogHeadline}
</h1>
)}
<InputSearch
ref={inputSearchRef}
className="my-10px"
size="large"
onChange={searchHandler}
onCancel={clearSearchValue}
placeholder={intl.formatMessage({ id: 'common.search' })}
/>
{renderAssets}
</Dialog.Panel>
</div>
</Dialog>
),
[openMenu, onCloseMenu, dialogHeadline, searchHandler, clearSearchValue, intl, renderAssets]
)

const hideButton = !assets.length
const disableButton = disabled || hideButton

return (
<div>
<BaseButton
className={`group py-[2px] px-10px focus:outline-none ${className}`}
disabled={disableButton}
onClick={handleDropdownButtonClicked}>
<AssetData noTicker={!showAssetName} className="" asset={asset} network={network} />
{!hideButton && (
<ChevronDownIcon
className={`ease h-20px w-20px text-turquoise ${openMenu ? 'rotate-180' : 'rotate-0'}
group-hover:rotate-180
`}
/>
)}
</BaseButton>
{renderMenu}
</div>
)
}
2 changes: 1 addition & 1 deletion src/renderer/components/uielements/input/InputSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { emptyString } from '../../../helpers/stringHelper'
import { Size } from '../button'
import { Input, InputProps } from './Input'

export type Props = { classNameInput: string; onSearch?: (searchTxt: string) => void } & Omit<InputProps, 'uppercase'>
export type Props = { classNameInput?: string; onSearch?: (searchTxt: string) => void } & Omit<InputProps, 'uppercase'>

export const InputSearch = forwardRef<HTMLInputElement, Props>((props, ref): JSX.Element => {
const {
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/i18n/de/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ const common: CommonMessages = {
'common.manage': 'Manage',
'common.analytics': 'Analyse',
'common.asset.base': 'Base',
'common.asset.change': 'Ändere Asset',
'common.noResult': 'Kein Ergebnis',
'common.tx.type.swap': 'Swap',
'common.tx.type.donate': 'Spenden',
'common.tx.type.refund': 'Erstatten',
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/i18n/en/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ const common: CommonMessages = {
'common.manage': 'Manage',
'common.analytics': 'Analytics',
'common.asset.base': 'Base',
'common.asset.change': 'Change asset',
'common.noResult': 'No result',
'common.tx.type.swap': 'Swap',
'common.tx.type.donate': 'Donate',
'common.tx.type.refund': 'Refund',
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/i18n/fr/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ const common: CommonMessages = {
'common.manage': 'Gérer',
'common.analytics': 'Analyse',
'common.asset.base': 'Base',
'common.asset.change': 'Change asset - FR',
'common.noResult': 'No result - FR',
'common.tx.type.swap': 'Échange',
'common.tx.type.donate': 'Don',
'common.tx.type.refund': 'Remboursement',
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/i18n/ru/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ const common: CommonMessages = {
'common.manage': 'Управление',
'common.analytics': 'Аналитика',
'common.asset.base': 'Базовый',
'common.asset.change': 'Change asset - RU',
'common.noResult': 'No result - RU',
'common.tx.type.swap': 'Обмен',
'common.tx.type.donate': 'Донат',
'common.tx.type.refund': 'Возврат',
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/i18n/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ export type CommonMessageKey =
| 'common.all'
| 'common.analytics'
| 'common.asset.base'
| 'common.asset.change'
| 'common.noResult'
| 'common.tx.type.swap'
| 'common.tx.type.deposit'
| 'common.tx.type.refund'
Expand Down
4 changes: 4 additions & 0 deletions src/renderer/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,8 @@ body {
.card {
@apply rounded-[5px] border border-solid border-gray0 bg-bg0 p-[24px] dark:border-gray0d dark:bg-bg0d;
}
/* default ease */
.ease {
@apply transition duration-300 ease-in-out;
}
}