Adjust staking targets/overview sorting displays (#2999)

* Adjust staking targets/overview sorting displays

* Removce whack eslint bug comment
This commit is contained in:
Jaco Greeff 2020-06-20 14:02:42 +02:00 committed by GitHub
parent 73aa788e0f
commit d30b88b7f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 186 additions and 81 deletions

View File

@ -35,6 +35,21 @@ interface State {
foundStashes?: StakerState[];
}
function sortStashes (a: StakerState, b: StakerState): number {
return (a.isStashValidating ? 1 : (a.isStashNominating ? 5 : 99)) - (b.isStashValidating ? 1 : (b.isStashNominating ? 5 : 99));
}
function extractState (ownStashes: StakerState[]): State {
return {
bondedTotal: ownStashes.reduce((total: BN, { stakingLedger }) =>
stakingLedger
? total.add(stakingLedger.total.unwrap())
: total,
BN_ZERO),
foundStashes: ownStashes.sort(sortStashes)
};
}
function Actions ({ className = '', isInElection, next, ownStashes, targets, validators }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
@ -44,16 +59,7 @@ function Actions ({ className = '', isInElection, next, ownStashes, targets, val
const [{ bondedTotal, foundStashes }, setState] = useState<State>({});
useEffect((): void => {
ownStashes && setState({
bondedTotal: ownStashes.reduce((total: BN, { stakingLedger }) =>
stakingLedger
? total.add(stakingLedger.total.unwrap())
: total,
BN_ZERO),
foundStashes: ownStashes.sort((a, b) =>
(a.isStashValidating ? 1 : (a.isStashNominating ? 5 : 99)) - (b.isStashValidating ? 1 : (b.isStashNominating ? 5 : 99))
)
});
ownStashes && setState(extractState(ownStashes));
}, [ownStashes]);
const header = useMemo(() => [

View File

@ -31,9 +31,9 @@ interface Selected {
}
function autoPick (targets: SortedTargets): string[] {
return (targets.validators || []).reduce((result: string[], { key, numNominators }): string[] => {
return (targets.validators || []).reduce((result: string[], { key, numNominators, rewardPayout }): string[] => {
if (result.length < MAX_NOMINATIONS) {
if (numNominators && (numNominators < MAX_PAYOUTS)) {
if (numNominators && (numNominators < MAX_PAYOUTS) && !rewardPayout.isZero()) {
result.push(key);
}
}
@ -137,6 +137,7 @@ function Nominate ({ className = '', controllerId, next, nominating, onChange, s
value={
selected.map((validatorId) => (
<AddressMini
isHighlight={favorites.includes(validatorId)}
key={validatorId}
value={validatorId}
/>

View File

@ -32,8 +32,8 @@ interface Props {
onlineCount?: false | number;
onlineMessage?: boolean;
points?: string;
setNominators?: false | ((nominators: string[]) => void);
toggleFavorite: (accountId: string) => void;
withoutName: boolean;
}
interface StakingState {
@ -44,9 +44,7 @@ interface StakingState {
stakeOwn?: BN;
}
/* stylelint-disable */
const PERBILL_PERCENT = 10_000_000;
/* stylelint-enable */
function expandInfo ({ exposure, validatorPrefs }: DeriveStakingQuery): StakingState {
let nominators: [string, Balance][] = [];
@ -74,15 +72,15 @@ function expandInfo ({ exposure, validatorPrefs }: DeriveStakingQuery): StakingS
};
}
function checkVisibility (api: ApiPromise, address: string, filterName: string, accountInfo?: DeriveAccountInfo): boolean {
function checkVisibility (api: ApiPromise, address: string, filterName: string, withoutName: boolean, accountInfo?: DeriveAccountInfo): boolean {
let isVisible = false;
const filterLower = filterName.toLowerCase();
if (filterLower) {
if (filterLower || !withoutName) {
if (accountInfo) {
const { accountId, accountIndex, identity, nickname } = accountInfo;
if (accountId?.toString().includes(filterName) || accountIndex?.toString().includes(filterName)) {
if (withoutName && (accountId?.toString().includes(filterName) || accountIndex?.toString().includes(filterName))) {
isVisible = true;
} else if (api.query.identity && api.query.identity.identityOf) {
isVisible = (!!identity?.display && identity.display.toLowerCase().includes(filterLower)) ||
@ -106,7 +104,7 @@ function checkVisibility (api: ApiPromise, address: string, filterName: string,
return isVisible;
}
function Address ({ address, className = '', filterName, hasQueries, isAuthor, isElected, isFavorite, isMain, lastBlock, nominatedBy, onlineCount, onlineMessage, points, setNominators, toggleFavorite }: Props): React.ReactElement<Props> | null {
function Address ({ address, className = '', filterName, hasQueries, isAuthor, isElected, isFavorite, isMain, lastBlock, nominatedBy, onlineCount, onlineMessage, points, toggleFavorite, withoutName }: Props): React.ReactElement<Props> | null {
const { api } = useApi();
const { allAccounts } = useAccounts();
const accountInfo = useCall<DeriveAccountInfo>(api.derive.accounts.info, [address]);
@ -116,19 +114,14 @@ function Address ({ address, className = '', filterName, hasQueries, isAuthor, i
const [isNominating, setIsNominating] = useState(false);
useEffect((): void => {
if (stakingInfo) {
const info = expandInfo(stakingInfo);
setNominators && setNominators(info.nominators.map(([who]): string => who.toString()));
setStakingState(info);
}
}, [setNominators, stakingInfo]);
stakingInfo && setStakingState(expandInfo(stakingInfo));
}, [stakingInfo]);
useEffect((): void => {
setIsVisible(
checkVisibility(api, address, filterName, accountInfo)
checkVisibility(api, address, filterName, withoutName, accountInfo)
);
}, [api, accountInfo, address, filterName]);
}, [api, accountInfo, address, filterName, withoutName]);
useEffect((): void => {
!isMain && setIsNominating(
@ -144,8 +137,12 @@ function Address ({ address, className = '', filterName, hasQueries, isAuthor, i
[address]
);
if (!isVisible) {
return null;
}
return (
<tr className={`${className} ${(isAuthor || isNominating) ? 'isHighlight' : ''} ${!isVisible ? 'staking--hidden' : ''}`}>
<tr className={`${className} ${(isAuthor || isNominating) ? 'isHighlight' : ''}`}>
<Favorite
address={address}
isFavorite={isFavorite}

View File

@ -7,7 +7,7 @@ import { AccountId, Nominations } from '@polkadot/types/interfaces';
import { Authors } from '@polkadot/react-query/BlockAuthors';
import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react';
import { Input, Table } from '@polkadot/react-components';
import { Input, Table, Toggle } from '@polkadot/react-components';
import { useApi, useCall, useFavorites } from '@polkadot/react-hooks';
import { BlockAuthorsContext } from '@polkadot/react-query';
import { Option, StorageKey } from '@polkadot/types';
@ -88,7 +88,7 @@ function extractNominators (nominations: [StorageKey, Option<Nominations>][]): R
}, {});
}
function CurrentList ({ hasQueries, isIntentions, next, setNominators, stakingOverview }: Props): React.ReactElement<Props> | null {
function CurrentList ({ hasQueries, isIntentions, next, stakingOverview }: Props): React.ReactElement<Props> | null {
const { t } = useTranslation();
const { api } = useApi();
const { byAuthor, eraPoints, lastBlockAuthors } = useContext(isIntentions ? EmptyAuthorsContext : BlockAuthorsContext);
@ -98,6 +98,7 @@ function CurrentList ({ hasQueries, isIntentions, next, setNominators, stakingOv
const [{ elected, validators, waiting }, setFiltered] = useState<Filtered>({});
const [nameFilter, setNameFilter] = useState<string>('');
const [nominatedBy, setNominatedBy] = useState<Record<string, [string, number][]> | null>();
const [withoutName, setWithoutName] = useState(true);
useEffect((): void => {
stakingOverview && setFiltered(
@ -129,14 +130,30 @@ function CurrentList ({ hasQueries, isIntentions, next, setNominators, stakingOv
], [t]);
const filter = useMemo(() => (
<Input
autoFocus
isFull
label={t<string>('filter by name, address or index')}
onChange={setNameFilter}
value={nameFilter}
/>
), [nameFilter, t]);
<div>
<Input
autoFocus
isFull
label={t<string>('filter by name, address or index')}
onChange={setNameFilter}
value={nameFilter}
/>
<div className='staking--optionsBar'>
{api.query.identity && (
<Toggle
className='staking--buttonToggle'
label={
withoutName
? t<string>('with/without identity')
: t<string>('only with an identity')
}
onChange={setWithoutName}
value={withoutName}
/>
)}
</div>
</div>
), [api, nameFilter, t, withoutName]);
const _renderRows = useCallback(
(addresses?: AccountExtend[], isMain?: boolean): React.ReactNode[] =>
@ -155,11 +172,11 @@ function CurrentList ({ hasQueries, isIntentions, next, setNominators, stakingOv
onlineCount={recentlyOnline?.[address]?.blockCount.toNumber()}
onlineMessage={recentlyOnline?.[address]?.hasMessage}
points={eraPoints[address]}
setNominators={setNominators}
toggleFavorite={toggleFavorite}
withoutName={withoutName}
/>
)),
[byAuthor, eraPoints, hasQueries, lastBlockAuthors, nameFilter, nominatedBy, recentlyOnline, setNominators, toggleFavorite]
[byAuthor, eraPoints, hasQueries, lastBlockAuthors, nameFilter, nominatedBy, recentlyOnline, toggleFavorite, withoutName]
);
return isIntentions

View File

@ -16,7 +16,7 @@ interface Props {
className?: string;
isVisible: boolean;
next?: string[];
nominators: string[];
nominators?: string[];
stakingOverview?: DeriveStakingOverview;
}
@ -37,7 +37,7 @@ function Summary ({ className = '', isVisible, next, nominators, stakingOverview
{next.length}
</CardSummary>
)}
{!!nominators.length && (
{!!nominators?.length && (
<CardSummary label={t<string>('nominators')}>
{nominators.length}
</CardSummary>

View File

@ -13,18 +13,16 @@ interface Props extends BareProps {
hasQueries: boolean;
isIntentions?: boolean;
next?: string[];
setNominators?: (nominators: string[]) => void;
stakingOverview?: DeriveStakingOverview;
}
function Overview ({ className = '', hasQueries, isIntentions, next, setNominators, stakingOverview }: Props): React.ReactElement<Props> {
function Overview ({ className = '', hasQueries, isIntentions, next, stakingOverview }: Props): React.ReactElement<Props> {
return (
<div className={`staking--Overview ${className}`}>
<CurrentList
hasQueries={hasQueries}
isIntentions={isIntentions}
next={next}
setNominators={setNominators}
stakingOverview={stakingOverview}
/>
</div>

View File

@ -2,12 +2,16 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { DeriveAccountInfo } from '@polkadot/api-derive/types';
import { ValidatorInfo } from '../types';
import React, { useCallback } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import { ApiPromise } from '@polkadot/api';
import { AddressSmall, Icon, Toggle } from '@polkadot/react-components';
import { FormatBalance } from '@polkadot/react-query';
import { useApi, useCall } from '@polkadot/react-hooks';
import { formatNumber } from '@polkadot/util';
import keyring from '@polkadot/ui-keyring';
import MaxBadge from '../MaxBadge';
import Favorite from '../Overview/Address/Favorite';
@ -18,21 +22,61 @@ interface Props {
isSelected: boolean;
toggleFavorite: (accountId: string) => void;
toggleSelected: (accountId: string) => void;
withoutName: boolean;
}
function Validator ({ canSelect, info: { accountId, bondOther, bondOwn, bondTotal, commissionPer, isCommission, isFavorite, isNominating, key, numNominators, rankOverall, rewardPayout, validatorPayment }, isSelected, toggleFavorite, toggleSelected }: Props): React.ReactElement<Props> {
function checkVisibility (api: ApiPromise, accountInfo: DeriveAccountInfo): boolean {
let isVisible = false;
const { accountId, identity, nickname } = accountInfo;
if (api.query.identity && api.query.identity.identityOf) {
isVisible = !!(identity?.display && identity.display.toString());
} else if (nickname) {
isVisible = !!nickname.toString();
}
if (!isVisible && accountId) {
const account = keyring.getAddress(accountId.toString());
isVisible = !!account?.meta?.name;
}
return isVisible;
}
function Validator ({ canSelect, info, isSelected, toggleFavorite, toggleSelected, withoutName }: Props): React.ReactElement<Props> | null {
const { api } = useApi();
const accountInfo = useCall<DeriveAccountInfo>(api.derive.accounts.info, [info.accountId]);
const [hasName, setHasName] = useState(true);
useEffect((): void => {
if (accountInfo) {
const hasIdentity = checkVisibility(api, accountInfo);
info.hasIdentity = hasIdentity;
setHasName(hasIdentity);
}
}, [accountInfo, api, info]);
const _onQueryStats = useCallback(
(): void => {
window.location.hash = `/staking/query/${key}`;
window.location.hash = `/staking/query/${info.key}`;
},
[key]
[info.key]
);
const _toggleSelected = useCallback(
() => toggleSelected(key),
[key, toggleSelected]
() => toggleSelected(info.key),
[info.key, toggleSelected]
);
if (!hasName && !withoutName) {
return null;
}
const { accountId, bondOther, bondOwn, bondTotal, commissionPer, isCommission, isFavorite, isNominating, key, numNominators, rankOverall, rewardPayout, validatorPayment } = info;
return (
<tr className={`${isNominating ? 'isHighlight' : ''}`}>
<Favorite
@ -47,6 +91,7 @@ function Validator ({ canSelect, info: { accountId, bondOther, bondOwn, bondTota
<td className='address all'>
<AddressSmall value={accountId} />
</td>
<td className='number'>{numNominators || ''}</td>
<td className='number'>
{
isCommission
@ -62,7 +107,7 @@ function Validator ({ canSelect, info: { accountId, bondOther, bondOwn, bondTota
value={bondOther}
/>
</td>
<td className='number together'><FormatBalance value={rewardPayout} /></td>
<td className='number together'>{!rewardPayout.isZero() && <FormatBalance value={rewardPayout} />}</td>
<td>
{(canSelect || isSelected) && (
<Toggle

View File

@ -2,12 +2,14 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { DeriveStakingOverview } from '@polkadot/api-derive/types';
import { StakerState } from '@polkadot/react-hooks/types';
import { SortedTargets, TargetSortBy, ValidatorInfo } from '../types';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import styled from 'styled-components';
import { Icon, InputBalance, Table, Button } from '@polkadot/react-components';
import { Button, Icon, InputBalance, Table, Toggle } from '@polkadot/react-components';
import { useApi } from '@polkadot/react-hooks';
import { MAX_NOMINATIONS } from '../constants';
import { useTranslation } from '../translate';
@ -18,7 +20,9 @@ import useOwnNominators from './useOwnNominators';
interface Props {
className?: string;
next?: string[];
ownStashes?: StakerState[];
stakingOverview?: DeriveStakingOverview;
targets: SortedTargets;
}
@ -43,9 +47,11 @@ function sort (sortBy: TargetSortBy, sortFromMax: boolean, validators: Validator
function Targets ({ className = '', ownStashes, targets: { calcWith, lastReward, nominators, setCalcWith, toggleFavorite, totalStaked, validators } }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const ownNominators = useOwnNominators(ownStashes);
const [selected, setSelected] = useState<string[]>([]);
const [sorted, setSorted] = useState<number[] | undefined>();
const [withoutName, setWithoutName] = useState(true);
const [{ sortBy, sortFromMax }, setSortBy] = useState<SortState>({ sortBy: 'rankOverall', sortFromMax: true });
useEffect((): void => {
@ -75,11 +81,15 @@ function Targets ({ className = '', ownStashes, targets: { calcWith, lastReward,
const _selectProfitable = useCallback(
() => setSelected(
(validators || [])
.filter((_, index) => index < MAX_NOMINATIONS)
.map(({ key }) => key)
(validators || []).reduce((result: string[], { hasIdentity, key, rewardPayout }): string[] => {
if ((result.length < MAX_NOMINATIONS) && (withoutName || hasIdentity) && !rewardPayout.isZero()) {
result.push(key);
}
return result;
}, [])
),
[validators]
[validators, withoutName]
);
const labels = useMemo(
@ -87,15 +97,16 @@ function Targets ({ className = '', ownStashes, targets: { calcWith, lastReward,
rankBondOther: t<string>('other stake'),
rankBondOwn: t<string>('own stake'),
rankBondTotal: t<string>('total stake'),
rankComm: t<string>('commission'),
rankOverall: t<string>('profit/era est')
rankComm: t<string>('comm.'),
rankNumNominators: t<string>('nominators'),
rankOverall: t<string>('profit/era')
}),
[t]
);
const header = useMemo(() => [
[t('validators'), 'start', 4],
...['rankComm', 'rankBondTotal', 'rankBondOwn', 'rankBondOther', 'rankOverall'].map((header) => [
...['rankNumNominators', 'rankComm', 'rankBondTotal', 'rankBondOwn', 'rankBondOther', 'rankOverall'].map((header) => [
<>{labels[header]}<Icon name={sortBy === header ? (sortFromMax ? 'chevron down' : 'chevron up') : 'minus'} /></>,
sorted ? `isClickable ${sortBy === header ? 'ui--highlight--border' : ''} number` : 'number',
1,
@ -106,17 +117,33 @@ function Targets ({ className = '', ownStashes, targets: { calcWith, lastReward,
const filter = useMemo(() => (
sorted && (
<InputBalance
className='balanceInput'
help={t<string>('The amount that will be used on a per-validator basis to calculate profits for that validator.')}
isFull
isZeroable={false}
label={t<string>('amount to use for estimation')}
onChange={setCalcWith}
value={calcWith}
/>
<div>
<InputBalance
className='balanceInput'
help={t<string>('The amount that will be used on a per-validator basis to calculate profits for that validator.')}
isFull
isZeroable={false}
label={t<string>('amount to use for estimation')}
onChange={setCalcWith}
value={calcWith}
/>
<div className='staking--optionsBar'>
{api.query.identity && (
<Toggle
className='staking--buttonToggle'
label={
withoutName
? t<string>('with/without identity')
: t<string>('only with an identity')
}
onChange={setWithoutName}
value={withoutName}
/>
)}
</div>
</div>
)
), [calcWith, setCalcWith, sorted, t]);
), [api, calcWith, setCalcWith, sorted, t, withoutName]);
return (
<div className={className}>
@ -151,6 +178,7 @@ function Targets ({ className = '', ownStashes, targets: { calcWith, lastReward,
key={validators[index].key}
toggleFavorite={toggleFavorite}
toggleSelected={_toggleSelected}
withoutName={withoutName}
/>
)}
</Table>
@ -166,6 +194,7 @@ export default React.memo(styled(Targets)`
margin-left: 0.5rem;
}
}
.ui--Table {
overflow-x: auto;
}

View File

@ -6,7 +6,7 @@ import { DeriveStakingOverview } from '@polkadot/api-derive/types';
import { AppProps as Props } from '@polkadot/react-components/types';
import { ElectionStatus } from '@polkadot/types/interfaces';
import React, { useEffect, useMemo, useReducer, useState } from 'react';
import React, { useEffect, useMemo, useState } from 'react';
import { Route, Switch } from 'react-router';
import { useLocation } from 'react-router-dom';
import styled from 'styled-components';
@ -30,10 +30,6 @@ interface Validators {
validators?: string[];
}
function reduceNominators (nominators: string[], additional: string[]): string[] {
return nominators.concat(...additional.filter((nominator): boolean => !nominators.includes(nominator)));
}
function StakingApp ({ basePath, className = '' }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
@ -47,7 +43,6 @@ function StakingApp ({ basePath, className = '' }: Props): React.ReactElement<Pr
const isInElection = useCall<boolean>(api.query.staking?.eraElectionStatus, [], {
transform: (status: ElectionStatus) => status.isOpen
});
const [nominators, dispatchNominators] = useReducer(reduceNominators, [] as string[]);
const hasQueries = useMemo(
(): boolean =>
hasAccounts && !!(api.query.imOnline?.authoredBlocks) && !!(api.query.staking.activeEra),
@ -114,7 +109,7 @@ function StakingApp ({ basePath, className = '' }: Props): React.ReactElement<Pr
<Summary
isVisible={pathname === basePath}
next={next}
nominators={nominators}
nominators={targets.nominators}
stakingOverview={stakingOverview}
/>
<Switch>
@ -126,7 +121,9 @@ function StakingApp ({ basePath, className = '' }: Props): React.ReactElement<Pr
</Route>
<Route path={`${basePath}/targets`}>
<Targets
next={next}
ownStashes={ownStashes}
stakingOverview={stakingOverview}
targets={targets}
/>
</Route>
@ -152,7 +149,6 @@ function StakingApp ({ basePath, className = '' }: Props): React.ReactElement<Pr
className={basePath === pathname ? '' : 'staking--hidden'}
hasQueries={hasQueries}
next={next}
setNominators={dispatchNominators}
stakingOverview={stakingOverview}
/>
</main>
@ -175,4 +171,13 @@ export default React.memo(styled(StakingApp)`
margin: 2.5rem auto;
}
}
.staking--optionsBar {
text-align: right;
.staking--buttonToggle {
margin-left: 1.5rem;
margin-top: 0.5rem;
}
}
`);

View File

@ -33,6 +33,7 @@ interface ValidatorInfoRank {
rankBondOwn: number;
rankBondTotal: number;
rankComm: number;
rankNumNominators: number;
rankOverall: number;
rankPayment: number;
rankReward: number;
@ -45,6 +46,7 @@ export interface ValidatorInfo extends ValidatorInfoRank {
bondShare: number;
bondTotal: Balance;
commissionPer: number;
hasIdentity: boolean;
isCommission: boolean;
isFavorite: boolean;
isNominating: boolean;

View File

@ -44,6 +44,8 @@ function sortValidators (list: ValidatorInfo[]): ValidatorInfo[] {
.map(mapIndex('rankPayment'))
.sort((a, b) => a.rewardSplit.cmp(b.rewardSplit))
.map(mapIndex('rankReward'))
.sort((a, b) => b.numNominators - a.numNominators)
.map(mapIndex('rankNumNominators'))
.sort((a, b): number => {
const cmp = b.rewardPayout.cmp(a.rewardPayout);
@ -106,6 +108,7 @@ function extractInfo (allAccounts: string[], amount: BN = baseBalance(), elected
bondShare: 0,
bondTotal,
commissionPer: (((prefs as ValidatorPrefs).commission?.unwrap() || BN_ZERO).toNumber() / 10_000_000),
hasIdentity: false,
isCommission: !!(prefs as ValidatorPrefs).commission,
isFavorite: favorites.includes(key),
isNominating,
@ -115,6 +118,7 @@ function extractInfo (allAccounts: string[], amount: BN = baseBalance(), elected
rankBondOwn: 0,
rankBondTotal: 0,
rankComm: 0,
rankNumNominators: 0,
rankOverall: 0,
rankPayment: 0,
rankReward: 0,

View File

@ -22,6 +22,7 @@ interface Props extends BareProps {
bonded?: BN | BN[];
children?: React.ReactNode;
iconInfo?: React.ReactNode;
isHighlight?: boolean;
isPadded?: boolean;
isShort?: boolean;
label?: React.ReactNode;
@ -39,13 +40,13 @@ interface Props extends BareProps {
withShrink?: boolean;
}
function AddressMini ({ balance, bonded, children, className = '', iconInfo, isPadded = true, label, labelBalance, noLookup, summary, value, withAddress = true, withBalance = false, withBonded = false, withLockedVote = false, withName = true, withShrink = false, withSidebar = true }: Props): React.ReactElement<Props> | null {
function AddressMini ({ balance, bonded, children, className = '', iconInfo, isHighlight, isPadded = true, label, labelBalance, noLookup, summary, value, withAddress = true, withBalance = false, withBonded = false, withLockedVote = false, withName = true, withShrink = false, withSidebar = true }: Props): React.ReactElement<Props> | null {
if (!value) {
return null;
}
return (
<div className={classes('ui--AddressMini', isPadded ? 'padded' : '', withShrink ? 'withShrink' : '', className)}>
<div className={classes('ui--AddressMini', isHighlight ? 'isHighlight' : '', isPadded ? 'padded' : '', withShrink ? 'withShrink' : '', className)}>
{label && (
<label className='ui--AddressMini-label'>{label}</label>
)}