Skip to content
Open
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
5 changes: 5 additions & 0 deletions .changeset/align-key-value-columns.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@iota/apps-ui-kit': minor
---

Fix `KeyValueInfo` key text overflowing into the value column when it doesn't fit its column width, slightly increase the gap between the key and value columns, and add an optional `keyColumnWidth` prop (`KeyColumnWidth.Default` | `KeyColumnWidth.Wide`) to widen the key column relative to the value column.
147 changes: 92 additions & 55 deletions apps/explorer/src/pages/transaction-result/TransactionOverview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@
// SPDX-License-Identifier: Apache-2.0

import { useState } from 'react';
import { Badge, BadgeType, ButtonUnstyled, Divider, KeyValueInfo } from '@iota/apps-ui-kit';
import {
Badge,
BadgeType,
ButtonUnstyled,
Divider,
KeyColumnWidth,
KeyValueInfo,
} from '@iota/apps-ui-kit';
import {
CoinFiatValue,
TransactionAction,
Expand All @@ -21,15 +28,37 @@ import {
EpochLink,
ObjectLink,
} from '~/components';
import { useBreakpoint, useDeserializedSignatures, type SignaturePubkeyPair } from '~/hooks';
import { useDeserializedSignatures, type SignaturePubkeyPair } from '~/hooks';
import { getSendRecipientAddress, onCopySuccess } from '~/lib/utils';

// A fixed-width, invisible spacer passed as `KeyValueInfo`'s `keyIcon` to indent the key
// text without changing the row's width (which would misalign the value column).
const INDENT_SPACER = <span aria-hidden className="inline-block w-sm--rs" />;

// Draws a single continuous vertical rule alongside indented rows, instead of each
// row drawing its own border (which leaves visible gaps between rows).
function IndentGuide({ children }: { children: React.ReactNode }): JSX.Element {
return (
<div className="relative flex flex-col gap-xs">
<div className="pointer-events-none absolute inset-y-0 left-0 border-l border-iota-neutral-92 dark:border-iota-neutral-12" />
{children}
</div>
);
}

function SignatureBreakdown({ signature: data }: { signature: SignaturePubkeyPair }): JSX.Element {
const { signature, signatureScheme } = data;
return (
<div className="flex flex-col gap-xs">
<KeyValueInfo keyText="Scheme" value={signatureScheme} />
<IndentGuide>
<KeyValueInfo
keyColumnWidth={KeyColumnWidth.Wide}
keyIcon={INDENT_SPACER}
keyText="Scheme"
value={signatureScheme}
/>
<KeyValueInfo
keyColumnWidth={KeyColumnWidth.Wide}
keyIcon={INDENT_SPACER}
keyText="Address"
value={
<AddressLink
Expand All @@ -40,6 +69,8 @@ function SignatureBreakdown({ signature: data }: { signature: SignaturePubkeyPai
/>
{'publicKey' in data ? (
<KeyValueInfo
keyColumnWidth={KeyColumnWidth.Wide}
keyIcon={INDENT_SPACER}
keyText="IOTA Public Key"
value={data.publicKey.toIotaPublicKey()}
copyText={data.publicKey.toIotaPublicKey()}
Expand All @@ -48,13 +79,15 @@ function SignatureBreakdown({ signature: data }: { signature: SignaturePubkeyPai
/>
) : null}
<KeyValueInfo
keyColumnWidth={KeyColumnWidth.Wide}
keyIcon={INDENT_SPACER}
keyText="Signature"
copyText={toBase64(signature)}
onCopySuccess={onCopySuccess}
value={toBase64(signature)}
isTruncated
/>
</div>
</IndentGuide>
);
}

Expand Down Expand Up @@ -104,7 +137,6 @@ export function TransactionOverview({
const [showAllGasPayment, setShowAllGasPayment] = useState(false);
const [showGasFeeBreakdown, setShowGasFeeBreakdown] = useState(false);
const [showFullSignatures, setShowFullSignatures] = useState(false);
const isMediumOrAbove = useBreakpoint('md');
const { userSignatures, sponsorSignature } = useDeserializedSignatures(transaction);

const transactionKindName = transaction.transaction?.data.transaction?.kind;
Expand Down Expand Up @@ -136,25 +168,26 @@ export function TransactionOverview({
<div className="flex flex-col gap-sm p-md--rs md:max-w-4xl">
{transactionKindName && (
<KeyValueInfo
keyColumnWidth={KeyColumnWidth.Wide}
keyText="Transaction Type"
value={
<div className="whitespace-nowrap">
<Badge label={transactionKindName} type={BadgeType.PrimarySoft} />
</div>
}
fullwidth={!isMediumOrAbove}
/>
)}
<KeyValueInfo
keyColumnWidth={KeyColumnWidth.Wide}
keyText="Digest"
value={transaction.digest}
copyText={transaction.digest}
onCopySuccess={onCopySuccess}
isTruncated
fullwidth={!isMediumOrAbove}
/>
{transaction.checkpoint && (
<KeyValueInfo
keyColumnWidth={KeyColumnWidth.Wide}
keyText="Checkpoint"
value={
<CheckpointSequenceLink sequence={transaction.checkpoint}>
Expand All @@ -163,22 +196,22 @@ export function TransactionOverview({
}
copyText={transaction.checkpoint}
onCopySuccess={onCopySuccess}
fullwidth={!isMediumOrAbove}
/>
)}
{transaction.effects?.executedEpoch && (
<KeyValueInfo
keyColumnWidth={KeyColumnWidth.Wide}
keyText="Epoch"
value={
<EpochLink epoch={transaction.effects.executedEpoch}>
{transaction.effects.executedEpoch}
</EpochLink>
}
fullwidth={!isMediumOrAbove}
/>
)}
{transaction.timestampMs && (
<KeyValueInfo
keyColumnWidth={KeyColumnWidth.Wide}
keyText="Timestamp"
value={
<DateDisplay
Expand All @@ -187,41 +220,45 @@ export function TransactionOverview({
showTimeAgo
/>
}
fullwidth={!isMediumOrAbove}
/>
)}
{sender && (
<KeyValueInfo
keyColumnWidth={KeyColumnWidth.Wide}
keyText="Sender"
keyIcon={
<ArrowTopRight className="h-4 w-4 shrink-0 text-iota-neutral-40 dark:text-iota-neutral-60" />
}
value={<AddressLink address={sender} />}
copyText={sender}
onCopySuccess={onCopySuccess}
fullwidth={!isMediumOrAbove}
/>
)}
{recipient && (
<KeyValueInfo
keyColumnWidth={KeyColumnWidth.Wide}
keyText="Recipient"
keyIcon={
<ArrowBottomLeft className="h-4 w-4 shrink-0 text-iota-neutral-40 dark:text-iota-neutral-60" />
}
value={<AddressLink address={recipient} />}
copyText={recipient}
onCopySuccess={onCopySuccess}
fullwidth={!isMediumOrAbove}
/>
)}
{isProgrammableTransaction && totalGas && (
<div data-testid="gas-breakdown">
<KeyValueInfo
keyColumnWidth={KeyColumnWidth.Wide}
keyText="Total Gas Fee"
value={`${formattedTotalGas} ${totalGasSymbol}`}
supportingLabel={
<div className="flex flex-row items-baseline gap-xs">
<CoinFiatValue amount={totalGas ?? 0} withParentheses={false} />
value={
<div className="flex flex-col gap-xxs md:flex-row md:items-baseline md:gap-xs">
<div className="flex flex-row items-baseline gap-xs">
<span>
{formattedTotalGas} {totalGasSymbol}
</span>
<CoinFiatValue amount={totalGas ?? 0} withParentheses={false} />
</div>
{gasUsed && (
<ButtonUnstyled
className="flex flex-row items-center gap-xxxs text-label-md text-iota-primary-30 dark:text-iota-primary-80"
Expand All @@ -238,56 +275,60 @@ export function TransactionOverview({
)}
</div>
}
fullwidth={!isMediumOrAbove}
/>
</div>
)}
{isProgrammableTransaction && showGasFeeBreakdown && gasUsed && (
<div className="ml-xs flex flex-col gap-xs border-l border-iota-neutral-92 py-xxs pl-sm--rs dark:border-iota-neutral-12">
<IndentGuide>
{gasPrice && (
<KeyValueInfo
keyColumnWidth={KeyColumnWidth.Wide}
keyIcon={INDENT_SPACER}
keyText="Gas Price"
value={<GasFeeAmount amount={gasPrice} />}
fullwidth={!isMediumOrAbove}
/>
)}
{gasUsed.computationCost && (
<KeyValueInfo
keyColumnWidth={KeyColumnWidth.Wide}
keyIcon={INDENT_SPACER}
keyText="Computation Fee"
value={
<GasFeeAmount
amount={gasUsed.computationCost}
burnedAmount={gasUsed.computationCostBurned}
/>
}
fullwidth={!isMediumOrAbove}
/>
)}
{gasUsed.storageCost && (
<KeyValueInfo
keyColumnWidth={KeyColumnWidth.Wide}
keyIcon={INDENT_SPACER}
keyText="Storage Fee"
value={<GasFeeAmount amount={gasUsed.storageCost} />}
fullwidth={!isMediumOrAbove}
/>
)}
{gasUsed.storageRebate && (
<KeyValueInfo
keyColumnWidth={KeyColumnWidth.Wide}
keyIcon={INDENT_SPACER}
keyText="Storage Rebate"
value={<GasFeeAmount amount={-Number(gasUsed.storageRebate)} />}
fullwidth={!isMediumOrAbove}
/>
)}
</div>
</IndentGuide>
)}
{isProgrammableTransaction && gasBudget && (
<KeyValueInfo
keyColumnWidth={KeyColumnWidth.Wide}
keyText="Gas Budget"
value={`${formattedBudget} ${budgetSymbol}`}
fullwidth={!isMediumOrAbove}
/>
)}
{isProgrammableTransaction && !!gasPayment?.length && (
<KeyValueInfo
keyColumnWidth={KeyColumnWidth.Wide}
keyText="Gas Payment Objects"
value={
<div className="flex flex-wrap gap-x-sm gap-y-xxs">
Expand All @@ -313,53 +354,49 @@ export function TransactionOverview({
)}
</div>
}
fullwidth={!isMediumOrAbove}
/>
)}
{isProgrammableTransaction && gasOwner && (
<KeyValueInfo
keyColumnWidth={KeyColumnWidth.Wide}
keyText="Gas Object Owner"
value={<AddressLink address={gasOwner} />}
copyText={gasOwner}
onCopySuccess={onCopySuccess}
fullwidth={!isMediumOrAbove}
/>
)}
{!!signatures?.length && (
<KeyValueInfo
keyColumnWidth={KeyColumnWidth.Wide}
keyText={signatures.length > 1 ? 'User Signatures' : 'User Signature'}
value={
<div className="flex flex-col items-start gap-xs">
<ButtonUnstyled
className="flex flex-row items-center gap-xxxs text-label-md text-iota-primary-30 dark:text-iota-primary-80"
onClick={() => setShowFullSignatures(!showFullSignatures)}
>
{showFullSignatures ? 'Show Less' : 'Show More'}
<ArrowDown
className={clsx(
'h-4 w-4 transition-transform ease-linear',
showFullSignatures && 'rotate-180',
)}
/>
</ButtonUnstyled>
{showFullSignatures && (
<div className="flex w-full flex-col gap-md">
{[
...userSignatures,
...(sponsorSignature ? [sponsorSignature] : []),
].map((signature, index) => (
<div key={index} className="flex w-full flex-col gap-md">
{index > 0 && <Divider />}
<SignatureBreakdown signature={signature} />
</div>
))}
</div>
)}
</div>
<ButtonUnstyled
className="flex flex-row items-center gap-xxxs text-label-md text-iota-primary-30 dark:text-iota-primary-80"
onClick={() => setShowFullSignatures(!showFullSignatures)}
>
{showFullSignatures ? 'Show Less' : 'Show More'}
<ArrowDown
className={clsx(
'h-4 w-4 transition-transform ease-linear',
showFullSignatures && 'rotate-180',
)}
/>
</ButtonUnstyled>
}
fullwidth={!isMediumOrAbove}
/>
)}
{showFullSignatures && (
<div className="flex flex-col gap-md">
{[...userSignatures, ...(sponsorSignature ? [sponsorSignature] : [])].map(
(signature, index) => (
<div key={index} className="flex flex-col gap-md">
{index > 0 && <Divider />}
<SignatureBreakdown signature={signature} />
</div>
),
)}
</div>
)}
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,25 +35,27 @@ export function BalanceChanges({ changes }: BalanceChangesProps): JSX.Element |
return (
<div className="flex flex-col gap-xs">
<Title title="Balance Change" />
<TableCard
data={pageData}
columns={columns}
paginationOptions={paginationOptions}
totalLabel={supportingLabel}
pageSizeSelector={
paginationOptions && (
<Select
value={limit.toString()}
options={PAGE_SIZES_RANGE_10_50.map((size) => ({
label: `${size} / page`,
id: size.toString(),
}))}
size={SelectSize.Small}
onValueChange={(value) => setLimit(Number(value))}
/>
)
}
/>
<div className="px-md--rs">
<TableCard
data={pageData}
columns={columns}
paginationOptions={paginationOptions}
totalLabel={supportingLabel}
pageSizeSelector={
paginationOptions && (
<Select
value={limit.toString()}
options={PAGE_SIZES_RANGE_10_50.map((size) => ({
label: `${size} / page`,
id: size.toString(),
}))}
size={SelectSize.Small}
onValueChange={(value) => setLimit(Number(value))}
/>
)
}
/>
</div>
</div>
);
}
Loading
Loading