vj-ui-components
Version:
A collection of beautiful, customizable React UI components including versatile navigation with dual layout support (sidebar/top), stylish input fields with icon support, advanced search with recommendations and autocomplete, elegant modals with animation
310 lines (283 loc) • 8.51 kB
JSX
import React, { useState, forwardRef } from "react";
import { IconEye, IconEyeOff } from "@tabler/icons-react";
const Input = forwardRef(({
type = "text",
placeholder = "",
value,
onChange,
onFocus,
onBlur,
disabled = false,
error = false,
success = false,
required = false,
// Theming props
primaryColor = "#2563eb",
secondaryColor = "#1e40af",
backgroundColor = "rgba(255, 255, 255, 0.1)",
textColor = "#1f2937",
placeholderColor = "#6b7280",
borderColor = "rgba(255, 255, 255, 0.2)",
focusBorderColor = "#3b82f6",
errorColor = "#ef4444",
successColor = "#10b981",
// Icon and content props
leftIcon = null,
rightIcon = null,
leftContent = null,
rightContent = null,
iconColor = "#6b7280",
// Styling props
size = "md", // "sm", "md", "lg"
variant = "default", // "default", "filled", "outlined", "glassmorphism"
borderRadius = "12px",
className = "",
// Input specific props
showPasswordToggle = false,
label = "",
helperText = "",
maxLength,
autoComplete,
autoFocus = false,
readOnly = false,
...rest
}, ref) => {
const [focused, setFocused] = useState(false);
const [showPassword, setShowPassword] = useState(false);
// Handle focus
const handleFocus = (e) => {
setFocused(true);
if (onFocus) onFocus(e);
};
// Handle blur
const handleBlur = (e) => {
setFocused(false);
if (onBlur) onBlur(e);
};
// Toggle password visibility
const togglePasswordVisibility = () => {
setShowPassword(!showPassword);
};
// Get input type (handle password toggle)
const getInputType = () => {
if (type === "password" && showPasswordToggle) {
return showPassword ? "text" : "password";
}
return type;
};
// Get size styles
const getSizeStyles = () => {
switch (size) {
case "sm":
return {
padding: "8px 12px",
fontSize: "0.875rem",
minHeight: "36px",
};
case "lg":
return {
padding: "16px 20px",
fontSize: "1.125rem",
minHeight: "56px",
};
default: // md
return {
padding: "12px 16px",
fontSize: "1rem",
minHeight: "44px",
};
}
};
// Get variant styles
const getVariantStyles = () => {
const baseStyles = {
border: `1px solid ${error ? errorColor : success ? successColor : focused ? focusBorderColor : borderColor}`,
borderRadius: borderRadius,
transition: "all 0.2s cubic-bezier(0.4, 0, 0.2, 1)",
outline: "none",
width: "100%",
fontFamily: "'Segoe UI', Tahoma, Geneva, Verdana, sans-serif",
};
switch (variant) {
case "filled":
return {
...baseStyles,
background: backgroundColor,
backdropFilter: "none",
boxShadow: focused ? `0 0 0 3px ${focusBorderColor}20` : "none",
};
case "outlined":
return {
...baseStyles,
background: "transparent",
backdropFilter: "none",
boxShadow: focused ? `0 0 0 3px ${focusBorderColor}20` : "none",
};
case "glassmorphism":
return {
...baseStyles,
background: `linear-gradient(135deg, ${backgroundColor}, rgba(255, 255, 255, 0.05))`,
backdropFilter: "blur(10px)",
boxShadow: focused
? `0 8px 32px rgba(0, 0, 0, 0.12), 0 0 0 1px ${focusBorderColor}40`
: "0 4px 16px rgba(0, 0, 0, 0.08)",
border: `1px solid ${error ? errorColor : success ? successColor : focused ? focusBorderColor : 'rgba(255, 255, 255, 0.2)'}`,
};
default:
return {
...baseStyles,
background: "#ffffff",
boxShadow: focused
? `0 0 0 3px ${focusBorderColor}20, 0 2px 8px rgba(0, 0, 0, 0.1)`
: "0 1px 3px rgba(0, 0, 0, 0.1)",
};
}
};
// Get container styles
const getContainerStyles = () => {
const sizeStyles = getSizeStyles();
const variantStyles = getVariantStyles();
return {
...variantStyles,
display: "flex",
alignItems: "center",
position: "relative",
cursor: disabled ? "not-allowed" : "text",
opacity: disabled ? 0.6 : 1,
padding: sizeStyles.padding,
minHeight: sizeStyles.minHeight,
};
};
// Get input styles
const getInputStyles = () => {
const sizeStyles = getSizeStyles();
return {
background: "transparent",
border: "none",
outline: "none",
flex: 1,
color: disabled ? placeholderColor : textColor,
fontSize: sizeStyles.fontSize,
fontFamily: "inherit",
padding: 0,
margin: 0,
"::placeholder": {
color: placeholderColor,
opacity: 0.7,
},
};
};
// Get icon/content wrapper styles
const getIconWrapperStyles = (position) => ({
display: "flex",
alignItems: "center",
color: iconColor,
marginLeft: position === "right" ? "8px" : "0",
marginRight: position === "left" ? "8px" : "0",
flexShrink: 0,
});
return (
<div style={{ width: "100%" }} className={className}>
{/* Label */}
{label && (
<label
style={{
display: "block",
marginBottom: "6px",
fontSize: "0.875rem",
fontWeight: "500",
color: textColor,
fontFamily: "'Segoe UI', Tahoma, Geneva, Verdana, sans-serif",
}}
>
{label}
{required && (
<span style={{ color: errorColor, marginLeft: "2px" }}>*</span>
)}
</label>
)}
{/* Input Container */}
<div
style={getContainerStyles()}
onClick={() => {
if (!disabled && ref?.current) {
ref.current.focus();
}
}}
>
{/* Left Icon/Content */}
{(leftIcon || leftContent) && (
<div style={getIconWrapperStyles("left")}>
{leftIcon || leftContent}
</div>
)}
{/* Input Element */}
<input
ref={ref}
type={getInputType()}
value={value}
onChange={onChange}
onFocus={handleFocus}
onBlur={handleBlur}
placeholder={placeholder}
disabled={disabled}
required={required}
maxLength={maxLength}
autoComplete={autoComplete}
autoFocus={autoFocus}
readOnly={readOnly}
style={getInputStyles()}
{...rest}
/>
{/* Password Toggle */}
{type === "password" && showPasswordToggle && (
<button
type="button"
onClick={togglePasswordVisibility}
style={{
background: "none",
border: "none",
cursor: "pointer",
padding: "4px",
display: "flex",
alignItems: "center",
color: iconColor,
marginLeft: "8px",
borderRadius: "4px",
transition: "all 0.2s ease",
}}
onMouseEnter={(e) => {
e.target.style.background = "rgba(0, 0, 0, 0.1)";
}}
onMouseLeave={(e) => {
e.target.style.background = "none";
}}
>
{showPassword ? <IconEyeOff size={18} /> : <IconEye size={18} />}
</button>
)}
{/* Right Icon/Content */}
{(rightIcon || rightContent) && !showPasswordToggle && (
<div style={getIconWrapperStyles("right")}>
{rightIcon || rightContent}
</div>
)}
</div>
{/* Helper Text / Error Message */}
{(helperText || error) && (
<div
style={{
marginTop: "6px",
fontSize: "0.75rem",
color: error ? errorColor : success ? successColor : placeholderColor,
fontFamily: "'Segoe UI', Tahoma, Geneva, Verdana, sans-serif",
}}
>
{typeof error === "string" ? error : helperText}
</div>
)}
</div>
);
});
Input.displayName = "Input";
export default Input;