@razorpay/blade
Version:
The Design System that powers Razorpay
402 lines (399 loc) • 18.4 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 } from 'react';
import '../BaseInput/index.js';
import { getKeyboardAndAutocompleteProps } from '../BaseInput/utils.js';
import { useTaggedInput } from '../BaseInput/useTaggedInput.js';
import { useFormattedInput } from './useFormattedInput.js';
import isEmpty from '../../../utils/lodashButBetter/isEmpty.js';
import '../../Icons/index.js';
import '../../Button/IconButton/index.js';
import '../../../utils/metaAttribute/index.js';
import '../../Form/CharacterCounter/index.js';
import '../../Box/BaseBox/index.js';
import '../../Spinner/index.js';
import '../../../utils/assignWithoutSideEffects/index.js';
import '../../../utils/index.js';
import { useMergeRefs } from '../../../utils/useMergeRefs.js';
import { hintMarginTop } from '../../Form/formTokens.js';
import '../../Divider/index.js';
import '../../../utils/isValidAllowedChildren/index.js';
import '../../Dropdown/index.js';
import { dropdownComponentIds } from '../../Dropdown/dropdownComponentIds.js';
import '../../../utils/isIconComponent/index.js';
import { useDatePickerContext } from '../../DatePicker/DatePickerContext.js';
import { jsx, jsxs } from 'react/jsx-runtime';
import { getPlatformType } from '../../../utils/getPlatformType/getPlatformType.js';
import { getComponentId } from '../../../utils/isValidAllowedChildren/isValidAllowedChildren.js';
import { isIconComponent } from '../../../utils/isIconComponent/isIconComponent.js';
import { DropdownOverlay } from '../../Dropdown/DropdownOverlay.web.js';
import { IconButton } from '../../Button/IconButton/IconButton.js';
import CloseIcon from '../../Icons/CloseIcon/CloseIcon.js';
import { Spinner } from '../../Spinner/Spinner/Spinner.js';
import { BaseBox } from '../../Box/BaseBox/BaseBox.web.js';
import { Divider } from '../../Divider/Divider.js';
import { BaseInput } from '../BaseInput/BaseInput.js';
import { MetaConstants } from '../../../utils/metaAttribute/metaConstants.js';
import { CharacterCounter } from '../../Form/CharacterCounter/CharacterCounter.js';
import { assignWithoutSideEffects } from '../../../utils/assignWithoutSideEffects/assignWithoutSideEffects.js';
var _excluded = ["label", "accessibilityLabel", "labelPosition", "placeholder", "type", "defaultValue", "name", "value", "maxCharacters", "format", "onChange", "onClick", "onFocus", "onBlur", "onSubmit", "isDisabled", "necessityIndicator", "validationState", "errorText", "helpText", "successText", "isRequired", "icon", "prefix", "showClearButton", "onClearButtonClick", "isLoading", "suffix", "autoFocus", "keyboardReturnKeyType", "autoCompleteSuggestionType", "autoCapitalize", "testID", "size", "leadingIcon", "trailingIcon", "isTaggedInput", "tags", "onTagChange", "trailing", "leading", "labelSuffix", "labelTrailing", "onKeyDown"];
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; }
// need to do this to tell TS to infer type as TextInput of React Native and make it believe that `ref.current.clear()` exists
// eslint-disable-next-line @typescript-eslint/no-explicit-any
var isReactNative = function isReactNative(_textInputRef) {
return getPlatformType() === 'react-native';
};
var _TextInput = function _TextInput(_ref, ref) {
var _ref4;
var label = _ref.label,
accessibilityLabel = _ref.accessibilityLabel,
_ref$labelPosition = _ref.labelPosition,
labelPosition = _ref$labelPosition === void 0 ? 'top' : _ref$labelPosition,
placeholder = _ref.placeholder,
_ref$type = _ref.type,
type = _ref$type === void 0 ? 'text' : _ref$type,
defaultValue = _ref.defaultValue,
name = _ref.name,
value = _ref.value,
maxCharacters = _ref.maxCharacters,
format = _ref.format,
onChange = _ref.onChange,
onClick = _ref.onClick,
_onFocus = _ref.onFocus,
_onBlur = _ref.onBlur,
onSubmit = _ref.onSubmit,
isDisabled = _ref.isDisabled,
necessityIndicator = _ref.necessityIndicator,
validationState = _ref.validationState,
errorText = _ref.errorText,
helpText = _ref.helpText,
successText = _ref.successText,
isRequired = _ref.isRequired,
icon = _ref.icon,
prefix = _ref.prefix,
showClearButton = _ref.showClearButton,
onClearButtonClick = _ref.onClearButtonClick,
isLoading = _ref.isLoading,
suffix = _ref.suffix,
autoFocus = _ref.autoFocus,
keyboardReturnKeyType = _ref.keyboardReturnKeyType,
autoCompleteSuggestionType = _ref.autoCompleteSuggestionType,
autoCapitalize = _ref.autoCapitalize,
testID = _ref.testID,
_ref$size = _ref.size,
size = _ref$size === void 0 ? 'medium' : _ref$size,
leadingIcon = _ref.leadingIcon,
trailingIcon = _ref.trailingIcon,
isTaggedInput = _ref.isTaggedInput,
tags = _ref.tags,
onTagChange = _ref.onTagChange,
trailing = _ref.trailing,
leading = _ref.leading,
labelSuffix = _ref.labelSuffix,
labelTrailing = _ref.labelTrailing,
_onKeyDown = _ref.onKeyDown,
rest = _objectWithoutProperties(_ref, _excluded);
var textInputRef = React__default.useRef(null);
var mergedRef = useMergeRefs(ref, textInputRef);
var _useState = useState(false),
_useState2 = _slicedToArray(_useState, 2),
shouldShowClearButton = _useState2[0],
setShouldShowClearButton = _useState2[1];
var _useState3 = useState(autoFocus !== null && autoFocus !== void 0 ? autoFocus : false),
_useState4 = _slicedToArray(_useState3, 2),
isInputFocussed = _useState4[0],
setIsInputFocussed = _useState4[1];
var context = useDatePickerContext();
var isDatePickerBodyOpen = context === null || context === void 0 ? void 0 : context.isDatePickerBodyOpen;
if (false) {
if (format) {
var hasAlphanumeric = /[a-zA-Z0-9]/.test(format);
if (hasAlphanumeric) {
throw new Error("[Blade: TextInput] Invalid format \"".concat(format, "\". Only # and special characters allowed, no letters/numbers."));
}
}
}
var formattingResult = useFormattedInput({
format: format,
onChange: onChange,
value: value,
defaultValue: defaultValue
});
var inputValue = format ? formattingResult.formattedValue : value;
var effectiveMaxCharacters = format ? formattingResult.maxLength : maxCharacters;
var handleOnChange = React__default.useCallback(function (_ref2) {
var name = _ref2.name,
inputValue = _ref2.value;
if (format) {
formattingResult.handleChange({
name: name,
value: inputValue
});
} else {
onChange === null || onChange === void 0 || onChange({
name: name,
value: inputValue
});
}
}, [format, formattingResult.handleChange, onChange]);
var _useTaggedInput = useTaggedInput({
isTaggedInput: isTaggedInput,
tags: tags,
onTagChange: onTagChange,
isDisabled: isDisabled,
onChange: handleOnChange,
name: name,
value: inputValue,
inputRef: textInputRef
}),
activeTagIndex = _useTaggedInput.activeTagIndex,
setActiveTagIndex = _useTaggedInput.setActiveTagIndex,
getTags = _useTaggedInput.getTags,
handleTaggedInputKeydown = _useTaggedInput.handleTaggedInputKeydown,
handleTaggedInputChange = _useTaggedInput.handleTaggedInputChange,
handleTagsClear = _useTaggedInput.handleTagsClear;
var _React$useState = React__default.useState(false),
_React$useState2 = _slicedToArray(_React$useState, 2),
isTrailingDropDownOpen = _React$useState2[0],
setIsTrailingDropDownOpen = _React$useState2[1];
var _React$useState3 = React__default.useState(false),
_React$useState4 = _slicedToArray(_React$useState3, 2),
isLeadingDropDownOpen = _React$useState4[0],
setIsLeadingDropDownOpen = _React$useState4[1];
var textInputWrapperRef = useRef(null);
useEffect(function () {
if (isTrailingDropDownOpen && isLeadingDropDownOpen || isDatePickerBodyOpen && isLeadingDropDownOpen) {
setIsLeadingDropDownOpen(false);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isTrailingDropDownOpen, isDatePickerBodyOpen]);
useEffect(function () {
if (isLeadingDropDownOpen && isTrailingDropDownOpen) {
setIsTrailingDropDownOpen(false);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isLeadingDropDownOpen]);
var leadingDropDown = leading && getComponentId(leading) === 'Dropdown' ? leading : null;
var trailingDropdown = trailing && getComponentId(trailing) === 'Dropdown' ? trailing : null;
// we need to look into name of component and check if it 's and icon or a dropdown
var _leadingIcon = isIconComponent(leading) ? leading : undefined;
var _trailingIcon = isIconComponent(trailing) ? trailing : undefined;
var hasLeadingInteractionElement = !_leadingIcon && !leadingDropDown && leading;
var hasTrailingInteractionElement = !_trailingIcon && !trailingDropdown && trailing;
var renderDropdown = function renderDropdown(dropdown, isOpen, setIsOpen, defaultPlacement) {
if (!dropdown) {
return null;
}
return /*#__PURE__*/React__default.cloneElement(dropdown, {
selectionType: 'single',
isOpen: isOpen,
height: '100%',
onOpenChange: function onOpenChange(isOpen) {
setIsOpen(isOpen);
},
children: React__default.Children.map(dropdown.props.children, function (child) {
if (child.type === DropdownOverlay) {
return /*#__PURE__*/React__default.cloneElement(child, {
referenceRef: textInputWrapperRef,
_isNestedDropdown: true,
defaultPlacement: defaultPlacement
});
}
// Pass size to InputDropdownButton
if (getComponentId(child) === dropdownComponentIds.triggers.InputDropdownButton) {
return /*#__PURE__*/React__default.cloneElement(child, {
size: size
});
}
return child;
})
});
};
var renderLeadingDropDown = renderDropdown(leadingDropDown, isLeadingDropDownOpen, setIsLeadingDropDownOpen, 'bottom-start');
var renderTrailingDropDown = renderDropdown(trailingDropdown, isTrailingDropDownOpen, setIsTrailingDropDownOpen, 'bottom-end');
React__default.useEffect(function () {
setShouldShowClearButton(Boolean(showClearButton && (defaultValue !== null && defaultValue !== void 0 ? defaultValue : inputValue)));
}, [showClearButton, defaultValue, inputValue]);
var renderClearButton = function renderClearButton() {
return /*#__PURE__*/jsx(IconButton, {
size: "medium",
icon: CloseIcon,
onClick: function onClick() {
var _textInputRef$current;
if (isEmpty(inputValue) && textInputRef.current) {
// when the input field is uncontrolled take the ref and clear the input and then call the onClearButtonClick function
if (isReactNative(textInputRef.current)) {
textInputRef.current.clear();
textInputRef.current.focus();
} else if (textInputRef.current instanceof HTMLInputElement) {
textInputRef.current.value = '';
textInputRef.current.focus();
}
}
handleTagsClear();
// if the input field is controlled just call the click handler and the value change shall be left upto the consumer
onClearButtonClick === null || onClearButtonClick === void 0 || onClearButtonClick();
textInputRef === null || textInputRef === void 0 || (_textInputRef$current = textInputRef.current) === null || _textInputRef$current === void 0 || _textInputRef$current.focus();
setShouldShowClearButton(false);
},
isDisabled: isDisabled,
accessibilityLabel: "Clear Input Content"
});
};
var hasTrailingDropDown = Boolean(trailingDropdown);
var renderInteractionElement = function renderInteractionElement() {
if (isLoading) {
return /*#__PURE__*/jsx(Spinner, {
accessibilityLabel: "Loading Content",
color: "primary"
});
}
if (shouldShowClearButton && hasTrailingDropDown) {
return /*#__PURE__*/jsxs(BaseBox, {
display: "flex",
gap: "spacing.3",
children: [renderClearButton(), " ", /*#__PURE__*/jsx(Divider, {
orientation: "vertical"
})]
});
}
if (showClearButton && hasTrailingInteractionElement) {
return /*#__PURE__*/jsxs(BaseBox, {
display: "flex",
gap: "spacing.3",
children: [renderClearButton(), " ", /*#__PURE__*/jsx(Divider, {
orientation: "vertical"
}), " ", trailing]
});
}
if (shouldShowClearButton) {
return renderClearButton();
}
if (hasTrailingInteractionElement) {
return trailing;
}
return null;
};
return /*#__PURE__*/jsx(BaseInput, _objectSpread(_objectSpread({
id: "textinput",
componentName: MetaConstants.TextInput,
ref: mergedRef,
setInputWrapperRef: function setInputWrapperRef(wrapperNode) {
textInputWrapperRef.current = wrapperNode;
},
label: label,
labelSuffix: labelSuffix,
labelTrailing: labelTrailing,
accessibilityLabel: accessibilityLabel,
hideLabelText: !Boolean(label),
labelPosition: labelPosition,
placeholder: placeholder
// CONTROLLED/UNCONTROLLED INPUT LOGIC:
// For inputs WITHOUT format:
// - Use standard React controlled/uncontrolled logic
// - Controlled: user provides `value` prop → use `value` prop
// - Uncontrolled: user provides `defaultValue` prop → use `defaultValue` prop
//
// For inputs WITH format:
// - Formatting requires controlled mode to re-render and show formatted values
// - Case 1: Only `value` provided → defaultValue: undefined, value: formattedValue (normal controlled)
// - Case 2: Only `defaultValue` provided → defaultValue: undefined, value: formattedValue (convert to controlled)
// - Case 3: Both `value` and `defaultValue` provided → defaultValue: defaultValue, value: formattedValue (let BaseInput detect conflict and throw error)
//
,
defaultValue: format ? value !== undefined && defaultValue !== undefined ? defaultValue : undefined : defaultValue,
value: format ? inputValue : value,
name: name,
maxCharacters: effectiveMaxCharacters,
isDropdownTrigger: isTaggedInput,
tags: isTaggedInput ? getTags({
size: size
}) : undefined,
showAllTags: isInputFocussed,
maxTagRows: "single",
activeTagIndex: activeTagIndex,
setActiveTagIndex: setActiveTagIndex,
leadingDropDown: renderLeadingDropDown,
trailingDropDown: renderTrailingDropDown,
leadingInteractionElement: hasLeadingInteractionElement ? leading : null,
onChange: function onChange(_ref3) {
var name = _ref3.name,
value = _ref3.value;
if (showClearButton && value !== null && value !== void 0 && value.length) {
// show the clear button when the user starts typing in
setShouldShowClearButton(true);
}
if (shouldShowClearButton && !(value !== null && value !== void 0 && value.length)) {
// hide the clear button when the input field is empty
setShouldShowClearButton(false);
}
handleTaggedInputChange({
name: name,
value: value
});
handleOnChange({
name: name,
value: value
});
},
onClick: onClick,
onFocus: function onFocus(e) {
setIsInputFocussed(true);
_onFocus === null || _onFocus === void 0 || _onFocus(e);
},
onBlur: function onBlur(e) {
setIsInputFocussed(false);
_onBlur === null || _onBlur === void 0 || _onBlur(e);
},
onKeyDown: function onKeyDown(e) {
handleTaggedInputKeydown(e);
_onKeyDown === null || _onKeyDown === void 0 || _onKeyDown(e);
if (format) {
formattingResult.handleKeyDown(e.event);
}
},
onSubmit: onSubmit,
isDisabled: isDisabled,
necessityIndicator: necessityIndicator,
isRequired: isRequired,
leadingIcon: (_ref4 = _leadingIcon !== null && _leadingIcon !== void 0 ? _leadingIcon : leadingIcon) !== null && _ref4 !== void 0 ? _ref4 : icon,
prefix: prefix,
trailingInteractionElement: renderInteractionElement(),
trailingIcon: _trailingIcon !== null && _trailingIcon !== void 0 ? _trailingIcon : trailingIcon,
suffix: suffix,
validationState: validationState,
errorText: errorText,
helpText: helpText,
successText: successText,
trailingFooterSlot: function trailingFooterSlot(value) {
var _value$length;
return format ? null : effectiveMaxCharacters ? /*#__PURE__*/jsx(BaseBox, {
marginTop: hintMarginTop[size],
marginRight: "spacing.1",
children: /*#__PURE__*/jsx(CharacterCounter, {
currentCount: (_value$length = value === null || value === void 0 ? void 0 : value.length) !== null && _value$length !== void 0 ? _value$length : 0,
maxCount: effectiveMaxCharacters,
size: size
})
}) : null;
}
// eslint-disable-next-line jsx-a11y/no-autofocus
,
autoFocus: autoFocus,
testID: testID
}, getKeyboardAndAutocompleteProps({
type: type,
keyboardReturnKeyType: keyboardReturnKeyType,
autoCompleteSuggestionType: autoCompleteSuggestionType,
autoCapitalize: autoCapitalize
})), {}, {
size: size
}, rest));
};
var TextInput = /*#__PURE__*/assignWithoutSideEffects(/*#__PURE__*/React__default.forwardRef(_TextInput), {
displayName: 'TextInput'
});
export { TextInput };
//# sourceMappingURL=TextInput.js.map