@oxyhq/services
Version:
374 lines (371 loc) • 12.4 kB
JavaScript
"use strict";
import * as React from 'react';
import { I18nManager, Platform, StyleSheet, TextInput as NativeTextInput, View, Animated } from 'react-native';
import { useThemeColors } from "../../hooks/useThemeColors.js";
import { Underline } from "./Addons/Underline.js";
import { AdornmentSide, AdornmentType, InputMode } from "./Adornment/enums.js";
import TextFieldAdornment from "./Adornment/TextFieldAdornment.js";
import { getAdornmentConfig, getAdornmentStyleAdjustmentForNativeInput } from "./Adornment/TextFieldAdornment.js";
import { ADORNMENT_SIZE, LABEL_PADDING_TOP_DENSE, LABEL_WIGGLE_X_OFFSET, MAXIMIZED_LABEL_FONT_SIZE, MINIMIZED_LABEL_FONT_SIZE, MINIMIZED_LABEL_Y_OFFSET, MIN_DENSE_HEIGHT, MIN_DENSE_HEIGHT_WL } from "./constants.js";
import { adjustPaddingFlat, calculateFlatAffixTopPosition, calculateFlatInputHorizontalPadding, calculateInputHeight, calculateLabelTopPosition, calculatePadding, getConstants, getFlatInputColors } from "./helpers.js";
import InputLabel from "./Label/InputLabel.js";
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
const TextInputFlat = ({
disabled = false,
editable = true,
label,
error = false,
selectionColor: customSelectionColor,
cursorColor,
underlineColor,
underlineStyle,
activeUnderlineColor,
textColor,
dense,
style,
theme,
render = props => /*#__PURE__*/_jsx(NativeTextInput, {
...props
}),
multiline = false,
parentState,
innerRef,
onFocus,
forceFocus,
onBlur,
onChangeText,
onLayoutAnimatedText,
onLabelTextLayout,
onLeftAffixLayoutChange,
onRightAffixLayoutChange,
onInputLayout,
left,
right,
placeholderTextColor,
testID = 'text-input-flat',
contentStyle,
scaledLabel,
...rest
}) => {
const isAndroid = Platform.OS === 'android';
const {
isV3,
roundness
} = theme;
const font = isV3 ? theme.fonts.bodyLarge : theme.fonts.default;
const hasActiveOutline = parentState.focused || error;
const {
LABEL_PADDING_TOP,
FLAT_INPUT_OFFSET,
MIN_HEIGHT,
MIN_WIDTH
} = getConstants(isV3);
const {
fontSize: fontSizeStyle,
lineHeight: lineHeightStyle,
fontWeight,
height,
paddingHorizontal,
textAlign,
...viewStyle
} = StyleSheet.flatten(style) || {};
const fontSize = fontSizeStyle || MAXIMIZED_LABEL_FONT_SIZE;
// Always set a minimum lineHeight to prevent text cutoff
// Use at least 1.4x fontSize for better text spacing
const lineHeight = lineHeightStyle || fontSize * 1.4;
const isPaddingHorizontalPassed = paddingHorizontal !== undefined && typeof paddingHorizontal === 'number';
const adornmentConfig = getAdornmentConfig({
left,
right
});
let {
paddingLeft,
paddingRight
} = calculateFlatInputHorizontalPadding({
adornmentConfig,
isV3
});
if (isPaddingHorizontalPassed) {
paddingLeft = paddingHorizontal;
paddingRight = paddingHorizontal;
}
const {
leftLayout,
rightLayout
} = parentState;
const rightAffixWidth = right ? rightLayout.width || ADORNMENT_SIZE : ADORNMENT_SIZE;
const leftAffixWidth = left ? leftLayout.width || ADORNMENT_SIZE : ADORNMENT_SIZE;
const adornmentStyleAdjustmentForNativeInput = getAdornmentStyleAdjustmentForNativeInput({
adornmentConfig,
rightAffixWidth,
leftAffixWidth,
paddingHorizontal,
inputOffset: FLAT_INPUT_OFFSET,
mode: InputMode.Flat,
isV3
});
const {
inputTextColor,
activeColor,
underlineColorCustom,
placeholderColor,
errorColor,
backgroundColor,
selectionColor
} = getFlatInputColors({
underlineColor,
activeUnderlineColor,
customSelectionColor,
textColor,
disabled,
error,
theme
});
const themeColors = useThemeColors();
const containerStyle = {
backgroundColor: themeColors.inputBackground,
borderTopLeftRadius: theme.roundness,
borderTopRightRadius: theme.roundness
};
// Override text color with theme color (unless custom textColor prop is provided)
// If disabled, keep the original inputTextColor which handles opacity correctly
const finalInputTextColor = textColor ?? (disabled ? inputTextColor : themeColors.text);
const labelScale = MINIMIZED_LABEL_FONT_SIZE / fontSize;
const fontScale = MAXIMIZED_LABEL_FONT_SIZE / fontSize;
const labelWidth = parentState.labelLayout.width;
const labelHeight = parentState.labelLayout.height;
const labelHalfWidth = labelWidth / 2;
const labelHalfHeight = labelHeight / 2;
const baseLabelTranslateX = (I18nManager.getConstants().isRTL ? 1 : -1) * (labelHalfWidth - labelScale * labelWidth / 2) + (1 - labelScale) * (I18nManager.getConstants().isRTL ? -1 : 1) * paddingLeft;
const minInputHeight = dense ? (label ? MIN_DENSE_HEIGHT_WL : MIN_DENSE_HEIGHT) - LABEL_PADDING_TOP_DENSE : MIN_HEIGHT - LABEL_PADDING_TOP;
const inputHeight = calculateInputHeight(labelHeight, height, minInputHeight);
const topPosition = calculateLabelTopPosition(labelHeight, inputHeight, multiline && height ? 0 : !height ? minInputHeight / 2 : 0);
if (height && typeof height !== 'number') {
// eslint-disable-next-line
console.warn('Currently we support only numbers in height prop');
}
const paddingSettings = {
height: height ? +height : null,
labelHalfHeight,
offset: FLAT_INPUT_OFFSET,
multiline: multiline ? multiline : null,
dense: dense ? dense : null,
topPosition,
fontSize,
lineHeight,
label,
scale: fontScale,
isAndroid,
styles: StyleSheet.flatten(dense ? styles.inputFlatDense : styles.inputFlat)
};
const pad = calculatePadding(paddingSettings);
const paddingFlat = adjustPaddingFlat({
...paddingSettings,
pad
});
const baseLabelTranslateY = -labelHalfHeight - (topPosition + MINIMIZED_LABEL_Y_OFFSET);
const {
current: placeholderOpacityAnims
} = React.useRef([new Animated.Value(0), new Animated.Value(1)]);
const placeholderOpacity = hasActiveOutline ? parentState.labeled : placeholderOpacityAnims[parentState.labelLayout.measured ? 1 : 0];
// We don't want to show placeholder if label is displayed, because they overlap.
// Before it was done by setting placeholder's value to " ", but inputs have the same props
// what causes broken styles due to: https://github.com/facebook/react-native/issues/48249
const placeholderTextColorBasedOnState = parentState.displayPlaceholder ? placeholderTextColor ?? placeholderColor : 'transparent';
const minHeight = height || (dense ? label ? MIN_DENSE_HEIGHT_WL : MIN_DENSE_HEIGHT : MIN_HEIGHT);
const flatHeight = inputHeight + (!height ? dense ? LABEL_PADDING_TOP_DENSE : LABEL_PADDING_TOP : 0);
const iconTopPosition = (flatHeight - ADORNMENT_SIZE) / 2;
const leftAffixTopPosition = leftLayout.height ? calculateFlatAffixTopPosition({
height: flatHeight,
...paddingFlat,
affixHeight: leftLayout.height
}) : null;
const rightAffixTopPosition = rightLayout.height ? calculateFlatAffixTopPosition({
height: flatHeight,
...paddingFlat,
affixHeight: rightLayout.height
}) : null;
const labelProps = {
label,
onLayoutAnimatedText,
onLabelTextLayout,
placeholderOpacity,
labelError: error,
placeholderStyle: styles.placeholder,
baseLabelTranslateY,
baseLabelTranslateX,
font,
fontSize,
lineHeight,
fontWeight,
labelScale,
wiggleOffsetX: LABEL_WIGGLE_X_OFFSET,
topPosition,
paddingLeft: isAndroid ? I18nManager.isRTL ? paddingRight : paddingLeft : paddingLeft,
paddingRight: isAndroid ? I18nManager.isRTL ? paddingLeft : paddingRight : paddingRight,
hasActiveOutline,
activeColor,
placeholderColor,
errorColor,
roundness,
maxFontSizeMultiplier: rest.maxFontSizeMultiplier,
testID,
contentStyle,
inputContainerLayout: parentState.inputContainerLayout,
labelTextLayout: parentState.labelTextLayout,
opacity: parentState.value || parentState.focused ? parentState.labelLayout.measured ? 1 : 0 : 1,
isV3
};
const affixTopPosition = {
[AdornmentSide.Left]: leftAffixTopPosition,
[AdornmentSide.Right]: rightAffixTopPosition
};
const onAffixChange = {
[AdornmentSide.Left]: onLeftAffixLayoutChange,
[AdornmentSide.Right]: onRightAffixLayoutChange
};
let adornmentProps = {
paddingHorizontal,
adornmentConfig,
forceFocus,
topPosition: {
[AdornmentType.Affix]: affixTopPosition,
[AdornmentType.Icon]: iconTopPosition
},
onAffixChange,
isTextInputFocused: parentState.focused,
maxFontSizeMultiplier: rest.maxFontSizeMultiplier,
disabled
};
if (adornmentConfig.length) {
adornmentProps = {
...adornmentProps,
left,
right,
textStyle: {
...font,
fontSize,
lineHeight,
fontWeight
},
visible: parentState.labeled
};
}
return /*#__PURE__*/_jsxs(View, {
style: [containerStyle, viewStyle],
children: [/*#__PURE__*/_jsx(Underline, {
style: underlineStyle,
hasActiveOutline: hasActiveOutline,
parentState: parentState,
underlineColorCustom: underlineColorCustom,
error: error,
colors: {
error: errorColor
},
activeColor: activeColor,
theme: theme
}), /*#__PURE__*/_jsxs(View, {
onLayout: onInputLayout,
style: [styles.labelContainer, {
minHeight,
flex: 1,
flexShrink: 1
}],
children: [!isAndroid && multiline && !!label && !disabled &&
/*#__PURE__*/
// Workaround for: https://github.com/callstack/react-native-paper/issues/2799
// Patch for a multiline TextInput with fixed height, which allow to avoid covering input label with its value.
_jsx(View, {
testID: "patch-container",
pointerEvents: "none",
style: [StyleSheet.absoluteFill, dense ? styles.densePatchContainer : styles.patchContainer, {
backgroundColor: viewStyle.backgroundColor || containerStyle.backgroundColor,
left: paddingLeft,
right: paddingRight
}]
}), label ? /*#__PURE__*/_jsx(InputLabel, {
labeled: parentState.labeled,
error: parentState.error,
focused: parentState.focused,
scaledLabel: scaledLabel,
wiggle: Boolean(parentState.value && labelProps.labelError),
labelLayoutMeasured: parentState.labelLayout.measured,
labelLayoutWidth: parentState.labelLayout.width,
labelLayoutHeight: parentState.labelLayout.height,
...labelProps
}) : null, render?.({
...rest,
ref: innerRef,
onChangeText,
placeholder: rest.placeholder,
editable: !disabled && editable,
selectionColor,
cursorColor: typeof cursorColor === 'undefined' ? activeColor : cursorColor,
placeholderTextColor: placeholderTextColorBasedOnState,
onFocus,
onBlur,
underlineColorAndroid: 'transparent',
multiline,
style: [styles.input, multiline && height ? {
height: flatHeight
} : {}, paddingFlat, {
paddingLeft,
paddingRight,
...font,
fontSize,
lineHeight,
fontWeight,
color: finalInputTextColor,
textAlignVertical: multiline ? 'top' : 'center',
textAlign: textAlign ? textAlign : I18nManager.getConstants().isRTL ? 'right' : 'left',
flex: 1,
flexShrink: 1,
minWidth: 0
}, Platform.OS === 'web' ? {
outline: 'none',
outlineWidth: 0,
outlineStyle: 'none'
} : undefined, adornmentStyleAdjustmentForNativeInput, contentStyle],
testID
})]
}), /*#__PURE__*/_jsx(TextFieldAdornment, {
...adornmentProps
})]
});
};
export default TextInputFlat;
const styles = StyleSheet.create({
placeholder: {
position: 'absolute',
left: 0
},
labelContainer: {
paddingTop: 0,
paddingBottom: 0,
flexGrow: 1,
overflow: 'visible'
},
input: {
margin: 0,
flexGrow: 1,
flexShrink: 1,
minWidth: 0
},
inputFlat: {
paddingTop: 24,
paddingBottom: 12
},
inputFlatDense: {
paddingTop: 22,
paddingBottom: 10
},
patchContainer: {
height: 24,
zIndex: 2
},
densePatchContainer: {
height: 22,
zIndex: 2
}
});
//# sourceMappingURL=TextFieldFlat.js.map