Skip to content

Commit

Permalink
[explorer] - create expandable pane for long lists of items (MystenLa…
Browse files Browse the repository at this point in the history
…bs#10928)

## Description 

For transactions with long lists of inputs / effects / recipients, it's
almost unusable to show all of them at once.

Created a simple expandable list component to wrap these long lists and
only show a default number of items by default.

Sample Transaction: 

https://explorer.sui.io/txblock/EXQvxhcn2Xoubmnx6L6EKDsAWkPG8sq94B7cCLAqVYP7?network=devnet




## Test Plan 

How did you test the new or updated feature?

---
If your changes are not user-facing and not a breaking change, you can
skip the following section. Otherwise, please indicate what changed, and
then add to the Release Notes section as highlighted during the release
process.

### Type of Change (Check all that apply)

- [ ] user-visible impact
- [ ] breaking change for a client SDKs
- [ ] breaking change for FNs (FN binary must upgrade)
- [ ] breaking change for validators or node operators (must upgrade
binaries)
- [ ] breaking change for on-chain data layout
- [ ] necessitate either a data wipe or data migration

### Release notes
  • Loading branch information
mamos-mysten authored Apr 17, 2023
1 parent 8c4a044 commit f7fa829
Show file tree
Hide file tree
Showing 6 changed files with 174 additions and 96 deletions.
40 changes: 5 additions & 35 deletions apps/explorer/src/pages/transaction-result/TxLinks.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import cl from 'clsx';
import { useState, useCallback } from 'react';

import type { SuiObjectRef } from '@mysten/sui.js';

import styles from './TxLinks.module.css';

import { ExpandableList } from '~/ui/ExpandableList';
import { ObjectLink } from '~/ui/InternalLink';
import { IconTooltip } from '~/ui/Tooltip';

Expand All @@ -17,24 +15,14 @@ type Addresslist = {
links: SuiObjectRef[];
};
function TxLinks({ data }: { data: Addresslist }) {
const [viewMore, setViewMore] = useState(false);
const numberOfListItemsToShow = 3;
const viewAll = useCallback(() => {
setViewMore(!viewMore);
}, [viewMore]);
return (
<div className={styles.mutatedcreatedlist}>
<h3 className={styles.label}>{data.label}</h3>
<div className={styles.objectidlists}>
<ul>
{data.links
.slice(
0,
viewMore
? data.links.length
: numberOfListItemsToShow
)
.map((obj, idx) => (
<ExpandableList
defaultItemsToShow={3}
items={data.links.map((obj, idx) => (
<li key={idx}>
<div className="inline-flex items-center gap-1.5">
<ObjectLink
Expand All @@ -49,26 +37,8 @@ function TxLinks({ data }: { data: Addresslist }) {
</div>
</li>
))}
/>
</ul>
{data.links.length > numberOfListItemsToShow && (
<div className={styles.viewmore}>
<button
type="button"
className={cl([
styles.moretxbtn,
viewMore && styles.viewless,
])}
onClick={viewAll}
>
{viewMore
? 'View Less'
: 'View ' +
data.links.length +
' ' +
data.label}
</button>
</div>
)}
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import { type SuiCallArg } from '@mysten/sui.js';

import { ExpandableList } from '~/ui/ExpandableList';
import { AddressLink, ObjectLink } from '~/ui/InternalLink';
import { TableHeader } from '~/ui/TableHeader';

Expand All @@ -19,52 +20,55 @@ export function Inputs({ inputs }: Props) {
<>
<TableHeader>Inputs</TableHeader>
<ul className="flex flex-col gap-y-3">
{inputs.map((input, index) => {
if (typeof input !== 'object') {
return (
<li key={index}>
<AddressLink
noTruncate
address={String(input)}
/>
</li>
);
}

if ('valueType' in input && 'value' in input) {
if (input.valueType === 'address') {
<ExpandableList
defaultItemsToShow={10}
items={inputs.map((input, index) => {
if (typeof input !== 'object') {
return (
<li key={index}>
<AddressLink
noTruncate
address={String(input.value)}
address={String(input)}
/>
</li>
);
}

return (
<li key={index}>
<div className="mt-1 break-all text-bodySmall font-medium text-steel-dark">
{JSON.stringify(input.value)}
</div>
</li>
);
}
if ('valueType' in input && 'value' in input) {
if (input.valueType === 'address') {
return (
<li key={index}>
<AddressLink
noTruncate
address={String(input.value)}
/>
</li>
);
}

return (
<li key={index}>
<div className="mt-1 break-all text-bodySmall font-medium text-steel-dark">
{JSON.stringify(input.value)}
</div>
</li>
);
}

if (input.type === 'object') {
return (
<li key={index}>
<ObjectLink
noTruncate
objectId={input.objectId}
/>
</li>
);
}
if (input.type === 'object') {
return (
<li key={index}>
<ObjectLink
noTruncate
objectId={input.objectId}
/>
</li>
);
}

return null;
})}
return null;
})}
/>
</ul>
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { type SuiTransaction } from '@mysten/sui.js';

import { Transaction } from './Transaction';

import { ExpandableList } from '~/ui/ExpandableList';
import { TableHeader } from '~/ui/TableHeader';

interface Props {
Expand All @@ -19,15 +20,18 @@ export function Transactions({ transactions }: Props) {
<>
<TableHeader>Transactions</TableHeader>
<ul className="flex flex-col gap-8">
{transactions.map((transaction, index) => {
const [[type, data]] = Object.entries(transaction);
<ExpandableList
items={transactions.map((transaction, index) => {
const [[type, data]] = Object.entries(transaction);

return (
<li key={index}>
<Transaction type={type} data={data} />
</li>
);
})}
return (
<li key={index}>
<Transaction type={type} data={data} />
</li>
);
})}
defaultItemsToShow={10}
/>
</ul>
</>
);
Expand Down
55 changes: 55 additions & 0 deletions apps/explorer/src/ui/ExpandableList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import { ChevronDown12 } from '@mysten/icons';
import { type ReactNode, useMemo, useState } from 'react';

import { Link } from './Link';
import { Text } from './Text';

interface ExpandableListProps {
items: ReactNode[];
defaultItemsToShow: number;
}

export function ExpandableList({
items,
defaultItemsToShow,
}: ExpandableListProps) {
const [showAll, setShowAll] = useState(false);

const itemsDisplayed = useMemo(
() => (showAll ? items : items?.slice(0, defaultItemsToShow)),
[showAll, items, defaultItemsToShow]
);

const handleShowAllClick = () =>
setShowAll((prevShowAll: boolean) => !prevShowAll);

return (
<>
{itemsDisplayed.map((item, index) => (
<div key={index}>{item}</div>
))}
{items.length > defaultItemsToShow && (
<div className="mt-4 flex cursor-pointer items-center gap-1 text-steel hover:text-steel-dark">
<Link
variant="text"
onClick={handleShowAllClick}
after={
<ChevronDown12
height={12}
width={12}
className={showAll ? 'rotate-180' : ''}
/>
}
>
<Text variant="bodySmall/medium">
{showAll ? 'Show Less' : 'Show All'}
</Text>
</Link>
</div>
)}
</>
);
}
44 changes: 26 additions & 18 deletions apps/explorer/src/ui/TransactionAddressSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { CoinFormat } from '@mysten/core';
import { CheckFill16 } from '@mysten/icons';

import { CoinBalance } from './CoinBalance';
import { ExpandableList } from './ExpandableList';
import { Heading } from './Heading';
import { TransactionAddress } from './TransactionAddress';

Expand Down Expand Up @@ -58,26 +59,33 @@ export function RecipientTransactionAddresses({
title={recipients.length > 1 ? 'Recipients' : 'Recipient'}
>
<div className="flex flex-col gap-4">
{recipients.map(({ address, amount, coinType }, i) => (
<div
className="flex flex-col gap-0.5"
key={`${address}-${i}`}
>
<TransactionAddress
icon={<CheckFill16 className="text-success" />}
address={address}
/>
{amount ? (
<div className="ml-6">
<CoinBalance
amount={amount}
coinType={coinType}
format={CoinFormat.FULL}
<ExpandableList
defaultItemsToShow={3}
items={recipients.map(
({ address, amount, coinType }, i) => (
<div
className="flex flex-col gap-0.5"
key={`${address}-${i}`}
>
<TransactionAddress
icon={
<CheckFill16 className="text-success" />
}
address={address}
/>
{amount ? (
<div className="ml-6">
<CoinBalance
amount={amount}
coinType={coinType}
format={CoinFormat.FULL}
/>
</div>
) : null}
</div>
) : null}
</div>
))}
)
)}
/>
</div>
</TransactionAddressSection>
);
Expand Down
37 changes: 37 additions & 0 deletions apps/explorer/src/ui/stories/ExpandableList.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import { type ReactNode } from 'react';

import { ExpandableList } from '../ExpandableList';
import { type InputProps } from '../Input';
import { Text } from '../Text';

import type { Meta, StoryObj } from '@storybook/react';

export default {
component: ExpandableList,
} as Meta;

function ListItem({ children }: { children: ReactNode }) {
return (
<li>
<Text color="steel-darker" variant="bodySmall/normal">
{children}
</Text>
</li>
);
}

export const Default: StoryObj<InputProps> = {
render: () => (
<ul>
<ExpandableList
defaultItemsToShow={3}
items={Array.from({ length: 10 }).map((_, index) => (
<ListItem key={index}>Item {index}</ListItem>
))}
/>
</ul>
),
};

0 comments on commit f7fa829

Please sign in to comment.