Skip to content

Commit

Permalink
dev(Balance): ability to hide symbol programmatically
Browse files Browse the repository at this point in the history
  • Loading branch information
theborakompanioni committed Nov 29, 2023
1 parent 55e1ee5 commit af54564
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 79 deletions.
7 changes: 7 additions & 0 deletions src/components/Balance.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,13 @@ describe('<Balance />', () => {
expect(screen.getByTestId('frozen-symbol')).toBeVisible()
})

it('should render balance without symbol', () => {
render(<Balance valueString={'123.456'} convertToUnit={SATS} showBalance={true} frozen={true} showSymbol={false} />)
expect(screen.getByTestId('sats-amount')).toBeVisible()
expect(screen.getByTestId('frozen-symbol')).toBeVisible()
expect(screen.queryByTestId('sats-symbol')).not.toBeInTheDocument()
})

it('should toggle visibility of initially hidden balance on click by default', () => {
render(<Balance valueString={`21`} convertToUnit={SATS} showBalance={false} />)
expect(screen.queryByTestId(`sats-amount`)).not.toBeInTheDocument()
Expand Down
153 changes: 74 additions & 79 deletions src/components/Balance.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { MouseEventHandler, useCallback, useEffect, useMemo, useState } from 'react'
import { PropsWithChildren, MouseEventHandler, useCallback, useEffect, useMemo, useState } from 'react'
import classNames from 'classnames'
import Sprite from './Sprite'
import { SATS, BTC, btcToSats, satsToBtc, isValidNumber, formatBtc, formatSats } from '../utils'
Expand All @@ -15,46 +15,6 @@ 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} slashed-zeroes`}
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((digit, index) => (
<span key={index} data-digit={digit}>
{digit}
</span>
))}
</span>
</span>
)
}

const SatsAmountComponent = ({ value }: { value: number }) => {
return (
<span className={`${styles.satsAmount} slashed-zeroes`} data-testid="sats-amount" data-raw-value={value}>
{formatSats(value)}
</span>
)
}

const BTC_SYMBOL = (
<span data-testid="bitcoin-symbol" className={styles.bitcoinSymbol}>
{'\u20BF'}
Expand All @@ -76,25 +36,78 @@ const FROZEN_SYMBOL = (
)

interface BalanceComponentProps {
symbol: JSX.Element
value: JSX.Element
symbol?: JSX.Element
showSymbol?: boolean
frozen?: boolean
}

const BalanceComponent = ({ symbol, value, frozen = false }: BalanceComponentProps) => {
const BalanceComponent = ({
symbol,
showSymbol = true,
frozen = false,
children,
}: PropsWithChildren<BalanceComponentProps>) => {
return (
<span
className={classNames(styles.balance, 'balance-hook', 'd-inline-flex align-items-center', {
[styles.frozen]: frozen,
})}
>
{frozen && FROZEN_SYMBOL}
{value}
{symbol}
{children}
{showSymbol && symbol}
</span>
)
}

const DECIMAL_POINT_CHAR = '.'

type BitcoinBalanceProps = Omit<BalanceComponentProps, 'symbol'> & { value: number }

const BitcoinBalance = ({ value, ...props }: BitcoinBalanceProps) => {
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 (
<BalanceComponent symbol={BTC_SYMBOL} {...props}>
<span
className={`${styles.bitcoinAmount} slashed-zeroes`}
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((digit, index) => (
<span key={index} data-digit={digit}>
{digit}
</span>
))}
</span>
</span>
</BalanceComponent>
)
}

type SatsBalanceProps = Omit<BalanceComponentProps, 'symbol'> & { value: number }

const SatsBalance = ({ value, ...props }: SatsBalanceProps) => {
return (
<BalanceComponent symbol={SAT_SYMBOL} {...props}>
<span className={`${styles.satsAmount} slashed-zeroes`} data-testid="sats-amount" data-raw-value={value}>
{formatSats(value)}
</span>
</BalanceComponent>
)
}

/**
* Options argument for Balance component.
*
Expand All @@ -109,12 +122,11 @@ const BalanceComponent = ({ symbol, value, frozen = false }: BalanceComponentPro
* @param {loading}: A loading flag that renders a placeholder while true.
* @param {enableVisibilityToggle}: A flag that controls whether the balance can mask/unmask when clicked
*/
interface BalanceProps {
type BalanceProps = Omit<BalanceComponentProps, 'symbol'> & {
valueString: string
convertToUnit: Unit
showBalance?: boolean
enableVisibilityToggle?: boolean
frozen?: boolean
}

/**
Expand All @@ -125,7 +137,7 @@ export default function Balance({
convertToUnit,
showBalance = false,
enableVisibilityToggle = !showBalance,
frozen = false,
...props
}: BalanceProps) {
const [isBalanceVisible, setIsBalanceVisible] = useState(showBalance)
const displayMode = useMemo(() => getDisplayMode(convertToUnit, isBalanceVisible), [convertToUnit, isBalanceVisible])
Expand All @@ -134,64 +146,47 @@ export default function Balance({
setIsBalanceVisible(showBalance)
}, [showBalance])

const toggleVisibility: MouseEventHandler = useCallback((e) => {
const toggleVisibility: MouseEventHandler = (e) => {
e.preventDefault()
e.stopPropagation()

setIsBalanceVisible((current) => !current)
}, [])
}

const balanceComponent = useMemo(() => {
if (displayMode === DISPLAY_MODE_HIDDEN) {
return (
<BalanceComponent
symbol={<Sprite symbol="hide" width="1.2em" height="1.2em" className={styles.hideSymbol} />}
value={<span className="slashed-zeroes">{'*****'}</span>}
frozen={frozen}
/>
{...props}
>
<span className="slashed-zeroes">{'*****'}</span>
</BalanceComponent>
)
}

const valueNumber = parseFloat(valueString)
if (!isValidNumber(valueNumber)) {
console.warn('<Balance /> component expects number input as string')
return <BalanceComponent symbol={<></>} value={<>{valueString}</>} frozen={frozen} />
return <BalanceComponent {...props}>{valueString}</BalanceComponent>
}

// Treat integers as sats.
const valueIsSats = valueString === parseInt(valueString, 10).toString()
// Treat decimal numbers as btc.
const valueIsBtc = !valueIsSats && valueString.indexOf('.') > -1

if (valueIsBtc && displayMode === DISPLAY_MODE_BTC)
return (
<BalanceComponent symbol={BTC_SYMBOL} value={<BitcoinAmountComponent value={valueNumber} />} frozen={frozen} />
)
if (valueIsSats && displayMode === DISPLAY_MODE_SATS)
return (
<BalanceComponent symbol={SAT_SYMBOL} value={<SatsAmountComponent value={valueNumber} />} frozen={frozen} />
)
if (valueIsBtc && displayMode === DISPLAY_MODE_BTC) return <BitcoinBalance value={valueNumber} {...props} />
if (valueIsSats && displayMode === DISPLAY_MODE_SATS) return <SatsBalance value={valueNumber} {...props} />

if (valueIsBtc && displayMode === DISPLAY_MODE_SATS)
return (
<BalanceComponent
symbol={SAT_SYMBOL}
value={<SatsAmountComponent value={btcToSats(valueString)} />}
frozen={frozen}
/>
)
return <SatsBalance value={btcToSats(valueString)} {...props} />
if (valueIsSats && displayMode === DISPLAY_MODE_BTC)
return (
<BalanceComponent
symbol={BTC_SYMBOL}
value={<BitcoinAmountComponent value={satsToBtc(valueString)} />}
frozen={frozen}
/>
)
return <BitcoinBalance value={satsToBtc(valueString)} {...props} />

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

if (!enableVisibilityToggle) {
return <>{balanceComponent}</>
Expand Down

0 comments on commit af54564

Please sign in to comment.