Feat/258 move key value table into monorepo (#320)

* move key-value-table to ui-toolkit and use tailwind

* add key-value-table to storybook

* override border width 1px from the styles of the app, remove td and th from children

* clone muted and numerical props to children elements

* proposal change table remove empty lines

* add Roboto mono to font-mono tailwind config

* remove labels and labelfor

* revert change on token-details-circulating

* export the whole components directory rather than explicitly individual components

* add classNames, add formatNumberPercentage, remove spans, add span in token details circulating

* data-testid=governance-proposal-enactmentDate and use span instead of div

* use custom spacing defined in tailwind & another README.md update for running cypress in watch mode

* update divs to span within the vesting table

* Update README.md

Co-authored-by: Dexter Edwards <dexter.edwards93@gmail.com>

* borders and text visible on both dark and light themes

* add headingLevel and use dl instead of tables

* update styling for dl inline

* remove added grey from tailwind

* ignore md files

Co-authored-by: madalinaraicu <“madalina@raygroup.uk”>
Co-authored-by: Dexter Edwards <dexter.edwards93@gmail.com>
This commit is contained in:
m.ray 2022-04-29 16:27:20 +03:00 committed by GitHub
parent 3af4e354cd
commit bd3268adf0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 416 additions and 434 deletions

View File

@ -3,3 +3,4 @@
/dist /dist
/coverage /coverage
__generated__ __generated__
*.md

View File

@ -19,6 +19,7 @@ The trading interface built based on a component toolkit. It will provide a way
### [UI toolkit](https://github.com/vegaprotocol/frontend-monorepo/tree/master/libs/ui-toolkit) ### [UI toolkit](https://github.com/vegaprotocol/frontend-monorepo/tree/master/libs/ui-toolkit)
The UI toolkit contains a set of components used to build interfaces that can interact with the Vega protocol, and follow the design style of the project. The UI toolkit contains a set of components used to build interfaces that can interact with the Vega protocol, and follow the design style of the project.
It contains a storybook that can be served with `yarn nx run ui-toolkit:storybook`.
### [Tailwind CSS config](https://github.com/vegaprotocol/frontend-monorepo/tree/master/libs/tailwindcss-config) ### [Tailwind CSS config](https://github.com/vegaprotocol/frontend-monorepo/tree/master/libs/tailwindcss-config)
@ -30,7 +31,7 @@ The Tailwind CSS config contains theme that align default config with Vega desig
Check you have the correct version of Node. You can [install NVM to switch between node versions](https://github.com/nvm-sh/nvm#installing-and-updating). Then `NVM install`. Check you have the correct version of Node. You can [install NVM to switch between node versions](https://github.com/nvm-sh/nvm#installing-and-updating). Then `NVM install`.
Before you build you will need to `yarn install` in the root directory. Before you build you will need to `yarn install` in the root directory.
The repository includes a number of template .env files for different networks. Copy from these to the .env file before `serve` to lauch app with different network. The repository includes a number of template .env files for different networks. Copy from these to the .env file before `serve` to launch app with different network. You can serve any application with `yarn nx run <name-of-app>:serve`.
### Build ### Build
@ -40,7 +41,9 @@ Run `nx serve my-app` for a dev server. Navigate to http://localhost:4200/. The
### Running tests ### Running tests
Run `nx test my-app` to execute the unit tests with [Jest](https://jestjs.io), or `nx affected:test` to execute just unit tests affected by a change. Run `yarn nx run <my-app>-e2e:e2e` to execute the e2e tests with [cypress](https://docs.cypress.io/). You can use the `--watch` flag to open the cypress tests UI in watch mode, see [cypress executor](https://nx.dev/packages/cypress/executors/cypress) for all CLI flags.
Run `nx test my-app` to execute the unit tests with [Jest](https://jestjs.io), or `nx affected:test` to execute just unit tests affected by a change. You can also use `--watch` with these test to run jest in watch mode, see [Jest executor](https://nx.dev/packages/jest/executors/jest) for all CLI flags.
Similarly `nx e2e my-app` will execute the end-to-end tests with [Cypress](https://www.cypress.io)., and `nx affected:e2e` will execute just the end-to-end tests affected by a change. Similarly `nx e2e my-app` will execute the end-to-end tests with [Cypress](https://www.cypress.io)., and `nx affected:e2e` will execute just the end-to-end tests affected by a change.
@ -59,6 +62,13 @@ Follow the following steps to start using a local network with the Vega Explorer
1. Start the explorer frontend application with the `.env.vegacapsule` env file 1. Start the explorer frontend application with the `.env.vegacapsule` env file
1. Go to [http://localhost:3000](http://localhost:3000) in your browser 1. Go to [http://localhost:3000](http://localhost:3000) in your browser
If you simply want to run Explorer locally, without using a local network:
```bash
cd apps/explorer && cp .env.testnet .env.local
yarn nx run explorer:serve
```
# 📑 License # 📑 License
[MIT](./LICENSE) [MIT](./LICENSE)

View File

@ -1 +0,0 @@
export { KeyValueTable, KeyValueTableRow } from './key-value-table';

View File

@ -1,85 +0,0 @@
@import '../../styles/colors';
@import '../../styles/fonts';
$ns: 'key-value-table';
.#{$ns}__header,
.#{$ns}__footer {
margin: 10px 0 5px;
}
.#{$ns}__header {
font-size: 16px;
font-weight: 500;
margin-bottom: 7px;
}
.#{$ns}__row {
@media (max-width: 640px) {
display: flex;
flex-direction: column;
}
}
.#{$ns} {
width: 100%;
border-collapse: collapse;
border-spacing: 0;
margin-bottom: 10px;
word-break: break-all;
tr {
border-bottom: 1px solid $white;
&:first-child {
border-top: 1px solid $white;
}
}
th {
word-break: break-word;
text-align: left;
font-weight: 500;
color: $white;
text-transform: uppercase;
}
th,
td {
vertical-align: top;
padding: 5px;
}
td {
color: $text-color;
text-align: right;
}
.bp3-tag {
margin: 0 5px 5px 0;
}
&.#{$ns}--numerical {
td {
font-family: $font-mono;
& button {
font-family: $font-main;
}
}
}
&.#{$ns}--muted {
tr {
border-color: $gray1;
&:first-child {
border-top: none;
}
&:last-child {
border-bottom: none;
}
}
}
}

View File

@ -1,58 +0,0 @@
import './key-value-table.scss';
import * as React from 'react';
export interface KeyValueTableProps
extends React.HTMLAttributes<HTMLTableElement> {
title?: string;
numerical?: boolean; // makes all values monospace
children: React.ReactNode;
muted?: boolean;
}
export const KeyValueTable = ({
title,
numerical,
children,
muted,
className,
...rest
}: KeyValueTableProps) => {
return (
<React.Fragment>
{title && <h3 className="key-value-table__header">{title}</h3>}
<table
data-testid="key-value-table"
{...rest}
className={`key-value-table ${className ? className : ''} ${
numerical ? 'key-value-table--numerical' : ''
}
${muted ? 'key-value-table--muted' : ''}`}
>
<tbody>{children}</tbody>
</table>
</React.Fragment>
);
};
export interface KeyValueTableRowProps
extends React.HTMLAttributes<HTMLTableRowElement> {
children: [React.ReactNode, React.ReactNode];
className?: string;
}
export const KeyValueTableRow = ({
children,
className,
...rest
}: KeyValueTableRowProps) => {
return (
<tr
{...rest}
className={`key-value-table__row ${className ? className : ''}`}
>
{children[0]}
{children[1]}
</tr>
);
};

View File

@ -5,3 +5,9 @@ export const formatNumber = (value: BigNumber, decimals?: number) => {
typeof decimals === 'undefined' ? Math.max(value.dp(), 2) : decimals; typeof decimals === 'undefined' ? Math.max(value.dp(), 2) : decimals;
return value.dp(decimalPlaces).toFormat(decimalPlaces); return value.dp(decimalPlaces).toFormat(decimalPlaces);
}; };
export const formatNumberPercentage = (value: BigNumber, decimals?: number) => {
const decimalPlaces =
typeof decimals === 'undefined' ? Math.max(value.dp(), 2) : decimals;
return `${value.dp(decimalPlaces).toFormat(decimalPlaces)}%`;
};

View File

@ -8,10 +8,7 @@ import { Trans, useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import type { Tranche } from '@vegaprotocol/smart-contracts-sdk'; import type { Tranche } from '@vegaprotocol/smart-contracts-sdk';
import { import { KeyValueTable, KeyValueTableRow } from '@vegaprotocol/ui-toolkit';
KeyValueTable,
KeyValueTableRow,
} from '../../components/key-value-table';
import { useContracts } from '../../contexts/contracts/contracts-context'; import { useContracts } from '../../contexts/contracts/contracts-context';
import { DATE_FORMAT_LONG } from '../../lib/date-formats'; import { DATE_FORMAT_LONG } from '../../lib/date-formats';
import { formatNumber } from '../../lib/format-number'; import { formatNumber } from '../../lib/format-number';
@ -159,37 +156,34 @@ export const ClaimFlow = ({
<div> <div>
<KeyValueTable> <KeyValueTable>
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('Connected Ethereum address')}</th> {t('Connected Ethereum address')}
<td>{truncateMiddle(address)}</td> {truncateMiddle(address)}
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('Amount of VEGA')}</th> {t('Amount of VEGA')}
<td>
{state.claimData {state.claimData
? formatNumber(state.claimData.claim.amount) ? formatNumber(state.claimData.claim.amount)
: 'None'} : 'None'}
</td>
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('Claim expires')}</th> {t('Claim expires')}
<td>
{state.claimData?.claim.expiry {state.claimData?.claim.expiry
? format( ? format(
state.claimData?.claim.expiry * 1000, state.claimData?.claim.expiry * 1000,
DATE_FORMAT_LONG DATE_FORMAT_LONG
) )
: 'No expiry'} : 'No expiry'}
</td>
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('Starts unlocking')}</th> {t('Starts unlocking')}
<td>
{format(currentTranche.tranche_start, DATE_FORMAT_LONG)} {format(currentTranche.tranche_start, DATE_FORMAT_LONG)}
</td>
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('Fully unlocked')}</th> {t('Fully unlocked')}
<td>{format(currentTranche.tranche_end, DATE_FORMAT_LONG)}</td> {format(currentTranche.tranche_end, DATE_FORMAT_LONG)}
</KeyValueTableRow> </KeyValueTableRow>
</KeyValueTable> </KeyValueTable>
</div> </div>

View File

@ -1,10 +1,7 @@
import { format, isFuture } from 'date-fns'; import { format, isFuture } from 'date-fns';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import { KeyValueTable, KeyValueTableRow } from '@vegaprotocol/ui-toolkit';
KeyValueTable,
KeyValueTableRow,
} from '../../../../components/key-value-table';
import { DATE_FORMAT_DETAILED } from '../../../../lib/date-formats'; import { DATE_FORMAT_DETAILED } from '../../../../lib/date-formats';
import type { Proposals_proposals } from '../../proposals/__generated__/Proposals'; import type { Proposals_proposals } from '../../proposals/__generated__/Proposals';
import { CurrentProposalState } from '../current-proposal-state'; import { CurrentProposalState } from '../current-proposal-state';
@ -19,60 +16,50 @@ export const ProposalChangeTable = ({ proposal }: ProposalChangeTableProps) => {
const terms = proposal.terms; const terms = proposal.terms;
return ( return (
<KeyValueTable muted={true} data-testid="proposal-change-table"> <KeyValueTable data-testid="proposal-change-table" muted={true}>
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('id')}</th> {t('id')}
<td>{proposal.id}</td> {proposal.id}
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('state')}</th> {t('state')}
<td> <CurrentProposalState proposal={proposal} />
<CurrentProposalState proposal={proposal} />
</td>
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<th> {isFuture(new Date(terms.closingDatetime))
{isFuture(new Date(terms.closingDatetime)) ? t('closesOn')
? t('closesOn') : t('closedOn')}
: t('closedOn')} {format(new Date(terms.closingDatetime), DATE_FORMAT_DETAILED)}
</th>
<td>{format(new Date(terms.closingDatetime), DATE_FORMAT_DETAILED)}</td>
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<th> {isFuture(new Date(terms.enactmentDatetime))
{isFuture(new Date(terms.enactmentDatetime)) ? t('proposedEnactment')
? t('proposedEnactment') : t('enactedOn')}
: t('enactedOn')} {format(new Date(terms.enactmentDatetime), DATE_FORMAT_DETAILED)}
</th>
<td>
{format(new Date(terms.enactmentDatetime), DATE_FORMAT_DETAILED)}
</td>
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('proposedBy')}</th> {t('proposedBy')}
<td> <span style={{ wordBreak: 'break-word' }}>{proposal.party.id}</span>
<span style={{ wordBreak: 'break-word' }}>{proposal.party.id}</span>
</td>
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('proposedOn')}</th> {t('proposedOn')}
<td>{format(new Date(proposal.datetime), DATE_FORMAT_DETAILED)}</td> {format(new Date(proposal.datetime), DATE_FORMAT_DETAILED)}
</KeyValueTableRow> </KeyValueTableRow>
{proposal.rejectionReason ? ( {proposal.rejectionReason ? (
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('rejectionReason')}</th> {t('rejectionReason')}
<td>{proposal.rejectionReason}</td> {proposal.rejectionReason}
</KeyValueTableRow> </KeyValueTableRow>
) : null} ) : null}
{proposal.errorDetails ? ( {proposal.errorDetails ? (
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('errorDetails')}</th> {t('errorDetails')}
<td>{proposal.errorDetails}</td> {proposal.errorDetails}
</KeyValueTableRow> </KeyValueTableRow>
) : null} ) : null}
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('type')}</th> {t('type')}
<td>{proposal.terms.change.__typename}</td> {proposal.terms.change.__typename}
</KeyValueTableRow> </KeyValueTableRow>
</KeyValueTable> </KeyValueTable>
); );

View File

@ -1,10 +1,10 @@
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { KeyValueTable, KeyValueTableRow } from '@vegaprotocol/ui-toolkit';
import { import {
KeyValueTable, formatNumber,
KeyValueTableRow, formatNumberPercentage,
} from '../../../../components/key-value-table'; } from '../../../../lib/format-number';
import { formatNumber } from '../../../../lib/format-number';
import { useVoteInformation } from '../../hooks'; import { useVoteInformation } from '../../hooks';
import type { Proposals_proposals } from '../../proposals/__generated__/Proposals'; import type { Proposals_proposals } from '../../proposals/__generated__/Proposals';
@ -34,65 +34,65 @@ export const ProposalVotesTable = ({ proposal }: ProposalVotesTableProps) => {
return ( return (
<KeyValueTable <KeyValueTable
title={t('voteBreakdown')} title={t('voteBreakdown')}
numerical={true}
muted={true}
data-testid="proposal-votes-table" data-testid="proposal-votes-table"
muted={true}
numerical={true}
> >
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('willPass')}</th> {t('willPass')}
<td>{willPass ? '👍' : '👎'}</td> {willPass ? '👍' : '👎'}
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('majorityMet')}</th> {t('majorityMet')}
<td>{majorityMet ? '👍' : '👎'}</td> {majorityMet ? '👍' : '👎'}
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('participationMet')}</th> {t('participationMet')}
<td>{participationMet ? '👍' : '👎'}</td> {participationMet ? '👍' : '👎'}
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('tokenForProposal')}</th> {t('tokenForProposal')}
<td>{formatNumber(yesTokens, 2)}</td> {formatNumber(yesTokens, 2)}
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('tokensAgainstProposal')}</th> {t('tokensAgainstProposal')}
<td>{formatNumber(noTokens, 2)}</td> {formatNumber(noTokens, 2)}
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('participationRequired')}</th> {t('participationRequired')}
<td>{formatNumber(requiredParticipation)}%</td> {formatNumberPercentage(requiredParticipation)}
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('majorityRequired')}</th> {t('majorityRequired')}
<td>{formatNumber(requiredMajorityPercentage)}%</td> {formatNumberPercentage(requiredMajorityPercentage)}
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('numberOfVotingParties')}</th> {t('numberOfVotingParties')}
<td>{formatNumber(totalVotes, 0)}</td> {formatNumber(totalVotes, 0)}
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('totalTokensVotes')}</th> {t('totalTokensVotes')}
<td>{formatNumber(totalTokensVoted, 2)}</td> {formatNumber(totalTokensVoted, 2)}
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('totalTokenVotedPercentage')}</th> {t('totalTokenVotedPercentage')}
<td>{formatNumber(totalTokensPercentage, 2)}%</td> {formatNumberPercentage(totalTokensPercentage, 2)}
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('numberOfForVotes')}</th> {t('numberOfForVotes')}
<td>{formatNumber(yesVotes, 0)}</td> {formatNumber(yesVotes, 0)}
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('numberOfAgainstVotes')}</th> {t('numberOfAgainstVotes')}
<td>{formatNumber(noVotes, 0)}</td> {formatNumber(noVotes, 0)}
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('yesPercentage')}</th> {t('yesPercentage')}
<td>{formatNumber(yesPercentage, 2)}%</td> {formatNumberPercentage(yesPercentage, 2)}
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('noPercentage')}</th> {t('noPercentage')}
<td>{formatNumber(noPercentage, 2)}%</td> {formatNumberPercentage(noPercentage, 2)}
</KeyValueTableRow> </KeyValueTableRow>
</KeyValueTable> </KeyValueTable>
); );

View File

@ -4,10 +4,7 @@ import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { Heading } from '../../../../components/heading'; import { Heading } from '../../../../components/heading';
import { import { KeyValueTable, KeyValueTableRow } from '@vegaprotocol/ui-toolkit';
KeyValueTable,
KeyValueTableRow,
} from '../../../../components/key-value-table';
import { getProposalName } from '../../../../lib/type-policies/proposal'; import { getProposalName } from '../../../../lib/type-policies/proposal';
import type { Proposals_proposals } from '../../proposals/__generated__/Proposals'; import type { Proposals_proposals } from '../../proposals/__generated__/Proposals';
import { CurrentProposalState } from '../current-proposal-state'; import { CurrentProposalState } from '../current-proposal-state';
@ -33,36 +30,34 @@ export const ProposalsList = ({ proposals }: ProposalsListProps) => {
</Link> </Link>
<KeyValueTable muted={true}> <KeyValueTable muted={true}>
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('state')}</th> {t('state')}
<td data-testid="governance-proposal-state"> <span data-testid="governance-proposal-state">
<CurrentProposalState proposal={proposal} /> <CurrentProposalState proposal={proposal} />
</td> </span>
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<th> {isFuture(new Date(proposal.terms.closingDatetime))
{isFuture(new Date(proposal.terms.closingDatetime)) ? t('closesOn')
? t('closesOn') : t('closedOn')}
: t('closedOn')}
</th> <span data-testid="governance-proposal-closingDate">
<td data-testid="governance-proposal-closingDate">
{format( {format(
new Date(proposal.terms.closingDatetime), new Date(proposal.terms.closingDatetime),
DATE_FORMAT_DETAILED DATE_FORMAT_DETAILED
)} )}
</td> </span>
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<th> {isFuture(new Date(proposal.terms.enactmentDatetime))
{isFuture(new Date(proposal.terms.enactmentDatetime)) ? t('proposedEnactment')
? t('proposedEnactment') : t('enactedOn')}
: t('enactedOn')}
</th> <span data-testid="governance-proposal-enactmentDate">
<td data-testid="governance-proposal-enactmentDate">
{format( {format(
new Date(proposal.terms.enactmentDatetime), new Date(proposal.terms.enactmentDatetime),
DATE_FORMAT_DETAILED DATE_FORMAT_DETAILED
)} )}
</td> </span>
</KeyValueTableRow> </KeyValueTableRow>
</KeyValueTable> </KeyValueTable>
</li> </li>

View File

@ -37,8 +37,8 @@ export const TokenDetailsCirculating = ({
}) => { }) => {
const totalCirculating = sumCirculatingTokens(tranches); const totalCirculating = sumCirculatingTokens(tranches);
return ( return (
<td data-testid="circulating-supply"> <span data-testid="circulating-supply">
{formatNumber(totalCirculating, 2)} {formatNumber(totalCirculating, 2)}
</td> </span>
); );
}; };

View File

@ -3,10 +3,7 @@ import './token-details.scss';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { EtherscanLink } from '@vegaprotocol/ui-toolkit'; import { EtherscanLink } from '@vegaprotocol/ui-toolkit';
import { import { KeyValueTable, KeyValueTableRow } from '@vegaprotocol/ui-toolkit';
KeyValueTable,
KeyValueTableRow,
} from '../../../components/key-value-table';
import { ADDRESSES } from '../../../config'; import { ADDRESSES } from '../../../config';
import { useTranches } from '../../../hooks/use-tranches'; import { useTranches } from '../../../hooks/use-tranches';
import type { BigNumber } from '../../../lib/bignumber'; import type { BigNumber } from '../../../lib/bignumber';
@ -26,36 +23,34 @@ export const TokenDetails = ({
return ( return (
<KeyValueTable className={'token-details'}> <KeyValueTable className={'token-details'}>
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('Token address')}</th> {t('Token address')}
<td data-testid="token-address"> <EtherscanLink
<EtherscanLink data-testid="token-address"
address={ADDRESSES.vegaTokenAddress} address={ADDRESSES.vegaTokenAddress}
text={ADDRESSES.vegaTokenAddress} text={ADDRESSES.vegaTokenAddress}
className="font-mono" className="font-mono"
/> />
</td>
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('Vesting contract')}</th> {t('Vesting contract')}
<td data-testid="token-contract"> <EtherscanLink
<EtherscanLink data-testid="token-contract"
address={ADDRESSES.vestingAddress} address={ADDRESSES.vestingAddress}
text={ADDRESSES.vestingAddress} text={ADDRESSES.vestingAddress}
className="font-mono" className="font-mono"
/> />
</td>
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('Total supply')}</th> {t('Total supply')}
<td data-testid="total-supply">{formatNumber(totalSupply, 2)}</td> <span data-testid="total-supply">{formatNumber(totalSupply, 2)}</span>
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('Circulating supply')}</th> {t('Circulating supply')}
<TokenDetailsCirculating tranches={tranches} /> <TokenDetailsCirculating tranches={tranches} />
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('Staked on Vega validator')}</th> {t('Staked on Vega validator')}
<td data-testid="staked">{formatNumber(totalStaked, 2)}</td> <span data-testid="staked">{formatNumber(totalStaked, 2)}</span>
</KeyValueTableRow> </KeyValueTableRow>
</KeyValueTable> </KeyValueTable>
); );

View File

@ -3,10 +3,7 @@ import './vesting-table.scss';
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import { KeyValueTable, KeyValueTableRow } from '@vegaprotocol/ui-toolkit';
KeyValueTable,
KeyValueTableRow,
} from '../../../components/key-value-table';
import { BigNumber } from '../../../lib/bignumber'; import { BigNumber } from '../../../lib/bignumber';
import { formatNumber } from '../../../lib/format-number'; import { formatNumber } from '../../../lib/format-number';
@ -42,29 +39,29 @@ export const VestingTable = ({
data-testid="vesting-table-total" data-testid="vesting-table-total"
className="vesting-table__top-solid-border" className="vesting-table__top-solid-border"
> >
<th>{t('Vesting VEGA')}</th> <span>{t('Vesting VEGA')}</span>
<td>{formatNumber(total)}</td> {formatNumber(total)}
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow data-testid="vesting-table-locked"> <KeyValueTableRow data-testid="vesting-table-locked">
<th> <span>
<div className="vesting-table__indicator-square vesting-table__indicator-square--locked"></div> <span className="vesting-table__indicator-square vesting-table__indicator-square--locked"></span>
{t('Locked')} {t('Locked')}
</th> </span>
<td>{formatNumber(locked)}</td> {formatNumber(locked)}
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow data-testid="vesting-table-unlocked"> <KeyValueTableRow data-testid="vesting-table-unlocked">
<th> <span>
<div className="vesting-table__indicator-square vesting-table__indicator-square--unlocked"></div> <span className="vesting-table__indicator-square vesting-table__indicator-square--unlocked"></span>
{t('Unlocked')} {t('Unlocked')}
</th> </span>
<td>{formatNumber(vested)}</td> {formatNumber(vested)}
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow data-testid="vesting-table-staked"> <KeyValueTableRow data-testid="vesting-table-staked">
<th> <span>
<div className="vesting-table__indicator-square vesting-table__indicator-square--staked"></div> <span className="vesting-table__indicator-square vesting-table__indicator-square--staked"></span>
{t('Associated')} {t('Associated')}
</th> </span>
<td>{formatNumber(associated)}</td> {formatNumber(associated)}
</KeyValueTableRow> </KeyValueTableRow>
</KeyValueTable> </KeyValueTable>
<div className="vesting-table__progress-bar"> <div className="vesting-table__progress-bar">

View File

@ -4,10 +4,7 @@ import React from 'react';
import { Trans, useTranslation } from 'react-i18next'; import { Trans, useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { import { KeyValueTable, KeyValueTableRow } from '@vegaprotocol/ui-toolkit';
KeyValueTable,
KeyValueTableRow,
} from '../../components/key-value-table';
import { BigNumber } from '../../lib/bignumber'; import { BigNumber } from '../../lib/bignumber';
import { formatNumber } from '../../lib/format-number'; import { formatNumber } from '../../lib/format-number';
import { Routes } from '../router-config'; import { Routes } from '../router-config';
@ -41,16 +38,15 @@ export const Tranche0Table = ({
<> <>
<KeyValueTable numerical={true}> <KeyValueTable numerical={true}>
<KeyValueTableRow data-testid="tranche-table-total"> <KeyValueTableRow data-testid="tranche-table-total">
<th> <span className="tranche-table__label">
<span className="tranche-table__label"> {t('Tranche')} {trancheId}
{t('Tranche')} {trancheId} </span>
</span>
</th> <span>{formatNumber(total)}</span>
<td>{formatNumber(total)}</td>
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow data-testid="tranche-table-locked"> <KeyValueTableRow data-testid="tranche-table-locked">
<th>{t('Locked')}</th> {t('Locked')}
<td>{formatNumber(total)}</td> <span>{formatNumber(total)}</span>
</KeyValueTableRow> </KeyValueTableRow>
</KeyValueTable> </KeyValueTable>
<div className="tranche-table__footer" data-testid="tranche-table-footer"> <div className="tranche-table__footer" data-testid="tranche-table-footer">

View File

@ -5,10 +5,7 @@ import { format } from 'date-fns';
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import { KeyValueTable, KeyValueTableRow } from '@vegaprotocol/ui-toolkit';
KeyValueTable,
KeyValueTableRow,
} from '../../../components/key-value-table';
import { BigNumber } from '../../../lib/bignumber'; import { BigNumber } from '../../../lib/bignumber';
import { DATE_FORMAT_DETAILED } from '../../../lib/date-formats'; import { DATE_FORMAT_DETAILED } from '../../../lib/date-formats';
import type { import type {
@ -122,26 +119,30 @@ export const RewardTable = ({ reward, delegations }: RewardTableProps) => {
</h3> </h3>
<KeyValueTable> <KeyValueTable>
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('rewardType')}</th> {t('rewardType')}
<td>{DEFAULT_REWARD_TYPE}</td> <span>{DEFAULT_REWARD_TYPE}</span>
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('yourStake')}</th> {t('yourStake')}
<td>{stakeForEpoch.toString()}</td> <span>{stakeForEpoch.toString()}</span>
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('reward')}</th> {t('reward')}
<td> <span>
{reward.amountFormatted} {t('VEGA')} {reward.amountFormatted} {t('VEGA')}
</td> </span>
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('shareOfReward')}</th> {t('shareOfReward')}
<td>{new BigNumber(reward.percentageOfTotal).dp(2).toString()}%</td> <span>
{new BigNumber(reward.percentageOfTotal).dp(2).toString()}%
</span>
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('received')}</th> {t('received')}
<td>{format(new Date(reward.receivedAt), DATE_FORMAT_DETAILED)}</td> <span>
{format(new Date(reward.receivedAt), DATE_FORMAT_DETAILED)}
</span>
</KeyValueTableRow> </KeyValueTableRow>
</KeyValueTable> </KeyValueTable>
</div> </div>

View File

@ -4,10 +4,7 @@ import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { EtherscanLink } from '@vegaprotocol/ui-toolkit'; import { EtherscanLink } from '@vegaprotocol/ui-toolkit';
import { import { KeyValueTable, KeyValueTableRow } from '@vegaprotocol/ui-toolkit';
KeyValueTable,
KeyValueTableRow,
} from '../../components/key-value-table';
import { BigNumber } from '../../lib/bignumber'; import { BigNumber } from '../../lib/bignumber';
import { formatNumber } from '../../lib/format-number'; import { formatNumber } from '../../lib/format-number';
import type { Staking_nodes } from './__generated__/Staking'; import type { Staking_nodes } from './__generated__/Staking';
@ -37,59 +34,59 @@ export const ValidatorTable = ({
return ( return (
<KeyValueTable data-testid="validator-table"> <KeyValueTable data-testid="validator-table">
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('id')}:</th> <span>{t('id')}:</span>
<td className="validator-table__cell">{node.id}</td> <span className="validator-table__cell">{node.id}</span>
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('VEGA ADDRESS / PUBLIC KEY')}</th> <span>{t('VEGA ADDRESS / PUBLIC KEY')}</span>
<td className="validator-table__cell">{node.pubkey}</td> <span className="validator-table__cell">{node.pubkey}</span>
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('ABOUT THIS VALIDATOR')}</th> <span>{t('ABOUT THIS VALIDATOR')}</span>
<td> <span>
<a href={node.infoUrl}>{node.infoUrl}</a> <a href={node.infoUrl}>{node.infoUrl}</a>
</td> </span>
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('IP ADDRESS')}</th> <span>{t('IP ADDRESS')}</span>
<td>{node.location}</td> <span>{node.location}</span>
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('ETHEREUM ADDRESS')}</th> <span>{t('ETHEREUM ADDRESS')}</span>
<td> <span>
<EtherscanLink <EtherscanLink
text={node.ethereumAdddress} text={node.ethereumAdddress}
address={node.ethereumAdddress} address={node.ethereumAdddress}
/> />
</td> </span>
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('TOTAL STAKE')}</th> <span>{t('TOTAL STAKE')}</span>
<td>{node.stakedTotalFormatted}</td> <span>{node.stakedTotalFormatted}</span>
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('PENDING STAKE')}</th> <span>{t('PENDING STAKE')}</span>
<td>{node.pendingStakeFormatted}</td> <span>{node.pendingStakeFormatted}</span>
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('STAKED BY OPERATOR')}</th> <span>{t('STAKED BY OPERATOR')}</span>
<td>{node.stakedByOperatorFormatted}</td> <span>{node.stakedByOperatorFormatted}</span>
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('STAKED BY DELEGATES')}</th> <span>{t('STAKED BY DELEGATES')}</span>
<td>{node.stakedByDelegatesFormatted}</td> <span>{node.stakedByDelegatesFormatted}</span>
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('STAKE SHARE')}</th> <span>{t('STAKE SHARE')}</span>
<td>{stakePercentage}</td> <span>{stakePercentage}</span>
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('OWN STAKE (THIS EPOCH)')}</th> <span>{t('OWN STAKE (THIS EPOCH)')}</span>
<td>{formatNumber(stakeThisEpoch)}</td> <span>{formatNumber(stakeThisEpoch)}</span>
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('NOMINATED (THIS EPOCH)')}</th> <span>{t('NOMINATED (THIS EPOCH)')}</span>
<td>{node.stakedByDelegatesFormatted}</td> <span>{node.stakedByDelegatesFormatted}</span>
</KeyValueTableRow> </KeyValueTableRow>
</KeyValueTable> </KeyValueTable>
); );

View File

@ -1,9 +1,6 @@
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import { KeyValueTable, KeyValueTableRow } from '@vegaprotocol/ui-toolkit';
KeyValueTable,
KeyValueTableRow,
} from '../../components/key-value-table';
import { formatNumber } from '../../lib/format-number'; import { formatNumber } from '../../lib/format-number';
import type { BigNumber } from '../../lib/bignumber'; import type { BigNumber } from '../../lib/bignumber';
@ -23,12 +20,16 @@ export const YourStake = ({
<h2>{t('Your stake')}</h2> <h2>{t('Your stake')}</h2>
<KeyValueTable> <KeyValueTable>
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('Your Stake On Node (This Epoch)')}</th> {t('Your Stake On Node (This Epoch)')}
<td data-testid="stake-this-epoch">{formatNumber(stakeThisEpoch)}</td> <span data-testid="stake-this-epoch">
{formatNumber(stakeThisEpoch)}
</span>
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('Your Stake On Node (Next Epoch)')}</th> {t('Your Stake On Node (Next Epoch)')}
<td data-testid="stake-next-epoch">{formatNumber(stakeNextEpoch)}</td> <span data-testid="stake-next-epoch">
{formatNumber(stakeNextEpoch)}
</span>
</KeyValueTableRow> </KeyValueTableRow>
</KeyValueTable> </KeyValueTable>
</div> </div>

View File

@ -10,10 +10,7 @@ import { useTranslation } from 'react-i18next';
import { EtherscanLink } from '@vegaprotocol/ui-toolkit'; import { EtherscanLink } from '@vegaprotocol/ui-toolkit';
import { Heading } from '../../components/heading'; import { Heading } from '../../components/heading';
import { import { KeyValueTable, KeyValueTableRow } from '@vegaprotocol/ui-toolkit';
KeyValueTable,
KeyValueTableRow,
} from '../../components/key-value-table';
import { SplashLoader } from '../../components/splash-loader'; import { SplashLoader } from '../../components/splash-loader';
import { TransactionButton } from '../../components/transaction-button'; import { TransactionButton } from '../../components/transaction-button';
import { VegaWalletContainer } from '../../components/vega-wallet-container'; import { VegaWalletContainer } from '../../components/vega-wallet-container';
@ -197,46 +194,46 @@ export const Withdrawal = ({
<div> <div>
<KeyValueTable> <KeyValueTable>
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('Withdraw')}</th> {t('Withdraw')}
<td> <span>
{addDecimal( {addDecimal(
new BigNumber(withdrawal.amount), new BigNumber(withdrawal.amount),
withdrawal.asset.decimals withdrawal.asset.decimals
)}{' '} )}{' '}
{withdrawal.asset.symbol} {withdrawal.asset.symbol}
</td> </span>
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('from')}</th> {t('from')}
<td>{truncateMiddle(withdrawal.party.id)}</td> <span>{truncateMiddle(withdrawal.party.id)}</span>
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('toEthereum')}</th> {t('toEthereum')}
<td> <span>
<EtherscanLink <EtherscanLink
address={withdrawal.details?.receiverAddress as string} address={withdrawal.details?.receiverAddress as string}
text={truncateMiddle( text={truncateMiddle(
withdrawal.details?.receiverAddress as string withdrawal.details?.receiverAddress as string
)} )}
/> />
</td> </span>
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('created')}</th> {t('created')}
<td> <span>
{format( {format(
new Date(withdrawal.createdTimestamp), new Date(withdrawal.createdTimestamp),
DATE_FORMAT_DETAILED DATE_FORMAT_DETAILED
)} )}
</td> </span>
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<th>{t('Signature')}</th> {t('Signature')}
<td title={erc20Approval?.signatures}> <span title={erc20Approval?.signatures}>
{!erc20Approval?.signatures {!erc20Approval?.signatures
? t('Loading') ? t('Loading')
: truncateMiddle(erc20Approval.signatures)} : truncateMiddle(erc20Approval.signatures)}
</td> </span>
</KeyValueTableRow> </KeyValueTableRow>
</KeyValueTable> </KeyValueTable>
<TransactionButton <TransactionButton

View File

@ -107,7 +107,7 @@ module.exports = {
full: '9999px', full: '9999px',
}, },
fontFamily: { fontFamily: {
mono: defaultTheme.fontFamily.mono, mono: ['Roboto Mono', ...defaultTheme.fontFamily.mono],
serif: defaultTheme.fontFamily.serif, serif: defaultTheme.fontFamily.serif,
sans: [ sans: [
'"Helvetica Neue"', '"Helvetica Neue"',

View File

@ -0,0 +1,24 @@
export * from './ag-grid';
export * from './async-renderer';
export * from './button';
export * from './callout';
export * from './card';
export * from './copy-with-tooltip';
export * from './dialog';
export * from './etherscan-link';
export * from './form-group';
export * from './icon';
export * from './indicator';
export * from './input';
export * from './input-error';
export * from './key-value-table';
export * from './loader';
export * from './lozenge';
export * from './select';
export * from './splash';
export * from './text-area';
export * from './theme-switcher';
export * from './toggle';
export * from './tooltip';
export * from './vega-logo';
export * from './syntax-highlighter';

View File

@ -0,0 +1 @@
export * from './key-value-table';

View File

@ -0,0 +1,48 @@
import type { Story, Meta } from '@storybook/react';
import { KeyValueTable, KeyValueTableRow } from './key-value-table';
export default {
component: KeyValueTable,
title: 'KeyValueTable',
} as Meta;
const Template: Story = (args) => (
<KeyValueTable {...args}>
<KeyValueTableRow>
{'Token address'}
{'0x888'}
</KeyValueTableRow>
<KeyValueTableRow>
{'Value'}
{888}
</KeyValueTableRow>
<KeyValueTableRow>
{'Token address'}
{'0x888'}
</KeyValueTableRow>
</KeyValueTable>
);
export const Muted = Template.bind({});
Muted.args = {
title: 'Muted table',
muted: true,
numerical: false,
headingLevel: 5, // h5
};
export const Numerical = Template.bind({});
Numerical.args = {
title: 'Numerical table',
muted: false,
numerical: true,
headingLevel: 5, // h5
};
export const Normal = Template.bind({});
Normal.args = {
headingLevel: 5, // h5
title: 'Normal table',
muted: false,
numerical: false,
};

View File

@ -1,31 +1,34 @@
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import type { KeyValueTableProps } from './key-value-table';
import { KeyValueTable, KeyValueTableRow } from './key-value-table'; import { KeyValueTable, KeyValueTableRow } from './key-value-table';
const props = { const props: KeyValueTableProps = {
title: 'Title', title: 'Title',
headingLevel: 3,
children: undefined,
}; };
it('Renders the correct elements', () => { it('Renders the correct elements', () => {
const { container } = render( const { container } = render(
<KeyValueTable {...props}> <KeyValueTable {...props}>
<KeyValueTableRow> <KeyValueTableRow>
<td>My label</td> <span>My label</span>
<td>My value</td> <span>My value</span>
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<td>My label 2</td> <span>My label 2</span>
<td>My value 2</td> <span>My value 2</span>
</KeyValueTableRow> </KeyValueTableRow>
</KeyValueTable> </KeyValueTable>
); );
expect(screen.getByText(props.title)).toBeInTheDocument(); expect(screen.getByText(props.title || '')).toBeInTheDocument();
expect(container.querySelector('.key-value-table')).toBeInTheDocument(); expect(screen.getByTestId('key-value-table')).toBeInTheDocument();
expect(container.querySelectorAll('.key-value-table__row')).toHaveLength(2); expect(container.getElementsByTagName('dl')).toHaveLength(2);
const rows = container.querySelectorAll('.key-value-table__row'); const rows = container.getElementsByTagName('dl');
// Row 1 // Row 1
expect(rows[0].firstChild).toHaveTextContent('My label'); expect(rows[0].firstChild).toHaveTextContent('My label');
expect(rows[0].children[1]).toHaveTextContent('My value'); expect(rows[0].children[1]).toHaveTextContent('My value');
@ -37,30 +40,30 @@ it('Renders the correct elements', () => {
it('Applies numeric class if prop is passed', () => { it('Applies numeric class if prop is passed', () => {
render( render(
<KeyValueTable numerical={true} {...props}> <KeyValueTable {...props} numerical={true}>
<KeyValueTableRow> <KeyValueTableRow>
<td>My label</td> <span>My label</span>
<td>My value</td> <span>My value</span>
</KeyValueTableRow> </KeyValueTableRow>
</KeyValueTable> </KeyValueTable>
); );
expect(screen.getByTestId('key-value-table')).toHaveClass( expect(screen.getByTestId('key-value-table')).toHaveClass(
'key-value-table--numerical' 'w-full border-collapse mb-8 [border-spacing:0] break-all'
); );
}); });
it('Applies muted class if prop is passed', () => { it('Applies muted class if prop is passed', () => {
render( render(
<KeyValueTable muted={true} {...props}> <KeyValueTable {...props} muted={true}>
<KeyValueTableRow> <KeyValueTableRow>
<td>My label</td> <span>My label</span>
<td>My value</td> <span>My value</span>
</KeyValueTableRow> </KeyValueTableRow>
</KeyValueTable> </KeyValueTable>
); );
expect(screen.getByTestId('key-value-table')).toHaveClass( expect(screen.getByTestId('key-value-table')).toHaveClass(
'key-value-table--muted' 'w-full border-collapse mb-8 [border-spacing:0] break-all'
); );
}); });

View File

@ -0,0 +1,95 @@
import classNames from 'classnames';
import * as React from 'react';
export interface KeyValueTableProps
extends React.HTMLAttributes<HTMLTableElement> {
title?: string;
children: React.ReactNode;
headingLevel?: 1 | 2 | 3 | 4 | 5 | 6;
muted?: boolean;
numerical?: boolean;
}
export const KeyValueTable = ({
title,
children,
className,
muted,
numerical,
headingLevel,
...rest
}: KeyValueTableProps) => {
const TitleTag: keyof JSX.IntrinsicElements = headingLevel
? `h${headingLevel}`
: 'div';
return (
<React.Fragment>
{title && (
<TitleTag className={`text-h${headingLevel} mt-8 mb-4`}>
{title}
</TitleTag>
)}
<div
data-testid="key-value-table"
{...rest}
className={`w-full border-collapse mb-8 [border-spacing:0] break-all ${
className ? className : ''
}`}
>
<div>
{children &&
React.Children.map(
children,
(child) =>
child &&
React.cloneElement(
child as React.ReactElement<KeyValueTableRowProps>,
{
muted,
numerical,
}
)
)}
</div>
</div>
</React.Fragment>
);
};
export interface KeyValueTableRowProps
extends React.HTMLAttributes<HTMLTableRowElement> {
children: [React.ReactNode, React.ReactNode];
className?: string;
numerical?: boolean; // makes all values monospace
muted?: boolean;
}
export const KeyValueTableRow = ({
children,
className,
muted,
numerical,
}: KeyValueTableRowProps) => {
const dlClassName = classNames(
'flex flex-wrap justify-between items-center border-b first:border-t border-black dark:border-white',
{
'border-black/60 dark:border-white/60 first:[border-top:none] last:[border-bottom:none]':
muted,
},
className
);
const dtClassName = `break-normal font-medium uppercase align-top p-4`;
const ddClassName = classNames(
'align-top p-4 text-black/60 dark:text-white/60 break-normal',
{
'font-mono': numerical,
}
);
return (
<dl className={dlClassName}>
<dt className={dtClassName}>{children[0]}</dt>
<dd className={ddClassName}>{children[1]}</dd>
</dl>
);
};

View File

@ -1,27 +1,5 @@
// Components // Components
export { AgGridLazy, AgGridDynamic } from './components/ag-grid'; export * from './components';
export { AsyncRenderer } from './components/async-renderer';
export { Button, AnchorButton } from './components/button';
export { Callout } from './components/callout';
export { CopyWithTooltip } from './components/copy-with-tooltip';
export { EtherscanLink } from './components/etherscan-link';
export { FormGroup } from './components/form-group';
export { Icon } from './components/icon';
export { Input } from './components/input';
export { InputError } from './components/input-error';
export { Lozenge } from './components/lozenge';
export { Loader } from './components/loader';
export { Select } from './components/select';
export { Splash } from './components/splash';
export { TextArea } from './components/text-area';
export { ThemeSwitcher } from './components/theme-switcher';
export { Toggle } from './components/toggle';
export { Dialog } from './components/dialog/dialog';
export { VegaLogo } from './components/vega-logo';
export { Tooltip } from './components/tooltip';
export { Indicator } from './components/indicator';
export { Card } from './components/card';
export { SyntaxHighlighter } from './components/syntax-highlighter';
// Utils // Utils
export * from './utils/intent'; export * from './utils/intent';