UNPKG

@razorpay/blade

Version:

The Design System that powers Razorpay

402 lines (399 loc) 18.4 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 } 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 (true) { 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