@razorpay/blade
Version:
The Design System that powers Razorpay
395 lines (379 loc) • 19.5 kB
JavaScript
import _typeof from '@babel/runtime/helpers/typeof';
import _defineProperty from '@babel/runtime/helpers/defineProperty';
import _slicedToArray from '@babel/runtime/helpers/slicedToArray';
import _objectWithoutProperties from '@babel/runtime/helpers/objectWithoutProperties';
import React__default from 'react';
import { useDatesContext } from '@mantine/dates';
import { stripDelimiters, validateAndParseDateInput, rangeFormattedValue, getTextInputFormat, finalInputFormat, getFormattedDate, rangeInputPlaceHolder } from './utils.js';
import { useDatePickerContext } from './DatePickerContext.js';
import '../Box/BaseBox/index.js';
import '../Input/TextInput/index.js';
import '../../utils/index.js';
import '../Icons/index.js';
import '../../utils/makeAnalyticsAttribute/index.js';
import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
import CalendarIcon from '../Icons/CalendarIcon/CalendarIcon.js';
import { TextInput } from '../Input/TextInput/TextInput.js';
import { isReactNative } from '../../utils/platform/isReactNative.js';
import { makeAnalyticsAttribute } from '../../utils/makeAnalyticsAttribute/makeAnalyticsAttribute.js';
import { BaseBox } from '../Box/BaseBox/BaseBox.web.js';
var _excluded = ["format", "date", "setControlledValue", "effectiveSelectionType", "leadingDropdown", "tags", "id", "selectedPresetLabel", "showClearButton", "onClearButtonClick"],
_excluded2 = ["value", "name", "isRequired", "isDisabled"],
_excluded3 = ["selectionType", "referenceProps", "inputRef", "date", "label", "labelPosition", "labelSuffix", "labelTrailing", "autoFocus", "name", "size", "necessityIndicator", "successText", "errorText", "helpText", "format", "placeholder", "setControlledValue", "leadingDropdown", "selectedPreset", "excludeDate", "minDate", "maxDate", "effectiveSelectionType", "showClearButton", "onClearButtonClick", "selectedPresetLabel"];
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 _DateInput = function _DateInput(props, ref) {
var _datePickerContext$is, _datePickerContext$di;
var format = props.format,
date = props.date,
setControlledValue = props.setControlledValue,
effectiveSelectionType = props.effectiveSelectionType,
leadingDropdown = props.leadingDropdown,
tags = props.tags,
id = props.id,
selectedPresetLabel = props.selectedPresetLabel,
showClearButton = props.showClearButton,
onClearButtonClick = props.onClearButtonClick,
textInputProps = _objectWithoutProperties(props, _excluded);
// Use context to check datepicker state - more reliable than prop drilling
var datePickerContext = useDatePickerContext();
var isPopupOpen = (_datePickerContext$is = datePickerContext === null || datePickerContext === void 0 ? void 0 : datePickerContext.isDatePickerBodyOpen) !== null && _datePickerContext$is !== void 0 ? _datePickerContext$is : false;
var displayFormat = (_datePickerContext$di = datePickerContext === null || datePickerContext === void 0 ? void 0 : datePickerContext.displayFormat) !== null && _datePickerContext$di !== void 0 ? _datePickerContext$di : 'default';
var isCompactMode = displayFormat === 'compact';
var _React$useState = React__default.useState(['']),
_React$useState2 = _slicedToArray(_React$useState, 2),
inputValue = _React$useState2[0],
setInputValue = _React$useState2[1];
var _React$useState3 = React__default.useState(undefined),
_React$useState4 = _slicedToArray(_React$useState3, 2),
validationError = _React$useState4[0],
setValidationError = _React$useState4[1];
var _React$useState5 = React__default.useState(false),
_React$useState6 = _slicedToArray(_React$useState5, 2),
isFocused = _React$useState6[0],
setIsFocused = _React$useState6[1];
var shouldShowCalendarIcon = !Boolean(leadingDropdown);
// Determine selection type: prefer preset context calculation over props
// This handles "Today" presets that should display as single even though data is range
var isRange = effectiveSelectionType === 'single' ? false : effectiveSelectionType === 'range' || props.selectionType === 'range';
// Sync internal input state with external formatted values from parent component
// textInputProps.value comes from DatePickerInput as formatted strings: ["25/12/2024", "31/12/2024"]
// We strip delimiters for internal processing: ["25122024", "31122024"]
// This prevents double formatting and helps to validate the input easier during user typing
React__default.useEffect(function () {
if (textInputProps.value) {
setInputValue(isRange ? [stripDelimiters(textInputProps.value[0]), stripDelimiters(textInputProps.value[1])] : [stripDelimiters(textInputProps.value[0])]);
}
}, [textInputProps.value, isRange, format]);
// Clear validation error only when the actual selected date changes
// (e.g., user selected a valid date from the calendar). This avoids
// clearing errors during typing/blur unless the value truly updated.
React__default.useEffect(function () {
setValidationError(undefined);
}, [date]);
var applyDateValue = React__default.useCallback(function (inputValue) {
var shouldClearWhenEmpty = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
if (inputValue !== null && inputValue !== void 0 && inputValue.trim()) {
// Validate input and get parsed dates in one atomic operation (includes all constraints)
var validation = validateAndParseDateInput(inputValue, isRange, format, {
excludeDate: props.excludeDate,
minDate: props.minDate,
maxDate: props.maxDate
});
if (validation.shouldBlock) {
return; // Block invalid input to prevent data corruption
}
// Apply the pre-parsed date values to controlled state (no redundant parsing)
if (validation.parsedValue !== undefined) {
var finalValue = validation.parsedValue;
// Special handling: if preset context shows single but props expect range
// (like "Today" preset), convert single date back to same-day range
if (effectiveSelectionType === 'single' && props.selectionType === 'range' && validation.parsedValue instanceof Date) {
finalValue = [validation.parsedValue, validation.parsedValue];
}
setControlledValue === null || setControlledValue === void 0 || setControlledValue(finalValue);
}
} else if (shouldClearWhenEmpty) {
// Clear controlled value when input is emptied (onChange only, not onBlur)
setControlledValue === null || setControlledValue === void 0 || setControlledValue(isRange ? [null, null] : null);
}
}, [isRange, setControlledValue, effectiveSelectionType, props.selectionType, format, props.excludeDate, props.minDate, props.maxDate]);
var handleInputChange = function handleInputChange(_ref) {
var value = _ref.value;
var inputValue = value !== null && value !== void 0 ? value : '';
setValidationError(undefined);
if (inputValue !== null && inputValue !== void 0 && inputValue.trim()) {
var validation = validateAndParseDateInput(inputValue, isRange, format, {
excludeDate: props.excludeDate,
minDate: props.minDate,
maxDate: props.maxDate
});
if (validation.shouldBlock && validation.error) {
setValidationError(validation.error);
}
}
// Apply changes immediately during typing (with empty clearing enabled)
applyDateValue(inputValue, true);
};
var handleBlur = React__default.useCallback(function (params) {
var _ref2, _params$event$target$, _params$event;
var currentInputValue = (_ref2 = (_params$event$target$ = (_params$event = params.event) === null || _params$event === void 0 ? void 0 : _params$event.target.value) !== null && _params$event$target$ !== void 0 ? _params$event$target$ : params.value) !== null && _ref2 !== void 0 ? _ref2 : '';
setValidationError(undefined);
if (currentInputValue !== null && currentInputValue !== void 0 && currentInputValue.trim()) {
// Validate complete input and show errors to user on blur (includes all constraints)
var validation = validateAndParseDateInput(currentInputValue, isRange, format, {
excludeDate: props.excludeDate,
minDate: props.minDate,
maxDate: props.maxDate
});
if (validation.shouldBlock && validation.error) {
setValidationError(validation.error);
return; // Don't apply invalid values
}
}
// Apply final value on blur (without empty clearing to preserve existing dates)
applyDateValue(currentInputValue, false);
}, [applyDateValue, isRange]);
var handleFocus = React__default.useCallback(function () {
setIsFocused(true);
}, []);
var handleBlurWithFocusState = React__default.useCallback(function (params) {
setIsFocused(false);
handleBlur(params);
}, [handleBlur]);
// Compute input props based on whether we're showing a preset label or date input
// DatePicker is "active" when input is focused OR popup is open (from context)
// This ensures consistent behavior when focus moves between input and calendar
var isDatePickerActive = isFocused || isPopupOpen;
// Check if there's actually a valid date selection. in range mode we need to check if both dates are not null
var hasValidSelection = isRange ? (date === null || date === void 0 ? void 0 : date[0]) && (date === null || date === void 0 ? void 0 : date[1]) : Boolean(date);
// Show preset label only when:
// 1. displayFormat is 'compact'
// 2. There's a preset label to show
// 3. There's actually a valid date selection (not cleared)
// 4. DatePicker is NOT active (closed and not focused)
var showPresetLabel = displayFormat === 'compact' && selectedPresetLabel && hasValidSelection && !isDatePickerActive;
var getInputDisplayProps = function getInputDisplayProps() {
var _textInputProps$error;
if (showPresetLabel) {
// Preset label mode: show clean input with just the label (not focused)
return {
type: 'text',
value: selectedPresetLabel,
leadingIcon: CalendarIcon,
format: undefined,
validationState: textInputProps.validationState,
errorText: textInputProps.errorText,
onChange: handleInputChange,
onBlur: handleBlurWithFocusState,
onFocus: handleFocus
};
}
// Date input mode: show formatted date with full editing capabilities
var dateDisplayValue = isRange ? rangeFormattedValue(inputValue[0], inputValue[1]) : inputValue[0];
var dateInputFormat = isRange ? getTextInputFormat(finalInputFormat(inputValue[0], inputValue[1], format), true) : getTextInputFormat(format, false);
// In compact mode: always show icon instead of dropdown (presets accessed via popup sidebar)
// In non-compact mode: show the dropdown as normal
// Note: We must conditionally include `leading` only when defined, otherwise `leading: undefined`
// would override/hide `leadingIcon` in the TextInput component
var showLeadingDropdown = isCompactMode ? undefined : leadingDropdown;
var showLeadingIcon = isCompactMode || shouldShowCalendarIcon ? CalendarIcon : undefined;
return _objectSpread(_objectSpread({
type: 'number',
value: dateDisplayValue,
leadingIcon: showLeadingIcon
}, showLeadingDropdown ? {
leading: showLeadingDropdown
} : {}), {}, {
format: dateInputFormat,
validationState: validationError ? 'error' : textInputProps.validationState,
errorText: (_textInputProps$error = textInputProps.errorText) !== null && _textInputProps$error !== void 0 ? _textInputProps$error : validationError,
onChange: handleInputChange,
onBlur: handleBlurWithFocusState,
onFocus: handleFocus
});
};
return /*#__PURE__*/jsx(TextInput, _objectSpread(_objectSpread(_objectSpread({}, textInputProps), {}, {
ref: ref
}, getInputDisplayProps()), {}, {
onClick: function onClick(e) {
var _textInputProps$onCli;
if (textInputProps.isDisabled) {
return;
}
(_textInputProps$onCli = textInputProps.onClick) === null || _textInputProps$onCli === void 0 || _textInputProps$onCli.call(textInputProps, e);
},
onKeyDown: function onKeyDown(_ref3) {
var _textInputProps$onKey;
var event = _ref3.event;
// @ts-expect-error
(_textInputProps$onKey = textInputProps.onKeyDown) === null || _textInputProps$onKey === void 0 || _textInputProps$onKey.call(textInputProps, event);
},
showClearButton: showClearButton,
onClearButtonClick: onClearButtonClick
}));
};
var DateInput = /*#__PURE__*/React__default.forwardRef(_DateInput);
var HiddenInput = function HiddenInput(_ref4) {
var value = _ref4.value,
name = _ref4.name,
isRequired = _ref4.isRequired,
isDisabled = _ref4.isDisabled,
rest = _objectWithoutProperties(_ref4, _excluded2);
if (isReactNative()) return /*#__PURE__*/jsx(Fragment, {});
return /*#__PURE__*/jsx("input", _objectSpread({
hidden: true,
name: name,
value: value,
required: isRequired,
disabled: isDisabled,
readOnly: true
}, makeAnalyticsAttribute(rest)));
};
var _DatePickerInput = function _DatePickerInput(_ref5, ref) {
var selectionType = _ref5.selectionType,
referenceProps = _ref5.referenceProps,
inputRef = _ref5.inputRef,
date = _ref5.date,
label = _ref5.label,
labelPosition = _ref5.labelPosition,
labelSuffix = _ref5.labelSuffix,
labelTrailing = _ref5.labelTrailing,
autoFocus = _ref5.autoFocus,
name = _ref5.name,
_ref5$size = _ref5.size,
size = _ref5$size === void 0 ? 'medium' : _ref5$size,
necessityIndicator = _ref5.necessityIndicator,
successText = _ref5.successText,
errorText = _ref5.errorText,
helpText = _ref5.helpText,
format = _ref5.format,
placeholder = _ref5.placeholder,
setControlledValue = _ref5.setControlledValue,
leadingDropdown = _ref5.leadingDropdown,
selectedPreset = _ref5.selectedPreset,
excludeDate = _ref5.excludeDate,
minDate = _ref5.minDate,
maxDate = _ref5.maxDate,
effectiveSelectionType = _ref5.effectiveSelectionType,
showClearButton = _ref5.showClearButton,
onClearButtonClick = _ref5.onClearButtonClick,
selectedPresetLabel = _ref5.selectedPresetLabel,
props = _objectWithoutProperties(_ref5, _excluded3);
var _useDatesContext = useDatesContext(),
locale = _useDatesContext.locale;
if (selectionType == 'single') {
var dateValue = getFormattedDate({
date: date,
format: format,
labelSeparator: '-',
locale: locale,
type: 'default'
});
return /*#__PURE__*/jsxs(BaseBox, {
width: "100%",
children: [/*#__PURE__*/jsx(HiddenInput, {
value: dateValue,
name: name,
isRequired: props.isRequired,
isDisabled: props.isDisabled
}), /*#__PURE__*/jsx(DateInput, _objectSpread(_objectSpread({
ref: ref,
id: "start-date",
labelPosition: labelPosition,
label: label,
placeholder: placeholder || format,
popupId: referenceProps['aria-controls'],
isPopupExpanded: referenceProps['aria-expanded'],
hasPopup: referenceProps['aria-haspopup'],
size: size,
autoFocus: autoFocus,
value: [dateValue],
componentName: "DatePickerInput",
necessityIndicator: necessityIndicator,
successText: successText,
errorText: errorText,
helpText: helpText,
labelSuffix: labelSuffix,
labelTrailing: labelTrailing,
leadingDropdown: leadingDropdown,
date: date,
setControlledValue: setControlledValue,
format: format,
selectionType: selectionType,
excludeDate: excludeDate,
minDate: minDate,
maxDate: maxDate,
effectiveSelectionType: effectiveSelectionType,
showClearButton: showClearButton,
onClearButtonClick: onClearButtonClick,
selectedPresetLabel: selectedPresetLabel
}, props), referenceProps))]
});
}
if (selectionType == 'range') {
var startValue = getFormattedDate({
type: 'default',
date: date[0],
format: format,
labelSeparator: '-',
locale: locale
});
var endValue = getFormattedDate({
type: 'default',
date: date[1],
format: format,
labelSeparator: '-',
locale: locale
});
return /*#__PURE__*/jsxs(BaseBox, {
width: "100%",
children: [/*#__PURE__*/jsx(HiddenInput, {
value: "".concat(startValue),
name: name === null || name === void 0 ? void 0 : name.start,
isRequired: props.isRequired,
isDisabled: props.isDisabled
}), /*#__PURE__*/jsx(HiddenInput, _objectSpread({
value: endValue,
name: name === null || name === void 0 ? void 0 : name.end,
isRequired: props.isRequired,
isDisabled: props.isDisabled
}, makeAnalyticsAttribute(props))), /*#__PURE__*/jsx(DateInput, _objectSpread(_objectSpread({
ref: ref,
id: "range-date",
labelPosition: labelPosition,
label: _typeof(label) === 'object' ? label === null || label === void 0 ? void 0 : label.start : label,
placeholder: rangeInputPlaceHolder(placeholder, format),
popupId: referenceProps['aria-controls'],
isPopupExpanded: referenceProps['aria-expanded'],
hasPopup: referenceProps['aria-haspopup'],
size: size,
autoFocus: autoFocus,
value: [startValue, endValue],
componentName: "DatePickerInputRange",
necessityIndicator: necessityIndicator,
successText: _typeof(successText) === 'object' ? successText === null || successText === void 0 ? void 0 : successText.start : successText,
errorText: _typeof(errorText) === 'object' ? errorText === null || errorText === void 0 ? void 0 : errorText.start : errorText,
helpText: _typeof(helpText) === 'object' ? helpText === null || helpText === void 0 ? void 0 : helpText.start : helpText,
labelSuffix: labelSuffix,
labelTrailing: labelTrailing,
format: format,
leadingDropdown: leadingDropdown,
date: date,
setControlledValue: setControlledValue,
selectionType: selectionType,
excludeDate: excludeDate,
minDate: minDate,
maxDate: maxDate,
effectiveSelectionType: effectiveSelectionType,
showClearButton: showClearButton,
onClearButtonClick: onClearButtonClick,
selectedPresetLabel: selectedPresetLabel
}, props), referenceProps))]
});
}
return /*#__PURE__*/jsx(Fragment, {});
};
var DatePickerInput = /*#__PURE__*/React__default.forwardRef(_DatePickerInput);
export { DatePickerInput };
//# sourceMappingURL=DateInput.web.js.map