Skip to content

Commit

Permalink
feat(ui): #2053: ActionView component (#2055)
Browse files Browse the repository at this point in the history
* fix(ui): #2053: add `smallTechnical` variant to Typography component

* feat(ui): #2053: Add base for the ActionView component

* feat(ui): #2053: Implement Spend and Output action views

* feat(ui): #2053: implement SwapAction view

* feat(ui): #2053: implement opaque views for existing actions

* feat(ui): #2053: create skeleton files for unimplemented actions

* chore: changeset

* fix(ui): #2053: after review
  • Loading branch information
VanishMax authored Feb 17, 2025
1 parent b97fb7c commit 07aa2fe
Show file tree
Hide file tree
Showing 45 changed files with 976 additions and 11 deletions.
5 changes: 5 additions & 0 deletions .changeset/chilly-oranges-scream.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@penumbra-zone/ui': minor
---

Implement ActionView component
75 changes: 75 additions & 0 deletions packages/ui/src/ActionView/action-view.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { FC } from 'react';
import { ActionView as ActionViewMessage } from '@penumbra-zone/protobuf/penumbra/core/transaction/v1/transaction_pb';
import { ActionViewType, ActionViewValueType } from './types';
import { UnknownAction } from './actions/unknown';

import { SpendAction } from './actions/spend';
import { OutputAction } from './actions/output';
import { SwapAction } from './actions/swap';
import { SwapClaimAction } from './actions/swap-claim';
import { DelegateAction } from './actions/delegate';
import { DelegatorVoteAction } from './actions/delegator-vote';
import { IbcRelayAction } from './actions/ibc-relay';
import { Ics20WithdrawalAction } from './actions/ics-20-withdrawal';
import { UndelegateAction } from './actions/undelegate';
import { UndelegateClaimAction } from './actions/undelegate-claim';
import { PositionCloseAction } from './actions/position-close';
import { PositionOpenAction } from './actions/position-open';
import { PositionRewardClaimAction } from './actions/position-reward-claim';
import { PositionWithdrawAction } from './actions/position-withdraw';
import { ProposalDepositClaimAction } from './actions/proposal-deposit-claim';
import { ProposalSubmitAction } from './actions/proposal-submit';
import { ProposalWithdrawAction } from './actions/proposal-withdraw';
import { ValidatorDefinitionAction } from './actions/validator-definition';
import { ValidatorVoteAction } from './actions/validator-vote';
import { DutchAuctionEndAction } from './actions/dutch-auction-end';
import { DutchAuctionScheduleAction } from './actions/dutch-auction-schedule';
import { DutchAuctionWithdrawAction } from './actions/dutch-auction-withdraw';
import { CommunityPoolDepositAction } from './actions/community-pool-deposit';
import { CommunityPoolOutputAction } from './actions/community-pool-output';
import { CommunityPoolSpendAction } from './actions/community-pool-spend';

export interface ActionViewProps {
action: ActionViewMessage;
}

const componentMap = {
spend: SpendAction,
output: OutputAction,
swap: SwapAction,
// TODO: Implement the actions below
swapClaim: SwapClaimAction,
delegate: DelegateAction,
delegatorVote: DelegatorVoteAction,
undelegate: UndelegateAction,
undelegateClaim: UndelegateClaimAction,
ibcRelayAction: IbcRelayAction,
ics20Withdrawal: Ics20WithdrawalAction,
positionClose: PositionCloseAction,
positionOpen: PositionOpenAction,
positionRewardClaim: PositionRewardClaimAction,
positionWithdraw: PositionWithdrawAction,
proposalDepositClaim: ProposalDepositClaimAction,
proposalSubmit: ProposalSubmitAction,
proposalWithdraw: ProposalWithdrawAction,
validatorDefinition: ValidatorDefinitionAction,
validatorVote: ValidatorVoteAction,
actionDutchAuctionEnd: DutchAuctionEndAction,
actionDutchAuctionSchedule: DutchAuctionScheduleAction,
actionDutchAuctionWithdraw: DutchAuctionWithdrawAction,
communityPoolDeposit: CommunityPoolDepositAction,
communityPoolOutput: CommunityPoolOutputAction,
communityPoolSpend: CommunityPoolSpendAction,
unknown: UnknownAction,
} as const satisfies Record<ActionViewType | 'unknown', unknown>;

/**
* In Penumbra, each transaction has 'actions' of different types,
* representing a blockchain state change performed by a transaction.
*/
export const ActionView = ({ action }: ActionViewProps) => {
const type = action.actionView.case ?? 'unknown';
const Component = componentMap[type] as FC<{ value?: ActionViewValueType }>;

return <Component value={action.actionView.value} />;
};
29 changes: 29 additions & 0 deletions packages/ui/src/ActionView/actions/action-row.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { ReactNode } from 'react';
import cn from 'clsx';
import { detailTechnical } from '../../utils/typography';
import { CopyToClipboardButton } from '../../CopyToClipboardButton';
import { Density } from '../../Density';

export interface ActionRowProps {
label: ReactNode;
info: ReactNode;
copyText?: string;
}

export const ActionRow = ({ label, copyText, info }: ActionRowProps) => {
return (
<div className={cn('flex items-center gap-2 text-text-secondary', detailTechnical)}>
{label}
<div className='h-px grow border-t border-dashed border-other-tonalStroke stroke-1' />
{info}

{copyText && (
<Density key='swap-claim' slim>
<div className='size-4 [&>button]:text-neutral-light'>
<CopyToClipboardButton text={copyText} />
</div>
</Density>
)}
</div>
);
};
11 changes: 11 additions & 0 deletions packages/ui/src/ActionView/actions/community-pool-deposit.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { CommunityPoolDeposit } from '@penumbra-zone/protobuf/penumbra/core/component/governance/v1/governance_pb';
import { UnknownAction } from './unknown';

export interface CommunityPoolDepositActionProps {
value: CommunityPoolDeposit;
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars -- unimplemented
export const CommunityPoolDepositAction = (_: CommunityPoolDepositActionProps) => {
return <UnknownAction label='Community Pool Deposit' opaque={false} />;
};
11 changes: 11 additions & 0 deletions packages/ui/src/ActionView/actions/community-pool-output.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { CommunityPoolOutput } from '@penumbra-zone/protobuf/penumbra/core/component/governance/v1/governance_pb';
import { UnknownAction } from './unknown';

export interface CommunityPoolOutputActionProps {
value: CommunityPoolOutput;
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars -- unimplemented
export const CommunityPoolOutputAction = (_: CommunityPoolOutputActionProps) => {
return <UnknownAction label='Community Pool Output' opaque={false} />;
};
11 changes: 11 additions & 0 deletions packages/ui/src/ActionView/actions/community-pool-spend.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { CommunityPoolSpend } from '@penumbra-zone/protobuf/penumbra/core/component/governance/v1/governance_pb';
import { UnknownAction } from './unknown';

export interface CommunityPoolSpendActionProps {
value: CommunityPoolSpend;
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars -- unimplemented
export const CommunityPoolSpendAction = (_: CommunityPoolSpendActionProps) => {
return <UnknownAction label='Community Pool Spend' opaque={false} />;
};
11 changes: 11 additions & 0 deletions packages/ui/src/ActionView/actions/delegate.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Delegate } from '@penumbra-zone/protobuf/penumbra/core/component/stake/v1/stake_pb';
import { UnknownAction } from './unknown';

export interface DelegateActionProps {
value: Delegate;
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars -- unimplemented
export const DelegateAction = (_: DelegateActionProps) => {
return <UnknownAction label='Delegate' opaque={false} />;
};
10 changes: 10 additions & 0 deletions packages/ui/src/ActionView/actions/delegator-vote.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { DelegatorVoteView } from '@penumbra-zone/protobuf/penumbra/core/component/governance/v1/governance_pb';
import { UnknownAction } from './unknown';

export interface DelegatorVoteActionProps {
value: DelegatorVoteView;
}

export const DelegatorVoteAction = ({ value }: DelegatorVoteActionProps) => {
return <UnknownAction label='Delegator Vote' opaque={value.delegatorVote.case === 'opaque'} />;
};
11 changes: 11 additions & 0 deletions packages/ui/src/ActionView/actions/dutch-auction-end.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { UnknownAction } from './unknown';
import { ActionDutchAuctionEnd } from '@penumbra-zone/protobuf/penumbra/core/component/auction/v1/auction_pb';

export interface DutchAuctionEndActionProps {
value: ActionDutchAuctionEnd;
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars -- unimplemented
export const DutchAuctionEndAction = (_: DutchAuctionEndActionProps) => {
return <UnknownAction label='Dutch Auction End' opaque={false} />;
};
11 changes: 11 additions & 0 deletions packages/ui/src/ActionView/actions/dutch-auction-schedule.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { ActionDutchAuctionScheduleView } from '@penumbra-zone/protobuf/penumbra/core/component/auction/v1/auction_pb';
import { UnknownAction } from './unknown';

export interface DutchAuctionScheduleActionProps {
value: ActionDutchAuctionScheduleView;
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars -- unimplemented
export const DutchAuctionScheduleAction = (_: DutchAuctionScheduleActionProps) => {
return <UnknownAction label='Dutch Auction Schedule' opaque={false} />;
};
11 changes: 11 additions & 0 deletions packages/ui/src/ActionView/actions/dutch-auction-withdraw.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { ActionDutchAuctionWithdrawView } from '@penumbra-zone/protobuf/penumbra/core/component/auction/v1/auction_pb';
import { UnknownAction } from './unknown';

export interface DutchAuctionWithdrawActionProps {
value: ActionDutchAuctionWithdrawView;
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars -- unimplemented
export const DutchAuctionWithdrawAction = (_: DutchAuctionWithdrawActionProps) => {
return <UnknownAction label='Dutch Auction Withdraw' opaque={false} />;
};
11 changes: 11 additions & 0 deletions packages/ui/src/ActionView/actions/ibc-relay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { IbcRelay } from '@penumbra-zone/protobuf/penumbra/core/component/ibc/v1/ibc_pb';
import { UnknownAction } from './unknown';

export interface IbcRelayActionProps {
value: IbcRelay;
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars -- unimplemented
export const IbcRelayAction = (_: IbcRelayActionProps) => {
return <UnknownAction label='IBC Relay' opaque={false} />;
};
11 changes: 11 additions & 0 deletions packages/ui/src/ActionView/actions/ics-20-withdrawal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Ics20Withdrawal } from '@penumbra-zone/protobuf/penumbra/core/component/ibc/v1/ibc_pb';
import { UnknownAction } from './unknown';

export interface Ics20WithdrawalActionProps {
value: Ics20Withdrawal;
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars -- unimplemented
export const Ics20WithdrawalAction = (_: Ics20WithdrawalActionProps) => {
return <UnknownAction label='ICS 20 Withdrawal' opaque={false} />;
};
18 changes: 18 additions & 0 deletions packages/ui/src/ActionView/actions/incognito-icon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export const IncognitoIcon = () => {
return (
<svg
className='size-4'
xmlns='http://www.w3.org/2000/svg'
width='16'
height='16'
viewBox='0 0 16 16'
fill='currentColor'
>
<path
fillRule='evenodd'
clipRule='evenodd'
d='M3.34267 6.55733C3.33656 6.59346 3.33343 6.63003 3.33333 6.66667H2.66667C2.48986 6.66667 2.32029 6.7369 2.19526 6.86193C2.07024 6.98695 2 7.15652 2 7.33333C2 7.51014 2.07024 7.67971 2.19526 7.80474C2.32029 7.92976 2.48986 8 2.66667 8H13.3333C13.5101 8 13.6797 7.92976 13.8047 7.80474C13.9298 7.67971 14 7.51014 14 7.33333C14 7.15652 13.9298 6.98695 13.8047 6.86193C13.6797 6.7369 13.5101 6.66667 13.3333 6.66667H12.6667C12.6666 6.63003 12.6634 6.59346 12.6573 6.55733L12.1767 3.67067C12.0987 3.20368 11.8576 2.7795 11.4963 2.47358C11.1349 2.16766 10.6768 1.99985 10.2033 2H5.79667C5.32312 1.99988 4.8649 2.16778 4.50354 2.47383C4.14219 2.77989 3.90115 3.20423 3.82333 3.67133L3.34267 6.55733ZM4.66667 8.66667C5.25797 8.66652 5.83256 8.8629 6.30007 9.22494C6.76758 9.58698 7.10151 10.0941 7.24933 10.6667H8.75067C8.91449 10.0397 9.30091 9.49374 9.83778 9.1308C10.3747 8.76786 11.0253 8.61273 11.6681 8.69438C12.311 8.77602 12.9022 9.08886 13.3313 9.57448C13.7604 10.0601 13.9981 10.6853 14 11.3333C14.0006 11.9828 13.7642 12.6103 13.3351 13.0978C12.906 13.5854 12.3136 13.8996 11.6693 13.9814C11.025 14.0632 10.3729 13.9071 9.83554 13.5423C9.29816 13.1774 8.9124 12.6291 8.75067 12H7.24933C7.08551 12.627 6.69909 13.1729 6.16222 13.5359C5.62535 13.8988 4.97473 14.0539 4.33185 13.9723C3.68897 13.8906 3.09779 13.5778 2.66868 13.0922C2.23958 12.6066 2.00189 11.9814 2 11.3333C2 10.6261 2.28095 9.94781 2.78105 9.44772C3.28115 8.94762 3.95942 8.66667 4.66667 8.66667Z'
/>
</svg>
);
};
31 changes: 31 additions & 0 deletions packages/ui/src/ActionView/actions/output.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { OutputView } from '@penumbra-zone/protobuf/penumbra/core/component/shielded_pool/v1/shielded_pool_pb';
import { getNote } from '@penumbra-zone/getters/output-view';
import { getAddress } from '@penumbra-zone/getters/note-view';
import { Density } from '../../Density';
import { useDensity } from '../../utils/density';
import { ActionWrapper } from './wrapper';
import { ValueViewComponent } from '../../ValueView';
import { AddressViewComponent } from '../../AddressView';

export interface OutputActionProps {
value: OutputView;
}

export const OutputAction = ({ value }: OutputActionProps) => {
const density = useDensity();

return (
<ActionWrapper title='Output' opaque={value.outputView.case === 'opaque'}>
{value.outputView.case === 'visible' && (
<Density slim>
<ValueViewComponent
signed='positive'
priority={density === 'sparse' ? 'primary' : 'tertiary'}
valueView={getNote(value).value}
/>
<AddressViewComponent addressView={getAddress(getNote(value))} />
</Density>
)}
</ActionWrapper>
);
};
11 changes: 11 additions & 0 deletions packages/ui/src/ActionView/actions/position-close.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { PositionClose } from '@penumbra-zone/protobuf/penumbra/core/component/dex/v1/dex_pb';
import { UnknownAction } from './unknown';

export interface PositionCloseActionProps {
value: PositionClose;
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars -- unimplemented
export const PositionCloseAction = (_: PositionCloseActionProps) => {
return <UnknownAction label='Position Close' opaque={false} />;
};
11 changes: 11 additions & 0 deletions packages/ui/src/ActionView/actions/position-open.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { PositionOpen } from '@penumbra-zone/protobuf/penumbra/core/component/dex/v1/dex_pb';
import { UnknownAction } from './unknown';

export interface PositionOpenActionProps {
value: PositionOpen;
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars -- unimplemented
export const PositionOpenAction = (_: PositionOpenActionProps) => {
return <UnknownAction label='Position Open' opaque={false} />;
};
11 changes: 11 additions & 0 deletions packages/ui/src/ActionView/actions/position-reward-claim.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { PositionRewardClaim } from '@penumbra-zone/protobuf/penumbra/core/component/dex/v1/dex_pb';
import { UnknownAction } from './unknown';

export interface PositionRewardClaimActionProps {
value: PositionRewardClaim;
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars -- unimplemented
export const PositionRewardClaimAction = (_: PositionRewardClaimActionProps) => {
return <UnknownAction label='Position Reward Claim' opaque={false} />;
};
11 changes: 11 additions & 0 deletions packages/ui/src/ActionView/actions/position-withdraw.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { PositionWithdraw } from '@penumbra-zone/protobuf/penumbra/core/component/dex/v1/dex_pb';
import { UnknownAction } from './unknown';

export interface PositionWithdrawActionProps {
value: PositionWithdraw;
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars -- unimplemented
export const PositionWithdrawAction = (_: PositionWithdrawActionProps) => {
return <UnknownAction label='Position Withdraw' opaque={false} />;
};
11 changes: 11 additions & 0 deletions packages/ui/src/ActionView/actions/proposal-deposit-claim.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { ProposalDepositClaim } from '@penumbra-zone/protobuf/penumbra/core/component/governance/v1/governance_pb';
import { UnknownAction } from './unknown';

export interface ProposalDepositClaimActionProps {
value: ProposalDepositClaim;
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars -- unimplemented
export const ProposalDepositClaimAction = (_: ProposalDepositClaimActionProps) => {
return <UnknownAction label='Proposal Deposit Claim' opaque={false} />;
};
11 changes: 11 additions & 0 deletions packages/ui/src/ActionView/actions/proposal-submit.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { ProposalSubmit } from '@penumbra-zone/protobuf/penumbra/core/component/governance/v1/governance_pb';
import { UnknownAction } from './unknown';

export interface ProposalSubmitActionProps {
value: ProposalSubmit;
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars -- unimplemented
export const ProposalSubmitAction = (_: ProposalSubmitActionProps) => {
return <UnknownAction label='Proposal Submit' opaque={false} />;
};
11 changes: 11 additions & 0 deletions packages/ui/src/ActionView/actions/proposal-withdraw.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { ProposalWithdraw } from '@penumbra-zone/protobuf/penumbra/core/component/governance/v1/governance_pb';
import { UnknownAction } from './unknown';

export interface ProposalWithdrawActionProps {
value: ProposalWithdraw;
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars -- unimplemented
export const ProposalWithdrawAction = (_: ProposalWithdrawActionProps) => {
return <UnknownAction label='Proposal Withdraw' opaque={false} />;
};
29 changes: 29 additions & 0 deletions packages/ui/src/ActionView/actions/spend.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { SpendView } from '@penumbra-zone/protobuf/penumbra/core/component/shielded_pool/v1/shielded_pool_pb';
import { Density } from '../../Density';
import { ActionWrapper } from './wrapper';
import { ValueViewComponent } from '../../ValueView';
import { AddressViewComponent } from '../../AddressView';
import { useDensity } from '../../utils/density';

export interface SpendActionProps {
value: SpendView;
}

export const SpendAction = ({ value }: SpendActionProps) => {
const density = useDensity();

return (
<ActionWrapper title='Spend' opaque={value.spendView.case === 'opaque'}>
{value.spendView.case === 'visible' && (
<Density slim>
<ValueViewComponent
signed='negative'
priority={density === 'sparse' ? 'primary' : 'tertiary'}
valueView={value.spendView.value.note?.value}
/>
<AddressViewComponent addressView={value.spendView.value.note?.address} />
</Density>
)}
</ActionWrapper>
);
};
Loading

0 comments on commit 07aa2fe

Please sign in to comment.