diff --git a/package.json b/package.json index d7457f042..e6bbbf635 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,10 @@ ".": { "require": "./lib/index.js", "default": "./esm/index.js" + }, + "./hook-form": { + "require": "./lib/components/HookForm/index.js", + "default": "./esm/components/HookForm/index.js" } }, "sideEffects": false, @@ -73,6 +77,7 @@ "lodash.without": "^4.4.0", "memoize-one": "^5.1.1", "prop-types": "^15.7.2", + "react-hook-form": "^7.39.0", "react-imask": "^6.2.2", "react-resize-detector": "^4.2.3", "react-select-plus": "1.2.0", diff --git a/src/components/HookForm/Form.stories.tsx b/src/components/HookForm/Form.stories.tsx new file mode 100755 index 000000000..512133c2f --- /dev/null +++ b/src/components/HookForm/Form.stories.tsx @@ -0,0 +1,84 @@ +import React from 'react'; +import { SubmitHandler } from 'react-hook-form'; +import { FormFeedback } from 'reactstrap'; +import Button from '../Button/Button'; +import FormGroup from '../Form/FormGroup'; +import Label from '../Label/Label'; +import Form from './Form'; +import Input from './Input'; + +export default { + title: 'react-hook-form', +}; + +interface FormInputs { + email: string; + age: string; + select: string; +} + +export const LiveExample = () => { + const handleSubmit: SubmitHandler = (formData) => { + console.log(formData); + }; + + return ( +
+ {({ formState: { errors, dirtyFields } }) => ( + <> +
+ + value === 'email' || 'incorrect'} + /> + {errors.email?.message} + Looks good! +
+
+ + + {errors.age?.message} +
+
+ + + + + + + + + {errors.select?.message} +
+
+ Radio Buttons + + {' '} + + + + {' '} + + +
+ + + )} +
+ ); +}; diff --git a/src/components/HookForm/Form.tsx b/src/components/HookForm/Form.tsx new file mode 100644 index 000000000..8caf16e20 --- /dev/null +++ b/src/components/HookForm/Form.tsx @@ -0,0 +1,29 @@ +import React, { ReactNode } from 'react'; +import { + useForm, + FormProvider, + SubmitHandler, + UseFormProps, + UseFormReturn, + FieldValues, +} from 'react-hook-form'; +import GearsForm from '../Form/Form'; + +interface FormProps extends UseFormProps { + onSubmit: SubmitHandler; + children: (useFormReturn: UseFormReturn) => ReactNode | ReactNode; +} + +const Form = ({ children, onSubmit, ...useFormProps }: FormProps) => { + const useFormReturn = useForm(useFormProps); + + return ( + + + {typeof children === 'function' ? children(useFormReturn) : children} + + + ); +}; + +export default Form; diff --git a/src/components/HookForm/Input.tsx b/src/components/HookForm/Input.tsx new file mode 100644 index 000000000..f64b6abe9 --- /dev/null +++ b/src/components/HookForm/Input.tsx @@ -0,0 +1,68 @@ +import React, { ComponentProps } from 'react'; +import { useFormContext, RegisterOptions, Validate, ValidationRule } from 'react-hook-form'; +import GearsInput from '../Input/Input'; + +type ValueAsNumber = boolean | undefined; +type ValueAsDate = boolean | undefined; + +type DetermineValidateValue< + TValueAsNumber extends ValueAsNumber, + TValueAsDate extends ValueAsDate +> = TValueAsNumber extends true ? number : TValueAsDate extends true ? Date : string; + +type InputProps = Omit< + ComponentProps, + keyof RegisterOptions +> & + Omit & { + name: string; + validate?: + | Validate> + | Record>>; + valueAsNumber?: TValueAsNumber; + valueAsDate?: TValueAsDate; + }; + +const extractValue = ( + objOrValue?: ValidationRule +) => (typeof objOrValue === 'object' ? objOrValue.value : objOrValue); + +const Input = < + TValueAsNumber extends ValueAsNumber = undefined, + TValueAsDate extends ValueAsDate = undefined +>({ + name, + valueAsNumber, + valueAsDate, + validate, + max, + maxLength, + min, + minLength, + pattern, + required, + ...restProps +}: InputProps) => { + const { register } = useFormContext(); + const { ref, ...restRegister } = register(name, { + valueAsNumber, + valueAsDate, + validate, + max, + min, + pattern, + required, + }); + const gearsInputProps = { + ...restProps, + max: extractValue(max), + maxLength: extractValue(maxLength), + min: extractValue(min), + minLength: extractValue(minLength), + required: !!required, + }; + + return ; +}; + +export default Input; diff --git a/src/components/HookForm/index.tsx b/src/components/HookForm/index.tsx new file mode 100644 index 000000000..2777192a1 --- /dev/null +++ b/src/components/HookForm/index.tsx @@ -0,0 +1,2 @@ +export { default as Form } from './Form'; +export { default as Input } from './Input'; diff --git a/yarn.lock b/yarn.lock index 64bad7c95..c619f4333 100644 --- a/yarn.lock +++ b/yarn.lock @@ -140,6 +140,7 @@ __metadata: raf-stub: ^3.0.0 react: ^16.14.0 react-dom: ^16.14.0 + react-hook-form: ^7.39.0 react-imask: ^6.2.2 react-resize-detector: ^4.2.3 react-select-plus: 1.2.0 @@ -14718,6 +14719,15 @@ __metadata: languageName: node linkType: hard +"react-hook-form@npm:^7.39.0": + version: 7.39.0 + resolution: "react-hook-form@npm:7.39.0" + peerDependencies: + react: ^16.8.0 || ^17 || ^18 + checksum: f0f9a081ee634d1125ddde6e8896717d1416da95e1c07c57814a6421b9ab4cc8c057e8431c879c921dffde3d5cb1531cd8dfaa203f0a87e8a0da371a4f381a26 + languageName: node + linkType: hard + "react-imask@npm:^6.2.2": version: 6.4.2 resolution: "react-imask@npm:6.4.2"