├── src ├── fields │ ├── index.ts │ └── typing.ts ├── index.ts ├── components │ ├── InputController │ │ ├── CustomComponentController │ │ │ └── CustomComponentController.tsx │ │ ├── DatePickerController │ │ │ └── DatePickerController.tsx │ │ ├── TextFieldController │ │ │ └── TextFieldController.tsx │ │ ├── RadioGroupController │ │ │ └── RadioGroupController.tsx │ │ ├── CheckboxController │ │ │ └── CheckboxController.tsx │ │ ├── SwitchController │ │ │ └── SwitchController.tsx │ │ ├── SelectController │ │ │ └── SelectController.tsx │ │ └── AutocompleteController │ │ │ └── AutocompleteController.tsx │ ├── index.ts │ └── form │ │ └── FormFields.tsx ├── stories │ ├── Switch.stories.tsx │ ├── CheckboxController.stories.tsx │ ├── RadioGroup.stories.tsx │ ├── Textfield.stories.tsx │ ├── Select.stories.tsx │ ├── DatePicker.stories.tsx │ ├── Autocomplete.stories.tsx │ └── Form.stories.tsx └── form │ └── typing.ts ├── .yarnrc.yml ├── .gitignore ├── postcss.config.js ├── .storybook ├── preview.js └── main.ts ├── .prettierrc ├── .changeset ├── config.json └── README.md ├── .babelrc.json ├── rollup.config.mjs ├── package.json ├── CHANGELOG.md ├── tsconfig.json ├── README.md └── yarn-error.log /src/fields/index.ts: -------------------------------------------------------------------------------- 1 | export * from './typing'; 2 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | 3 | yarnPath: .yarn/releases/yarn-4.9.1.cjs 4 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './components'; 2 | export * from './fields'; 3 | export * from './form/typing'; 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .vscode 4 | .env 5 | README-pv.md 6 | /.yarn/install-state.gz 7 | /storybook-static 8 | build-storybook.log -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | // Add your installed PostCSS plugins here: 3 | plugins: [ 4 | // require('autoprefixer'), 5 | // require('postcss-color-rebeccapurple'), 6 | ] 7 | }; 8 | -------------------------------------------------------------------------------- /.storybook/preview.js: -------------------------------------------------------------------------------- 1 | export const parameters = { 2 | controls: { 3 | matchers: { 4 | color: /(background|color)$/i, 5 | date: /Date$/ 6 | } 7 | } 8 | }; 9 | export const tags = ['autodocs', 'autodocs']; 10 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "bracketSpacing": true, 3 | "printWidth": 120, 4 | "singleQuote": true, 5 | "trailingComma": "none", 6 | "tabWidth": 4, 7 | "useTabs": false, 8 | "react/jsx-max-props-per-line": [ 9 | 1, 10 | { 11 | "when": "always" 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@3.0.3/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "restricted", 8 | "baseBranch": "main", 9 | "updateInternalDependencies": "patch", 10 | "ignore": [] 11 | } 12 | -------------------------------------------------------------------------------- /.babelrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "sourceType": "unambiguous", 3 | "presets": [ 4 | [ 5 | "@babel/preset-env", 6 | { 7 | "targets": { 8 | "chrome": 100, 9 | "safari": 15, 10 | "firefox": 91 11 | } 12 | } 13 | ], 14 | "@babel/preset-typescript", 15 | "@babel/preset-react" 16 | ], 17 | "plugins": [] 18 | } -------------------------------------------------------------------------------- /src/components/InputController/CustomComponentController/CustomComponentController.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { CustomComponentControllerProps } from '../../../fields/index'; 3 | 4 | export const CustomComponentController: React.FC = ({ 5 | name, 6 | control, 7 | CustomComponent, 8 | ...rest 9 | }) => { 10 | return ; 11 | }; 12 | -------------------------------------------------------------------------------- /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.storybook/main.ts: -------------------------------------------------------------------------------- 1 | import { StorybookConfig } from '@storybook/react-webpack5'; 2 | 3 | const config: StorybookConfig = { 4 | stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'], 5 | addons: [ 6 | '@storybook/addon-links', 7 | '@storybook/addon-webpack5-compiler-babel', 8 | '@chromatic-com/storybook', 9 | '@storybook/addon-docs' 10 | ], 11 | 12 | framework: { 13 | name: '@storybook/react-webpack5', 14 | options: {} 15 | }, 16 | 17 | docs: {}, 18 | 19 | typescript: { 20 | reactDocgen: 'react-docgen-typescript' 21 | } 22 | }; 23 | 24 | export default config; 25 | -------------------------------------------------------------------------------- /src/components/index.ts: -------------------------------------------------------------------------------- 1 | // TextField Controller 2 | export { TextFieldController } from './InputController/TextFieldController/TextFieldController'; 3 | // Select Controller 4 | export { SelectController } from './InputController/SelectController/SelectController'; 5 | // Switch Controller 6 | export { SwitchController } from './InputController/SwitchController/SwitchController'; 7 | // RadioGroup Controller 8 | export { RadioGroupController } from './InputController/RadioGroupController/RadioGroupController'; 9 | // Checkbox Controller 10 | export { CheckboxController } from './InputController/CheckboxController/CheckboxController'; 11 | // Autocomplete Controller 12 | export { AutocompleteController } from './InputController/AutocompleteController/AutocompleteController'; 13 | // Autocomplete Controller 14 | export { DatePickerController } from './InputController/DatePickerController/DatePickerController'; 15 | // Form Fields 16 | export { FormFields } from './form/FormFields'; 17 | -------------------------------------------------------------------------------- /src/stories/Switch.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Meta, StoryFn } from '@storybook/react-webpack5'; 3 | import { SwitchController } from '../components/InputController/SwitchController/SwitchController'; 4 | import { useForm } from 'react-hook-form'; 5 | import { SwitchControllerProps } from '../fields'; 6 | 7 | const meta: Meta = { 8 | title: 'Switch Controller', 9 | component: SwitchController, 10 | argTypes: { 11 | children: { 12 | control: { 13 | type: 'text' 14 | } 15 | } 16 | }, 17 | parameters: { 18 | controls: { expanded: true } 19 | } 20 | }; 21 | 22 | export default meta; 23 | 24 | const Template: StoryFn = (args) => { 25 | const { control } = useForm(); 26 | 27 | return ; 28 | }; 29 | 30 | export const Switch = Template.bind({}); 31 | 32 | Switch.args = { 33 | name: 'switch', 34 | defaultChecked: true, 35 | label: 'Switch' 36 | }; 37 | -------------------------------------------------------------------------------- /src/stories/CheckboxController.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Meta, StoryFn } from '@storybook/react-webpack5'; 3 | import { CheckboxController } from '../components/InputController/CheckboxController/CheckboxController'; 4 | import { useForm } from 'react-hook-form'; 5 | import { CheckboxControllerProps } from '../fields'; 6 | 7 | const meta: Meta = { 8 | title: 'Checkbox Controller', 9 | component: CheckboxController, 10 | argTypes: { 11 | children: { 12 | control: { 13 | type: 'text' 14 | } 15 | } 16 | }, 17 | parameters: { 18 | controls: { expanded: true } 19 | } 20 | }; 21 | 22 | export default meta; 23 | 24 | const Template: StoryFn = (args) => { 25 | const { control } = useForm(); 26 | 27 | return ; 28 | }; 29 | 30 | export const Checkbox = Template.bind({}); 31 | 32 | Checkbox.args = { 33 | name: 'checkbox', 34 | label: 'Checkbox Controller', 35 | }; 36 | -------------------------------------------------------------------------------- /src/stories/RadioGroup.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Meta, StoryFn } from '@storybook/react-webpack5'; 3 | import { RadioGroupController } from '../components/InputController/RadioGroupController/RadioGroupController'; 4 | import { useForm } from 'react-hook-form'; 5 | import { RadioGroupControllerProps } from '../fields'; 6 | 7 | const meta: Meta = { 8 | title: 'RadioGroup Controller', 9 | component: RadioGroupController, 10 | argTypes: { 11 | children: { 12 | control: { 13 | type: 'text' 14 | } 15 | } 16 | }, 17 | parameters: { 18 | controls: { expanded: true } 19 | } 20 | }; 21 | 22 | export default meta; 23 | 24 | const Template: StoryFn = (args) => { 25 | const { control } = useForm(); 26 | 27 | return ; 28 | }; 29 | 30 | export const FormGroup = Template.bind({}); 31 | 32 | FormGroup.args = { 33 | name: 'formGroup', 34 | defaultValue: '', 35 | label: 'RadioGroup Controller', 36 | options: [ 37 | { label: 'Option 1', value: 'option1' }, 38 | { label: 'Option 2', value: 'option2' } 39 | ], 40 | }; 41 | -------------------------------------------------------------------------------- /src/stories/Textfield.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Meta, StoryFn } from '@storybook/react-webpack5'; 3 | import { TextFieldController } from '../components/InputController/TextFieldController/TextFieldController'; 4 | import { useForm } from 'react-hook-form'; 5 | import { TextFieldControllerProps } from '../fields'; 6 | import { object, string } from 'yup'; 7 | import { yupResolver } from '@hookform/resolvers/yup'; 8 | 9 | const meta: Meta = { 10 | title: 'TextField Controller', 11 | component: TextFieldController, 12 | argTypes: { 13 | children: { 14 | control: { 15 | type: 'text' 16 | } 17 | } 18 | }, 19 | parameters: { 20 | controls: { expanded: true } 21 | } 22 | }; 23 | 24 | export default meta; 25 | 26 | const schema = object().shape({ 27 | textfield: string().min(10).max(20).required() 28 | }); 29 | 30 | const Template: StoryFn = (args: TextFieldControllerProps) => { 31 | const { control, handleSubmit } = useForm({ 32 | resolver: yupResolver(schema) 33 | }); 34 | 35 | return ( 36 |
{})}> 37 | 38 | 39 | 40 | ); 41 | }; 42 | 43 | export const TextField = Template.bind({}); 44 | 45 | TextField.args = { 46 | name: 'textfield', 47 | defaultValue: '', 48 | variant: 'outlined', 49 | label: 'Text Field Controller', 50 | }; 51 | -------------------------------------------------------------------------------- /src/components/InputController/DatePickerController/DatePickerController.tsx: -------------------------------------------------------------------------------- 1 | import { DatePicker } from '@mui/x-date-pickers'; 2 | import React from 'react'; 3 | import { Controller } from 'react-hook-form'; 4 | import { DatePickerControllerProps } from '../../../fields'; 5 | 6 | export const DatePickerController: React.FC = ({ 7 | name, 8 | control, 9 | parser, 10 | onChange, 11 | ...rest 12 | }) => { 13 | const { defaultValue, ...restProps } = rest; 14 | return ( 15 | { 23 | return ( 24 | { 37 | controllerOnChange?.(...args); 38 | onChange?.(...args); 39 | }} 40 | value={value ? parser(value) : null} 41 | /> 42 | ); 43 | }} 44 | /> 45 | ); 46 | }; 47 | 48 | export default DatePickerController; 49 | -------------------------------------------------------------------------------- /rollup.config.mjs: -------------------------------------------------------------------------------- 1 | import resolve from '@rollup/plugin-node-resolve'; 2 | import commonjs from '@rollup/plugin-commonjs'; 3 | import typescript from '@rollup/plugin-typescript'; 4 | import dts from 'rollup-plugin-dts'; 5 | import terser from '@rollup/plugin-terser'; 6 | 7 | import peerDepsExternal from 'rollup-plugin-peer-deps-external'; 8 | 9 | export default [ 10 | { 11 | input: 'src/index.ts', 12 | output: { file: 'dist/esm/index.js', format: 'esm', sourcemap: true }, 13 | plugins: [peerDepsExternal(), resolve(), commonjs(), typescript({ 14 | tsconfig: './tsconfig.json', 15 | compilerOptions: { 16 | outDir: 'dist/esm', 17 | declaration: true, 18 | emitDeclarationOnly: false 19 | } 20 | }), terser()], 21 | onwarn(warning, warn) { 22 | if (warning.code === 'MODULE_LEVEL_DIRECTIVE') { 23 | return; 24 | } 25 | warn(warning); 26 | } 27 | }, 28 | { 29 | input: 'src/index.ts', 30 | output: { file: 'dist/cjs/index.js', format: 'cjs', sourcemap: true }, 31 | plugins: [peerDepsExternal(), resolve(), commonjs(), typescript({ 32 | tsconfig: './tsconfig.json', 33 | compilerOptions: { 34 | outDir: 'dist/cjs', 35 | declaration: false, 36 | emitDeclarationOnly: false 37 | } 38 | }), terser()], 39 | onwarn(warning, warn) { 40 | if (warning.code === 'MODULE_LEVEL_DIRECTIVE') { 41 | return; 42 | } 43 | warn(warning); 44 | } 45 | }, 46 | { 47 | input: 'dist/esm/index.d.ts', 48 | output: [{ file: 'dist/index.d.ts', format: 'esm' }], 49 | plugins: [dts()] 50 | } 51 | ]; 52 | -------------------------------------------------------------------------------- /src/stories/Select.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Meta, StoryFn } from '@storybook/react-webpack5'; 3 | import { SelectController } from '../components/InputController/SelectController/SelectController'; 4 | import { useForm } from 'react-hook-form'; 5 | import { SelectControllerProps } from '../fields'; 6 | 7 | const meta: Meta = { 8 | title: 'Select Controller', 9 | component: SelectController, 10 | argTypes: { 11 | children: { 12 | control: { 13 | type: 'text' 14 | } 15 | } 16 | }, 17 | parameters: { 18 | controls: { expanded: true } 19 | } 20 | }; 21 | 22 | export default meta; 23 | 24 | const Template: StoryFn = (args) => { 25 | const { control } = useForm(); 26 | 27 | return ; 28 | }; 29 | 30 | export const Select = Template.bind({}); 31 | 32 | Select.args = { 33 | name: 'select', 34 | label: 'Select Controller', 35 | defaultValue: '', 36 | options: [ 37 | { label: 'Option One', value: 'option-one', entity: { id: 'entity-one' } }, 38 | { label: 'Option Two', value: 'option-two', entity: { id: 'entity-two' } } 39 | ], 40 | optionValue: 'entity.id', 41 | variant: 'outlined', 42 | fullWidth: true 43 | }; 44 | 45 | export const SelectMultiple = Template.bind({}); 46 | 47 | SelectMultiple.args = { 48 | name: 'selectMultiple', 49 | label: 'Select Multiple Controller', 50 | defaultValue: [], 51 | options: [ 52 | { label: 'Option One', value: 'option-one', entity: { id: 'entity-one' } }, 53 | { label: 'Option Two', value: 'option-two', entity: { id: 'entity-two' } } 54 | ], 55 | optionValue: 'entity.id', 56 | variant: 'outlined', 57 | multiple: true, 58 | fullWidth: true, 59 | }; 60 | -------------------------------------------------------------------------------- /src/components/InputController/TextFieldController/TextFieldController.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Controller } from 'react-hook-form'; 3 | import TextField from '@mui/material/TextField'; 4 | import { TextFieldControllerProps } from '../../../fields/index'; 5 | 6 | export const TextFieldController: React.FC = ({ 7 | name, 8 | control, 9 | defaultValue, 10 | type = 'text', 11 | onChange, 12 | onBlur, 13 | ...rest 14 | }) => { 15 | return ( 16 | { 21 | return ( 22 | { 37 | restField?.onChange?.(...args); 38 | onChange?.(...args); 39 | }} 40 | onBlur={(...args) => { 41 | restField?.onBlur?.(); 42 | onBlur?.(...args); 43 | }} 44 | disabled={restField.disabled ?? rest.disabled} 45 | /> 46 | ); 47 | }} 48 | /> 49 | ); 50 | }; 51 | -------------------------------------------------------------------------------- /src/components/InputController/RadioGroupController/RadioGroupController.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Controller } from 'react-hook-form'; 3 | import { FormControl, FormControlLabel, FormHelperText, FormLabel, Radio, RadioGroup } from '@mui/material'; 4 | import { RadioGroupControllerProps } from '../../../fields'; 5 | 6 | export const RadioGroupController: React.FC = ({ 7 | name, 8 | label, 9 | defaultValue, 10 | control, 11 | options, 12 | onChange, 13 | onBlur, 14 | ...rest 15 | }) => { 16 | return ( 17 | ( 22 | 23 | {label && {label}} 24 | { 28 | onChange?.(event, value); 29 | field.onChange(event); 30 | }} 31 | onBlur={(...args) => { 32 | field?.onBlur?.(); 33 | onBlur?.(...args); 34 | }} 35 | > 36 | {options.map((option, index) => ( 37 | } 41 | label={option.label} 42 | /> 43 | ))} 44 | 45 | {fieldState?.error?.message || rest.helperText} 46 | 47 | )} 48 | /> 49 | ); 50 | }; 51 | 52 | export default RadioGroupController; 53 | -------------------------------------------------------------------------------- /src/components/InputController/CheckboxController/CheckboxController.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Controller } from 'react-hook-form'; 3 | import { Checkbox, FormControl, FormControlLabel, FormGroup, FormHelperText } from '@mui/material'; 4 | 5 | import { CheckboxControllerProps } from '../../../fields'; 6 | 7 | export const CheckboxController: React.FC = ({ 8 | control, 9 | name, 10 | defaultValue, 11 | onChange, 12 | onBlur, 13 | ...rest 14 | }) => { 15 | return ( 16 | { 21 | return ( 22 | 23 | 24 | { 32 | onChange?.(e, checked); 33 | field.onChange(e.target.checked); 34 | }} 35 | onBlur={(...args) => { 36 | onBlur?.(...args); 37 | field.onBlur(); 38 | }} 39 | /> 40 | } 41 | /> 42 | 43 | {fieldState?.error?.message || rest.helperText} 44 | 45 | ); 46 | }} 47 | /> 48 | ); 49 | }; 50 | -------------------------------------------------------------------------------- /src/stories/DatePicker.stories.tsx: -------------------------------------------------------------------------------- 1 | import { yupResolver } from '@hookform/resolvers/yup'; 2 | import { LocalizationProvider } from '@mui/x-date-pickers'; 3 | import { AdapterMoment } from '@mui/x-date-pickers/AdapterMoment'; 4 | import { Meta, StoryFn } from '@storybook/react-webpack5'; 5 | import moment from 'moment'; 6 | import React from 'react'; 7 | import { useForm } from 'react-hook-form'; 8 | import { date, object } from 'yup'; 9 | import DatePickerController from '../components/InputController/DatePickerController/DatePickerController'; 10 | import { DatePickerControllerProps } from '../fields'; 11 | 12 | const meta: Meta = { 13 | title: 'DatePicker Controller', 14 | component: DatePickerController, 15 | argTypes: { 16 | children: { 17 | control: { 18 | type: 'text' 19 | } 20 | } 21 | }, 22 | parameters: { 23 | controls: { expanded: true } 24 | } 25 | }; 26 | 27 | export default meta; 28 | 29 | const schema = object().shape({ 30 | datePicker: date() 31 | .nullable() 32 | .transform((v) => (v instanceof Date && !isNaN(v as any) ? v : null)) 33 | }); 34 | 35 | const Template: StoryFn = (args) => { 36 | const { control, handleSubmit, watch } = useForm({ 37 | resolver: yupResolver(schema) 38 | }); 39 | 40 | const handleOnChange = (value: any) => { 41 | console.log({ 42 | watch: watch('datePicker'), 43 | onChange: value 44 | }); 45 | }; 46 | 47 | return ( 48 | 49 |
{ 51 | console.log(data); 52 | })} 53 | > 54 | 55 | 56 | 57 |
58 | ); 59 | }; 60 | 61 | export const DatePicker = Template.bind({}); 62 | 63 | DatePicker.args = { 64 | name: 'datePicker', 65 | label: 'Date Picker Controller', 66 | format: 'YYYY-MM-DD', 67 | parser: (date) => moment(date) 68 | }; 69 | -------------------------------------------------------------------------------- /src/components/InputController/SwitchController/SwitchController.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Controller } from 'react-hook-form'; 3 | import FormControl from '@mui/material/FormControl'; 4 | import FormHelperText from '@mui/material/FormHelperText'; 5 | import FormGroup from '@mui/material/FormGroup'; 6 | import FormControlLabel from '@mui/material/FormControlLabel'; 7 | import Switch from '@mui/material/Switch'; 8 | 9 | import { SwitchControllerProps } from '../../../fields/index'; 10 | 11 | export const SwitchController: React.FC = ({ 12 | control, 13 | name, 14 | label, 15 | defaultChecked, 16 | onChange, 17 | onBlur, 18 | ...rest 19 | }) => { 20 | return ( 21 | { 26 | return ( 27 | 28 | 29 | { 37 | field.onChange(Boolean(event.target.checked)); 38 | onChange && onChange(event, checked); 39 | }} 40 | onBlur={(...args) => { 41 | field?.onBlur?.(); 42 | onBlur?.(...args); 43 | }} 44 | /> 45 | } 46 | label={label} 47 | /> 48 | 49 | {fieldState?.error?.message || rest?.helperText} 50 | 51 | ); 52 | }} 53 | /> 54 | ); 55 | }; 56 | -------------------------------------------------------------------------------- /src/stories/Autocomplete.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Meta, StoryFn } from '@storybook/react-webpack5'; 3 | import { AutocompleteController } from '../components/InputController/AutocompleteController/AutocompleteController'; 4 | import { useForm } from 'react-hook-form'; 5 | import { AutocompleteControllerProps } from '../fields'; 6 | import { yupResolver } from '@hookform/resolvers/yup'; 7 | import * as yup from 'yup'; 8 | 9 | const meta: Meta = { 10 | title: 'Autocomplete Controller', 11 | component: AutocompleteController, 12 | argTypes: { 13 | children: { 14 | control: { 15 | type: 'text' 16 | } 17 | } 18 | }, 19 | parameters: { 20 | controls: { expanded: true } 21 | } 22 | }; 23 | 24 | export default meta; 25 | 26 | const Template: StoryFn = (args) => { 27 | const schema = yup.object().shape({ 28 | autocomplete: args.multiple ? yup.array().of(yup.string()).min(2).required() : yup.string() 29 | }); 30 | 31 | const { handleSubmit, control } = useForm({ 32 | resolver: yupResolver(schema) 33 | }); 34 | 35 | const handleFormSubmit = (data: any) => { 36 | console.log({ data }); 37 | }; 38 | 39 | return ( 40 |
41 | 42 | 43 | 44 | ); 45 | }; 46 | 47 | export const Autocomplete = Template.bind({}); 48 | 49 | Autocomplete.args = { 50 | name: 'autocomplete', 51 | textFieldProps: { 52 | label: 'Autocomplete Controller', 53 | fullWidth: true, 54 | variant: 'outlined', 55 | placeholder: 'Search...' 56 | }, 57 | optionLabel: 'entity.id', 58 | optionValue: 'entity.id', 59 | options: [ 60 | { title: 'Option One', entity: { id: 'entity-one' }, value: 'option-one' }, 61 | { title: 'Option two', entity: { id: 'entity-two' }, value: 'option-two' } 62 | ], 63 | onBlur: () => console.log('Autocomplete onBlur called') 64 | }; 65 | 66 | export const AutocompleteMultiple = Template.bind({}); 67 | 68 | AutocompleteMultiple.args = { 69 | name: 'autocompleteMultiple', 70 | textFieldProps: { 71 | label: 'Autocomplete Multiple Controller', 72 | fullWidth: true 73 | }, 74 | defaultValue: [], 75 | optionLabel: 'title', 76 | optionValue: 'entity.id', 77 | options: [ 78 | { title: 'Option One', entity: { id: 'entity-one' }, value: 'option-one' }, 79 | { title: 'Option Two', entity: { id: 'entity-two' }, value: 'option-two' } 80 | ], 81 | multiple: true 82 | }; 83 | -------------------------------------------------------------------------------- /src/components/form/FormFields.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { TextFieldController } from '../InputController/TextFieldController/TextFieldController'; 3 | import { SelectController } from '../InputController/SelectController/SelectController'; 4 | import { AutocompleteController } from '../InputController/AutocompleteController/AutocompleteController'; 5 | import { CheckboxController } from '../InputController/CheckboxController/CheckboxController'; 6 | import { RadioGroupController } from '../InputController/RadioGroupController/RadioGroupController'; 7 | import { SwitchController } from '../InputController/SwitchController/SwitchController'; 8 | import { CustomComponentController } from '../InputController/CustomComponentController/CustomComponentController'; 9 | import DatePickerController from '../InputController/DatePickerController/DatePickerController'; 10 | 11 | import Grid, { type GridProps } from '@mui/material/Grid'; 12 | // Legacy Grid for backwards compatibility 13 | import GridLegacy, { type GridLegacyProps } from '@mui/material/GridLegacy'; 14 | 15 | import { FormFieldsProps, MuiRhfFieldComponentMap } from '../../form/typing'; 16 | 17 | const MuiFieldComponentMapper: MuiRhfFieldComponentMap = { 18 | textField: TextFieldController, 19 | select: SelectController, 20 | autocomplete: AutocompleteController, 21 | checkbox: CheckboxController, 22 | radioGroup: RadioGroupController, 23 | switch: SwitchController, 24 | datePicker: DatePickerController, 25 | custom: CustomComponentController 26 | }; 27 | 28 | export const FormFields: React.FC = ({ fields, control, shouldUseDeprecatedGrid = false }) => { 29 | const GridItem: React.FC = shouldUseDeprecatedGrid ? GridLegacy : Grid; 30 | 31 | return ( 32 | <> 33 | {fields 34 | ?.filter(({ hidden }) => !hidden) 35 | .map(({ fieldType, props, name, label, gridProps, ...rest }, index) => { 36 | const MuiRhfField = 37 | MuiFieldComponentMapper[fieldType as keyof MuiRhfFieldComponentMap] || TextFieldController; 38 | 39 | return ( 40 | 47 | 48 | 49 | ); 50 | })} 51 | 52 | ); 53 | }; 54 | -------------------------------------------------------------------------------- /src/form/typing.ts: -------------------------------------------------------------------------------- 1 | import { type TextFieldProps as MuiTextFieldProps } from '@mui/material'; 2 | import { type GridLegacyProps } from '@mui/material/GridLegacy'; 3 | import { type GridProps } from '@mui/material/Grid'; 4 | import { DatePickerProps } from '@mui/x-date-pickers'; 5 | import React from 'react'; 6 | import { Control } from 'react-hook-form'; 7 | import { 8 | AutocompleteControllerProps, 9 | AutocompleteProps, 10 | CheckboxControllerProps, 11 | CheckboxProps, 12 | CustomComponentControllerProps, 13 | DatePickerControllerProps, 14 | RadioGroupControllerProps, 15 | RadioGroupProps, 16 | SelectControllerProps, 17 | SelectProps, 18 | SwitchControllerProps, 19 | SwitchProps, 20 | TextFieldControllerProps 21 | } from '../fields'; 22 | 23 | type FieldTextFieldProps = { 24 | fieldType: 'textField'; 25 | props?: MuiTextFieldProps; 26 | }; 27 | 28 | type FieldSelectProps = { 29 | fieldType: 'select'; 30 | props: SelectProps; 31 | }; 32 | 33 | type FieldAutocompleteProps = { 34 | fieldType: 'autocomplete'; 35 | textFieldProps?: MuiTextFieldProps; 36 | props?: AutocompleteProps; 37 | }; 38 | 39 | type FieldCheckboxProps = { 40 | fieldType: 'checkbox'; 41 | props?: CheckboxProps; 42 | }; 43 | 44 | type FieldRadioGroupProps = { 45 | fieldType: 'radioGroup'; 46 | props: RadioGroupProps; 47 | }; 48 | 49 | type FieldSwitchProps = { 50 | fieldType: 'switch'; 51 | props: SwitchProps; 52 | }; 53 | 54 | type FieldDatePickerFieldProps = DatePickerProps & { 55 | fieldType: 'datePicker'; 56 | parser: (value: string) => T; 57 | props?: any; 58 | }; 59 | 60 | type FieldCustomComponentProps = { 61 | fieldType: 'custom'; 62 | CustomComponent: React.FC; 63 | props?: any; 64 | }; 65 | 66 | export type FieldProps = { 67 | hidden?: boolean; 68 | name: string; 69 | label?: string; 70 | gridProps?: GridProps | GridLegacyProps; 71 | } & ( 72 | | FieldTextFieldProps 73 | | FieldSelectProps 74 | | FieldAutocompleteProps 75 | | FieldCheckboxProps 76 | | FieldRadioGroupProps 77 | | FieldSwitchProps 78 | | FieldDatePickerFieldProps 79 | | FieldCustomComponentProps 80 | ); 81 | 82 | export interface FormFieldsProps { 83 | fields: FieldProps[]; 84 | control: Control; 85 | shouldUseDeprecatedGrid?: boolean; 86 | } 87 | 88 | export type MuiRhfFieldComponentMap = { 89 | textField: React.FC; 90 | select: React.FC; 91 | checkbox: React.FC; 92 | autocomplete: React.FC; 93 | radioGroup: React.FC; 94 | switch: React.FC; 95 | datePicker: React.FC; 96 | custom: React.FC; 97 | }; 98 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mui-rhf-library", 3 | "version": "3.0.0", 4 | "description": "A set of configured Material UI form inputs configured with React Hook Form.", 5 | "author": "dashty94", 6 | "license": "MIT", 7 | "keywords": [ 8 | "mui", 9 | "react", 10 | "form", 11 | "yup", 12 | "react-hook-form", 13 | "material-ui" 14 | ], 15 | "scripts": { 16 | "rollup": "rollup -c", 17 | "storybook": "storybook dev -p 6006", 18 | "build-storybook": "storybook build", 19 | "chromatic": "chromatic --exit-zero-on-changes" 20 | }, 21 | "devDependencies": { 22 | "@babel/core": "^7.28.4", 23 | "@babel/preset-env": "^7.28.3", 24 | "@babel/preset-react": "^7.27.1", 25 | "@babel/preset-typescript": "^7.27.1", 26 | "@chromatic-com/storybook": "^4.1.1", 27 | "@emotion/react": "^11.14.0", 28 | "@emotion/styled": "^11.14.1", 29 | "@hookform/resolvers": "^5.2.2", 30 | "@mui/material": "^7.3.2", 31 | "@mui/system": "^7.3.2", 32 | "@mui/x-date-pickers": "^8.11.3", 33 | "@rollup/plugin-commonjs": "^28.0.6", 34 | "@rollup/plugin-node-resolve": "^16.0.1", 35 | "@storybook/addon-docs": "^9.1.7", 36 | "@storybook/addon-links": "^9.1.7", 37 | "@storybook/addon-webpack5-compiler-babel": "^3.0.6", 38 | "@storybook/react-webpack5": "^9.1.5", 39 | "@types/lodash.get": "^4.4.9", 40 | "@types/react": "^19.1.13", 41 | "chromatic": "^13.2.0", 42 | "lodash.get": "^4.4.2", 43 | "moment": "^2.30.1", 44 | "react": "^19.1.1", 45 | "react-dom": "^19.1.1", 46 | "react-hook-form": "^7.63.0", 47 | "rollup": "^4.52.0", 48 | "rollup-plugin-dts": "^6.2.3", 49 | "rollup-plugin-peer-deps-external": "^2.2.4", 50 | "storybook": "^9.1.7", 51 | "tslib": "^2.8.1", 52 | "typescript": "^5.9.2", 53 | "webpack": "^5.101.3", 54 | "yup": "^1.7.0" 55 | }, 56 | "main": "dist/cjs/index.js", 57 | "module": "dist/esm/index.js", 58 | "files": [ 59 | "dist" 60 | ], 61 | "types": "dist/index.d.ts", 62 | "repository": { 63 | "type": "git", 64 | "url": "https://github.com/dashty94/mui-rhf-library" 65 | }, 66 | "dependencies": { 67 | "@changesets/cli": "^2.29.6", 68 | "@rollup/plugin-terser": "^0.4.4", 69 | "@rollup/plugin-typescript": "^12.1.4" 70 | }, 71 | "peerDependencies": { 72 | "@emotion/react": "^11.14.0", 73 | "@emotion/styled": "^11.14.1", 74 | "@mui/material": "^7.3.2", 75 | "@mui/system": "^7.3.2", 76 | "@mui/x-date-pickers": "^8.11.3", 77 | "react": "^19.1.1", 78 | "react-hook-form": "^7.62.0" 79 | }, 80 | "resolutions": { 81 | "@storybook/react-docgen-typescript-plugin": "npm:react-docgen-typescript-plugin@1.0.2", 82 | "socks": "^2.8.7", 83 | "serialize-javascript": "6.0.2", 84 | "brace-expansion@^2.0.1": "2.0.2" 85 | }, 86 | "packageManager": "yarn@4.9.1" 87 | } 88 | -------------------------------------------------------------------------------- /src/fields/typing.ts: -------------------------------------------------------------------------------- 1 | import { Control } from 'react-hook-form'; 2 | import { 3 | type TextFieldProps as MuiTextFieldProps, 4 | type SelectProps as MuiSelectProps, 5 | type SwitchProps as MuiSwitchProps, 6 | type FormControlProps as MuiFormControlProps, 7 | SelectChangeEvent, 8 | type AutocompleteProps as MuiAutocompleteProps, 9 | type CheckboxProps as MuiCheckboxProps 10 | } from '@mui/material'; 11 | import React from 'react'; 12 | import { DatePickerProps } from '@mui/x-date-pickers'; 13 | 14 | // Common input field props 15 | export type MuiRhfFieldProps = { 16 | control: Control; 17 | name: string; 18 | helperText?: React.ReactNode; 19 | }; 20 | 21 | // Option 22 | export type Option = { 23 | value: string; 24 | label: string; 25 | }; 26 | 27 | // TextField 28 | export type TextFieldProps = MuiTextFieldProps & { 29 | type?: React.HTMLInputTypeAttribute | undefined; 30 | }; 31 | export type TextFieldControllerProps = Omit & TextFieldProps; 32 | 33 | // Select 34 | export type SelectProps = MuiSelectProps & { 35 | defaultValue?: any; 36 | options: { disabled?: boolean; [key: string]: any }[]; 37 | optionValue?: string; 38 | optionLabel?: string; 39 | loading?: boolean; 40 | customOptionLabel?: (option: any) => any; 41 | }; 42 | export type SelectControllerProps = MuiRhfFieldProps & SelectProps; 43 | 44 | // Switch 45 | export type SwitchProps = Omit & { 46 | label: string; 47 | onChange?: (event: React.ChangeEvent, checked: boolean) => void; 48 | onBlur?: (event: React.FocusEvent) => void; 49 | helperText?: React.ReactNode; 50 | }; 51 | export type SwitchControllerProps = MuiRhfFieldProps & SwitchProps; 52 | 53 | // RadioGroup 54 | export type RadioGroupProps = MuiFormControlProps & { 55 | label?: string; 56 | defaultValue: string | number; 57 | options: Array