import { Icon } from '@/components/ui/icon'; import { Text } from '@/components/ui/text'; import { useColor } from '@/hooks/useColor'; import { BORDER_RADIUS, CORNERS, FONT_SIZE, HEIGHT } from '@/theme/globals'; import { LucideProps } from 'lucide-react-native'; import React, { forwardRef, ReactElement, useState } from 'react'; import { Pressable, TextInput, TextInputProps, TextStyle, View, ViewStyle, } from 'react-native'; export interface InputProps extends Omit { label?: string; error?: string; icon?: React.ComponentType; rightComponent?: React.ReactNode | (() => React.ReactNode); containerStyle?: ViewStyle; inputStyle?: TextStyle; labelStyle?: TextStyle; errorStyle?: TextStyle; variant?: 'filled' | 'outline'; disabled?: boolean; type?: 'input' | 'textarea'; placeholder?: string; rows?: number; // Only used when type="textarea" } export const Input = forwardRef( ( { label, error, icon, rightComponent, containerStyle, inputStyle, labelStyle, errorStyle, variant = 'filled', disabled = false, type = 'input', rows = 4, onFocus, onBlur, placeholder, ...props }, ref ) => { const [isFocused, setIsFocused] = useState(false); // Theme colors const cardColor = useColor('card'); const textColor = useColor('text'); const muted = useColor('textMuted'); const borderColor = useColor('border'); const primary = useColor('primary'); const danger = useColor('red'); const isTextarea = type === 'textarea'; // Calculate height based on type const getHeight = () => { if (isTextarea) { return rows * 20 + 32; // Approximate line height + padding } return HEIGHT; }; // Variant styles const getVariantStyle = (): ViewStyle => { const baseStyle: ViewStyle = { borderRadius: isTextarea ? BORDER_RADIUS : CORNERS, flexDirection: isTextarea ? 'column' : 'row', alignItems: isTextarea ? 'stretch' : 'center', minHeight: getHeight(), paddingHorizontal: 16, paddingVertical: isTextarea ? 12 : 0, }; switch (variant) { case 'outline': return { ...baseStyle, borderWidth: 1, borderColor: error ? danger : isFocused ? primary : borderColor, backgroundColor: 'transparent', }; case 'filled': default: return { ...baseStyle, borderWidth: 1, borderColor: error ? danger : cardColor, backgroundColor: disabled ? muted + '20' : cardColor, }; } }; const getInputStyle = (): TextStyle => ({ flex: 1, fontSize: FONT_SIZE, lineHeight: isTextarea ? 20 : undefined, color: disabled ? muted : error ? danger : textColor, paddingVertical: 0, // Remove default padding textAlignVertical: isTextarea ? 'top' : 'center', }); const handleFocus = (e: any) => { setIsFocused(true); onFocus?.(e); }; const handleBlur = (e: any) => { setIsFocused(false); onBlur?.(e); }; // Render right component - supports both direct components and functions const renderRightComponent = () => { if (!rightComponent) return null; // If it's a function, call it. Otherwise, render directly return typeof rightComponent === 'function' ? rightComponent() : rightComponent; }; const renderInputContent = () => ( {/* Input Container */} { if (!disabled && ref && 'current' in ref && ref.current) { ref.current.focus(); } }} disabled={disabled} > {isTextarea ? ( // Textarea Layout (Column) <> {/* Header section with icon, label, and right component */} {(icon || label || rightComponent) && ( {/* Left section - Icon + Label */} {icon && ( )} {label && ( {label} )} {/* Right Component */} {renderRightComponent()} )} {/* TextInput section */} ) : ( // Input Layout (Row) {/* Left section - Icon + Label (fixed width to simulate grid column) */} {icon && ( )} {label && ( {label} )} {/* TextInput section - takes remaining space */} {/* Right Component */} {renderRightComponent()} )} {/* Error Message */} {error && ( {error} )} ); return renderInputContent(); } ); export interface GroupedInputProps { children: React.ReactNode; containerStyle?: ViewStyle; title?: string; titleStyle?: TextStyle; } export const GroupedInput = ({ children, containerStyle, title, titleStyle, }: GroupedInputProps) => { const border = useColor('border'); const background = useColor('card'); const danger = useColor('red'); const childrenArray = React.Children.toArray(children); const errors = childrenArray .filter( (child): child is ReactElement => React.isValidElement(child) && !!(child.props as any).error ) .map((child) => child.props.error); const renderGroupedContent = () => ( {!!title && ( {title} )} {childrenArray.map((child, index) => ( {child} ))} {errors.length > 0 && ( {errors.map((error, i) => ( {error} ))} )} ); return renderGroupedContent(); }; export interface GroupedInputItemProps extends Omit { label?: string; error?: string; icon?: React.ComponentType; rightComponent?: React.ReactNode | (() => React.ReactNode); inputStyle?: TextStyle; labelStyle?: TextStyle; errorStyle?: TextStyle; disabled?: boolean; type?: 'input' | 'textarea'; rows?: number; // Only used when type="textarea" } export const GroupedInputItem = forwardRef( ( { label, error, icon, rightComponent, inputStyle, labelStyle, errorStyle, disabled, type = 'input', rows = 3, onFocus, onBlur, placeholder, ...props }, ref ) => { const [isFocused, setIsFocused] = useState(false); const text = useColor('text'); const muted = useColor('textMuted'); const primary = useColor('primary'); const danger = useColor('red'); const isTextarea = type === 'textarea'; const handleFocus = (e: any) => { setIsFocused(true); onFocus?.(e); }; const handleBlur = (e: any) => { setIsFocused(false); onBlur?.(e); }; const renderRightComponent = () => { if (!rightComponent) return null; return typeof rightComponent === 'function' ? rightComponent() : rightComponent; }; const renderItemContent = () => ( ref && 'current' in ref && ref.current?.focus()} disabled={disabled} style={{ opacity: disabled ? 0.6 : 1 }} > {isTextarea ? ( // Textarea Layout (Column) <> {/* Header section with icon, label, and right component */} {(icon || label || rightComponent) && ( {/* Icon & Label */} {icon && ( )} {label && ( {label} )} {/* Right Component */} {renderRightComponent()} )} {/* Textarea Input */} ) : ( // Input Layout (Row) {/* Icon & Label */} {icon && ( )} {label && ( {label} )} {/* Input */} {/* Right Component */} {renderRightComponent()} )} ); return renderItemContent(); } );