fcsc_ipi_frontend/ipi-survey-platform/src/components/common/FormControls.jsx
2025-10-27 12:52:07 +05:30

326 lines
8.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React from 'react';
import CaretDownActive from '../../assets/images/CaretDown - active.svg';
import CalendarIcon from '../../assets/images/uil_calender.svg';
const baseInputStyles = {
default: {
height: '40px',
border: '2px solid #CBA344',
borderRadius: '4px',
paddingLeft: '16px',
paddingRight: '16px',
transition: 'border-color 0.2s ease, color 0.2s ease',
backgroundColor: '#FFFFFF',
},
toolbar: {
height: '40px',
border: '1px solid #C3C6CB',
borderRadius: '8px',
paddingLeft: '16px',
paddingRight: '16px',
transition: 'border-color 0.2s ease, box-shadow 0.2s ease',
backgroundColor: '#FFFFFF',
color: '#5F646D',
},
};
const focusBorderColors = {
default: '#92722A',
toolbar: '#92722A',
};
const idleBorderColors = {
default: '#CBA344',
toolbar: '#C3C6CB',
};
export const TextField = ({
label,
placeholder,
value,
onChange,
type = 'text',
width = 'auto', // 'auto' | number | string
className = '',
style = {},
name,
id,
leftIcon,
onFocus,
onBlur,
variant = 'default',
}) => {
const wrapperStyle = {};
if (width !== 'auto') {
wrapperStyle.width = typeof width === 'number' ? `${width}px` : width;
}
const [focused, setFocused] = React.useState(false);
const handleFocus = (event) => {
setFocused(true);
onFocus?.(event);
};
const handleBlur = (event) => {
setFocused(false);
onBlur?.(event);
};
const baseStyle = baseInputStyles[variant] || baseInputStyles.default;
const inputStyle = { ...baseStyle, ...style };
if (leftIcon) {
inputStyle.paddingLeft = '40px';
}
inputStyle.borderColor = focused ? focusBorderColors[variant] : idleBorderColors[variant] || idleBorderColors.default;
if (variant !== 'toolbar') {
inputStyle.color = '#232528';
}
return (
<div style={wrapperStyle} className="w-full">
{label && (
<label htmlFor={id || name} className="block text-[14px] leading-[20px] font-medium text-[#232528] mb-1">
{label}
</label>
)}
<div className="relative">
<input
id={id || name}
name={name}
type={type}
value={value}
onChange={onChange}
placeholder={placeholder}
className={`w-full text-sm focus:outline-none ${className}`}
style={inputStyle}
onFocus={handleFocus}
onBlur={handleBlur}
/>
{leftIcon && (
<img src={leftIcon} alt="" className="pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4" />
)}
</div>
</div>
);
};
export const SelectField = ({
label,
value,
onChange,
options = [],
width = 'auto',
className = '',
style = {},
name,
id,
rightIcon = CaretDownActive,
placeholder,
onFocus,
onBlur,
variant = 'default',
allowClear = false,
onClear,
clearValue = '',
}) => {
const wrapperStyle = {};
if (width !== 'auto') {
wrapperStyle.width = typeof width === 'number' ? `${width}px` : width;
}
const baseStyle = baseInputStyles[variant] || baseInputStyles.default;
const inputStyle = { ...baseStyle, ...style };
const [focused, setFocused] = React.useState(false);
const normalizedClear = clearValue ?? '';
const hasValue = value !== undefined && value !== null && value !== '' && value !== normalizedClear;
const showClear = allowClear && hasValue;
if (rightIcon) {
inputStyle.paddingRight = '40px';
}
const handleFocus = (event) => {
setFocused(true);
onFocus?.(event);
};
const handleBlur = (event) => {
setFocused(false);
onBlur?.(event);
};
inputStyle.borderColor = focused ? focusBorderColors[variant] : idleBorderColors[variant] || idleBorderColors.default;
if (variant === 'toolbar') {
inputStyle.color = hasValue && value !== 'All' && value !== 'Status' && value !== 'Year' && value !== 'Quarter' && value !== 'Emirates' ? '#232528' : '#5F646D';
} else {
inputStyle.color = hasValue ? '#232528' : '#232528';
}
const handleClear = (event) => {
event.preventDefault();
event.stopPropagation();
if (onClear) {
onClear();
} else if (onChange) {
onChange({ target: { value: normalizedClear } });
}
};
return (
<div style={wrapperStyle} className="w-full">
{label && (
<label htmlFor={id || name} className="block text-[14px] leading-[20px] font-medium text-[#232528] mb-1">
{label}
</label>
)}
<div className="relative" style={{ width: '100%' }}>
<select
id={id || name}
name={name}
value={value}
onChange={onChange}
className={`w-full appearance-none text-sm focus:outline-none ${className}`}
style={inputStyle}
onFocus={handleFocus}
onBlur={handleBlur}
>
{placeholder && (
<option value="" disabled hidden>
{placeholder}
</option>
)}
{options.map((opt) => (
<option key={opt.value} value={opt.value}>
{opt.label}
</option>
))}
</select>
{showClear ? (
<button
type="button"
className="absolute right-3 top-1/2 -translate-y-1/2 h-5 w-5 rounded-full text-[#9CA3AF] hover:text-[#4B5563]"
onClick={handleClear}
aria-label="Clear selection"
>
×
</button>
) : (
rightIcon && <img src={rightIcon} alt="" className="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 h-4 w-4" />
)}
</div>
</div>
);
};
export const DateField = ({
label,
value,
onChange,
placeholder,
width = 'auto',
className = '',
style = {},
name,
id,
rightIcon = CalendarIcon,
onBlur,
onFocus,
...rest
}) => {
const inputRef = React.useRef(null);
const [isDateMode, setIsDateMode] = React.useState(Boolean(value));
const [focused, setFocused] = React.useState(false);
React.useEffect(() => {
if (typeof document === 'undefined') return;
if (document.getElementById('date-field-style')) return;
const styleElement = document.createElement('style');
styleElement.id = 'date-field-style';
styleElement.innerHTML = `
input.date-field::-webkit-calendar-picker-indicator {
opacity: 0;
position: absolute;
right: 0;
width: 40px;
height: 100%;
cursor: pointer;
}
input.date-field::-webkit-clear-button { display: none; }
input.date-field::-webkit-inner-spin-button { display: none; }
input.date-field { -webkit-appearance: none; }
input.date-field::placeholder { color: #9CA3AF; }
`;
document.head.appendChild(styleElement);
}, []);
const wrapperStyle = {};
if (width !== 'auto') {
wrapperStyle.width = typeof width === 'number' ? `${width}px` : width;
}
const inputStyle = {
...baseInputStyle,
...style,
color: value ? '#232528' : '#232528',
borderColor: focused ? '#92722A' : '#CBA344',
};
if (rightIcon) {
inputStyle.paddingRight = '40px';
}
React.useEffect(() => {
setIsDateMode(Boolean(value));
}, [value]);
const triggerPicker = () => {
setIsDateMode(true);
requestAnimationFrame(() => {
if (inputRef.current) {
inputRef.current.showPicker?.();
inputRef.current.focus?.();
}
});
};
const handleFocus = (event) => {
onFocus?.(event);
setFocused(true);
if (!isDateMode) {
triggerPicker();
}
};
const handleBlur = (event) => {
onBlur?.(event);
setFocused(false);
if (!value) {
setIsDateMode(false);
}
};
return (
<div style={wrapperStyle} className="w-full">
{label && (
<label htmlFor={id || name} className="block text-[14px] leading-[20px] font-medium text-[#232528] mb-1">
{label}
</label>
)}
<div className="relative">
<input
ref={inputRef}
id={id || name}
name={name}
type={isDateMode ? 'date' : 'text'}
value={value}
onChange={onChange}
placeholder={placeholder}
className={`date-field w-full text-sm focus:outline-none ${className}`}
style={inputStyle}
onFocus={handleFocus}
onBlur={handleBlur}
{...rest}
/>
{rightIcon && (
<button
type="button"
className="absolute right-3 top-1/2 -translate-y-1/2 h-4 w-4 grid place-items-center text-transparent"
onClick={triggerPicker}
tabIndex={-1}
aria-hidden="true"
>
<img src={rightIcon} alt="" className="pointer-events-none h-4 w-4" />
</button>
)}
</div>
</div>
);
};