Here is my code:
import classNames from 'classnames';
import { uniqueId } from 'lodash';
import React, { forwardRef, useCallback, useMemo } from 'react';
interface BaseInputProps {
className?: string;
placeholder?: string;
label?: string;
suffix?: string;
disabled?: boolean;
autoFocus?: boolean;
}
type TextInputProps = {
type?: 'search' | 'text';
value?: string;
} & BaseInputProps &
OnChangeProps<string>;
type NumberInputProps = {
type: 'number';
value?: number;
} & BaseInputProps &
OnChangeProps<number>;
type OnChangeProps<T> =
| {
isForwarded: true;
onChange?: (event: Event) => void;
}
| {
isForwarded?: false;
onChange?: (value: T) => void;
};
const Input = forwardRef<HTMLInputElement, TextInputProps | NumberInputProps>(
(
{
className,
isForwarded,
placeholder,
label,
suffix,
disabled,
autoFocus,
...props
},
ref
) => {
const handleOnChange = useCallback(
(event: React.ChangeEvent<HTMLInputElement> | Event) => {
if (isForwarded) {
props.onChange?.(event as Event);
} else if (props.type === 'number') {
props.onChange?.(event.target.valueAsNumber);
} else {
props.onChange?.(event.target.value);
}
},
[props.onChange, props.type]
);
const htmlId = useMemo(() => uniqueId('input_'), []);
return (
<div
className={classNames(className, 'flex flex-col items-stretch gap-1')}
>
{label && (
<label
htmlFor={htmlId}
className="mr-2 select-none font-medium text-sm text-carbon-800"
>
{label}
</label>
)}
<div className="flex flex-row items-center h-9 px-2 border-[1.5px] border-gray-200 rounded-md overflow-hidden hover:border-gray-300 focus-within:border-gray-300">
<input
id={htmlId}
ref={ref}
onChange={handleOnChange}
type={props.type}
value={props.value}
placeholder={placeholder || ''}
className={classNames(
className
?.split(' ')
.filter((c) => c.includes('bg-') || c.includes('text-'))
.join(' '),
'inline-block border-none w-full p-0 text-sm placeholder:text-gray-400 focus:ring-0'
)}
disabled={disabled}
autoFocus={autoFocus}
/>
{suffix && (
<span className="text-sm ml-2 text-carbon-600">{suffix}</span>
)}
</div>
</div>
);
}
);
Input.displayName = 'Input';
export default Input;
The component above needs the event to be of type Event
hence the weird conditional typing.
I can’t understand why
props.onChange?.(event as Event);
is triggering the following error:
- Argument of type ‘Event’ is not assignable to parameter of type ‘never’. [2345]
Do you have any clue?