UNPKG

@kietpt2003/react-native-core-ui

Version:
401 lines (400 loc) 17.1 kB
var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import React from 'react'; import { View, TextInput, StyleSheet, Animated, Easing, } from 'react-native'; import { colors, fontSize } from '../themes'; import { WEB } from '../utils'; import Text from '../Texts/Text'; const DEFAULT_ROWS = 3; const DEFAULT_MARGIN_TOP = 6; const DEFAULT_PADDING = 12; const DISABLED_COLOR = colors.disabled; const FOCUSED_COLOR = colors.primary; const ERROR_COLOR = colors.error; const DEFAULT_COLOR = colors.gray_D1D2D4; const MIN_HEIGHT = 48; const TextField = (_a) => { var { label, labelMinSize = fontSize._12, labelSize = fontSize._14, applyTopLabel = true, labelBackground, value, defaultValue, onChangeText, error, errorColor, focusedColor, helperText, helperTextColor, helperTextStyle, renderStartIcon, renderEndIcon, multiline = false, rows = DEFAULT_ROWS, maxRows, editable = true, containerStyle, inputContainerStyle, style, onFocus, onBlur } = _a, rest = __rest(_a, ["label", "labelMinSize", "labelSize", "applyTopLabel", "labelBackground", "value", "defaultValue", "onChangeText", "error", "errorColor", "focusedColor", "helperText", "helperTextColor", "helperTextStyle", "renderStartIcon", "renderEndIcon", "multiline", "rows", "maxRows", "editable", "containerStyle", "inputContainerStyle", "style", "onFocus", "onBlur"]); const [internalValue, setInternalValue] = React.useState(defaultValue !== null && defaultValue !== void 0 ? defaultValue : ''); const [focused, setFocused] = React.useState(false); const [containerWidth, setContainerWidth] = React.useState(0); const [containerHeight, setContainerHeight] = React.useState(0); const [labelHeight, setLabelHeight] = React.useState(0); const [helperHeight, setHelperHeight] = React.useState(0); const [contentHeight, setContentHeight] = React.useState(null); const [startIconWidth, setStartIconWidth] = React.useState(0); const [endIconWidth, setEndIconWidth] = React.useState(0); const [webInputHeight, setWebInputHeight] = React.useState(); const inputRef = React.useRef(null); //Handling dragging on Web const isApplyStartIcon = typeof renderStartIcon === 'function'; const isApplyEndIcon = typeof renderEndIcon === 'function'; const labelMaxWidth = containerWidth - (isApplyStartIcon ? startIconWidth : 0) - (isApplyEndIcon ? endIconWidth : 0) - DEFAULT_PADDING * 2; const isControlled = value !== undefined; const textValue = isControlled ? value : internalValue; const hasError = Boolean(error); const errorMessage = typeof error === 'string' ? error : undefined; const floatAnim = React.useRef(new Animated.Value(textValue ? 1 : 0)).current; const handleChange = React.useCallback((text) => { if (!isControlled) { setInternalValue(text); } onChangeText === null || onChangeText === void 0 ? void 0 : onChangeText(text); }, [isControlled, onChangeText]); const handleHideTopLabel = () => { if (applyTopLabel) { return undefined; } return floatAnim.interpolate({ inputRange: [0, 1], outputRange: [1, 0], }); }; const resolvePaddingSide = (rawStyle, isTop) => { const style = StyleSheet.flatten(rawStyle); const side = isTop ? 'paddingTop' : 'paddingBottom'; if (typeof (style === null || style === void 0 ? void 0 : style.paddingVertical) === 'number') { return style.paddingVertical; } if (typeof (style === null || style === void 0 ? void 0 : style.padding) === 'number') { return style.padding; } if (typeof (style === null || style === void 0 ? void 0 : style[side]) === 'number') { return style[side]; } return 0; }; const resolvePadding = (userStyle, fallbackStyle, isTop) => { const userPadding = resolvePaddingSide(userStyle, isTop); if (userPadding !== 0) return userPadding; return resolvePaddingSide(fallbackStyle, isTop); }; const calculatePadding = (isTop) => { const containerPadding = resolvePadding(containerStyle, styles.container, isTop); const inputPadding = resolvePadding(inputContainerStyle, styles.inputContainer, isTop); const textInputPadding = resolvePadding(style, styles.input, isTop); return containerPadding + inputPadding + textInputPadding; }; const getInsetY = (alignVertical) => { switch (alignVertical) { case 'center': return containerHeight / 2 - labelHeight; case 'bottom': return containerHeight - helperHeight - DEFAULT_MARGIN_TOP - labelHeight - calculatePadding(false); case 'top': default: return calculatePadding(true); } }; const resolveTextAlignVertical = () => { var _a; const textInputStyle = StyleSheet.flatten(style); if (WEB) { //Web only support textAlignVertical top return 'top'; } return (_a = textInputStyle === null || textInputStyle === void 0 ? void 0 : textInputStyle.textAlignVertical) !== null && _a !== void 0 ? _a : 'top'; }; const textAlignVertical = resolveTextAlignVertical(); const multilineStyle = React.useMemo(() => { if (!multiline || !contentHeight) { return null; } const padding = resolvePadding(style, styles.input, true) + resolvePadding(style, styles.input, false); return { minHeight: rows ? rows * (contentHeight - (WEB ? padding * 1.9 : padding / 1.3)) : undefined, maxHeight: maxRows ? maxRows * (contentHeight - padding / 1.1) : undefined, // maxHeight not work for auto add new line with maxRows on Web. Use height instead. }; }, [multiline, rows, maxRows, contentHeight]); const multilineWebStyle = React.useMemo(() => { if (!multiline || !WEB || !contentHeight || !webInputHeight) { return null; } const padding = resolvePadding(style, styles.input, true) + resolvePadding(style, styles.input, false); const minHeight = rows ? rows * (contentHeight - padding * 1.9) : 0; const maxHeight = maxRows ? maxRows * (contentHeight - padding * 1.9) : 0; if (webInputHeight > maxHeight) { return { height: maxHeight }; } if (webInputHeight > minHeight && webInputHeight <= maxHeight) { return { height: webInputHeight }; } return null; }, [multiline, rows, maxRows, contentHeight, webInputHeight]); const labelInsetY = React.useMemo(() => getInsetY(textAlignVertical), [ textAlignVertical, style, inputContainerStyle, containerStyle, containerHeight, helperHeight, labelHeight ]); const renderBackgroundLabel = () => { if (labelBackground) { return labelBackground; } if (!editable) { return DISABLED_COLOR; } return colors.white; }; const renderLabelColor = () => { if (hasError) { if (errorColor) { return errorColor; } return ERROR_COLOR; } if (focused) { if (focusedColor) { return focusedColor; } return FOCUSED_COLOR; } return DEFAULT_COLOR; }; const renderInputBorderColor = () => { if (hasError) { if (errorColor) { return errorColor; } return ERROR_COLOR; } if (focused) { if (focusedColor) { return focusedColor; } return FOCUSED_COLOR; } return DEFAULT_COLOR; }; const renderHelperTextColor = () => { if (helperTextColor) { return helperTextColor; } if (hasError) { if (errorColor) { return errorColor; } return ERROR_COLOR; } if (!editable) { return DEFAULT_COLOR; } return colors.black; }; const labelViewStyle = { position: 'absolute', left: isApplyStartIcon ? startIconWidth + DEFAULT_PADDING : DEFAULT_PADDING, top: applyTopLabel ? floatAnim.interpolate({ inputRange: [0, 1], outputRange: [labelInsetY, -labelMinSize / 1.5], }) : labelInsetY, maxWidth: labelMaxWidth, overflow: 'hidden', backgroundColor: renderBackgroundLabel(), paddingHorizontal: DEFAULT_PADDING, opacity: handleHideTopLabel(), borderRadius: 5 }; const labelStyle = { fontSize: applyTopLabel ? floatAnim.interpolate({ inputRange: [0, 1], outputRange: [labelSize, labelMinSize], }) : labelSize, color: renderLabelColor(), }; React.useEffect(() => { Animated.timing(floatAnim, { toValue: focused || textValue ? 1 : 0, duration: 180, easing: Easing.out(Easing.ease), useNativeDriver: false, }).start(); }, [focused, textValue, floatAnim]); React.useEffect(() => { if (!WEB || !multiline) { return; } const el = inputRef.current; if (!el) { return; } let isDragging = false; let startY = 0; let startScrollTop = 0; const onMouseDown = (e) => { isDragging = true; startY = e.clientY; startScrollTop = el.scrollTop; }; const onMouseMove = (e) => { if (!isDragging) { return; } el.scrollTop = startScrollTop - (e.clientY - startY); }; const onMouseUp = () => { isDragging = false; }; el.addEventListener('mousedown', onMouseDown); window.addEventListener('mousemove', onMouseMove); window.addEventListener('mouseup', onMouseUp); return () => { el.removeEventListener('mousedown', onMouseDown); window.removeEventListener('mousemove', onMouseMove); window.removeEventListener('mouseup', onMouseUp); }; }, [multiline]); const multilineStyleSignature = React.useMemo(() => { if (!style) return ''; const s = Array.isArray(style) ? Object.assign({}, ...style) : style; return [ s.minHeight, s.maxHeight, s.paddingTop, s.paddingBottom, s.lineHeight, s.fontSize, ].join('|'); }, [style]); React.useEffect(() => { let timeoutID; if (contentHeight) { handleChange(''); timeoutID = setTimeout(() => setContentHeight(null), 1000); //Wait until the text change for onContentSizeChange get the init height before reset multilineStyle } return () => { if (timeoutID) { clearTimeout(timeoutID); } }; }, [multilineStyleSignature]); return (_jsxs(View, { onLayout: e => { var _a, _b, _c, _d; setContainerWidth((_b = (_a = e === null || e === void 0 ? void 0 : e.nativeEvent) === null || _a === void 0 ? void 0 : _a.layout) === null || _b === void 0 ? void 0 : _b.width); setContainerHeight((_d = (_c = e === null || e === void 0 ? void 0 : e.nativeEvent) === null || _c === void 0 ? void 0 : _c.layout) === null || _d === void 0 ? void 0 : _d.height); }, style: [styles.container, containerStyle], children: [_jsxs(View, { style: [ styles.inputContainer, { borderColor: renderInputBorderColor() }, !editable && styles.disabled, inputContainerStyle, ], children: [label && (_jsx(Animated.View, { pointerEvents: 'none', onLayout: e => { var _a, _b; setLabelHeight((_b = (_a = e === null || e === void 0 ? void 0 : e.nativeEvent) === null || _a === void 0 ? void 0 : _a.layout) === null || _b === void 0 ? void 0 : _b.height); }, style: labelViewStyle, children: _jsx(Animated.Text, { numberOfLines: 1, ellipsizeMode: "tail", style: labelStyle, children: label }) })), isApplyStartIcon && (_jsx(View, { style: styles.iconStart, onLayout: e => { setStartIconWidth(e.nativeEvent.layout.width); }, children: renderStartIcon({ focused: focused, hasError: hasError, editable: editable, }) })), _jsx(TextInput, Object.assign({}, rest, { ref: inputRef, style: [ styles.input, multiline && styles.multiline, multilineStyle, multilineWebStyle, WEB && !editable && styles.webDisabled, !editable && styles.inputDisabled, style, ], value: textValue, onChangeText: handleChange, editable: editable, multiline: multiline, onFocus: e => { if (!editable) { return; } setFocused(true); onFocus && onFocus(e); }, onBlur: e => { setFocused(false); onBlur && onBlur(e); }, onContentSizeChange: e => { if (multiline && WEB) { setWebInputHeight(e.nativeEvent.contentSize.height); } if (!multiline || contentHeight) { return; } setContentHeight(e.nativeEvent.contentSize.height); } })), isApplyEndIcon && (_jsx(View, { style: styles.iconEnd, onLayout: e => { var _a, _b; setEndIconWidth((_b = (_a = e.nativeEvent) === null || _a === void 0 ? void 0 : _a.layout) === null || _b === void 0 ? void 0 : _b.width); }, children: renderEndIcon({ focused: focused, hasError: hasError, editable: editable, }) }))] }), (errorMessage || helperText) && (_jsx(Text, { color: renderHelperTextColor(), onLayout: e => { var _a, _b; setHelperHeight((_b = (_a = e === null || e === void 0 ? void 0 : e.nativeEvent) === null || _a === void 0 ? void 0 : _a.layout) === null || _b === void 0 ? void 0 : _b.height); }, style: [styles.helperText, helperTextStyle], children: errorMessage !== null && errorMessage !== void 0 ? errorMessage : helperText }))] })); }; const styles = StyleSheet.create({ container: { width: '100%', }, inputContainer: { minHeight: MIN_HEIGHT, borderWidth: 1, borderColor: DEFAULT_COLOR, borderRadius: 8, flexDirection: 'row', alignItems: 'center', paddingHorizontal: DEFAULT_PADDING, backgroundColor: colors.white, }, focusedBorder: { borderColor: FOCUSED_COLOR, }, input: Object.assign({ flex: 1, fontSize: fontSize._14, color: colors.black, paddingVertical: DEFAULT_PADDING }, (WEB ? { outlineStyle: 'none', overflow: 'hidden', } : {})), multiline: { textAlignVertical: 'top', alignItems: 'flex-start' }, iconStart: { marginRight: DEFAULT_PADDING, }, iconEnd: { marginLeft: DEFAULT_PADDING, }, helperText: { marginTop: DEFAULT_MARGIN_TOP, fontSize: WEB ? fontSize._10 : fontSize._12, paddingHorizontal: DEFAULT_PADDING }, disabled: { backgroundColor: DISABLED_COLOR, }, webDisabled: Object.assign({}, (WEB ? { cursor: 'not-allowed', } : {})), inputDisabled: { color: DEFAULT_COLOR } }); export default TextField;