diff --git a/CHANGELOG.md b/CHANGELOG.md index f57bff07b4..ab36efc106 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ ### Fixes +- Fixed performance issue on stake pool list view ([PR 2924](https://github.com/input-output-hk/daedalus/pull/2924)) - Fixed catalyst fund name ([PR 2946](https://github.com/input-output-hk/daedalus/pull/2946)) - Fixed position of popup on syncing screen ([PR 2921](https://github.com/input-output-hk/daedalus/pull/2921)) - Fixed issue with missing character when copying address from PDF ([PR 2925](https://github.com/input-output-hk/daedalus/pull/2925)) diff --git a/package.json b/package.json index 8c35df26dd..30d27dd123 100644 --- a/package.json +++ b/package.json @@ -103,6 +103,7 @@ "@types/qrcode.react": "^1.0.2", "@types/react": "^17.0.38", "@types/react-svg-inline": "^2.1.3", + "@types/react-table": "^7.7.9", "@typescript-eslint/eslint-plugin": "^5.10.1", "@typescript-eslint/parser": "^5.10.1", "asar": "2.1.0", @@ -260,6 +261,7 @@ "react-router": "5.2.0", "react-router-dom": "5.2.0", "react-svg-inline": "2.1.1", + "react-table": "7.7.0", "react-virtualized": "9.22.3", "recharts": "1.8.5", "rimraf": "3.0.2", diff --git a/source/renderer/app/components/staking/stake-pools/StakePoolsTable.messages.tsx b/source/renderer/app/components/staking/stake-pools/StakePoolsTable.messages.tsx new file mode 100644 index 0000000000..07e1c7ae23 --- /dev/null +++ b/source/renderer/app/components/staking/stake-pools/StakePoolsTable.messages.tsx @@ -0,0 +1,110 @@ +import { defineMessages } from 'react-intl'; + +export const messages = defineMessages({ + tableHeaderRank: { + id: 'staking.stakePools.tableHeader.rank', + defaultMessage: '!!!Rank', + description: 'Table header "Rank" label on stake pools list view page', + }, + tableHeaderRankTooltip: { + id: 'staking.stakePools.tooltip.rankingTooltip', + defaultMessage: + '!!!

A hierarchical ranking based on the potential rewards you will earn if you delegate the intended amount of stake to this pool, assuming that it reaches saturation.

*Stake pools with the potential rewards estimated at zero have the same ranking. Please set the stake slider to a higher value for more pools to get potential rewards estimated at more than zero.

', + description: '"Rank" tooltip for the Stake Pools Table.', + }, + tableHeaderTicker: { + id: 'staking.stakePools.tableHeader.ticker', + defaultMessage: '!!!Ticker', + description: 'Table header "Ticker" label on stake pools list view page', + }, + tableHeaderSaturation: { + id: 'staking.stakePools.tableHeader.saturation', + defaultMessage: '!!!Saturation', + description: + 'Table header "Saturation" label on stake pools list view page', + }, + tableHeaderSaturationTooltip: { + id: 'staking.stakePools.tooltip.saturationTooltip', + defaultMessage: + '!!!Saturation measures the stake in the pool and indicates the point at which rewards stop increasing with increases in stake. This capping mechanism encourages decentralization by discouraging users from delegating to oversaturated stake pools.', + description: '"Saturation" tooltip for the Stake Pools Table.', + }, + tableHeaderPerformance: { + id: 'staking.stakePools.tableHeader.performance', + defaultMessage: '!!!Performance', + description: + 'Table header "Performance" label on stake pools list view page', + }, + tableHeaderUptime: { + id: 'staking.stakePools.tableHeader.uptime', + defaultMessage: '!!!Uptime (days)', + description: 'Table header "Uptime" label on stake pools list view page', + }, + tableHeaderMargin: { + id: 'staking.stakePools.tableHeader.margin', + defaultMessage: '!!!Margin', + description: 'Table header "Margin" label on stake pools list view page', + }, + tableHeaderMarginTooltip: { + id: 'staking.stakePools.tooltip.profitMarginTooltip', + defaultMessage: + "!!!The pool's profit, defined as the rewards percentage kept by the pool from the stake that was delegated to it.", + description: '"Pool margin" tooltip for the Stake Pools Table.', + }, + tableHeaderRoi: { + id: 'staking.stakePools.tableHeader.roi', + defaultMessage: '!!!Roi', + description: 'Table header "Roi" label on stake pools list view page', + }, + tableHeaderCost: { + id: 'staking.stakePools.tableHeader.cost', + defaultMessage: '!!!Cost (ADA)', + description: 'Table header "Cost" label on stake pools list view page', + }, + tableHeaderCostTooltip: { + id: 'staking.stakePools.tooltip.costPerEpochTooltip', + defaultMessage: + '!!!Fixed operational costs that the stake pool retains from any rewards earned during each epoch.', + description: '"Cost per epoch" tooltip for the Stake Pools Table.', + }, + tableHeaderProducedBlocks: { + id: 'staking.stakePools.tableHeader.producedBlocks', + defaultMessage: '!!!Produced Blocks', + description: + 'Table header "Produced Blocks" label on stake pools list view page', + }, + tableHeaderProducedBlocksTooltip: { + id: 'staking.stakePools.tooltip.producedBlocksTooltip', + defaultMessage: + '!!!The total number of blocks the stake pool has produced.', + description: '"Blocks" tooltip for the Stake Pools Table.', + }, + tableHeaderPotentialRewards: { + id: 'staking.stakePools.tableHeader.potentialRewards', + defaultMessage: '!!!Potential rewards', + description: + 'Table header "Potential rewards" label on stake pools list view page', + }, + tableHeaderPotentialRewardsTooltip: { + id: 'staking.stakePools.tooltip.potentialRewardsTooltip', + defaultMessage: + "!!!An estimation of the potential rewards you will earn per epoch if you delegate the intended amount of stake. The system looks at the pool's parameters and historical performance data to calculate potential rewards, assuming that the pool reaches optimal saturation.", + description: '"Rewards" tooltip for the Stake Pools Table.', + }, + tableHeaderPledge: { + id: 'staking.stakePools.tableHeader.pledge', + defaultMessage: '!!!Pledge (ADA)', + description: 'Table header "Pledge" label on stake pools list view page', + }, + tableHeaderPledgeTooltip: { + id: 'staking.stakePools.tooltip.pledgeTooltip', + defaultMessage: + '!!!The amount of stake that a pool operator contributes to a pool. Pools with higher pledge amounts earn more rewards for themselves and their delegators. Pools that do not honor their pledge earn zero rewards and accrue low ranking.', + description: '"Pledge" tooltip for the Stake Pools Table.', + }, + tableHeaderRetiring: { + id: 'staking.stakePools.tableHeader.retiring', + defaultMessage: '!!!Retiring in', + description: 'Table header "Retiring" label on stake pools list view page', + }, +}); diff --git a/source/renderer/app/components/staking/stake-pools/StakePoolsTable.scss b/source/renderer/app/components/staking/stake-pools/StakePoolsTable.scss index c87f455cea..cda9cd648a 100644 --- a/source/renderer/app/components/staking/stake-pools/StakePoolsTable.scss +++ b/source/renderer/app/components/staking/stake-pools/StakePoolsTable.scss @@ -92,12 +92,12 @@ } } - table { + .table { border-style: hidden; user-select: text; width: 100%; - thead { + .thead { background: var(--theme-bordered-box-background-color); border-radius: 4px 4px 0 0; box-shadow: 0 10px 10px -10px rgba(0, 0, 0, 0.2); @@ -108,7 +108,7 @@ top: 0; z-index: $sticky-header-z-index; - tr { + .tr { border: 0; display: flex; justify-content: space-between; @@ -131,8 +131,8 @@ } } - tbody { - tr { + .tbody { + .tr { display: flex; justify-content: space-between; width: 100%; @@ -145,11 +145,11 @@ } } - tr { + .tr { border-bottom: 1px solid var(--theme-staking-table-border-color); } - th { + .th { color: var(--theme-staking-font-color-regular); cursor: pointer; font-family: var(--font-semibold); @@ -207,7 +207,7 @@ } } - td { + .td { color: var(--theme-staking-font-color-regular); font-family: var(--font-regular); font-size: 12px; @@ -266,6 +266,7 @@ display: flex; flex-direction: row; height: 100%; + justify-content: center; } %saturationContainer { @@ -346,8 +347,8 @@ } } - th, - td { + .th, + .td { &:nth-child(1), &:nth-child(3), &:nth-child(4), diff --git a/source/renderer/app/components/staking/stake-pools/StakePoolsTable.tsx b/source/renderer/app/components/staking/stake-pools/StakePoolsTable.tsx index ca95bf4e2a..4fd9214c3f 100644 --- a/source/renderer/app/components/staking/stake-pools/StakePoolsTable.tsx +++ b/source/renderer/app/components/staking/stake-pools/StakePoolsTable.tsx @@ -1,134 +1,38 @@ -import React, { Component } from 'react'; +import React, { useEffect, useState, useCallback } from 'react'; import { observer } from 'mobx-react'; -import { orderBy } from 'lodash'; import classNames from 'classnames'; -import { defineMessages, intlShape, FormattedHTMLMessage } from 'react-intl'; -import { PopOver } from 'react-polymorph/lib/components/PopOver'; +import { injectIntl } from 'react-intl'; +import { useTable, useFlexLayout } from 'react-table'; +import List from 'react-virtualized/dist/commonjs/List'; +import WindowScroller from 'react-virtualized/dist/commonjs/WindowScroller'; +import AutoSizer from 'react-virtualized/dist/commonjs/AutoSizer'; +import { Intl } from '../../../types/i18nTypes'; +import { StakingPageScrollContext } from '../layouts/StakingWithNavigation'; import styles from './StakePoolsTable.scss'; import StakePool from '../../../domains/StakePool'; import LoadingSpinner from '../../widgets/LoadingSpinner'; import BorderedBox from '../../widgets/BorderedBox'; -import { StakePoolsTableHeader } from './StakePoolsTableHeader'; -import { StakePoolsTableBody } from './StakePoolsTableBody'; +import { StakePoolsTableHeaderCell } from './StakePoolsTableHeaderCell'; +import { + useSortedStakePoolList, + useCreateColumns, + StakePoolsOrder, + StakePoolSortableProps, +} from './hooks'; -const messages = defineMessages({ - tableHeaderRank: { - id: 'staking.stakePools.tableHeader.rank', - defaultMessage: '!!!Rank', - description: 'Table header "Rank" label on stake pools list view page', - }, - tableHeaderRankTooltip: { - id: 'staking.stakePools.tooltip.rankingTooltip', - defaultMessage: - '!!!

A hierarchical ranking based on the potential rewards you will earn if you delegate the intended amount of stake to this pool, assuming that it reaches saturation.

*Stake pools with the potential rewards estimated at zero have the same ranking. Please set the stake slider to a higher value for more pools to get potential rewards estimated at more than zero.

', - description: '"Rank" tooltip for the Stake Pools Table.', - }, - tableHeaderTicker: { - id: 'staking.stakePools.tableHeader.ticker', - defaultMessage: '!!!Ticker', - description: 'Table header "Ticker" label on stake pools list view page', - }, - tableHeaderSaturation: { - id: 'staking.stakePools.tableHeader.saturation', - defaultMessage: '!!!Saturation', - description: - 'Table header "Saturation" label on stake pools list view page', - }, - tableHeaderSaturationTooltip: { - id: 'staking.stakePools.tooltip.saturationTooltip', - defaultMessage: - '!!!Saturation measures the stake in the pool and indicates the point at which rewards stop increasing with increases in stake. This capping mechanism encourages decentralization by discouraging users from delegating to oversaturated stake pools.', - description: '"Saturation" tooltip for the Stake Pools Table.', - }, - tableHeaderPerformance: { - id: 'staking.stakePools.tableHeader.performance', - defaultMessage: '!!!Performance', - description: - 'Table header "Performance" label on stake pools list view page', - }, - tableHeaderUptime: { - id: 'staking.stakePools.tableHeader.uptime', - defaultMessage: '!!!Uptime (days)', - description: 'Table header "Uptime" label on stake pools list view page', - }, - tableHeaderMargin: { - id: 'staking.stakePools.tableHeader.margin', - defaultMessage: '!!!Margin', - description: 'Table header "Margin" label on stake pools list view page', - }, - tableHeaderMarginTooltip: { - id: 'staking.stakePools.tooltip.profitMarginTooltip', - defaultMessage: - "!!!The pool's profit, defined as the rewards percentage kept by the pool from the stake that was delegated to it.", - description: '"Pool margin" tooltip for the Stake Pools Table.', - }, - tableHeaderRoi: { - id: 'staking.stakePools.tableHeader.roi', - defaultMessage: '!!!Roi', - description: 'Table header "Roi" label on stake pools list view page', - }, - tableHeaderCost: { - id: 'staking.stakePools.tableHeader.cost', - defaultMessage: '!!!Cost (ADA)', - description: 'Table header "Cost" label on stake pools list view page', - }, - tableHeaderCostTooltip: { - id: 'staking.stakePools.tooltip.costPerEpochTooltip', - defaultMessage: - '!!!Fixed operational costs that the stake pool retains from any rewards earned during each epoch.', - description: '"Cost per epoch" tooltip for the Stake Pools Table.', - }, - tableHeaderProducedBlocks: { - id: 'staking.stakePools.tableHeader.producedBlocks', - defaultMessage: '!!!Produced Blocks', - description: - 'Table header "Produced Blocks" label on stake pools list view page', - }, - tableHeaderProducedBlocksTooltip: { - id: 'staking.stakePools.tooltip.producedBlocksTooltip', - defaultMessage: - '!!!The total number of blocks the stake pool has produced.', - description: '"Blocks" tooltip for the Stake Pools Table.', - }, - tableHeaderPotentialRewards: { - id: 'staking.stakePools.tableHeader.potentialRewards', - defaultMessage: '!!!Potential rewards', - description: - 'Table header "Potential rewards" label on stake pools list view page', - }, - tableHeaderPotentialRewardsTooltip: { - id: 'staking.stakePools.tooltip.potentialRewardsTooltip', - defaultMessage: - "!!!An estimation of the potential rewards you will earn per epoch if you delegate the intended amount of stake. The system looks at the pool's parameters and historical performance data to calculate potential rewards, assuming that the pool reaches optimal saturation.", - description: '"Rewards" tooltip for the Stake Pools Table.', - }, - tableHeaderPledge: { - id: 'staking.stakePools.tableHeader.pledge', - defaultMessage: '!!!Pledge (ADA)', - description: 'Table header "Pledge" label on stake pools list view page', - }, - tableHeaderPledgeTooltip: { - id: 'staking.stakePools.tooltip.pledgeTooltip', - defaultMessage: - '!!!The amount of stake that a pool operator contributes to a pool. Pools with higher pledge amounts earn more rewards for themselves and their delegators. Pools that do not honor their pledge earn zero rewards and accrue low ranking.', - description: '"Pledge" tooltip for the Stake Pools Table.', - }, - tableHeaderRetiring: { - id: 'staking.stakePools.tableHeader.retiring', - defaultMessage: '!!!Retiring in', - description: 'Table header "Retiring" label on stake pools list view page', - }, -}); -export const defaultTableOrdering = { - ranking: 'asc', - ticker: 'asc', - saturation: 'asc', - cost: 'asc', - profitMargin: 'asc', - producedBlocks: 'desc', - nonMyopicMemberRewards: 'desc', - pledge: 'asc', - retiring: 'asc', +export const defaultTableOrdering: Record< + StakePoolSortableProps, + StakePoolsOrder +> = { + ranking: StakePoolsOrder.Asc, + ticker: StakePoolsOrder.Asc, + saturation: StakePoolsOrder.Asc, + cost: StakePoolsOrder.Asc, + profitMargin: StakePoolsOrder.Asc, + producedBlocks: StakePoolsOrder.Desc, + nonMyopicMemberRewards: StakePoolsOrder.Desc, + pledge: StakePoolsOrder.Asc, + retiring: StakePoolsOrder.Asc, }; // Maximum number of stake pools for which we do not need to use the preloading const PRELOADER_THRESHOLD = 100; @@ -142,277 +46,212 @@ type Props = { onSelect?: (...args: Array) => any; containerClassName: string; numberOfRankedStakePools: number; - selectedPoolId?: number | null | undefined; + selectedPoolId?: string; onOpenExternalLink: (...args: Array) => any; currentLocale: string; onTableHeaderMouseEnter: (...args: Array) => any; onTableHeaderMouseLeave: (...args: Array) => any; + intl: Intl; }; type State = { isPreloading: boolean; - stakePoolsOrder: string; - stakePoolsSortBy: string; + stakePoolsOrder: StakePoolsOrder; + stakePoolsSortBy: StakePoolSortableProps; }; -const initialState = { +const initialState: State = { isPreloading: true, - stakePoolsOrder: 'asc', + stakePoolsOrder: StakePoolsOrder.Asc, stakePoolsSortBy: 'ranking', }; -@observer -class StakePoolsTable extends Component { - static contextTypes = { - intl: intlShape.isRequired, - }; - static defaultProps = { - isListActive: true, - showWithSelectButton: false, - }; - state = { ...initialState }; - scrollableDomElement: HTMLElement | null | undefined = null; - searchInput: HTMLElement | null | undefined = null; - _isMounted = false; +function StakePoolsTableComponent({ + stakePoolsList = [], + listName, + onTableHeaderMouseEnter, + onTableHeaderMouseLeave, + showWithSelectButton = false, + intl, + numberOfRankedStakePools, + currentTheme, + onOpenExternalLink, + containerClassName, + onSelect, +}: Props) { + const [state, setState] = useState(initialState); - componentDidMount() { - this._isMounted = true; - setTimeout(() => { - if (this._isMounted) - this.setState({ - isPreloading: false, - }); - }, 0); - } + useEffect(() => { + setState((s) => ({ ...s, isPreloading: false })); + }, []); - componentWillUnmount() { - this._isMounted = false; - this.scrollableDomElement = document.querySelector( - `.${this.props.containerClassName}` - ); - } + const handleSort = useCallback( + (newSortBy: StakePoolSortableProps) => { + const { stakePoolsOrder, stakePoolsSortBy } = state; + let newOrder = defaultTableOrdering[newSortBy]; + + if (newSortBy === stakePoolsSortBy) { + newOrder = + stakePoolsOrder === StakePoolsOrder.Asc + ? StakePoolsOrder.Desc + : StakePoolsOrder.Asc; + } + + setState((s) => ({ + ...s, + stakePoolsOrder: newOrder, + stakePoolsSortBy: newSortBy, + })); + }, + [state] + ); + + const { isPreloading, stakePoolsSortBy, stakePoolsOrder } = state; - handleSort = (newSortBy: string) => { - const { stakePoolsOrder, stakePoolsSortBy } = this.state; - let newOrder = defaultTableOrdering[newSortBy]; + const sortedStakePoolList = useSortedStakePoolList({ + order: stakePoolsOrder, + sortBy: stakePoolsSortBy, + stakePoolList: stakePoolsList, + }); - if (newSortBy === stakePoolsSortBy) { - newOrder = stakePoolsOrder === 'asc' ? 'desc' : 'asc'; - } + const columns = useCreateColumns({ + containerClassName, + currentTheme, + intl, + numberOfRankedStakePools, + onOpenExternalLink, + onSelect, + showWithSelectButton, + }); - this.setState({ - stakePoolsOrder: newOrder, - stakePoolsSortBy: newSortBy, - }); - }; + const { + getTableProps, + getTableBodyProps, + headerGroups, + rows, + prepareRow, + } = useTable( + { + columns, + data: sortedStakePoolList, + }, + useFlexLayout + ); - render() { - const { - currentTheme, - onOpenExternalLink, - showWithSelectButton, - stakePoolsList, - containerClassName, - numberOfRankedStakePools, - listName, - onSelect, - selectedPoolId, - currentLocale, - onTableHeaderMouseEnter, - onTableHeaderMouseLeave, - } = this.props; - const { isPreloading, stakePoolsSortBy, stakePoolsOrder } = this.state; - const { intl } = this.context; - const componentClasses = classNames([styles.component, listName]); - if (stakePoolsList.length > PRELOADER_THRESHOLD && isPreloading) + const RenderRow = useCallback( + ({ index, style }) => { + const row = rows[index]; + prepareRow(row); return ( -
- +
+ {row.cells.map((cell) => { + return ( + /* eslint-disable-next-line react/jsx-key */ +
+ {cell.render('Cell')} +
+ ); + })}
); - const sortedStakePoolList = orderBy( - stakePoolsList.map((stakePool) => { - let calculatedPledge; - let calculatedCost; - let formattedTicker; + }, + [prepareRow, rows] + ); - if (stakePoolsSortBy === 'ticker') { - formattedTicker = stakePool.ticker - .replace(/[^\w\s]/gi, '') - .toLowerCase(); - } + const componentClasses = classNames([styles.component, listName]); - if (stakePoolsSortBy === 'pledge') { - const formattedPledgeValue = stakePool.pledge.toFixed(2); - calculatedPledge = Number( - parseFloat(formattedPledgeValue).toFixed(2) - ); - } + if (stakePoolsList.length > PRELOADER_THRESHOLD && isPreloading) + return ( +
+ +
+ ); - if (stakePoolsSortBy === 'cost') { - const formattedCostValue = stakePool.cost.toFixed(2); - calculatedCost = Number(parseFloat(formattedCostValue).toFixed(2)); - } + return ( + + {(stakePoolsScrollContext) => ( + + {({ + height, + isScrolling, + registerChild, + onChildScroll, + scrollTop, + }) => ( +
+
+ {sortedStakePoolList.length > 0 && ( + +
+
+ {headerGroups.map((headerGroup) => ( + /* eslint-disable-next-line react/jsx-key */ +
+ {headerGroup.headers.map((column) => ( + + {column.render('Header')} + + ))} +
+ ))} +
- return { - ...stakePool, - calculatedPledge, - calculatedCost, - formattedTicker, - }; - }), - [ - 'formattedTicker', - 'calculatedPledge', - 'calculatedCost', - stakePoolsSortBy, - ], - // @ts-ignore ts-migrate(2769) FIXME: No overload matches this call. - [stakePoolsOrder, stakePoolsOrder, stakePoolsOrder, stakePoolsOrder] - ); - const availableTableHeaders = [ - { - name: 'ranking', - title: ( - - + + {({ width }) => ( +
+ +
+ )} +
+
+
+ )}
- } - > - {intl.formatMessage(messages.tableHeaderRank)} - - ), - }, - { - name: 'ticker', - title: intl.formatMessage(messages.tableHeaderTicker), - }, - { - name: 'saturation', - title: ( - - {intl.formatMessage(messages.tableHeaderSaturation)} - - ), - }, - { - name: 'cost', - title: ( - - {intl.formatMessage(messages.tableHeaderCost)} - - ), - }, - { - name: 'profitMargin', - title: ( - - {intl.formatMessage(messages.tableHeaderMargin)} - - ), - }, - { - name: 'producedBlocks', - title: ( - - {intl.formatMessage(messages.tableHeaderProducedBlocks)} - - ), - }, - { - name: 'nonMyopicMemberRewards', - title: ( - - {intl.formatMessage(messages.tableHeaderPotentialRewards)} - - ), - }, - { - name: 'pledge', - title: ( - - {intl.formatMessage(messages.tableHeaderPledge)} - - ), - }, - { - name: 'retiring', - title: intl.formatMessage(messages.tableHeaderRetiring), - }, - ]; - return ( -
-
- {sortedStakePoolList.length > 0 && ( - - - - - - - - - - -
-
+
)} -
-
- ); - } +
+ )} +
+ ); } -export { StakePoolsTable }; +export const StakePoolsTable = injectIntl(observer(StakePoolsTableComponent)); diff --git a/source/renderer/app/components/staking/stake-pools/StakePoolsTableBody.tsx b/source/renderer/app/components/staking/stake-pools/StakePoolsTableBody.tsx deleted file mode 100644 index 69d1b24e77..0000000000 --- a/source/renderer/app/components/staking/stake-pools/StakePoolsTableBody.tsx +++ /dev/null @@ -1,134 +0,0 @@ -import React, { Component } from 'react'; -import { observer } from 'mobx-react'; -import { intlShape } from 'react-intl'; -import { get, map } from 'lodash'; -import BigNumber from 'bignumber.js'; -import moment from 'moment'; -import classNames from 'classnames'; -import { - formattedWalletAmount, - toFixedUserFormat, -} from '../../../utils/formatters'; -import { PoolPopOver } from '../widgets/PoolPopOver'; -import styles from './StakePoolsTable.scss'; -import { getColorFromRange, getSaturationColor } from '../../../utils/colors'; -import StakePool from '../../../domains/StakePool'; - -type TableBodyProps = { - sortedStakePoolList: StakePool; - numberOfRankedStakePools: number; - currentTheme: string; - onOpenExternalLink: (...args: Array) => any; - showWithSelectButton?: boolean; - containerClassName: string; - onSelect?: (poolId: string) => void; - selectedPoolId?: number | null | undefined; -}; - -@observer -class StakePoolsTableBody extends Component { - static contextTypes = { - intl: intlShape.isRequired, - }; - - render() { - const { - sortedStakePoolList, - numberOfRankedStakePools, - currentTheme, - onSelect, - onOpenExternalLink, - showWithSelectButton, - containerClassName, - selectedPoolId, - } = this.props; - const { intl } = this.context; - return map(sortedStakePoolList, (stakePool, key) => { - const rank = get(stakePool, 'ranking', ''); - const ticker = get(stakePool, 'ticker', ''); - const saturation = get(stakePool, 'saturation', ''); - const cost = new BigNumber(get(stakePool, 'cost', '')); - const margin = get(stakePool, 'profitMargin', ''); - const producedBlocks = get(stakePool, 'producedBlocks', ''); - const pledge = new BigNumber(get(stakePool, 'pledge', '')); - const retiring = get(stakePool, 'retiring', ''); - const memberRewards = new BigNumber( - get(stakePool, 'potentialRewards', '') - ); - const potentialRewards = formattedWalletAmount(memberRewards); - const retirement = - retiring && moment(retiring).locale(intl.locale).fromNow(true); - const pledgeValue = formattedWalletAmount(pledge, false, false); - const costValue = formattedWalletAmount(cost, false, false); - const progressBarContentClassnames = classNames([ - styles.progressBarContent, - styles[getSaturationColor(saturation)], - ]); - const color = getColorFromRange(rank, numberOfRankedStakePools); - return ( - - - {!memberRewards.isZero() ? ( - rank - ) : ( - <> - {numberOfRankedStakePools + 1} - * - - )} - - - - - {ticker} - - - - -
-
-
-
-
-
-
- {`${toFixedUserFormat(saturation, 2)}%`} -
-
- - {costValue} - {`${toFixedUserFormat(margin, 2)}%`} - {toFixedUserFormat(producedBlocks, 0)} - {potentialRewards} - {pledgeValue} - - {retirement ? ( - {retirement} - ) : ( - '-' - )} - - - ); - }); - } -} - -export { StakePoolsTableBody }; diff --git a/source/renderer/app/components/staking/stake-pools/StakePoolsTableHeader.tsx b/source/renderer/app/components/staking/stake-pools/StakePoolsTableHeader.tsx deleted file mode 100644 index dd63573049..0000000000 --- a/source/renderer/app/components/staking/stake-pools/StakePoolsTableHeader.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import React, { Component } from 'react'; -import { observer } from 'mobx-react'; -import { map } from 'lodash'; -import classNames from 'classnames'; -import SVGInline from 'react-svg-inline'; -import styles from './StakePoolsTable.scss'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../../assets/images/ascendi... Remove this comment to see the full error message -import sortIcon from '../../../assets/images/ascending.inline.svg'; -import { defaultTableOrdering } from './StakePoolsTable'; - -type TableHeaderProps = { - availableTableHeaders: Array<{ - name: string; - title: any; - }>; - stakePoolsSortBy: string; - stakePoolsOrder: string; - onHandleSort: (...args: Array) => any; -}; - -@observer -class StakePoolsTableHeader extends Component { - render() { - const { - availableTableHeaders, - stakePoolsSortBy, - stakePoolsOrder, - onHandleSort, - } = this.props; - return map(availableTableHeaders, (tableHeader) => { - const isSorted = - tableHeader.name === stakePoolsSortBy || - (tableHeader.name === 'ticker' && stakePoolsSortBy === 'ticker'); - const defaultOrdering = defaultTableOrdering[tableHeader.name]; - const sortIconClasses = classNames([ - styles.sortIcon, - isSorted ? styles.sorted : null, - isSorted && styles[`${stakePoolsOrder}CurrentOrdering`], - styles[`${defaultOrdering}DefaultOrdering`], - ]); - return ( - onHandleSort(tableHeader.name)} - > - {tableHeader.title} - - - ); - }); - } -} - -export { StakePoolsTableHeader }; diff --git a/source/renderer/app/components/staking/stake-pools/StakePoolsTableHeaderCell.tsx b/source/renderer/app/components/staking/stake-pools/StakePoolsTableHeaderCell.tsx new file mode 100644 index 0000000000..22575f18ce --- /dev/null +++ b/source/renderer/app/components/staking/stake-pools/StakePoolsTableHeaderCell.tsx @@ -0,0 +1,50 @@ +import React, { Component } from 'react'; +import { observer } from 'mobx-react'; +import classNames from 'classnames'; +import SVGInline from 'react-svg-inline'; +import styles from './StakePoolsTable.scss'; +import sortIcon from '../../../assets/images/ascending.inline.svg'; +import { defaultTableOrdering } from './StakePoolsTable'; +import { StakePoolsOrder, StakePoolSortableProps } from './hooks'; + +type TableHeaderProps = { + name: string; + stakePoolsSortBy: StakePoolSortableProps; + stakePoolsOrder: StakePoolsOrder; + onHandleSort: (name: string) => void; + children: React.ReactNode; +}; + +@observer +class StakePoolsTableHeaderCell extends Component { + render() { + const { + name, + stakePoolsSortBy, + stakePoolsOrder, + onHandleSort, + children, + ...headerProps + } = this.props; + const isSorted = name === stakePoolsSortBy; + const defaultOrdering = defaultTableOrdering[name]; + const sortIconClasses = classNames([ + styles.sortIcon, + isSorted ? styles.sorted : null, + isSorted && styles[`${stakePoolsOrder}CurrentOrdering`], + styles[`${defaultOrdering}DefaultOrdering`], + ]); + return ( +
onHandleSort(name)} + {...headerProps} + > + {children} + +
+ ); + } +} + +export { StakePoolsTableHeaderCell }; diff --git a/source/renderer/app/components/staking/stake-pools/hooks/index.ts b/source/renderer/app/components/staking/stake-pools/hooks/index.ts new file mode 100644 index 0000000000..ac08a1ed77 --- /dev/null +++ b/source/renderer/app/components/staking/stake-pools/hooks/index.ts @@ -0,0 +1,6 @@ +export { useCreateColumns } from './useCreateColumns'; +export { + useSortedStakePoolList, + StakePoolsOrder, +} from './useSortedStakePoolList'; +export { StakePoolSortableProps } from './types'; diff --git a/source/renderer/app/components/staking/stake-pools/hooks/types.ts b/source/renderer/app/components/staking/stake-pools/hooks/types.ts new file mode 100644 index 0000000000..6b751348b5 --- /dev/null +++ b/source/renderer/app/components/staking/stake-pools/hooks/types.ts @@ -0,0 +1,14 @@ +import StakePool from '../../../../domains/StakePool'; + +export type StakePoolSortableProps = keyof Pick< + StakePool, + | 'ranking' + | 'ticker' + | 'saturation' + | 'cost' + | 'profitMargin' + | 'producedBlocks' + | 'nonMyopicMemberRewards' + | 'pledge' + | 'retiring' +>; diff --git a/source/renderer/app/components/staking/stake-pools/hooks/useCreateColumns.tsx b/source/renderer/app/components/staking/stake-pools/hooks/useCreateColumns.tsx new file mode 100644 index 0000000000..f4fcccbdb5 --- /dev/null +++ b/source/renderer/app/components/staking/stake-pools/hooks/useCreateColumns.tsx @@ -0,0 +1,267 @@ +import React, { useMemo } from 'react'; +import classNames from 'classnames'; +import BigNumber from 'bignumber.js'; +import moment from 'moment'; +import { FormattedHTMLMessage } from 'react-intl'; +import { Column } from 'react-table'; +import { PopOver } from 'react-polymorph/lib/components/PopOver'; +import { PoolPopOver } from '../../widgets/PoolPopOver'; +import { Intl } from '../../../../types/i18nTypes'; +import styles from '../StakePoolsTable.scss'; +import StakePool from '../../../../domains/StakePool'; +import { + getColorFromRange, + getSaturationColor, +} from '../../../../utils/colors'; +import { + formattedWalletAmount, + toFixedUserFormat, +} from '../../../../utils/formatters'; +import { messages } from '../StakePoolsTable.messages'; +import { StakePoolSortableProps } from './types'; + +type UseCreateColumnsArgs = { + currentTheme: string; + showWithSelectButton?: boolean; + onSelect?: (poolId: string) => void; + containerClassName: string; + numberOfRankedStakePools: number; + onOpenExternalLink: (url: string) => void; + intl: Intl; +}; + +type StakePoolColumn = Column & { + id: StakePoolSortableProps; + accessor: StakePoolSortableProps; +}; + +export const useCreateColumns = ({ + numberOfRankedStakePools, + intl, + currentTheme, + onOpenExternalLink, + onSelect, + containerClassName, + showWithSelectButton, +}: UseCreateColumnsArgs) => + useMemo( + () => [ + { + id: 'ranking', + Header: ( + + +
+ } + > + {intl.formatMessage(messages.tableHeaderRank)} + + ), + accessor: 'ranking', + Cell: ({ row }) => { + const { potentialRewards, ranking } = row.original; + const memberRewards = new BigNumber(potentialRewards); + + return ( + <> + {!memberRewards.isZero() ? ( + ranking + ) : ( + <> + {numberOfRankedStakePools + 1} + * + + )} + + ); + }, + }, + + { + id: 'ticker', + Header: intl.formatMessage(messages.tableHeaderTicker), + accessor: 'ticker', + Cell: ({ row }) => { + const stakePool = row.original; + const color = getColorFromRange( + stakePool.ranking, + numberOfRankedStakePools + ); + + return ( + + + {stakePool.ticker} + + + ); + }, + }, + { + id: 'saturation', + Header: ( + + {intl.formatMessage(messages.tableHeaderSaturation)} + + ), + accessor: 'saturation', + Cell: ({ row }) => { + const { saturation } = row.original; + const progressBarContentClassnames = classNames([ + styles.progressBarContent, + styles[getSaturationColor(saturation)], + ]); + + return ( +
+
+
+
+
+
+
+ {`${toFixedUserFormat(saturation, 2)}%`} +
+
+ ); + }, + }, + { + id: 'cost', + Header: ( + + {intl.formatMessage(messages.tableHeaderCost)} + + ), + accessor: 'cost', + Cell: ({ value }) => { + const cost = new BigNumber(value); + const costValue = formattedWalletAmount(cost, false, false); + + return costValue; + }, + }, + { + id: 'profitMargin', + Header: ( + + {intl.formatMessage(messages.tableHeaderMargin)} + + ), + accessor: 'profitMargin', + Cell: ({ value }) => { + return `${toFixedUserFormat(value, 2)}%`; + }, + }, + { + id: 'producedBlocks', + Header: ( + + {intl.formatMessage(messages.tableHeaderProducedBlocks)} + + ), + accessor: 'producedBlocks', + Cell: ({ value }) => { + return toFixedUserFormat(value, 0); + }, + }, + { + id: 'nonMyopicMemberRewards', + Header: ( + + {intl.formatMessage(messages.tableHeaderPotentialRewards)} + + ), + accessor: 'nonMyopicMemberRewards', + Cell: ({ row }) => { + const stakePool = row.original; + const memberRewards = new BigNumber(stakePool.potentialRewards); + const potentialRewards = formattedWalletAmount(memberRewards); + return potentialRewards; + }, + }, + { + id: 'pledge', + Header: ( + + {intl.formatMessage(messages.tableHeaderPledge)} + + ), + accessor: 'pledge', + Cell: ({ row }) => { + const stakePool = row.original; + const pledge = new BigNumber(stakePool.pledge); + const pledgeValue = formattedWalletAmount(pledge, false, false); + return pledgeValue; + }, + }, + { + id: 'retiring', + Header: intl.formatMessage(messages.tableHeaderRetiring), + accessor: 'retiring', + Cell: ({ row }) => { + const stakePool = row.original; + const retirement = + stakePool.retiring && + moment(stakePool.retiring).locale(intl.locale).fromNow(true); + + return ( + <> + {retirement ? ( + {retirement} + ) : ( + '-' + )} + + ); + }, + }, + ], + [numberOfRankedStakePools] + ); diff --git a/source/renderer/app/components/staking/stake-pools/hooks/useSortedStakePoolList.tsx b/source/renderer/app/components/staking/stake-pools/hooks/useSortedStakePoolList.tsx new file mode 100644 index 0000000000..216970e196 --- /dev/null +++ b/source/renderer/app/components/staking/stake-pools/hooks/useSortedStakePoolList.tsx @@ -0,0 +1,55 @@ +import { useMemo } from 'react'; +import { orderBy } from 'lodash'; +import StakePool from '../../../../domains/StakePool'; +import { StakePoolSortableProps } from './types'; + +export enum StakePoolsOrder { + Asc = 'asc', + Desc = 'desc', +} + +interface UseSortedStakePoolListArgs { + stakePoolList: StakePool[]; + sortBy: StakePoolSortableProps; + order: StakePoolsOrder; +} + +export const useSortedStakePoolList = ({ + stakePoolList, + sortBy, + order, +}: UseSortedStakePoolListArgs) => + useMemo( + () => + orderBy( + stakePoolList.map((stakePool) => { + let calculatedPledge; + let calculatedCost; + let formattedTicker; + + if (sortBy === 'ticker') { + formattedTicker = stakePool.ticker + .replace(/[^\w\s]/gi, '') + .toLowerCase(); + } + + if (sortBy === 'pledge') { + calculatedPledge = parseFloat(stakePool.pledge.toFixed(2)); + } + + if (sortBy === 'cost') { + calculatedCost = parseFloat(stakePool.cost.toFixed(2)); + } + + return { + ...stakePool, + calculatedPledge, + calculatedCost, + formattedTicker, + }; + }), + ['formattedTicker', 'calculatedPledge', 'calculatedCost', sortBy], + [order, order, order, order] + ), + [stakePoolList, order, sortBy] + ); diff --git a/source/renderer/app/i18n/locales/defaultMessages.json b/source/renderer/app/i18n/locales/defaultMessages.json index ca037a45b1..95bf5045f1 100644 --- a/source/renderer/app/i18n/locales/defaultMessages.json +++ b/source/renderer/app/i18n/locales/defaultMessages.json @@ -6784,13 +6784,13 @@ "description": "Table header \"Rank\" label on stake pools list view page", "end": { "column": 3, - "line": 19 + "line": 8 }, - "file": "source/renderer/app/components/staking/stake-pools/StakePoolsTable.tsx", + "file": "source/renderer/app/components/staking/stake-pools/StakePoolsTable.messages.tsx", "id": "staking.stakePools.tableHeader.rank", "start": { "column": 19, - "line": 15 + "line": 4 } }, { @@ -6798,13 +6798,13 @@ "description": "\"Rank\" tooltip for the Stake Pools Table.", "end": { "column": 3, - "line": 25 + "line": 14 }, - "file": "source/renderer/app/components/staking/stake-pools/StakePoolsTable.tsx", + "file": "source/renderer/app/components/staking/stake-pools/StakePoolsTable.messages.tsx", "id": "staking.stakePools.tooltip.rankingTooltip", "start": { "column": 26, - "line": 20 + "line": 9 } }, { @@ -6812,13 +6812,13 @@ "description": "Table header \"Ticker\" label on stake pools list view page", "end": { "column": 3, - "line": 30 + "line": 19 }, - "file": "source/renderer/app/components/staking/stake-pools/StakePoolsTable.tsx", + "file": "source/renderer/app/components/staking/stake-pools/StakePoolsTable.messages.tsx", "id": "staking.stakePools.tableHeader.ticker", "start": { "column": 21, - "line": 26 + "line": 15 } }, { @@ -6826,13 +6826,13 @@ "description": "Table header \"Saturation\" label on stake pools list view page", "end": { "column": 3, - "line": 36 + "line": 25 }, - "file": "source/renderer/app/components/staking/stake-pools/StakePoolsTable.tsx", + "file": "source/renderer/app/components/staking/stake-pools/StakePoolsTable.messages.tsx", "id": "staking.stakePools.tableHeader.saturation", "start": { "column": 25, - "line": 31 + "line": 20 } }, { @@ -6840,13 +6840,13 @@ "description": "\"Saturation\" tooltip for the Stake Pools Table.", "end": { "column": 3, - "line": 42 + "line": 31 }, - "file": "source/renderer/app/components/staking/stake-pools/StakePoolsTable.tsx", + "file": "source/renderer/app/components/staking/stake-pools/StakePoolsTable.messages.tsx", "id": "staking.stakePools.tooltip.saturationTooltip", "start": { "column": 32, - "line": 37 + "line": 26 } }, { @@ -6854,13 +6854,13 @@ "description": "Table header \"Performance\" label on stake pools list view page", "end": { "column": 3, - "line": 48 + "line": 37 }, - "file": "source/renderer/app/components/staking/stake-pools/StakePoolsTable.tsx", + "file": "source/renderer/app/components/staking/stake-pools/StakePoolsTable.messages.tsx", "id": "staking.stakePools.tableHeader.performance", "start": { "column": 26, - "line": 43 + "line": 32 } }, { @@ -6868,13 +6868,13 @@ "description": "Table header \"Uptime\" label on stake pools list view page", "end": { "column": 3, - "line": 53 + "line": 42 }, - "file": "source/renderer/app/components/staking/stake-pools/StakePoolsTable.tsx", + "file": "source/renderer/app/components/staking/stake-pools/StakePoolsTable.messages.tsx", "id": "staking.stakePools.tableHeader.uptime", "start": { "column": 21, - "line": 49 + "line": 38 } }, { @@ -6882,13 +6882,13 @@ "description": "Table header \"Margin\" label on stake pools list view page", "end": { "column": 3, - "line": 58 + "line": 47 }, - "file": "source/renderer/app/components/staking/stake-pools/StakePoolsTable.tsx", + "file": "source/renderer/app/components/staking/stake-pools/StakePoolsTable.messages.tsx", "id": "staking.stakePools.tableHeader.margin", "start": { "column": 21, - "line": 54 + "line": 43 } }, { @@ -6896,13 +6896,13 @@ "description": "\"Pool margin\" tooltip for the Stake Pools Table.", "end": { "column": 3, - "line": 64 + "line": 53 }, - "file": "source/renderer/app/components/staking/stake-pools/StakePoolsTable.tsx", + "file": "source/renderer/app/components/staking/stake-pools/StakePoolsTable.messages.tsx", "id": "staking.stakePools.tooltip.profitMarginTooltip", "start": { "column": 28, - "line": 59 + "line": 48 } }, { @@ -6910,13 +6910,13 @@ "description": "Table header \"Roi\" label on stake pools list view page", "end": { "column": 3, - "line": 69 + "line": 58 }, - "file": "source/renderer/app/components/staking/stake-pools/StakePoolsTable.tsx", + "file": "source/renderer/app/components/staking/stake-pools/StakePoolsTable.messages.tsx", "id": "staking.stakePools.tableHeader.roi", "start": { "column": 18, - "line": 65 + "line": 54 } }, { @@ -6924,13 +6924,13 @@ "description": "Table header \"Cost\" label on stake pools list view page", "end": { "column": 3, - "line": 74 + "line": 63 }, - "file": "source/renderer/app/components/staking/stake-pools/StakePoolsTable.tsx", + "file": "source/renderer/app/components/staking/stake-pools/StakePoolsTable.messages.tsx", "id": "staking.stakePools.tableHeader.cost", "start": { "column": 19, - "line": 70 + "line": 59 } }, { @@ -6938,13 +6938,13 @@ "description": "\"Cost per epoch\" tooltip for the Stake Pools Table.", "end": { "column": 3, - "line": 80 + "line": 69 }, - "file": "source/renderer/app/components/staking/stake-pools/StakePoolsTable.tsx", + "file": "source/renderer/app/components/staking/stake-pools/StakePoolsTable.messages.tsx", "id": "staking.stakePools.tooltip.costPerEpochTooltip", "start": { "column": 26, - "line": 75 + "line": 64 } }, { @@ -6952,13 +6952,13 @@ "description": "Table header \"Produced Blocks\" label on stake pools list view page", "end": { "column": 3, - "line": 86 + "line": 75 }, - "file": "source/renderer/app/components/staking/stake-pools/StakePoolsTable.tsx", + "file": "source/renderer/app/components/staking/stake-pools/StakePoolsTable.messages.tsx", "id": "staking.stakePools.tableHeader.producedBlocks", "start": { "column": 29, - "line": 81 + "line": 70 } }, { @@ -6966,13 +6966,13 @@ "description": "\"Blocks\" tooltip for the Stake Pools Table.", "end": { "column": 3, - "line": 92 + "line": 81 }, - "file": "source/renderer/app/components/staking/stake-pools/StakePoolsTable.tsx", + "file": "source/renderer/app/components/staking/stake-pools/StakePoolsTable.messages.tsx", "id": "staking.stakePools.tooltip.producedBlocksTooltip", "start": { "column": 36, - "line": 87 + "line": 76 } }, { @@ -6980,13 +6980,13 @@ "description": "Table header \"Potential rewards\" label on stake pools list view page", "end": { "column": 3, - "line": 98 + "line": 87 }, - "file": "source/renderer/app/components/staking/stake-pools/StakePoolsTable.tsx", + "file": "source/renderer/app/components/staking/stake-pools/StakePoolsTable.messages.tsx", "id": "staking.stakePools.tableHeader.potentialRewards", "start": { "column": 31, - "line": 93 + "line": 82 } }, { @@ -6994,13 +6994,13 @@ "description": "\"Rewards\" tooltip for the Stake Pools Table.", "end": { "column": 3, - "line": 104 + "line": 93 }, - "file": "source/renderer/app/components/staking/stake-pools/StakePoolsTable.tsx", + "file": "source/renderer/app/components/staking/stake-pools/StakePoolsTable.messages.tsx", "id": "staking.stakePools.tooltip.potentialRewardsTooltip", "start": { "column": 38, - "line": 99 + "line": 88 } }, { @@ -7008,13 +7008,13 @@ "description": "Table header \"Pledge\" label on stake pools list view page", "end": { "column": 3, - "line": 109 + "line": 98 }, - "file": "source/renderer/app/components/staking/stake-pools/StakePoolsTable.tsx", + "file": "source/renderer/app/components/staking/stake-pools/StakePoolsTable.messages.tsx", "id": "staking.stakePools.tableHeader.pledge", "start": { "column": 21, - "line": 105 + "line": 94 } }, { @@ -7022,13 +7022,13 @@ "description": "\"Pledge\" tooltip for the Stake Pools Table.", "end": { "column": 3, - "line": 115 + "line": 104 }, - "file": "source/renderer/app/components/staking/stake-pools/StakePoolsTable.tsx", + "file": "source/renderer/app/components/staking/stake-pools/StakePoolsTable.messages.tsx", "id": "staking.stakePools.tooltip.pledgeTooltip", "start": { "column": 28, - "line": 110 + "line": 99 } }, { @@ -7036,17 +7036,17 @@ "description": "Table header \"Retiring\" label on stake pools list view page", "end": { "column": 3, - "line": 120 + "line": 109 }, - "file": "source/renderer/app/components/staking/stake-pools/StakePoolsTable.tsx", + "file": "source/renderer/app/components/staking/stake-pools/StakePoolsTable.messages.tsx", "id": "staking.stakePools.tableHeader.retiring", "start": { "column": 23, - "line": 116 + "line": 105 } } ], - "path": "source/renderer/app/components/staking/stake-pools/StakePoolsTable.json" + "path": "source/renderer/app/components/staking/stake-pools/StakePoolsTable.messages.json" }, { "descriptors": [ diff --git a/source/renderer/app/types/i18nTypes.ts b/source/renderer/app/types/i18nTypes.ts index ebd255b854..1d101f218d 100644 --- a/source/renderer/app/types/i18nTypes.ts +++ b/source/renderer/app/types/i18nTypes.ts @@ -9,4 +9,5 @@ export type Intl = { message: ReactIntlMessage, values?: Record ) => string; + locale: string; }; diff --git a/storybook/stories/staking/StakePoolsTable.stories.tsx b/storybook/stories/staking/StakePoolsTable.stories.tsx index 3db6532c27..22f5c94d0e 100644 --- a/storybook/stories/staking/StakePoolsTable.stories.tsx +++ b/storybook/stories/staking/StakePoolsTable.stories.tsx @@ -14,69 +14,56 @@ const listTitle = { type Props = { currentTheme: string; }; -export const StakePoolsTableStory = (props: Props) => ( - -
- -

{ + return ( + +
- -

- + + + ( max: 300, step: 1, }) - ).length - } - onTableHeaderMouseEnter={() => {}} - onTableHeaderMouseLeave={() => {}} - /> -
-
-); + )} + currentLocale="en-US" + currentTheme={props.currentTheme} + onOpenExternalLink={action('onOpenExternalLink')} + containerClassName="StakingWithNavigation_page" + numberOfRankedStakePools={ + STAKE_POOLS.slice( + 0, + number('Pools', 300, { + range: true, + min: 37, + max: 300, + step: 1, + }) + ).length + } + onTableHeaderMouseEnter={() => {}} + onTableHeaderMouseLeave={() => {}} + /> +
+ + ); +}; diff --git a/yarn.lock b/yarn.lock index 499acabd4e..07ec68460f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3292,6 +3292,12 @@ dependencies: "@types/react" "*" +"@types/react-table@^7.7.9": + version "7.7.9" + resolved "https://registry.yarnpkg.com/@types/react-table/-/react-table-7.7.9.tgz#ea82875775fc6ee71a28408dcc039396ae067c92" + dependencies: + "@types/react" "*" + "@types/react-textarea-autosize@^4.3.3": version "4.3.5" resolved "https://registry.yarnpkg.com/@types/react-textarea-autosize/-/react-textarea-autosize-4.3.5.tgz#6c4d2753fa1864c98c0b2b517f67bb1f6e4c46de" @@ -14432,6 +14438,10 @@ react-syntax-highlighter@^11.0.2: prismjs "^1.8.4" refractor "^2.4.1" +react-table@7.7.0: + version "7.7.0" + resolved "https://registry.yarnpkg.com/react-table/-/react-table-7.7.0.tgz#e2ce14d7fe3a559f7444e9ecfe8231ea8373f912" + react-textarea-autosize@^7.1.0: version "7.1.2" resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-7.1.2.tgz#70fdb333ef86bcca72717e25e623e90c336e2cda"