Skip to content

Commit

Permalink
Prettify action details in several action views (#767)
Browse files Browse the repository at this point in the history
* Tweak appearance of ViewBox; add label prop to ActionDetails

* Use ActionDetails in SwapViewComponent

* Prettify action details in several action views

* Remove unneeded class

* Update tests

* Fix SelectAccount
  • Loading branch information
jessepinho authored Mar 15, 2024
1 parent c410a21 commit 2d92fd7
Show file tree
Hide file tree
Showing 11 changed files with 104 additions and 92 deletions.
13 changes: 9 additions & 4 deletions apps/minifront/src/components/dashboard/assets-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,17 @@ export default function AssetsTable() {
{data.map((a, index) => (
<div key={index} className='flex flex-col gap-4'>
<div className='flex flex-col items-center justify-center'>
<div className='flex flex-col items-center justify-center gap-2 md:flex-row'>
<div className='flex items-center gap-2'>
<div className='flex max-w-full flex-col justify-center gap-2 md:flex-row'>
<div className='flex items-center justify-center gap-2'>
<AddressIcon address={a.address} size={20} />
<h2 className='font-bold md:text-base xl:text-xl'>Account #{a.index.account}</h2>
<h2 className='whitespace-nowrap font-bold md:text-base xl:text-xl'>
Account #{a.index.account}
</h2>
</div>

<div className='max-w-72 truncate'>
<AddressComponent address={a.address} />
</div>
<AddressComponent address={a.address} />
</div>
</div>

Expand Down
9 changes: 4 additions & 5 deletions packages/ui/components/ui/address-component.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,27 @@ import { describe, expect, test } from 'vitest';
import { render } from '@testing-library/react';
import { Address } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/keys/v1/keys_pb';
import { bech32ToUint8Array } from '@penumbra-zone/types/src/address';
import { shortenAddress } from '@penumbra-zone/types/src/string';

describe('<AddressComponent />', () => {
const address =
'penumbra1u7dk4qw6fz3vlwyjl88vlj6gqv4hcmz2vesm87t7rm0lvwmgqqkrp3zrdmfg6et86ggv4nwmnc8vy39uxyacwm8g7trk77ad0c8n4qt76ncvuukx6xlj8mskhyjpn4twkpwwl2';
const pbAddress = new Address({ inner: bech32ToUint8Array(address) });

test('renders the shortened address', () => {
test('renders the address', () => {
const { baseElement } = render(<AddressComponent address={pbAddress} />);

expect(baseElement).toHaveTextContent(shortenAddress(address));
expect(baseElement).toHaveTextContent(address);
});

test('uses text-muted-foreground for non-ephemeral addresses', () => {
const { getByText } = render(<AddressComponent address={pbAddress} />);

expect(getByText(shortenAddress(address))).toHaveClass('text-muted-foreground');
expect(getByText(address)).toHaveClass('text-muted-foreground');
});

test('uses colored text for ephemeral addresses', () => {
const { getByText } = render(<AddressComponent address={pbAddress} ephemeral />);

expect(getByText(shortenAddress(address))).toHaveClass('text-[#8D5728]');
expect(getByText(address)).toHaveClass('text-[#8D5728]');
});
});
7 changes: 4 additions & 3 deletions packages/ui/components/ui/address-component.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Address } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/keys/v1/keys_pb';
import { bech32Address } from '@penumbra-zone/types/src/address';
import { shortenAddress } from '@penumbra-zone/types/src/string';

interface AddressComponentProps {
address: Address;
Expand All @@ -15,8 +14,10 @@ export const AddressComponent = ({ address, ephemeral }: AddressComponentProps)
const bech32Addr = bech32Address(address);

return (
<span className={'font-mono' + (ephemeral ? ' text-[#8D5728]' : ' text-muted-foreground')}>
{shortenAddress(bech32Addr)}
<span
className={'font-mono' + (ephemeral ? ' text-[#8D5728]' : ' text-muted-foreground truncate')}
>
{bech32Addr}
</span>
);
};
2 changes: 1 addition & 1 deletion packages/ui/components/ui/card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export interface CardProps extends React.HTMLAttributes<HTMLDivElement> {

const Card = React.forwardRef<HTMLDivElement, CardProps>(
({ className, gradient, children, ...props }, ref) => {
const baseClasses = 'bg-charcoal rounded-lg shadow-sm p-[30px]';
const baseClasses = 'bg-charcoal rounded-lg shadow-sm p-[30px] overflow-hidden';
return (
<div
ref={ref}
Expand Down
8 changes: 5 additions & 3 deletions packages/ui/components/ui/select-account.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,12 @@ export const SelectAccount = ({ getAddrByIndex }: SelectAccountProps) => {
<AccountSwitcher account={index} onChange={setIndex} />

<div className='mt-4 flex items-center justify-between gap-1 break-all rounded-lg border bg-background px-3 py-4'>
<div className='flex items-center gap-[6px]'>
<AddressIcon address={address} size={24} />
<div className='flex items-center gap-[6px] overflow-hidden'>
<div className='shrink-0'>
<AddressIcon address={address} size={24} />
</div>

<p className='text-sm'>
<p className='truncate text-sm'>
<AddressComponent address={address} ephemeral={ephemeral} />
</p>
</div>
Expand Down
28 changes: 23 additions & 5 deletions packages/ui/components/ui/tx/view/action-details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,41 @@ import { ReactNode } from 'react';
* </ActionDetails>
* ```
*/
export const ActionDetails = ({ children }: { children: ReactNode }) => {
return <div className='flex flex-col gap-2'>{children}</div>;
export const ActionDetails = ({ children, label }: { children: ReactNode; label?: string }) => {
return (
<div className='flex flex-col gap-2'>
{!!label && <div className='font-bold'>{label}</div>}

{children}
</div>
);
};

const Separator = () => (
// eslint-disable-next-line tailwindcss/no-unnecessary-arbitrary-value
<div className='mx-2 h-px min-w-8 grow border-b-[1px] border-dotted border-light-brown' />
);

const ActionDetailsRow = ({ label, children }: { label: string; children: ReactNode }) => {
const ActionDetailsRow = ({
label,
children,
truncate,
}: {
label: string;
children: ReactNode;
/**
* If `children` is a string, passing `truncate` will automatically truncate
* the text if it doesn't fit in a single line.
*/
truncate?: boolean;
}) => {
return (
<div className='flex items-center justify-between'>
<span className='break-keep'>{label}</span>
<span className='whitespace-nowrap break-keep'>{label}</span>

<Separator />

{children}
{truncate ? <span className='truncate'>{children}</span> : children}
</div>
);
};
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/components/ui/tx/view/address-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const AddressViewComponent = ({ view, copyable = true }: AddressViewProps
copyable = isOneTimeAddress ? false : copyable;

return (
<div className='flex items-center gap-2'>
<div className='flex items-center gap-2 overflow-hidden'>
{accountIndex !== undefined ? (
<>
<AddressIcon address={view.addressView.value.address} size={14} />
Expand Down
26 changes: 10 additions & 16 deletions packages/ui/components/ui/tx/view/delegate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,38 @@ import { Delegate } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/
import { ViewBox } from './viewbox';
import { joinLoHiAmount } from '@penumbra-zone/types/src/amount';
import { bech32IdentityKey } from '@penumbra-zone/types/src/identity-key';
import { ActionDetails } from './action-details';

/**
* Render a `Delegate` action.
*
* @todo: Make this nicer :)
*/
export const DelegateComponent = ({ value }: { value: Delegate }) => {
return (
<ViewBox
label='Delegate'
visibleContent={
<div className='flex flex-col gap-2'>
<div>
<span className='font-bold'>Epoch index:</span> {value.epochIndex.toString()}
</div>
<ActionDetails>
<ActionDetails.Row label='Epoch index'>{value.epochIndex.toString()}</ActionDetails.Row>

{!!value.delegationAmount && (
<div>
<span className='font-bold'>Delegation amount:</span>{' '}
<ActionDetails.Row label='Delegation amount'>
{joinLoHiAmount(value.delegationAmount).toString()}
</div>
</ActionDetails.Row>
)}

{!!value.unbondedAmount && (
<div>
<span className='font-bold'>Unbonded amount:</span>{' '}
<ActionDetails.Row label='Unbonded amount'>
{joinLoHiAmount(value.unbondedAmount).toString()}
</div>
</ActionDetails.Row>
)}

{/** @todo: Render validator name/etc. after fetching? */}
{!!value.validatorIdentity && (
<div>
<span className='font-bold'>Validator identity:</span>{' '}
<ActionDetails.Row label='Validator identity' truncate>
{bech32IdentityKey(value.validatorIdentity)}
</div>
</ActionDetails.Row>
)}
</div>
</ActionDetails>
}
/>
);
Expand Down
67 changes: 31 additions & 36 deletions packages/ui/components/ui/tx/view/swap.tsx
Original file line number Diff line number Diff line change
@@ -1,56 +1,51 @@
import { ViewBox } from './viewbox';
import { SwapView } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/component/dex/v1/dex_pb';
import { bech32Address } from '@penumbra-zone/types/src/address';
import { fromBaseUnitAmount, joinLoHiAmount } from '@penumbra-zone/types/src/amount';
import { uint8ArrayToBase64 } from '@penumbra-zone/types/src/base64';
import { ActionDetails } from './action-details';
import { AddressView } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/keys/v1/keys_pb';
import { AddressViewComponent } from './address-view';

export const SwapViewComponent = ({ value }: { value: SwapView }) => {
if (value.swapView.case === 'visible') {
const { tradingPair, delta1I, delta2I, claimFee, claimAddress } =
value.swapView.value.swapPlaintext!;

const encodedAddress = bech32Address(claimAddress!);
const addressView = new AddressView({
addressView: { case: 'decoded', value: { address: claimAddress } },
});

return (
<ViewBox
label='Swap'
visibleContent={
<div className='flex flex-col gap-2'>
<div>
<b>Asset 1:</b>
<div className='ml-5'>
<b>ID: </b>
<div className='flex flex-col gap-8'>
<ActionDetails label='Asset 1'>
<ActionDetails.Row label='ID' truncate>
{uint8ArrayToBase64(tradingPair!.asset1!.inner)}
</div>
<div className='ml-5'>
<b>Amount: </b>
<span className='font-mono'>{joinLoHiAmount(delta1I!).toString()}</span>
</div>
</div>
<div>
<b>Asset 2:</b>
<div className='ml-5'>
<b>ID: </b>
</ActionDetails.Row>
<ActionDetails.Row label='Amount'>
{joinLoHiAmount(delta1I!).toString()}
</ActionDetails.Row>
</ActionDetails>

<ActionDetails label='Asset 2'>
<ActionDetails.Row label='ID' truncate>
{uint8ArrayToBase64(tradingPair!.asset2!.inner)}
</div>
<div className='ml-5'>
<b>Amount: </b>
<span className='font-mono'>{joinLoHiAmount(delta2I!).toString()}</span>
</div>
</div>
<div>
<b>Claim:</b>
<div className='ml-5'>
<b>Address: </b>
{encodedAddress}
</div>
<div className='ml-5'>
<b>Fee: </b>
<span className='font-mono'>
{fromBaseUnitAmount(claimFee!.amount!, 0).toFormat()} upenumbra
</span>
</div>
</div>
</ActionDetails.Row>
<ActionDetails.Row label='Amount'>
{joinLoHiAmount(delta2I!).toString()}
</ActionDetails.Row>
</ActionDetails>

<ActionDetails label='Claim'>
<ActionDetails.Row label='Address'>
<AddressViewComponent view={addressView} />
</ActionDetails.Row>
<ActionDetails.Row label='Fee'>
{fromBaseUnitAmount(claimFee!.amount!, 0).toFormat()} upenumbra
</ActionDetails.Row>
</ActionDetails>
</div>
}
/>
Expand Down
28 changes: 12 additions & 16 deletions packages/ui/components/ui/tx/view/undelegate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,40 @@ import { Undelegate } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/cor
import { ViewBox } from './viewbox';
import { joinLoHiAmount } from '@penumbra-zone/types/src/amount';
import { bech32IdentityKey } from '@penumbra-zone/types/src/identity-key';
import { ActionDetails } from './action-details';

/**
* Render an `Undelegate` action.
*
* @todo: Make this nicer :)
*/
export const UndelegateComponent = ({ value }: { value: Undelegate }) => {
return (
<ViewBox
label='Undelegate'
visibleContent={
<div className='flex flex-col gap-2'>
<div>
<span className='font-bold'>Epoch index:</span> {value.startEpochIndex.toString()}
</div>
<ActionDetails>
<ActionDetails.Row label='Epoch index'>
{value.startEpochIndex.toString()}
</ActionDetails.Row>

{!!value.delegationAmount && (
<div>
<span className='font-bold'>Delegation amount:</span>{' '}
<ActionDetails.Row label='Delegation amount'>
{joinLoHiAmount(value.delegationAmount).toString()}
</div>
</ActionDetails.Row>
)}

{!!value.unbondedAmount && (
<div>
<span className='font-bold'>Unbonded amount:</span>{' '}
<ActionDetails.Row label='Unbonded amount'>
{joinLoHiAmount(value.unbondedAmount).toString()}
</div>
</ActionDetails.Row>
)}

{/** @todo: Render validator name/etc. after fetching? */}
{!!value.validatorIdentity && (
<div>
<span className='font-bold'>Validator identity:</span>{' '}
<ActionDetails.Row label='Validator identity' truncate>
{bech32IdentityKey(value.validatorIdentity)}
</div>
</ActionDetails.Row>
)}
</div>
</ActionDetails>
}
/>
);
Expand Down
6 changes: 4 additions & 2 deletions packages/ui/components/ui/tx/view/viewbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ export interface ViewBoxProps {
visibleContent?: React.ReactElement;
}

const Label = ({ label }: { label: string }) => <span className='text-lg'>{label}</span>;

export const ViewBox = ({ label, visibleContent }: ViewBoxProps) => {
return (
<div
Expand All @@ -19,11 +21,11 @@ export const ViewBox = ({ label, visibleContent }: ViewBoxProps) => {
>
<div className='flex items-center gap-2 self-start'>
<span className={cn('text-base font-bold', !visibleContent ? 'text-gray-600' : '')}>
{visibleContent && label}
{visibleContent && <Label label={label} />}
{!visibleContent && (
<div className='flex gap-2'>
<IncognitoIcon fill='#4b5563' />
<span>{label}</span>
<Label label={label} />
</div>
)}
</span>
Expand Down

0 comments on commit 2d92fd7

Please sign in to comment.