UNPKG

@razorpay/blade

Version:

The Design System that powers Razorpay

352 lines (346 loc) 16.8 kB
import _defineProperty from '@babel/runtime/helpers/defineProperty'; import _slicedToArray from '@babel/runtime/helpers/slicedToArray'; import _objectWithoutProperties from '@babel/runtime/helpers/objectWithoutProperties'; import React__default, { useState, useRef, useEffect, useCallback } from 'react'; import styled from 'styled-components'; import { StyledCounterInput } from './StyledCounterInput.js'; import { COUNTER_INPUT_TOKEN } from './token.js'; import { CounterInputProvider } from './CounterInputContext.js'; import '../Input/BaseInput/index.js'; import '../../utils/metaAttribute/index.js'; import '../Box/styledProps/index.js'; import '../../utils/assignWithoutSideEffects/index.js'; import '../../utils/makeAnalyticsAttribute/index.js'; import { useControllableState } from '../../utils/useControllable.js'; import { getOuterMotionRef } from '../../utils/getMotionRefs.js'; import '../Box/BaseBox/index.js'; import '../Form/index.js'; import { useFormId } from '../Form/useFormId.js'; import { useId } from '../../utils/useId.js'; import '../BladeProvider/index.js'; import '../../utils/index.js'; import '../Icons/index.js'; import '../ProgressBar/index.js'; import getIn from '../../utils/lodashButBetter/get.js'; import { mergeRefs } from '../../utils/useMergeRefs.js'; import '../../utils/getFocusRingStyles/index.js'; import { jsx, jsxs } from 'react/jsx-runtime'; import { makeBorderSize } from '../../utils/makeBorderSize/makeBorderSize.js'; import { makeSpace } from '../../utils/makeSpace/makeSpace.js'; import { castWebType } from '../../utils/platform/castUtils.js'; import { makeMotionTime } from '../../utils/makeMotionTime/makeMotionTime.web.js'; import { getFocusRingStyles } from '../../utils/getFocusRingStyles/getFocusRingStyles.web.js'; import useTheme from '../BladeProvider/useTheme.js'; import { useBreakpoint } from '../../utils/useBreakpoint/useBreakpoint.js'; import { metaAttribute } from '../../utils/metaAttribute/metaAttribute.web.js'; import { MetaConstants } from '../../utils/metaAttribute/metaConstants.js'; import { getStyledProps } from '../Box/styledProps/getStyledProps.js'; import { makeAnalyticsAttribute } from '../../utils/makeAnalyticsAttribute/makeAnalyticsAttribute.js'; import { BaseBox } from '../Box/BaseBox/BaseBox.web.js'; import { FormLabel } from '../Form/FormLabel.js'; import MinusIcon from '../Icons/MinusIcon/MinusIcon.js'; import { BaseInput } from '../Input/BaseInput/BaseInput.js'; import PlusIcon from '../Icons/PlusIcon/PlusIcon.js'; import { ProgressBar } from '../ProgressBar/ProgressBar.js'; import { assignWithoutSideEffects } from '../../utils/assignWithoutSideEffects/assignWithoutSideEffects.js'; var _excluded = ["label", "accessibilityLabel", "labelPosition", "name", "value", "defaultValue", "min", "max", "emphasis", "size", "isLoading", "isDisabled", "onChange", "onFocus", "onBlur", "testID", "_motionMeta"]; function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } var StyledCounterButton = /*#__PURE__*/styled.button.withConfig({ displayName: "CounterInputweb__StyledCounterButton", componentId: "l7imdl-0" })(["background-color:transparent;border:none;cursor:", ";display:flex;align-items:center;justify-content:center;border-radius:", ";padding:", ";margin:", ";transition-property:background-color,color;transition-duration:", ";transition-timing-function:", ";&:focus-visible{", "}&:hover:not(:disabled){", "}"], function (props) { return props.disabled ? 'not-allowed' : 'pointer'; }, function (_ref) { var $borderRadius = _ref.$borderRadius; return makeBorderSize($borderRadius); }, function (_ref2) { var $padding = _ref2.$padding; return makeSpace($padding); }, function (_ref3) { var $margin = _ref3.$margin; return $margin.map(function (m) { return makeSpace(m); }).join(' '); }, function (_ref4) { var theme = _ref4.theme; return castWebType(makeMotionTime(getIn(theme.motion, 'duration.xquick'))); }, function (_ref5) { var theme = _ref5.theme; return getIn(theme.motion, 'easing.standard'); }, function (_ref6) { var theme = _ref6.theme; return getFocusRingStyles({ theme: theme, negativeOffset: true }); }, function (_ref7) { var theme = _ref7.theme, $emphasis = _ref7.$emphasis; if ($emphasis === 'subtle') { return "\n background-color: ".concat(theme.colors.interactive.background.gray.fadedHighlighted, " !important;\n color: ").concat(theme.colors.interactive.icon.gray.normal, " !important;\n "); } else { return "\n background-color: ".concat(theme.colors.interactive.background.primary.fadedHighlighted, " !important;\n color: ").concat(theme.colors.interactive.icon.primary.normal, " !important;\n "); } }); // Icon size mapping for counter input var ICON_SIZE_MAP = { xsmall: 'small', small: 'small', medium: 'large', large: 'xlarge' }; var _CounterInput = /*#__PURE__*/React__default.forwardRef(function (_ref8, ref) { var _internalValue$toStri; var label = _ref8.label, accessibilityLabel = _ref8.accessibilityLabel, _ref8$labelPosition = _ref8.labelPosition, labelPosition = _ref8$labelPosition === void 0 ? 'top' : _ref8$labelPosition, name = _ref8.name, value = _ref8.value, defaultValue = _ref8.defaultValue, _ref8$min = _ref8.min, min = _ref8$min === void 0 ? 0 : _ref8$min, max = _ref8.max, _ref8$emphasis = _ref8.emphasis, emphasis = _ref8$emphasis === void 0 ? 'subtle' : _ref8$emphasis, _ref8$size = _ref8.size, size = _ref8$size === void 0 ? 'medium' : _ref8$size, _ref8$isLoading = _ref8.isLoading, isLoading = _ref8$isLoading === void 0 ? false : _ref8$isLoading, _ref8$isDisabled = _ref8.isDisabled, isDisabled = _ref8$isDisabled === void 0 ? false : _ref8$isDisabled, _onChange = _ref8.onChange, onFocus = _ref8.onFocus, onBlur = _ref8.onBlur, testID = _ref8.testID, _motionMeta = _ref8._motionMeta, rest = _objectWithoutProperties(_ref8, _excluded); var _useControllableState = useControllableState({ value: value, defaultValue: defaultValue, onChange: function onChange(newValue) { return _onChange === null || _onChange === void 0 ? void 0 : _onChange({ value: newValue }); } }), _useControllableState2 = _slicedToArray(_useControllableState, 2), internalValue = _useControllableState2[0], setInternalValue = _useControllableState2[1]; var _useFormId = useFormId('counter-input'), inputId = _useFormId.inputId; var idBase = useId('counter-input'); var labelId = "".concat(idBase, "-label"); var _useTheme = useTheme(), theme = _useTheme.theme; var _useBreakpoint = useBreakpoint({ breakpoints: theme.breakpoints }), matchedDeviceType = _useBreakpoint.matchedDeviceType; var isLabelLeftPositioned = labelPosition === 'left' && matchedDeviceType === 'desktop'; var emphasisTokens = COUNTER_INPUT_TOKEN.emphasis[emphasis]; var _isDisabled = isDisabled || isLoading; var _useState = useState(''), _useState2 = _slicedToArray(_useState, 2), animationClass = _useState2[0], setAnimationClass = _useState2[1]; var lastActionRef = useRef(null); var previousValueRef = useRef(internalValue); var containerRef = useRef(null); // Track Tab navigation to show focus ring only on keyboard navigation (not mouse clicks) // Note: :focus-visible doesn't work for text inputs - shows ring on both Tab and click // TODO: Re-evaluate and remove later once focus style is unified from design useEffect(function () { var container = containerRef.current; if (!container) return undefined; var handleTabKey = function handleTabKey(event) { if (event.key === 'Tab') { container.classList.add('counter-input-keyboard-focus'); } }; var handleMouseDown = function handleMouseDown() { container.classList.remove('counter-input-keyboard-focus'); }; document.addEventListener('keydown', handleTabKey, true); document.addEventListener('mousedown', handleMouseDown, true); return function () { document.removeEventListener('keydown', handleTabKey, true); document.removeEventListener('mousedown', handleMouseDown, true); }; }, []); // Animation effect - triggers only when value actually changes useEffect(function () { // Only animate if: // 1. We have a stored action (button was clicked) // 2. Value actually changed from previous value // 3. Not currently loading if (lastActionRef.current && !isLoading && internalValue !== previousValueRef.current) { var _animationClass = lastActionRef.current === 'increment' ? '__blade-counter-input-animate-slide-up' : '__blade-counter-input-animate-slide-down'; setAnimationClass(_animationClass); setTimeout(function () { return setAnimationClass(''); }, 300); lastActionRef.current = null; } previousValueRef.current = internalValue; }, [internalValue, isLoading]); var handleInputChange = useCallback(function (_ref9) { var inputValue = _ref9.value; var numericValue = inputValue ? parseInt(inputValue, 10) : min; var validValue = isNaN(numericValue) ? min : numericValue; var constrainedValue = validValue; if (constrainedValue < min) constrainedValue = min; if (max !== undefined && constrainedValue > max) constrainedValue = max; setInternalValue(function () { return constrainedValue; }); }, [min, max, _onChange, name]); var handleIncrement = useCallback(function () { if (_isDisabled) return; var newValue = (internalValue !== null && internalValue !== void 0 ? internalValue : min) + 1; var constrainedValue = max !== undefined ? Math.min(newValue, max) : newValue; lastActionRef.current = 'increment'; setInternalValue(function () { return constrainedValue; }); }, [internalValue, min, max, _isDisabled, setInternalValue]); var handleDecrement = useCallback(function () { if (_isDisabled) return; var newValue = (internalValue !== null && internalValue !== void 0 ? internalValue : min) - 1; var constrainedValue = Math.max(newValue, min); lastActionRef.current = 'decrement'; setInternalValue(function () { return constrainedValue; }); }, [internalValue, min, _isDisabled, setInternalValue]); var isDecrementDisabled = _isDisabled || (internalValue !== null && internalValue !== void 0 ? internalValue : min) <= min; var isIncrementDisabled = _isDisabled || max !== undefined && (internalValue !== null && internalValue !== void 0 ? internalValue : min) >= max; var contextValue = { size: size, emphasis: emphasis, isDisabled: isDisabled, isLoading: isLoading, color: emphasisTokens.color, disabledTextColor: emphasisTokens.disabledColor, isInsideCounterInput: true }; return /*#__PURE__*/jsx(CounterInputProvider, { value: contextValue, children: /*#__PURE__*/jsx(StyledCounterInput, _objectSpread(_objectSpread(_objectSpread(_objectSpread({ className: "__blade-counter-input", "data-emphasis": emphasis, ref: mergeRefs(getOuterMotionRef({ _motionMeta: _motionMeta, ref: ref }), containerRef) }, metaAttribute({ name: MetaConstants.CounterInput, testID: testID })), getStyledProps(rest)), makeAnalyticsAttribute(rest)), {}, { children: /*#__PURE__*/jsxs(BaseBox, { display: "flex", flexDirection: isLabelLeftPositioned ? 'row' : 'column', alignItems: isLabelLeftPositioned ? 'center' : undefined, children: [label && /*#__PURE__*/jsx(FormLabel, { as: "label", position: labelPosition, id: labelId, htmlFor: inputId, size: size, children: label }), /*#__PURE__*/jsxs(BaseBox, { display: "flex", position: "relative", alignItems: "center", flexDirection: "column", backgroundColor: isLoading || isDisabled ? emphasisTokens.loadingOrDisabledBgColor : emphasisTokens.backgroundColor, width: "".concat(COUNTER_INPUT_TOKEN.width[size], "px"), height: "".concat(COUNTER_INPUT_TOKEN.height[size], "px"), borderRadius: COUNTER_INPUT_TOKEN.containerBorderRadius[size], borderWidth: "thin", borderColor: _isDisabled ? emphasisTokens.disabledBorderColor : emphasisTokens.borderColor, children: [/*#__PURE__*/jsxs(BaseBox, { display: "flex", alignItems: "center", flexDirection: "row", children: [/*#__PURE__*/jsx(StyledCounterButton, { className: "__blade-counter-input-decrement-button", onClick: handleDecrement, $padding: COUNTER_INPUT_TOKEN.iconPadding[size], $margin: COUNTER_INPUT_TOKEN.decrementIconMargin, $emphasis: emphasis, $borderRadius: COUNTER_INPUT_TOKEN.buttonBorderRadius[size], "aria-label": "Decrement value", disabled: isDecrementDisabled, style: { color: isDecrementDisabled ? getIn(theme.colors, emphasisTokens.disabledIconColor, '') : getIn(theme.colors, emphasisTokens.iconColor, '') }, children: /*#__PURE__*/jsx(MinusIcon, { size: ICON_SIZE_MAP[size], color: "currentColor" }) }), /*#__PURE__*/jsx(BaseBox, { className: animationClass, children: /*#__PURE__*/jsx(BaseInput, { ref: ref, id: inputId, as: "input", name: name, type: "number", componentName: MetaConstants.CounterInput, label: "", accessibilityLabel: accessibilityLabel, value: (_internalValue$toStri = internalValue === null || internalValue === void 0 ? void 0 : internalValue.toString()) !== null && _internalValue$toStri !== void 0 ? _internalValue$toStri : '', onChange: handleInputChange, onFocus: onFocus, onBlur: onBlur, isDisabled: isDisabled, size: size, textAlign: "center" // Accessibility attributes for spinbutton , role: "spinbutton", "aria-valuemin": min, "aria-valuemax": max, "aria-valuenow": internalValue !== null && internalValue !== void 0 ? internalValue : min }) }), /*#__PURE__*/jsx(StyledCounterButton, { className: "__blade-counter-input-increment-button", onClick: handleIncrement, $padding: COUNTER_INPUT_TOKEN.iconPadding[size], $margin: COUNTER_INPUT_TOKEN.incrementIconMargin, $emphasis: emphasis, $borderRadius: COUNTER_INPUT_TOKEN.buttonBorderRadius[size], "aria-label": "Increment value", disabled: isIncrementDisabled, style: { color: isIncrementDisabled ? getIn(theme.colors, emphasisTokens.disabledIconColor, '') : getIn(theme.colors, emphasisTokens.iconColor, '') }, children: /*#__PURE__*/jsx(PlusIcon, { size: ICON_SIZE_MAP[size], color: "currentColor" }) })] }), isLoading && /*#__PURE__*/jsx(BaseBox, { width: "100%", position: "absolute", bottom: "spacing.0", children: /*#__PURE__*/jsx(ProgressBar, { color: emphasisTokens.progressBarColor, showPercentage: false, value: 1, isIndeterminate: true, _oscillation: true }) })] })] }) })) }); }); var CounterInput = /*#__PURE__*/assignWithoutSideEffects(_CounterInput, { componentId: MetaConstants.CounterInput }); export { CounterInput }; //# sourceMappingURL=CounterInput.web.js.map