Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prettify action details in several action views #767

Merged
merged 6 commits into from
Mar 15, 2024
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
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} />
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<AddressComponent /> no longer shortens addresses to a specific number of characters — instead, it's more flexible, expanding or contracting to fit its container. So we'll manually narrow it via a parent <div /> here.

</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';
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To ensure truncating strings works

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>}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With a label:
image

Without a label:
image


{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>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before:
image

After:
image

<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'>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before:
image

After:
image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(In follow-up PR(s), this will move closer to this proposed design.)

<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>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before:
image

After:
image

<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>;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just making the label a little bigger, to differentiate from the view box's contents


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
Loading