-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
* feat(minifront): #1211: extend BalanceSelector to accept metadata assets * feat(minifront): #1211: apply correct validations for swap action * chore: changesets * chore: fix linting * chore: fix linting * fix(minifront): #1211: set initial swap assets correctly * fix(minifront): #1211: make the code cleaner after the review * fix(ui): #1211: don't let IconInput accept all InputProps
- Loading branch information
Showing
20 changed files
with
345 additions
and
202 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
--- | ||
'minifront': minor | ||
'@repo/ui': patch | ||
--- | ||
|
||
Minifront: | ||
|
||
- extend `BalanceSelector` to show not only assets with balances but all available assets | ||
- fix the issues with empty wallets not rendering a swap block correctly | ||
- reduce the height of `BalanceSelecor` and `AssetSelector` to `90dvh` | ||
- autofocus the search inputs in `BalanceSelecor` and `AssetSelector` | ||
- change validations of the swap input to allow entering any possible values | ||
|
||
UI: allow passing `autoFocus` attribute to the `IconInput` component |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
114 changes: 0 additions & 114 deletions
114
apps/minifront/src/components/shared/balance-selector.tsx
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
58 changes: 58 additions & 0 deletions
58
apps/minifront/src/components/shared/selectors/balance-item.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import { BalanceOrMetadata, isBalance, isMetadata } from './helpers'; | ||
import { getAddressIndex } from '@penumbra-zone/getters/address-view'; | ||
import { getMetadataFromBalancesResponseOptional } from '@penumbra-zone/getters/balances-response'; | ||
import { useMemo } from 'react'; | ||
import { DialogClose } from '@repo/ui/components/ui/dialog'; | ||
import { cn } from '@repo/ui/lib/utils'; | ||
import { AssetIcon } from '@repo/ui/components/ui/tx/view/asset-icon'; | ||
import { ValueViewComponent } from '@repo/ui/components/ui/tx/view/value'; | ||
|
||
interface BalanceItemProps { | ||
asset: BalanceOrMetadata; | ||
value?: BalanceOrMetadata; | ||
onSelect: (value: BalanceOrMetadata) => void; | ||
} | ||
|
||
export const BalanceItem = ({ asset, value, onSelect }: BalanceItemProps) => { | ||
const account = isBalance(asset) ? getAddressIndex(asset.accountAddress).account : undefined; | ||
const metadata = isMetadata(asset) ? asset : getMetadataFromBalancesResponseOptional(asset); | ||
|
||
const isSelected = useMemo(() => { | ||
if (!value) return false; | ||
if (isMetadata(value) && isMetadata(asset)) { | ||
return value.equals(asset); | ||
} | ||
if (isBalance(value) && isBalance(asset)) { | ||
return value.equals(asset); | ||
} | ||
return false; | ||
}, [asset, value]); | ||
|
||
return ( | ||
<div className='flex flex-col'> | ||
<DialogClose onClick={() => onSelect(asset)}> | ||
<div | ||
className={cn( | ||
'grid grid-cols-5 py-[10px] gap-3 cursor-pointer hover:bg-light-brown hover:px-4 hover:-mx-4 font-bold text-muted-foreground', | ||
isSelected && 'bg-light-brown px-4 -mx-4', | ||
)} | ||
> | ||
{metadata && ( | ||
<div className='col-span-2 flex items-center justify-start gap-1'> | ||
<AssetIcon metadata={metadata} /> | ||
<p className='truncate'>{metadata.symbol || 'Unknown asset'}</p> | ||
</div> | ||
)} | ||
|
||
<div className='col-span-2 flex justify-end'> | ||
{isBalance(asset) && ( | ||
<ValueViewComponent showIcon={false} showDenom={false} view={asset.balanceView} /> | ||
)} | ||
</div> | ||
|
||
<p className='flex justify-center'>{account}</p> | ||
</div> | ||
</DialogClose> | ||
</div> | ||
); | ||
}; |
103 changes: 103 additions & 0 deletions
103
apps/minifront/src/components/shared/selectors/balance-selector.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
import { MagnifyingGlassIcon } from '@radix-ui/react-icons'; | ||
import { useId, useState } from 'react'; | ||
import { IconInput } from '@repo/ui/components/ui/icon-input'; | ||
import { Dialog, DialogContent, DialogHeader } from '@repo/ui/components/ui/dialog'; | ||
import { ValueViewComponent } from '@repo/ui/components/ui/tx/view/value'; | ||
import { BalancesResponse } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/view/v1/view_pb'; | ||
import { Box } from '@repo/ui/components/ui/box'; | ||
import { motion } from 'framer-motion'; | ||
import { Metadata } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/asset/v1/asset_pb'; | ||
import { emptyBalanceResponse } from '../../../utils/empty-balance-response'; | ||
import { bySearch } from './search-filters'; | ||
import { BalanceOrMetadata, isMetadata, mergeBalancesAndAssets } from './helpers'; | ||
import { BalanceItem } from './balance-item'; | ||
|
||
interface BalanceSelectorProps { | ||
value: BalancesResponse | undefined; | ||
onChange: (selection: BalancesResponse) => void; | ||
balances?: BalancesResponse[]; | ||
assets?: Metadata[]; | ||
} | ||
|
||
/** | ||
* Renders balances the user holds, and allows the user to select one. This is | ||
* useful for a form where the user wants to send/sell/swap an asset that they | ||
* already hold. | ||
* | ||
* Use `<AssetSelector />` if you want to render assets that aren't tied to any | ||
* balance. | ||
*/ | ||
export default function BalanceSelector({ | ||
value, | ||
balances, | ||
onChange, | ||
assets, | ||
}: BalanceSelectorProps) { | ||
const [search, setSearch] = useState(''); | ||
const [isOpen, setIsOpen] = useState(false); | ||
const layoutId = useId(); | ||
|
||
const allAssets = mergeBalancesAndAssets(balances, assets); | ||
const filteredBalances = search ? allAssets.filter(bySearch(search)) : allAssets; | ||
|
||
const onSelect = (asset: BalanceOrMetadata) => { | ||
if (!isMetadata(asset)) { | ||
onChange(asset); | ||
return; | ||
} | ||
onChange(emptyBalanceResponse(asset)); | ||
}; | ||
|
||
return ( | ||
<> | ||
{!isOpen && ( | ||
<motion.div | ||
layout | ||
layoutId={layoutId} | ||
className='flex min-w-[100px] max-w-[200px] cursor-pointer items-center justify-center rounded-lg bg-light-brown px-2' | ||
onClick={() => setIsOpen(true)} | ||
> | ||
<ValueViewComponent view={value?.balanceView} showValue={false} /> | ||
</motion.div> | ||
)} | ||
|
||
{isOpen && ( | ||
<> | ||
{/* 0-opacity placeholder for layout's sake */} | ||
<div className='flex min-w-[100px] max-w-[200px] px-2 opacity-0'> | ||
<ValueViewComponent view={value?.balanceView} showValue={false} /> | ||
</div> | ||
</> | ||
)} | ||
|
||
<Dialog open={isOpen} onOpenChange={setIsOpen}> | ||
<DialogContent layoutId={layoutId}> | ||
<div className='flex max-h-[90dvh] flex-col'> | ||
<DialogHeader>Select asset</DialogHeader> | ||
<div className='flex shrink flex-col gap-4 overflow-auto p-4'> | ||
<Box spacing='compact'> | ||
<IconInput | ||
icon={<MagnifyingGlassIcon className='size-5 text-muted-foreground' />} | ||
value={search} | ||
onChange={setSearch} | ||
autoFocus | ||
placeholder='Search assets...' | ||
/> | ||
</Box> | ||
<div className='mt-2 grid grid-cols-4 gap-3 font-headline text-base font-semibold'> | ||
<p className='col-span-2 flex justify-start'>Asset</p> | ||
<p className='flex justify-end'>Balance</p> | ||
<p className='flex justify-center'>Account</p> | ||
</div> | ||
<div className='flex flex-col gap-2'> | ||
{filteredBalances.map((asset, i) => ( | ||
<BalanceItem key={i} asset={asset} value={value} onSelect={onSelect} /> | ||
))} | ||
</div> | ||
</div> | ||
</div> | ||
</DialogContent> | ||
</Dialog> | ||
</> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { BalancesResponse } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/view/v1/view_pb'; | ||
import { Metadata } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/asset/v1/asset_pb'; | ||
import { getMetadataFromBalancesResponseOptional } from '@penumbra-zone/getters/balances-response'; | ||
|
||
export type BalanceOrMetadata = BalancesResponse | Metadata; | ||
|
||
export const isMetadata = (asset: BalancesResponse | Metadata): asset is Metadata => { | ||
return asset.getType().typeName === Metadata.typeName; | ||
}; | ||
|
||
export const isBalance = (asset: BalancesResponse | Metadata): asset is BalancesResponse => { | ||
return asset.getType().typeName === BalancesResponse.typeName; | ||
}; | ||
|
||
export const mergeBalancesAndAssets = ( | ||
balances: BalancesResponse[] = [], | ||
assets: Metadata[] = [], | ||
): BalanceOrMetadata[] => { | ||
const filteredAssets = assets.filter(asset => { | ||
return !balances.some(balance => { | ||
const balanceMetadata = getMetadataFromBalancesResponseOptional(balance); | ||
return balanceMetadata?.equals(asset); | ||
}); | ||
}); | ||
return [...balances, ...filteredAssets]; | ||
}; |
Oops, something went wrong.