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
/coverage
__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)
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)
@ -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`.
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
@ -40,7 +41,9 @@ Run `nx serve my-app` for a dev server. Navigate to http://localhost:4200/. The
### 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.
@ -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. 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
[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;
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 type { Tranche } from '@vegaprotocol/smart-contracts-sdk';
import {
KeyValueTable,
KeyValueTableRow,
} from '../../components/key-value-table';
import { KeyValueTable, KeyValueTableRow } from '@vegaprotocol/ui-toolkit';
import { useContracts } from '../../contexts/contracts/contracts-context';
import { DATE_FORMAT_LONG } from '../../lib/date-formats';
import { formatNumber } from '../../lib/format-number';
@ -159,37 +156,34 @@ export const ClaimFlow = ({
<div>
<KeyValueTable>
<KeyValueTableRow>
<th>{t('Connected Ethereum address')}</th>
<td>{truncateMiddle(address)}</td>
{t('Connected Ethereum address')}
{truncateMiddle(address)}
</KeyValueTableRow>
<KeyValueTableRow>
<th>{t('Amount of VEGA')}</th>
<td>
{state.claimData
? formatNumber(state.claimData.claim.amount)
: 'None'}
</td>
{t('Amount of VEGA')}
{state.claimData
? formatNumber(state.claimData.claim.amount)
: 'None'}
</KeyValueTableRow>
<KeyValueTableRow>
<th>{t('Claim expires')}</th>
<td>
{state.claimData?.claim.expiry
? format(
state.claimData?.claim.expiry * 1000,
DATE_FORMAT_LONG
)
: 'No expiry'}
</td>
{t('Claim expires')}
{state.claimData?.claim.expiry
? format(
state.claimData?.claim.expiry * 1000,
DATE_FORMAT_LONG
)
: 'No expiry'}
</KeyValueTableRow>
<KeyValueTableRow>
<th>{t('Starts unlocking')}</th>
<td>
{format(currentTranche.tranche_start, DATE_FORMAT_LONG)}
</td>
{t('Starts unlocking')}
{format(currentTranche.tranche_start, DATE_FORMAT_LONG)}
</KeyValueTableRow>
<KeyValueTableRow>
<th>{t('Fully unlocked')}</th>
<td>{format(currentTranche.tranche_end, DATE_FORMAT_LONG)}</td>
{t('Fully unlocked')}
{format(currentTranche.tranche_end, DATE_FORMAT_LONG)}
</KeyValueTableRow>
</KeyValueTable>
</div>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,10 +4,7 @@ import React from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import {
KeyValueTable,
KeyValueTableRow,
} from '../../components/key-value-table';
import { KeyValueTable, KeyValueTableRow } from '@vegaprotocol/ui-toolkit';
import { BigNumber } from '../../lib/bignumber';
import { formatNumber } from '../../lib/format-number';
import { Routes } from '../router-config';
@ -41,16 +38,15 @@ export const Tranche0Table = ({
<>
<KeyValueTable numerical={true}>
<KeyValueTableRow data-testid="tranche-table-total">
<th>
<span className="tranche-table__label">
{t('Tranche')} {trancheId}
</span>
</th>
<td>{formatNumber(total)}</td>
<span className="tranche-table__label">
{t('Tranche')} {trancheId}
</span>
<span>{formatNumber(total)}</span>
</KeyValueTableRow>
<KeyValueTableRow data-testid="tranche-table-locked">
<th>{t('Locked')}</th>
<td>{formatNumber(total)}</td>
{t('Locked')}
<span>{formatNumber(total)}</span>
</KeyValueTableRow>
</KeyValueTable>
<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 { useTranslation } from 'react-i18next';
import {
KeyValueTable,
KeyValueTableRow,
} from '../../../components/key-value-table';
import { KeyValueTable, KeyValueTableRow } from '@vegaprotocol/ui-toolkit';
import { BigNumber } from '../../../lib/bignumber';
import { DATE_FORMAT_DETAILED } from '../../../lib/date-formats';
import type {
@ -122,26 +119,30 @@ export const RewardTable = ({ reward, delegations }: RewardTableProps) => {
</h3>
<KeyValueTable>
<KeyValueTableRow>
<th>{t('rewardType')}</th>
<td>{DEFAULT_REWARD_TYPE}</td>
{t('rewardType')}
<span>{DEFAULT_REWARD_TYPE}</span>
</KeyValueTableRow>
<KeyValueTableRow>
<th>{t('yourStake')}</th>
<td>{stakeForEpoch.toString()}</td>
{t('yourStake')}
<span>{stakeForEpoch.toString()}</span>
</KeyValueTableRow>
<KeyValueTableRow>
<th>{t('reward')}</th>
<td>
{t('reward')}
<span>
{reward.amountFormatted} {t('VEGA')}
</td>
</span>
</KeyValueTableRow>
<KeyValueTableRow>
<th>{t('shareOfReward')}</th>
<td>{new BigNumber(reward.percentageOfTotal).dp(2).toString()}%</td>
{t('shareOfReward')}
<span>
{new BigNumber(reward.percentageOfTotal).dp(2).toString()}%
</span>
</KeyValueTableRow>
<KeyValueTableRow>
<th>{t('received')}</th>
<td>{format(new Date(reward.receivedAt), DATE_FORMAT_DETAILED)}</td>
{t('received')}
<span>
{format(new Date(reward.receivedAt), DATE_FORMAT_DETAILED)}
</span>
</KeyValueTableRow>
</KeyValueTable>
</div>

View File

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

View File

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

View File

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

View File

@ -107,7 +107,7 @@ module.exports = {
full: '9999px',
},
fontFamily: {
mono: defaultTheme.fontFamily.mono,
mono: ['Roboto Mono', ...defaultTheme.fontFamily.mono],
serif: defaultTheme.fontFamily.serif,
sans: [
'"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 type { KeyValueTableProps } from './key-value-table';
import { KeyValueTable, KeyValueTableRow } from './key-value-table';
const props = {
const props: KeyValueTableProps = {
title: 'Title',
headingLevel: 3,
children: undefined,
};
it('Renders the correct elements', () => {
const { container } = render(
<KeyValueTable {...props}>
<KeyValueTableRow>
<td>My label</td>
<td>My value</td>
<span>My label</span>
<span>My value</span>
</KeyValueTableRow>
<KeyValueTableRow>
<td>My label 2</td>
<td>My value 2</td>
<span>My label 2</span>
<span>My value 2</span>
</KeyValueTableRow>
</KeyValueTable>
);
expect(screen.getByText(props.title)).toBeInTheDocument();
expect(screen.getByText(props.title || '')).toBeInTheDocument();
expect(container.querySelector('.key-value-table')).toBeInTheDocument();
expect(container.querySelectorAll('.key-value-table__row')).toHaveLength(2);
expect(screen.getByTestId('key-value-table')).toBeInTheDocument();
expect(container.getElementsByTagName('dl')).toHaveLength(2);
const rows = container.querySelectorAll('.key-value-table__row');
const rows = container.getElementsByTagName('dl');
// Row 1
expect(rows[0].firstChild).toHaveTextContent('My label');
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', () => {
render(
<KeyValueTable numerical={true} {...props}>
<KeyValueTable {...props} numerical={true}>
<KeyValueTableRow>
<td>My label</td>
<td>My value</td>
<span>My label</span>
<span>My value</span>
</KeyValueTableRow>
</KeyValueTable>
);
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', () => {
render(
<KeyValueTable muted={true} {...props}>
<KeyValueTable {...props} muted={true}>
<KeyValueTableRow>
<td>My label</td>
<td>My value</td>
<span>My label</span>
<span>My value</span>
</KeyValueTableRow>
</KeyValueTable>
);
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
export { AgGridLazy, AgGridDynamic } from './components/ag-grid';
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';
export * from './components';
// Utils
export * from './utils/intent';