UNPKG

@chayns-components/core

Version:

A set of beautiful React components for developing your own applications with chayns.

248 lines (246 loc) • 8.53 kB
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