@kadconsulting/dry
Version:
KAD Reusable Component Library
88 lines • 6.84 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { v4 as uuid } from 'uuid';
import classnames from 'classnames';
import InputMask from 'react-input-mask';
import { Label as DryLabel } from './constituents/Label';
import { ErrorText as DryErrorText } from './constituents/ErrorText';
import { HintText as DryHintText } from './constituents/HintText';
import { InputAdornment as DryInputAdornment } from './constituents/InputAdornment';
import { useLayoutEffect, useMemo, useRef, useState, forwardRef } from 'react';
import './TextInput.scss';
export const BASE_INPUT_PADDING_HORIZONTAL = 8;
/**
* The most atomic text input consisting of a container, an accessibility label, optional helper text below that swaps out optionally with an
* error message. Can be used to compose more complex text inputs, such as those with adornments for phone numbers, etc.
*/
const TextInput = forwardRef(({ ContainerProps, size = 'sm', Label: UserLabelNodes, LabelProps, labelRef, maskProps, InputAdornmentLeft, InputAdornmentLeftProps, InputAdornmentRight, InputAdornmentRightProps, ErrorText: UserErrorNodes, ErrorTextProps, errorTextRef, HintText: UserHintTextNodes, HintTextProps, HintTextRef, disabled, value, inputType = 'text', width, labelColor, inputTextColor, inputBackgroundColor, required, ...props }, ref) => {
const leftAdornmentRef = useRef(null);
const rightAdornmentRef = useRef(null);
/** If an id is not provided, ensure that one is generated, so that the label is accessible */
const [id] = useState(() => props.id || `input-${uuid()}`);
/** Computed pixel values for how much padding should be added to the input to prevent occlusion by the input adornment */
const [adornmentAwareLeftPadding, setAdornmentAwareLeftPadding] = useState(0);
const [adornmentAwareRightPadding, setAdornmentAwareRightPadding] = useState(0);
/**
* rome-ignore lint/nursery/useExhaustiveDependencies:
*
* We need this effect to fire if InputAdornmentLeft or InputAdornmentRight change,
* so that layout updates can take changes within the adornments into account.
* For example, the currency symbol's length can change, $ vs. Fr (francs)
*/
useLayoutEffect(() => {
if (!leftAdornmentRef.current && !rightAdornmentRef.current)
return;
// Get the computed width of the input adornment(s)
const leftAdornmentWidth = leftAdornmentRef.current?.getBoundingClientRect().width || 0;
const rightAdornmentWidth = rightAdornmentRef.current?.getBoundingClientRect().width || 0;
// Add gap to the computed width of the input adornment
setAdornmentAwareLeftPadding(leftAdornmentWidth);
setAdornmentAwareRightPadding(rightAdornmentWidth);
}, [
InputAdornmentLeft,
InputAdornmentRight,
setAdornmentAwareLeftPadding,
setAdornmentAwareRightPadding,
]);
const inputProps = useMemo(() => ({
id,
style: {
paddingLeft: `${adornmentAwareLeftPadding + BASE_INPUT_PADDING_HORIZONTAL}px`,
paddingRight: `${adornmentAwareRightPadding + BASE_INPUT_PADDING_HORIZONTAL}px`,
},
...props,
}), [id, props, adornmentAwareLeftPadding, adornmentAwareRightPadding]);
const Label = useMemo(() => (_jsx(DryLabel, { required: required, labelColor: labelColor, htmlFor: id, ref: labelRef, className: classnames('dry-textInput__label', LabelProps?.className), ...LabelProps, children: UserLabelNodes })), [UserLabelNodes, LabelProps, id, labelRef]);
const LeftAdornment = useMemo(() => InputAdornmentLeft && (_jsx(DryInputAdornment, { position: 'left', ref: leftAdornmentRef, ...InputAdornmentLeftProps, className: classnames(InputAdornmentLeftProps?.className), children: InputAdornmentLeft })), [InputAdornmentLeft]);
const MaskedInput = useMemo(() => {
if (!maskProps)
return null;
return (_jsx(InputMask, { ...maskProps, ...inputProps, className: classnames('dry-textInput__input', 'dry-textInput__input--masked', `dry-textInput__input--size-${size}`, { 'dry-textInput__input--disabled': disabled }, { 'dry-textInput__input--error': !!UserErrorNodes }, inputProps.className) }));
}, [maskProps, inputProps, UserErrorNodes, size, disabled]);
const Input = useMemo(() => {
// TODO-dry: we could add this as a utility function in DRY
const genStyle = () => {
const baseStyle = inputTextColor
? {
color: inputTextColor,
}
: {};
if (inputBackgroundColor) {
baseStyle.backgroundColor = inputBackgroundColor;
}
const overLayStyle = {};
return { ...baseStyle, ...overLayStyle };
};
return (_jsx("input", { ...inputProps, type: inputType, value: value, disabled: disabled, style: genStyle(), className: classnames('dry-textInput__input', 'dry-textInput__input--unmasked', `dry-textInput__input--size-${size}`, { 'dry-textInput__input--error': !!UserErrorNodes }, { 'dry-textInput__input--disabled': disabled }, inputProps.className) }));
}, [inputProps, UserErrorNodes, size, disabled]);
const RightAdornment = useMemo(() => InputAdornmentRight && (_jsx(DryInputAdornment, { position: 'right', ref: rightAdornmentRef, ...InputAdornmentRightProps, className: classnames(InputAdornmentRightProps?.className), children: InputAdornmentRight })), [InputAdornmentRight]);
const ErrorText = useMemo(() => UserErrorNodes && (_jsx(DryErrorText, { ref: errorTextRef, ...ErrorTextProps, className: classnames('dry-textInput__errorText', ErrorTextProps?.className), children: UserErrorNodes })), [UserErrorNodes, ErrorTextProps, errorTextRef]);
const HintText = useMemo(() => UserHintTextNodes && (_jsx(DryHintText, { ref: HintTextRef, ...HintTextProps, className: classnames('dry-textInput__HintText', HintTextProps?.className), children: UserHintTextNodes })), [UserHintTextNodes, HintTextProps, HintTextRef]);
/**
* Prioritize displaying error text over helper text because it's
* likely to be more specific to user's current validation situation.
*/
const DisplayedHintTextNode = useMemo(() => ErrorText || HintText, [ErrorText, HintText]);
return (_jsxs("div", { ref: ref, ...ContainerProps, className: classnames('dry-textInput', ContainerProps?.className), style: { width: width ? `${width}px` : '100%' }, children: [Label, _jsxs("div", { className: 'dry-textInput__adornmentContext', children: [LeftAdornment, maskProps ? MaskedInput : Input, RightAdornment] }), DisplayedHintTextNode, inputTextColor && (_jsx("style", { children: ` .dry-textInput__input::placeholder {color: ${inputTextColor}; }` }))] }));
});
export default TextInput;
//# sourceMappingURL=TextInput.js.map