fix(striker-ui): add UncontrolledInput

main
Tsu-ba-me 1 year ago
parent f941496c98
commit 782bbd2bfa
  1. 106
      striker-ui/components/UncontrolledInput.tsx
  2. 1
      striker-ui/lib/consts/INPUT_TYPES.ts
  3. 43
      striker-ui/types/UncontrolledInput.d.ts

@ -0,0 +1,106 @@
import {
ForwardedRef,
ReactElement,
cloneElement,
forwardRef,
useCallback,
useEffect,
useImperativeHandle,
useMemo,
useState,
} from 'react';
import INPUT_TYPES from '../lib/consts/INPUT_TYPES';
import MAP_TO_VALUE_CONVERTER from '../lib/consts/MAP_TO_VALUE_CONVERTER';
const UncontrolledInput = forwardRef(
<ValueType extends keyof MapToInputType, InputElement extends ReactElement>(
props: UncontrolledInputProps<InputElement>,
ref: ForwardedRef<UncontrolledInputForwardedRefContent<ValueType>>,
) => {
const {
input,
onChange = ({ handlers: { base, origin } }, ...args) => {
base?.call(null, ...args);
origin?.call(null, ...args);
},
onMount,
onUnmount,
} = props;
const { props: inputProps } = input;
const { valueKey, valueType } = useMemo(() => {
const { type } = inputProps;
let vkey: 'checked' | 'value' = 'value';
let vtype: keyof MapToInputType = 'string';
if (type === INPUT_TYPES.checkbox) {
vkey = 'checked';
vtype = 'boolean';
}
return {
valueKey: vkey,
valueType: vtype,
};
}, [inputProps]);
const {
onChange: inputOnChange,
[valueKey]: inputValue,
...restInputProps
} = inputProps;
const [value, setValue] = useState<MapToInputType[ValueType]>(inputValue);
const baseChangeEventHandler = useCallback<ReactChangeEventHandler>(
({ target: { [valueKey]: changed } }) => {
const converted = MAP_TO_VALUE_CONVERTER[valueType](
changed,
) as MapToInputType[ValueType];
setValue(converted);
},
[valueKey, valueType],
);
const changeEventHandler = useCallback<ReactChangeEventHandler>(
(...args) =>
onChange?.call(
null,
{ handlers: { base: baseChangeEventHandler, origin: inputOnChange } },
...args,
),
[baseChangeEventHandler, inputOnChange, onChange],
);
// Handle mount/unmount events; these only happen once hence no deps
useEffect(() => {
onMount?.call(null);
return onUnmount;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useImperativeHandle(
ref,
() => ({
get: () => value,
set: setValue,
}),
[value],
);
return cloneElement(input, {
...restInputProps,
onChange: changeEventHandler,
[valueKey]: value,
});
},
);
UncontrolledInput.displayName = 'UncontrolledInput';
export default UncontrolledInput;

@ -1,4 +1,5 @@
const INPUT_TYPES = { const INPUT_TYPES = {
checkbox: 'checkbox',
number: 'number', number: 'number',
password: 'password', password: 'password',
text: 'text', text: 'text',

@ -0,0 +1,43 @@
type MuiInputBaseProps = import('@mui/material').InputBaseProps;
type ReactChangeEventHandler =
import('react').ChangeEventHandler<HTMLInputElement>;
type MuiInputBasePropsBlurEventHandler = Exclude<
MuiInputBaseProps['onBlur'],
undefined
>;
type MuiInputBasePropsFocusEventHandler = Exclude<
MuiInputBaseProps['onFocus'],
undefined
>;
type UncontrolledInputEventHandler<HandlerType> = (
toolbox: { handlers: { base?: HandlerType; origin?: HandlerType } },
...rest: Parameters<HandlerType>
) => ReturnType<HandlerType>;
type UncontrolledInputComponentMountEventHandler = () => void;
type UncontrolledInputComponentUnmountEventHandler = () => void;
type UncontrolledInputOptionalProps = {
onBlur?: UncontrolledInputEventHandler<MuiInputBasePropsBlurEventHandler>;
onChange?: UncontrolledInputEventHandler<ReactChangeEventHandler>;
onFocus?: UncontrolledInputEventHandler<MuiInputBasePropsFocusEventHandler>;
onMount?: UncontrolledInputComponentMountEventHandler;
onUnmount?: UncontrolledInputComponentUnmountEventHandler;
};
type UncontrolledInputProps<InputElement extends import('react').ReactElement> =
UncontrolledInputOptionalProps & {
input: InputElement;
};
type UncontrolledInputForwardedRefContent<
ValueType extends keyof MapToInputType,
> = {
get: () => MapToInputType[ValueType];
set: (value: MapToInputType[ValueType]) => void;
};
Loading…
Cancel
Save