326 lines
8.7 KiB
JavaScript
326 lines
8.7 KiB
JavaScript
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>
|
||
);
|
||
};
|