-
Joel Jeremy Marquez authored
* Auto insert decimals to mobile split transaction amounts * autoDecimals prop * Fix typecheck error
Joel Jeremy Marquez authored* Auto insert decimals to mobile split transaction amounts * autoDecimals prop * Fix typecheck error
AmountInput.tsx 4.28 KiB
// @ts-strict-ignore
import React, {
type Ref,
useRef,
useState,
useEffect,
type FocusEventHandler,
} from 'react';
import { evalArithmetic } from 'loot-core/src/shared/arithmetic';
import { amountToInteger, appendDecimals } from 'loot-core/src/shared/util';
import { useLocalPref } from '../../hooks/useLocalPref';
import { useMergedRefs } from '../../hooks/useMergedRefs';
import { SvgAdd, SvgSubtract } from '../../icons/v1';
import { type CSSProperties, theme } from '../../style';
import { Button } from '../common/Button';
import { InputWithContent } from '../common/InputWithContent';
import { View } from '../common/View';
import { useFormat } from '../spreadsheet/useFormat';
type AmountInputProps = {
id?: string;
inputRef?: Ref<HTMLInputElement>;
value: number;
zeroSign?: '-' | '+';
onChangeValue?: (value: string) => void;
onFocus?: FocusEventHandler<HTMLInputElement>;
onBlur?: FocusEventHandler<HTMLInputElement>;
onUpdate?: (amount: number) => void;
style?: CSSProperties;
textStyle?: CSSProperties;
focused?: boolean;
disabled?: boolean;
autoDecimals?: boolean;
};
export function AmountInput({
id,
inputRef,
value: initialValue,
zeroSign = '-', // + or -
onFocus,
onBlur,
onChangeValue,
onUpdate,
style,
textStyle,
focused,
disabled = false,
autoDecimals = false,
}: AmountInputProps) {
const format = useFormat();
const negative = (initialValue === 0 && zeroSign === '-') || initialValue < 0;
const initialValueAbsolute = format(Math.abs(initialValue || 0), 'financial');
const [value, setValue] = useState(initialValueAbsolute);
useEffect(() => setValue(initialValueAbsolute), [initialValueAbsolute]);
const buttonRef = useRef();
const ref = useRef<HTMLInputElement>(null);
const mergedRef = useMergedRefs<HTMLInputElement>(inputRef, ref);
const [hideFraction = false] = useLocalPref('hideFraction');
useEffect(() => {
if (focused) {
ref.current?.focus();
}
}, [focused]);
function onSwitch() {
fireUpdate(!negative);
}
function getAmount(negate) {
const valueOrInitial = Math.abs(amountToInteger(evalArithmetic(value)));
return negate ? valueOrInitial * -1 : valueOrInitial;
}
function onInputTextChange(val) {
val = autoDecimals ? appendDecimals(val, hideFraction) : val;
setValue(val ? val : '');
onChangeValue?.(val);
}
function fireUpdate(negate) {
onUpdate?.(getAmount(negate));
}
function onInputAmountBlur(e) {
if (!ref.current?.contains(e.relatedTarget)) {
fireUpdate(negative);
}
onBlur?.(e);
}
return (
<InputWithContent
id={id}
inputRef={mergedRef}
inputMode="decimal"
leftContent={
<Button
type="bare"
disabled={disabled}
aria-label={`Make ${negative ? 'positive' : 'negative'}`}
style={{ padding: '0 7px' }}
onPointerUp={onSwitch}
onPointerDown={e => e.preventDefault()}
ref={buttonRef}
>
{negative ? (
<SvgSubtract style={{ width: 8, height: 8, color: 'inherit' }} />
) : (
<SvgAdd style={{ width: 8, height: 8, color: 'inherit' }} />
)}
</Button>
}
value={value}
disabled={disabled}
focused={focused}
style={{ flex: 1, alignItems: 'stretch', ...style }}
inputStyle={{ paddingLeft: 0, ...textStyle }}
onKeyUp={e => {
if (e.key === 'Enter') {
fireUpdate(negative);
}
}}
onChangeValue={onInputTextChange}
onBlur={onInputAmountBlur}
onFocus={onFocus}
/>
);
}
export function BetweenAmountInput({ defaultValue, onChange }) {
const [num1, setNum1] = useState(defaultValue.num1);
const [num2, setNum2] = useState(defaultValue.num2);
return (
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
<AmountInput
value={num1}
onUpdate={value => {
setNum1(value);
onChange({ num1: value, num2 });
}}
style={{ color: theme.formInputText }}
/>
<View style={{ margin: '0 5px' }}>and</View>
<AmountInput
value={num2}
onUpdate={value => {
setNum2(value);
onChange({ num1, num2: value });
}}
style={{ color: theme.formInputText }}
/>
</View>
);
}