You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
93 lines
3.1 KiB
JavaScript
93 lines
3.1 KiB
JavaScript
import React, {useState, useRef} from 'react'
|
|
import useMeasure from 'use-measure'
|
|
|
|
import styles from './styles.module.css'
|
|
|
|
function truncateFixed(num, fixed) {
|
|
var re = new RegExp('^-?\\d+(?:\.\\d{0,' + (fixed || -1) + '})?');
|
|
return num.toString().match(re)[0];
|
|
}
|
|
|
|
export default function Input({label, prefix, numDecimals, error, hint, name, value, onChange, onBlur, isValid}){
|
|
const [currentDecimals, setCurrentDecimals] = useState((numDecimals === undefined || value === undefined || value === '') ? -1 : numDecimals)
|
|
const currentValue = value === undefined ? 0 : typeof value === 'number' ? value : typeof value === 'string' ? parseInt(value || '0', 10) : 0
|
|
|
|
const spanRef = useRef();
|
|
const {width} = useMeasure(spanRef);
|
|
|
|
const updateParent = value => onChange({target: {value}})
|
|
|
|
const onKeyDown = (ev) => {
|
|
const {key} = ev
|
|
switch(key) {
|
|
case 'Backspace':
|
|
setCurrentDecimals(dec => Math.max(dec - 1, -1))
|
|
if(currentDecimals > 0) {
|
|
updateParent(parseFloat(truncateFixed(currentValue, currentDecimals - 1)))
|
|
} else {
|
|
updateParent(Math.floor(currentValue / 10))
|
|
}
|
|
break;
|
|
|
|
case '.':
|
|
setCurrentDecimals(0)
|
|
break;
|
|
|
|
case '0':
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
case '6':
|
|
case '7':
|
|
case '8':
|
|
case '9':
|
|
const digit = parseInt(key, 10)
|
|
// If we're still in the whole numbers
|
|
if(currentDecimals < 0) {
|
|
updateParent(currentValue * 10 + digit)
|
|
} else if (currentDecimals === 0) {
|
|
// Add decimal
|
|
setCurrentDecimals(dec => dec + 1)
|
|
updateParent(parseFloat(currentValue.toFixed(0) + '.' + digit))
|
|
} else if (currentDecimals < numDecimals) {
|
|
// Add to existing decimals
|
|
setCurrentDecimals(dec => dec + 1)
|
|
updateParent(parseFloat(currentValue.toFixed(currentDecimals) + digit))
|
|
} else {
|
|
// Can we play a tick noise of some sort?
|
|
}
|
|
break;
|
|
|
|
default:
|
|
return;
|
|
}
|
|
|
|
ev.preventDefault();
|
|
}
|
|
|
|
const filledText = ((prefix + ' ') || '')
|
|
+ (Math.floor(currentValue) || '0')
|
|
+ (currentDecimals >= 0 ? '.' : '')
|
|
+ (currentDecimals > 0 ? currentValue.toFixed(currentDecimals).split('.')[1] : '')
|
|
|
|
const remainingText =
|
|
(currentDecimals < 0 ? '.' : '')
|
|
+ (numDecimals !== undefined && currentDecimals < numDecimals ? '0'.repeat(numDecimals - Math.max(currentDecimals, 0)) : '')
|
|
|
|
return (
|
|
<div className={styles.formElementContainer}>
|
|
<label htmlFor={name}>{label}:</label>
|
|
<div className={styles.complexInput + ((isValid && !error)?'':' ' + styles.invalid)}>
|
|
<input style={{textIndent: width}} type="text" name={name} value="" onChange={()=>{}} onKeyDown={onKeyDown} onBlur={onBlur} />
|
|
<div className={styles.inputDecorators}>
|
|
<span ref={spanRef} className={styles.numFilled}>{filledText}</span>
|
|
<span className={styles.numRemaining}>{remainingText}</span>
|
|
</div>
|
|
</div>
|
|
{(hint || error) && <span className={styles.hint}>{error || (isValid ? '' : hint)}</span>}
|
|
</div>
|
|
)
|
|
}
|