Skip to content

Commit

Permalink
ui(style): use only css to style bitcoin amount
Browse files Browse the repository at this point in the history
  • Loading branch information
theborakompanioni committed Nov 6, 2023
1 parent 0f3ec21 commit 31cfff0
Show file tree
Hide file tree
Showing 11 changed files with 184 additions and 241 deletions.
34 changes: 34 additions & 0 deletions src/components/Balance.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
:root {
--jam-deemphasize-opacity: 0.5;
}

[data-theme='dark'] {
--jam-deemphasize-opacity: 0.5;
}

.bitcoinSymbol {
padding-right: 0.1em;
}

/** Integer Part **/
.bitcoinAmount[data-integer-part-is-zero="true"] .integerPart,
/** Decimal Point **/
.bitcoinAmount[data-integer-part-is-zero="true"] .decimalPoint,
.bitcoinAmount[data-fractional-part-starts-with-zero="true"] .decimalPoint,
/** Fractional Part **/
.bitcoinAmount[data-integer-part-is-zero="false"] .fractionalPart,
.bitcoinAmount[data-integer-part-is-zero="true"] .fractionalPart :nth-child(1):is(span[data-value="0"]),
.bitcoinAmount[data-integer-part-is-zero="true"] .fractionalPart :nth-child(1):is(span[data-value="0"]) + span[data-value="0"],
.bitcoinAmount[data-integer-part-is-zero="true"] .fractionalPart :nth-child(1):is(span[data-value="0"]) + span[data-value="0"] + span[data-value="0"],
.bitcoinAmount[data-integer-part-is-zero="true"] .fractionalPart :nth-child(1):is(span[data-value="0"]) + span[data-value="0"] + span[data-value="0"] + span[data-value="0"],
.bitcoinAmount[data-integer-part-is-zero="true"] .fractionalPart :nth-child(1):is(span[data-value="0"]) + span[data-value="0"] + span[data-value="0"] + span[data-value="0"] + span[data-value="0"],
.bitcoinAmount[data-integer-part-is-zero="true"] .fractionalPart :nth-child(1):is(span[data-value="0"]) + span[data-value="0"] + span[data-value="0"] + span[data-value="0"] + span[data-value="0"] + span[data-value="0"],
.bitcoinAmount[data-integer-part-is-zero="true"] .fractionalPart :nth-child(1):is(span[data-value="0"]) + span[data-value="0"] + span[data-value="0"] + span[data-value="0"] + span[data-value="0"] + span[data-value="0"] + span[data-value="0"],
.bitcoinAmount[data-integer-part-is-zero="true"] .fractionalPart :nth-child(1):is(span[data-value="0"]) + span[data-value="0"] + span[data-value="0"] + span[data-value="0"] + span[data-value="0"] + span[data-value="0"] + span[data-value="0"] + span[data-value="0"] {
opacity: var(--jam-deemphasize-opacity);
}

.bitcoinAmount .fractionalPart :nth-child(3)::before,
.bitcoinAmount .fractionalPart :nth-child(6)::before {
content: '\202F';
}
95 changes: 22 additions & 73 deletions src/components/Balance.test.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
import { act } from 'react-dom/test-utils'
import { render } from '@testing-library/react'
import user from '@testing-library/user-event'
import { render, screen } from '../testUtils'
import { act } from 'react-dom/test-utils'
import { screen } from '../testUtils'
import { BTC, SATS } from '../utils'

import Balance from './Balance'
import { FormatBtcProps } from '../format'

const TEST_FORMAT_BTC_PROPS: FormatBtcProps = {
deemphasize: (val) => val, // render as plain string (to be able to use `getByText`)
}

describe('<Balance />', () => {
it('should render invalid param as given', () => {
Expand All @@ -17,16 +12,14 @@ describe('<Balance />', () => {
})

it('should render BTC using satscomma formatting', () => {
render(
<Balance valueString={'123.456'} convertToUnit={BTC} showBalance={true} formatBtcProps={TEST_FORMAT_BTC_PROPS} />,
)
expect(screen.getByText(`123.45 600 000`)).toBeInTheDocument()
render(<Balance valueString={'123.456'} convertToUnit={BTC} showBalance={true} />)
expect(screen.getByTestId('bitcoin-amount').dataset.formattedValue).toBe(`123.45600000`)
})

it('should hide balance for BTC by default', () => {
render(<Balance valueString={'123.456'} convertToUnit={BTC} />)
expect(screen.getByText(`*****`)).toBeInTheDocument()
expect(screen.queryByText(`123.45 600 000`)).not.toBeInTheDocument()
expect(screen.queryByTestId('bitcoin-amount')).not.toBeInTheDocument()
})

it('should hide balance for SATS by default', () => {
Expand All @@ -36,15 +29,8 @@ describe('<Balance />', () => {
})

it('should render a string BTC value correctly as BTC', () => {
render(
<Balance
valueString={'123.03224961'}
convertToUnit={BTC}
showBalance={true}
formatBtcProps={TEST_FORMAT_BTC_PROPS}
/>,
)
expect(screen.getByText(`123.03 224 961`)).toBeInTheDocument()
render(<Balance valueString={'123.03224961'} convertToUnit={BTC} showBalance={true} />)
expect(screen.getByTestId('bitcoin-amount').dataset.formattedValue).toBe(`123.03224961`)
})

it('should render a string BTC value correctly as SATS', () => {
Expand All @@ -53,15 +39,8 @@ describe('<Balance />', () => {
})

it('should render a zero string BTC value correctly as BTC', () => {
render(
<Balance
valueString={'0.00000000'}
convertToUnit={BTC}
showBalance={true}
formatBtcProps={TEST_FORMAT_BTC_PROPS}
/>,
)
expect(screen.getByText(`0.00 000 000`)).toBeInTheDocument()
render(<Balance valueString={'0.00000000'} convertToUnit={BTC} showBalance={true} />)
expect(screen.getByTestId('bitcoin-amount').dataset.formattedValue).toBe(`0.00000000`)
})

it('should render a zero string BTC value correctly as SATS', () => {
Expand All @@ -70,15 +49,8 @@ describe('<Balance />', () => {
})

it('should render a large string BTC value correctly as BTC', () => {
render(
<Balance
valueString={'20999999.97690000'}
convertToUnit={BTC}
showBalance={true}
formatBtcProps={TEST_FORMAT_BTC_PROPS}
/>,
)
expect(screen.getByText(`20,999,999.97 690 000`)).toBeInTheDocument()
render(<Balance valueString={'20999999.97690000'} convertToUnit={BTC} showBalance={true} />)
expect(screen.getByTestId('bitcoin-amount').dataset.formattedValue).toBe(`20,999,999.97690000`)
})

it('should render a large string BTC value correctly as SATS', () => {
Expand All @@ -87,15 +59,8 @@ describe('<Balance />', () => {
})

it('should render a max string BTC value correctly as BTC', () => {
render(
<Balance
valueString={'21000000.00000000'}
convertToUnit={BTC}
showBalance={true}
formatBtcProps={TEST_FORMAT_BTC_PROPS}
/>,
)
expect(screen.getByText(`21,000,000.00 000 000`)).toBeInTheDocument()
render(<Balance valueString={'21000000.00000000'} convertToUnit={BTC} showBalance={true} />)
expect(screen.getByTestId('bitcoin-amount').dataset.formattedValue).toBe(`21,000,000.00000000`)
})

it('should render a max string BTC value correctly as SATS', () => {
Expand All @@ -109,15 +74,13 @@ describe('<Balance />', () => {
})

it('should render a string SATS value correctly as BTC', () => {
render(
<Balance valueString={'43000'} convertToUnit={BTC} showBalance={true} formatBtcProps={TEST_FORMAT_BTC_PROPS} />,
)
expect(screen.getByText(`0.00 043 000`)).toBeInTheDocument()
render(<Balance valueString={'43000'} convertToUnit={BTC} showBalance={true} />)
expect(screen.getByTestId('bitcoin-amount').dataset.formattedValue).toBe(`0.00043000`)
})

it('should render a zero string SATS value correctly as BTC', () => {
render(<Balance valueString={'0'} convertToUnit={BTC} showBalance={true} formatBtcProps={TEST_FORMAT_BTC_PROPS} />)
expect(screen.getByText(`0.00 000 000`)).toBeInTheDocument()
render(<Balance valueString={'0'} convertToUnit={BTC} showBalance={true} />)
expect(screen.getByTestId('bitcoin-amount').dataset.formattedValue).toBe(`0.00000000`)
})

it('should render a zero string SATS value correctly as SATS', () => {
Expand All @@ -126,15 +89,8 @@ describe('<Balance />', () => {
})

it('should render a large string SATS value correctly as BTC', () => {
render(
<Balance
valueString={'2099999997690000'}
convertToUnit={BTC}
showBalance={true}
formatBtcProps={TEST_FORMAT_BTC_PROPS}
/>,
)
expect(screen.getByText(`20,999,999.97 690 000`)).toBeInTheDocument()
render(<Balance valueString={'2099999997690000'} convertToUnit={BTC} showBalance={true} />)
expect(screen.getByTestId('bitcoin-amount').dataset.formattedValue).toBe(`20,999,999.97690000`)
})

it('should render a large string SATS value correctly as SATS', () => {
Expand All @@ -143,15 +99,8 @@ describe('<Balance />', () => {
})

it('should render a max string SATS value correctly as BTC', () => {
render(
<Balance
valueString={'2100000000000000'}
convertToUnit={BTC}
showBalance={true}
formatBtcProps={TEST_FORMAT_BTC_PROPS}
/>,
)
expect(screen.getByText(`21,000,000.00 000 000`)).toBeInTheDocument()
render(<Balance valueString={'2100000000000000'} convertToUnit={BTC} showBalance={true} />)
expect(screen.getByTestId('bitcoin-amount').dataset.formattedValue).toBe(`21,000,000.00000000`)
})

it('should render a max string SATS value correctly as SATS', () => {
Expand Down
85 changes: 66 additions & 19 deletions src/components/Balance.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { MouseEventHandler, useCallback, useEffect, useMemo, useState } from 'react'
import { SATS, BTC, btcToSats, satsToBtc, isValidNumber } from '../utils'
import { FormatBtcProps, formatBtc, formatSats } from '../format'
import { SATS, BTC, btcToSats, satsToBtc, isValidNumber, formatBtc, formatSats } from '../utils'
import Sprite from './Sprite'
import styles from './Balance.module.css'

const DISPLAY_MODE_BTC = 0
const DISPLAY_MODE_SATS = 1
Expand All @@ -14,6 +14,42 @@ const getDisplayMode = (unit: Unit, showBalance: boolean) => {
return DISPLAY_MODE_HIDDEN
}

const DECIMAL_POINT_CHAR = '.'

const BitcoinAmountComponent = ({ value }: { value: number }) => {
const numberString = formatBtc(value)
const [integerPart, fractionalPart] = numberString.split(DECIMAL_POINT_CHAR)

const fractionPartArray = fractionalPart.split('')
const integerPartIsZero = integerPart === '0'
const fractionalPartStartsWithZero = fractionPartArray[0] === '0'

return (
<span
className={styles.bitcoinAmount}
data-testid="bitcoin-amount"
data-integer-part-is-zero={integerPartIsZero}
data-fractional-part-starts-with-zero={fractionalPartStartsWithZero}
data-raw-value={value}
data-formatted-value={numberString}
>
<span className={styles.integerPart}>{integerPart}</span>
<span className={styles.decimalPoint}>{DECIMAL_POINT_CHAR}</span>
<span className={styles.fractionalPart}>
{fractionPartArray.map((val, index) => (
<span key={index} data-value={val}>
{val}
</span>
))}
</span>
</span>
)
}

const SatsAmountComponent = ({ value }: { value: number }) => {
return <>{formatSats(value)}</>
}

interface BalanceComponentProps {
symbol: JSX.Element
value: string | JSX.Element
Expand All @@ -22,14 +58,18 @@ interface BalanceComponentProps {

const BalanceComponent = ({ symbol, value, symbolIsPrefix }: BalanceComponentProps) => {
return (
<span className="d-inline-flex align-items-center">
<span className="d-inline-flex align-items-center balance-hook">
{symbolIsPrefix && symbol}
<span className="d-inline-flex align-items-center slashed-zeroes balance-value-hook">{value}</span>
<span className="slashed-zeroes">{value}</span>
{!symbolIsPrefix && symbol}
</span>
)
}

const BTC_SYMBOL_ELEMENT = <span className={styles.bitcoinSymbol}>{'\u20BF'}</span>

const SAT_SYMBOL_ELEMENT = <Sprite className={styles.satsSymbol} symbol="sats" width="1.2em" height="1.2em" />

/**
* Options argument for Balance component.
*
Expand All @@ -49,7 +89,6 @@ interface BalanceProps {
convertToUnit: Unit
showBalance?: boolean
enableVisibilityToggle?: boolean
formatBtcProps?: FormatBtcProps
}

/**
Expand All @@ -60,7 +99,6 @@ export default function Balance({
convertToUnit,
showBalance = false,
enableVisibilityToggle = !showBalance,
formatBtcProps,
}: BalanceProps) {
const [isBalanceVisible, setIsBalanceVisible] = useState(showBalance)
const displayMode = useMemo(() => getDisplayMode(convertToUnit, isBalanceVisible), [convertToUnit, isBalanceVisible])
Expand Down Expand Up @@ -102,34 +140,43 @@ export default function Balance({
// Treat decimal numbers as btc.
const valueIsBtc = !valueIsSats && valueString.indexOf('.') > -1

const btcSymbol = (
<span className="balance-symbol-hook" style={{ paddingRight: '0.1em' }}>
{'\u20BF'}
</span>
)
const satSymbol = <Sprite className="balance-symbol-hook" symbol="sats" width="1.2em" height="1.2em" />

if (valueIsBtc && displayMode === DISPLAY_MODE_BTC)
return (
<BalanceComponent symbol={btcSymbol} value={formatBtc(valueNumber, formatBtcProps)} symbolIsPrefix={true} />
<BalanceComponent
symbol={BTC_SYMBOL_ELEMENT}
value={<BitcoinAmountComponent value={valueNumber} />}
symbolIsPrefix={true}
/>
)
if (valueIsSats && displayMode === DISPLAY_MODE_SATS)
return <BalanceComponent symbol={satSymbol} value={formatSats(valueNumber)} symbolIsPrefix={false} />
return (
<BalanceComponent
symbol={SAT_SYMBOL_ELEMENT}
value={<SatsAmountComponent value={valueNumber} />}
symbolIsPrefix={false}
/>
)

if (valueIsBtc && displayMode === DISPLAY_MODE_SATS)
return <BalanceComponent symbol={satSymbol} value={formatSats(btcToSats(valueString))} symbolIsPrefix={false} />
return (
<BalanceComponent
symbol={SAT_SYMBOL_ELEMENT}
value={<SatsAmountComponent value={btcToSats(valueString)} />}
symbolIsPrefix={false}
/>
)
if (valueIsSats && displayMode === DISPLAY_MODE_BTC)
return (
<BalanceComponent
symbol={btcSymbol}
value={formatBtc(satsToBtc(valueString), formatBtcProps)}
symbol={BTC_SYMBOL_ELEMENT}
value={<BitcoinAmountComponent value={satsToBtc(valueString)} />}
symbolIsPrefix={true}
/>
)

console.warn('<Balance /> component cannot determine balance format')
return <BalanceComponent symbol={<></>} value={valueString} symbolIsPrefix={false} />
}, [valueString, displayMode, formatBtcProps])
}, [valueString, displayMode])

if (!enableVisibilityToggle) {
return <>{balanceComponent}</>
Expand Down
2 changes: 1 addition & 1 deletion src/components/Cheatsheet.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { PropsWithChildren } from 'react'
import { PropsWithChildren } from 'react'
import * as rb from 'react-bootstrap'
import { Link } from 'react-router-dom'
import { Trans, useTranslation } from 'react-i18next'
Expand Down
3 changes: 1 addition & 2 deletions src/components/Send/FeeBreakdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import classNames from 'classnames'
import Balance from '../Balance'
import * as rb from 'react-bootstrap'
import { useSettings } from '../../context/SettingsContext'
import { SATS, factorToPercentage } from '../../utils'
import { formatSats } from '../../format'
import { SATS, factorToPercentage, formatSats } from '../../utils'
import { FeeValues } from '../../hooks/Fees'
import { AmountSats } from '../../libs/JmWalletApi'

Expand Down
3 changes: 1 addition & 2 deletions src/components/Send/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@ import { buildCoinjoinRequirementSummary } from '../../hooks/CoinjoinRequirement

import { routes } from '../../constants/routes'
import { JM_MINIMUM_MAKERS_DEFAULT } from '../../constants/config'
import { SATS, isValidNumber, scrollToTop } from '../../utils'
import { formatSats } from '../../format'
import { SATS, formatSats, isValidNumber, scrollToTop } from '../../utils'

import {
initialNumCollaborators,
Expand Down
8 changes: 2 additions & 6 deletions src/components/fb/FidelityBondSteps.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,8 @@
gap: 2rem;
}

.jarsContainer :global .balance-value-hook {
font-size: 0.7rem;
}

.jarsContainer :global .balance-symbol-hook {
font-size: 0.7rem;
.jarsContainer :global .balance-hook {
font-size: 0.75rem;
}

@media only screen and (min-width: 768px) {
Expand Down
Loading

0 comments on commit 31cfff0

Please sign in to comment.