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

Commit

Permalink
Redesign AssetSelect (#2426)
Browse files Browse the repository at this point in the history
- [x] `AssetSelect2` component + story
- [x] Update `i18n`
  • Loading branch information
veado authored Oct 31, 2022
1 parent fb34f40 commit 1a81467
Show file tree
Hide file tree
Showing 10 changed files with 269 additions and 2 deletions.
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;
}
}

0 comments on commit 1a81467

Please sign in to comment.