@razorpay/blade
Version:
The Design System that powers Razorpay
352 lines (346 loc) • 16.8 kB
JavaScript
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