@chayns-components/core
Version:
A set of beautiful React components for developing your own applications with chayns.
248 lines (246 loc) • 8.53 kB
JavaScript
import { AnimatePresence } from 'motion/react';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { checkForValidAmount } from '../../utils/amountControl';
import { useKeyboardFocusHighlighting } from '../../hooks/useKeyboardFocusHighlighting';
import Icon from '../icon/Icon';
import { StyledAmountControl, StyledAmountControlInput, StyledAmountControlPseudoInput, StyledInputWrapper, StyledMotionAmountControlButton } from './AmountControl.styles';
const AmountControl = ({
amount,
icon,
iconColor,
isDisabled = false,
label,
maxAmount,
minAmount = 0,
onChange,
shouldEnableKeyboardHighlighting,
shouldForceLabel = false,
shouldShowAddIconOnMinAmount = false,
shouldShowIcon = true,
shouldShowWideInput = false,
step: stepProp = 1
}) => {
const shouldShowKeyboardHighlighting = useKeyboardFocusHighlighting(shouldEnableKeyboardHighlighting && !isDisabled);
const [amountValue, setAmountValue] = useState(minAmount);
const [inputValue, setInputValue] = useState(minAmount.toString());
const [displayState, setDisplayState] = useState('default');
const [isEditing, setIsEditing] = useState(false);
const step = useMemo(() => Number.isSafeInteger(stepProp) && stepProp >= 1 ? stepProp : 1, [stepProp]);
const inputRef = useRef(null);
/**
* This function controls the displayState
*/
useEffect(() => {
switch (true) {
case maxAmount && amountValue >= maxAmount:
setDisplayState('maxAmount');
return;
case amountValue > minAmount:
setDisplayState('normal');
return;
case amountValue === minAmount && amountValue >= 0 && shouldShowAddIconOnMinAmount:
setDisplayState('minAmount');
return;
default:
setDisplayState('default');
}
}, [amountValue, maxAmount, minAmount, shouldShowAddIconOnMinAmount]);
const hasFocus = useMemo(() => displayState !== 'default', [displayState]);
/**
* Function that sets the amountValue to the amount
*/
useEffect(() => {
if (typeof amount !== 'number') {
return;
}
setAmountValue(checkForValidAmount({
amount,
maxAmount,
minAmount
}));
setInputValue(checkForValidAmount({
amount,
maxAmount,
minAmount
}).toString());
}, [amount, maxAmount, minAmount]);
const handleAmountAdd = useCallback(() => {
setAmountValue(prevState => {
const newAmount = checkForValidAmount({
amount: prevState + step,
minAmount,
maxAmount
});
if (typeof onChange === 'function') {
onChange(newAmount);
}
return typeof amount === 'number' ? prevState : newAmount;
});
setInputValue(prevState => checkForValidAmount({
amount: Number(prevState) + step,
minAmount,
maxAmount
}).toString());
}, [amount, maxAmount, minAmount, onChange, step]);
const handleAmountRemove = useCallback(() => {
if (displayState === 'default') {
return;
}
setAmountValue(prevState => {
const newAmount = checkForValidAmount({
amount: prevState - step,
minAmount,
maxAmount
});
if (typeof onChange === 'function') {
onChange(newAmount);
}
return newAmount;
});
setInputValue(prevState => checkForValidAmount({
amount: Number(prevState) - step,
minAmount,
maxAmount
}).toString());
}, [displayState, maxAmount, minAmount, onChange, step]);
const handleFirstAmount = useCallback(() => {
if (isDisabled) {
return;
}
if (amountValue !== minAmount) {
return;
}
if (typeof onChange === 'function') {
onChange(minAmount + step);
}
setAmountValue(minAmount + step);
setInputValue((minAmount + step).toString());
}, [amountValue, isDisabled, minAmount, onChange, step]);
const handlePseudoInputClick = useCallback(() => {
if (isDisabled) {
return;
}
setIsEditing(true);
window.requestAnimationFrame(() => {
inputRef.current?.focus();
});
}, [isDisabled]);
const handleInputBlur = useCallback(() => {
const checkedValue = checkForValidAmount({
minAmount,
maxAmount,
amount: Math.round(Number(inputValue) / step) * step
});
setAmountValue(checkedValue);
setInputValue(checkedValue.toString());
setIsEditing(false);
if (typeof onChange === 'function') {
onChange(checkedValue);
}
if (inputValue === '') {
setInputValue(minAmount.toString());
}
}, [inputValue, maxAmount, minAmount, onChange, step]);
const handleInputChange = useCallback(event => {
const {
value
} = event.target;
const valueBefore = Number(value.replace(/\D/g, ''));
if (valueBefore < minAmount && minAmount === 0) {
setInputValue('0');
return;
}
setInputValue(valueBefore === 0 ? '' : valueBefore.toString());
}, [minAmount]);
let inputLabel = inputValue;
if (typeof label === 'string' && (displayState === 'default' || shouldForceLabel)) {
inputLabel = label;
}
const shouldShowPseudoInput = !isEditing && (inputValue === '0' || shouldForceLabel && typeof label === 'string' || inputLabel === label);
return useMemo(() => /*#__PURE__*/React.createElement(StyledAmountControl, {
onClick: handleFirstAmount,
$isDisabled: isDisabled
}, /*#__PURE__*/React.createElement(AnimatePresence, {
initial: false
}, ['normal'].includes(displayState) && /*#__PURE__*/React.createElement(StyledMotionAmountControlButton, {
$shouldShowKeyboardHighlighting: shouldShowKeyboardHighlighting,
key: "left_button",
initial: {
width: 0,
opacity: 0,
padding: 0
},
animate: {
width: 40,
opacity: 1,
padding: 0
},
exit: {
width: 0,
opacity: 0,
padding: 0
},
transition: {
duration: 0.2,
type: 'tween'
},
onClick: handleAmountRemove,
disabled: isDisabled || amountValue !== 0 && amountValue <= minAmount,
$isDisabled: isDisabled || amountValue !== 0 && amountValue <= minAmount
}, /*#__PURE__*/React.createElement(Icon, {
icons: ['fa fa-minus'],
size: 14,
color: "white"
}))), /*#__PURE__*/React.createElement(StyledInputWrapper, null, shouldShowPseudoInput ? /*#__PURE__*/React.createElement(StyledAmountControlPseudoInput, {
onClick: handlePseudoInputClick,
$shouldShowWideInput: shouldShowWideInput,
$shouldShowRightIcon: !['maxAmount'].includes(displayState)
}, inputLabel) : /*#__PURE__*/React.createElement(StyledAmountControlInput, {
ref: inputRef,
$displayState: displayState,
$shouldShowIcon: shouldShowIcon,
$shouldShowWideInput: shouldShowWideInput,
$hasFocus: hasFocus,
$shouldShowKeyboardHighlighting: shouldShowKeyboardHighlighting,
disabled: isDisabled,
onFocus: () => setIsEditing(true),
onBlur: handleInputBlur,
onChange: handleInputChange,
value: inputLabel
})), /*#__PURE__*/React.createElement(AnimatePresence, {
initial: false
}, /*#__PURE__*/React.createElement(StyledMotionAmountControlButton, {
$shouldShowKeyboardHighlighting: shouldShowKeyboardHighlighting,
key: "right_button",
initial: {
width: 0,
opacity: 0,
padding: 0
},
animate: {
width: 40,
opacity: 1,
padding: 0
},
exit: {
width: 0,
opacity: 0,
padding: 0
},
transition: {
duration: 0.2,
type: 'tween'
},
$color: displayState === 'maxAmount' ? 'rgb(32, 198, 90)' : undefined,
onClick: displayState === 'maxAmount' ? handleAmountRemove : handleAmountAdd,
disabled: isDisabled || (maxAmount ? amountValue >= maxAmount : false),
$isDisabled: isDisabled || (maxAmount ? amountValue >= maxAmount : false)
}, /*#__PURE__*/React.createElement(Icon, {
icons: !['normal'].includes(displayState) && icon ? [displayState === 'maxAmount' ? 'fa fa-check' : icon] : [displayState === 'maxAmount' ? 'fa fa-check' : 'fa fa-plus'],
size: 14,
color: iconColor ?? 'white'
})))), [amountValue, displayState, handleAmountAdd, handleAmountRemove, handleFirstAmount, handleInputBlur, handleInputChange, handlePseudoInputClick, hasFocus, icon, iconColor, inputLabel, isDisabled, maxAmount, minAmount, shouldShowIcon, shouldShowKeyboardHighlighting, shouldShowPseudoInput, shouldShowWideInput]);
};
AmountControl.displayName = 'AmountControl';
export default AmountControl;
//# sourceMappingURL=AmountControl.js.map