UNPKG

@geekyhawks/react-native-ui-components

Version:

A lightweight and reusable React Native UI components library with customizable Text, TextInput, FloatingLabelTextInput, Button, and more. Built with TypeScript, fully typed, and designed for easy integration into any React Native project.

207 lines 12 kB
"use strict"; /** * FloatingLabelTextInput * * A text input with a floating label that animates above the field * when focused or when a value is present. Supports style variants, * size variants, multiline input, and theme integration. * * Author: Geeky Hawks FZE LLC */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); 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; }; Object.defineProperty(exports, "__esModule", { value: true }); const react_1 = __importStar(require("react")); const react_native_1 = require("react-native"); const theme_1 = require("../../theme"); const Text_1 = require("../Text"); const resolveThemeColor_1 = require("../../theme/utils/resolveThemeColor"); /** Animated Text */ const AnimatedText = react_native_1.Animated.createAnimatedComponent(Text_1.Text); /** * FloatingLabelTextInput * * A customizable wrapper around React Native's `TextInput` with a floating label. * Applies default styleVariants, sizeVariants, theme colors, error/helper text handling, * and supports multiline input. */ const FloatingLabelTextInput = (_a) => { var _b, _c, _d, _e, _f, _g, _h, _j, _k; var { accessible = true, accessibilityLabel, accessibilityState, containerStyle, disabled = false, error, errorPosition = "left", errorTextStyle, fontFamily, fullWidth = true, helperText, helperTextStyle, inputContainerStyle, inputStyle, label, labelStyle, loading = false, multiline = false, numberOfLines = 1, passwordToggleIcons, secureTextEntry, size = "md", variant = "outline" } = _a, rest = __rest(_a, ["accessible", "accessibilityLabel", "accessibilityState", "containerStyle", "disabled", "error", "errorPosition", "errorTextStyle", "fontFamily", "fullWidth", "helperText", "helperTextStyle", "inputContainerStyle", "inputStyle", "label", "labelStyle", "loading", "multiline", "numberOfLines", "passwordToggleIcons", "secureTextEntry", "size", "variant"]); const { theme, floatingLabelTextInputStyleVariants, floatingLabelTextInputSizeVariants, } = (0, theme_1.useTheme)(); const isControlled = rest.value != null; const [isFocused, setFocused] = (0, react_1.useState)(false); const [uncontrolledValue, setUncontrolledValue] = (0, react_1.useState)(""); const [showPassword, setShowPassword] = (0, react_1.useState)(false); const sizeVariant = floatingLabelTextInputSizeVariants[size] || floatingLabelTextInputSizeVariants.md; const styleVariant = floatingLabelTextInputStyleVariants[variant] || floatingLabelTextInputStyleVariants.outline; const inputValue = isControlled ? rest.value : uncontrolledValue; const isError = !!error; const hasText = inputValue.length > 0; const containerHeight = Number((_c = (_b = styleVariant.container) === null || _b === void 0 ? void 0 : _b.minHeight) !== null && _c !== void 0 ? _c : 48); const labelAnim = (0, react_1.useRef)(new react_native_1.Animated.Value(0)).current; const focusAnim = (0, react_1.useRef)(new react_native_1.Animated.Value(0)).current; // Animate label lineHeight so that when shown as placeholder it occupies the full container height // and centers vertically. When floating, lineHeight collapses to labelFontSize. const labelLineHeight = labelAnim.interpolate({ inputRange: [0, 1], outputRange: [containerHeight, sizeVariant.labelFontSize], }); // Animate font size const labelFontSize = labelAnim.interpolate({ inputRange: [0, 1], outputRange: [sizeVariant.fontSize, sizeVariant.labelFontSize], }); // Animate label color const labelColor = labelAnim.interpolate({ inputRange: [0, 1], outputRange: [ theme.colors.muted, isError ? theme.colors.error : theme.colors.primary, ], }); const initialBorderColor = (_e = (0, resolveThemeColor_1.resolveThemeColor)((_d = styleVariant.container) === null || _d === void 0 ? void 0 : _d.borderColor, theme)) !== null && _e !== void 0 ? _e : theme.colors.border; const focusedBorderColor = (isError ? theme.colors.error : theme.colors.primary); // Animate border color const borderColor = focusAnim.interpolate({ inputRange: [0, 1], outputRange: [initialBorderColor, focusedBorderColor], }); const containerBackgroundColor = (_g = (0, resolveThemeColor_1.resolveThemeColor)((_f = styleVariant.container) === null || _f === void 0 ? void 0 : _f.backgroundColor, theme)) !== null && _g !== void 0 ? _g : "transparent"; const inputTextColor = (_j = (0, resolveThemeColor_1.resolveThemeColor)((_h = styleVariant.input) === null || _h === void 0 ? void 0 : _h.color, theme)) !== null && _j !== void 0 ? _j : theme.colors.text; const _l = (_k = styleVariant.input) !== null && _k !== void 0 ? _k : {}, { color: _discardColor } = _l, inputStyleVariant = __rest(_l, ["color"]); (0, react_1.useEffect)(() => { react_native_1.Animated.parallel([ react_native_1.Animated.timing(focusAnim, { toValue: isError || isFocused ? 1 : 0, duration: 200, useNativeDriver: false, }), react_native_1.Animated.timing(labelAnim, { toValue: hasText || isFocused ? 1 : 0, duration: 200, useNativeDriver: false, }), ]).start(); }, [hasText, isError, isFocused]); return (react_1.default.createElement(react_native_1.View, { style: [{ width: fullWidth ? "100%" : undefined }, containerStyle], accessible: accessible, accessibilityLabel: label || rest.placeholder, accessibilityState: Object.assign({ disabled }, (error ? { invalid: true } : {})) }, react_1.default.createElement(react_native_1.Animated.View, { style: [ styleVariant.container, { opacity: disabled ? 0.6 : 1, flexDirection: "row", alignItems: multiline ? "flex-start" : "center", borderColor: ["outline", "filled"].includes(variant) ? borderColor : undefined, borderBottomColor: variant === "underline" ? borderColor : undefined, backgroundColor: containerBackgroundColor, }, fullWidth && { width: "100%" }, inputContainerStyle, ] }, react_1.default.createElement(AnimatedText, { style: [ Object.assign({ position: "absolute", left: 0, transform: [ { translateY: labelAnim.interpolate({ inputRange: [0, 1], outputRange: [0, multiline ? -8 : -containerHeight / 2], }), }, ], fontSize: labelFontSize, lineHeight: labelLineHeight, color: labelColor }, (["outline", "filled"].includes(variant) && (hasText || isFocused) ? { backgroundColor: theme.colors.background, } : {})), styleVariant.label, fontFamily ? { fontFamily } : {}, labelStyle, ] }, label), react_1.default.createElement(react_native_1.TextInput, Object.assign({}, rest, { style: [ { flex: 1, color: theme.colors.text, fontFamily: theme.fontFamily }, { fontSize: sizeVariant.fontSize, paddingVertical: sizeVariant.paddingVertical, paddingHorizontal: 0, textAlignVertical: multiline ? "top" : "center", minHeight: multiline ? (sizeVariant.fontSize + sizeVariant.paddingVertical * 2) * (numberOfLines || 1) : undefined, }, inputStyleVariant, { color: inputTextColor }, fontFamily ? { fontFamily } : {}, inputStyle, ], secureTextEntry: secureTextEntry && !showPassword, placeholder: "", editable: !disabled && !loading, multiline: multiline, numberOfLines: numberOfLines, value: inputValue, onChangeText: (text) => { var _a; if (!isControlled) setUncontrolledValue(text); (_a = rest.onChangeText) === null || _a === void 0 ? void 0 : _a.call(rest, text); }, onFocus: (e) => { var _a; setFocused(true); (_a = rest.onFocus) === null || _a === void 0 ? void 0 : _a.call(rest, e); }, onBlur: (e) => { var _a; setFocused(false); (_a = rest.onBlur) === null || _a === void 0 ? void 0 : _a.call(rest, e); } })), secureTextEntry ? (react_1.default.createElement(react_native_1.TouchableOpacity, { onPress: () => setShowPassword((prev) => !prev) }, passwordToggleIcons ? (showPassword ? passwordToggleIcons.hide || null : passwordToggleIcons.show || null) : (react_1.default.createElement(Text_1.Text, { variant: "caption", color: theme.colors.primary, style: [ fontFamily ? { fontFamily } : {}, ] }, showPassword ? "Hide" : "Show")))) : null, loading && (react_1.default.createElement(react_native_1.ActivityIndicator, { size: "small", color: theme.colors.primary }))), isError ? (react_1.default.createElement(Text_1.Text, { variant: "caption", color: theme.colors.error, style: [ { marginTop: 4, textAlign: errorPosition }, fontFamily ? { fontFamily } : {}, errorTextStyle ] }, error)) : helperText ? (react_1.default.createElement(Text_1.Text, { variant: "caption", color: theme.colors.muted, style: [ { marginTop: 4 }, fontFamily ? { fontFamily } : {}, helperTextStyle ] }, helperText)) : null)); }; exports.default = FloatingLabelTextInput; //# sourceMappingURL=FloatingLabelTextInput.js.map