@onehat/ui
Version:
Base UI for OneHat apps
209 lines (194 loc) • 5.54 kB
JavaScript
import { cloneElement, forwardRef, isValidElement, useState, useEffect, useRef, } from 'react';
import {
Input, InputField, InputIcon, InputSlot,
} from '@project-components/Gluestack';
import clsx from 'clsx';
import {
hasWidth,
hasFlex,
} from '../../../Functions/tailwindFunctions.js';
import UiGlobals from '../../../UiGlobals.js';
import addIconProps from '../../../Functions/addIconProps.js';
import withComponent from '../../Hoc/withComponent.js';
import withTooltip from '../../Hoc/withTooltip.js';
import withValue from '../../Hoc/withValue.js';
import _ from 'lodash';
const InputElement = forwardRef((props, ref) => {
let {
value,
setValue,
maxLength,
autoSubmit = true, // automatically setValue after user stops typing for autoSubmitDelay
autoSubmitDelay = UiGlobals.autoSubmitDelay,
disableAutoFlex = false,
onKeyPress,
onChangeText,
leftElement = null,
leftIcon = null,
_leftIcon,
leftIconHandler,
rightElement = null,
rightIcon = null,
_rightIcon,
rightIconHandler,
placeholder,
textAlignIsCenter = false,
className,
...propsToPass
} = props,
styles = UiGlobals.styles,
debouncedSetValueRef = useRef(),
[localValue, setLocalValue] = useState(value),
isTypingRef = useRef(),
isTypingTimeoutRef = useRef(),
isTyping = () => {
return isTypingRef.current;
},
setIsTyping = (isTyping) => {
isTypingRef.current = isTyping;
if (isTyping) {
startIsTypingTimeout();
}
},
startIsTypingTimeout = () => {
clearIsTypingTimeout();
isTypingTimeoutRef.current = setTimeout(() => {
setIsTyping(false);
}, autoSubmitDelay + 1000);
},
clearIsTypingTimeout = () => {
if (isTypingTimeoutRef.current) {
clearTimeout(isTypingTimeoutRef.current);
}
},
onKeyPressLocal = (e) => {
if (e.key === 'Enter') {
debouncedSetValueRef.current?.cancel();
setValue(localValue);
}
if (onKeyPress) {
onKeyPress(e, localValue);
}
},
onChangeTextLocal = (value) => {
setIsTyping(true);
if (value === '') {
value = null; // empty string makes value null
setLocalValue(value);
} else if (!maxLength || maxLength >= value?.length ) {
setLocalValue(value);
}
if (autoSubmit) {
debouncedSetValueRef.current(value);
}
if (onChangeText) {
onChangeText(value);
}
};
useEffect(() => {
// Set up debounce fn
// Have to do this because otherwise, lodash tries to create a debounced version of the fn from only this render
debouncedSetValueRef.current?.cancel(); // Cancel any previous debounced fn
debouncedSetValueRef.current = _.debounce(setValue, autoSubmitDelay);
}, [setValue]);
useEffect(() => {
if (!isTyping() && value !== localValue) {
// Make local value conform to externally changed value
setLocalValue(value);
}
}, [value]);
if (localValue === null || typeof localValue === 'undefined') {
localValue = ''; // If the value is null or undefined, don't let this be an uncontrolled input
}
const style = props.style || {};
// auto-set width to flex if it's not already set another way
if (!disableAutoFlex && !hasWidth(props) && !hasFlex(props)) {
style.flex = 1;
}
let inputClassName = clsx(
'Input',
styles.FORM_INPUT_CLASSNAME,
),
inputFieldClassName = clsx(
'InputField',
'self-stretch',
'h-auto',
'w-full',
'p-2',
textAlignIsCenter ? 'text-center' : 'text-left',
styles.FORM_INPUT_CLASSNAME,
styles.FORM_INPUT_FIELD_CLASSNAME,
);
if (className) {
inputClassName += className;
}
if (leftElement) {
leftElement = <InputSlot className="leftElementInputSlot">{leftElement}</InputSlot>;
}
if (leftIcon) {
if (!_leftIcon) {
_leftIcon = {};
}
if (!_leftIcon.className) {
_leftIcon.className = '';
}
_leftIcon.className = 'leftInputIcon mr-2 ' + _leftIcon.className; // prepend the margin, so it can potentially be overridden
if (isValidElement(leftIcon)) {
if (_leftIcon) {
leftIcon = cloneElement(leftIcon, addIconProps(_leftIcon || {}));
}
} else {
leftIcon = <InputIcon as={leftIcon} {...addIconProps(_leftIcon || {})} />;
}
if (leftIconHandler) {
leftIcon = <InputSlot onPress={leftIconHandler} className="LeftInputSlot">
{leftIcon}
</InputSlot>;
}
}
if (rightElement) {
rightElement = <InputSlot className="rightElementInputSlot">{rightElement}</InputSlot>;
}
if (rightIcon) {
if (!_rightIcon) {
_rightIcon = {};
}
if (!_rightIcon.className) {
_rightIcon.className = '';
}
_rightIcon.className = 'rightInputIcon ml-2 ' + _rightIcon.className; // prepend the margin, so it can potentially be overridden
if (isValidElement(rightIcon)) {
if (_rightIcon) {
rightIcon = cloneElement(rightIcon, addIconProps(_rightIcon || {}));
}
} else {
rightIcon = <InputIcon as={rightIcon} {...addIconProps(_rightIcon || {})} />;
}
if (rightIconHandler) {
rightIcon = <InputSlot onPress={rightIconHandler} className="RightInputSlot">
{rightIcon}
</InputSlot>;
}
}
return <Input
className={inputClassName}
style={style}
>
{leftElement}
{leftIcon}
<InputField
ref={ref}
onChangeText={onChangeTextLocal}
onKeyPress={onKeyPressLocal}
value={localValue}
className={inputFieldClassName}
dataFocusVisible={true}
variant="outline"
placeholder={placeholder}
{...propsToPass}
/>
{rightElement}
{rightIcon}
</Input>;
});
export default withComponent(withValue(withTooltip(InputElement)));