Skip to content

Commit

Permalink
Update typings & fields, add MuiRhfForm component
Browse files Browse the repository at this point in the history
  • Loading branch information
clement-faure committed Feb 25, 2021
1 parent 828a686 commit ee4e815
Show file tree
Hide file tree
Showing 12 changed files with 271 additions and 35 deletions.
2 changes: 1 addition & 1 deletion src/components/MuiRhfAutocomplete.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Controller } from "react-hook-form";
import { TextField, TextFieldProps } from "@material-ui/core";
import { Autocomplete } from "@material-ui/lab";

import { MuiRhfFieldProps } from "~/typings";
import { MuiRhfFieldProps } from "~/models/fields";

type MuiRhfAutocompleteProps = MuiRhfFieldProps & {
defaultValue?: unknown;
Expand Down
8 changes: 1 addition & 7 deletions src/components/MuiRhfCheckbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,12 @@ import { Controller } from "react-hook-form";

import {
Checkbox,
CheckboxProps,
FormControl,
FormControlLabel,
FormHelperText,
} from "@material-ui/core";

import { MuiRhfFieldProps } from "~/typings";

type MuiRhfCheckboxProps = MuiRhfFieldProps &
Omit<CheckboxProps, "defaultValue" | "onChange" | "label"> & {
defaultValue?: unknown;
};
import { MuiRhfCheckboxProps } from "~/models/fields";

const MuiRhfCheckbox: React.FC<MuiRhfCheckboxProps> = ({
control,
Expand Down
153 changes: 153 additions & 0 deletions src/components/MuiRhfForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import React from "react";

import { Grid } from "@material-ui/core";

import {
MuiRhfFormProps,
fieldComponentMap,
defaultFieldComponent,
} from "~/models/form";

const MuiRhfForm: React.FC<MuiRhfFormProps> = ({
fields,
headers,
spacing = 1,
watch,
}) => {
return (
<>
{React.Children.toArray(
fields.map((fieldArray, key) => {
// Format of fieldArray is NOT valid, must be array of array
if (!Array.isArray(fieldArray)) {
return null;
}

const header = headers?.[key];

return (
<Grid container spacing={spacing}>
{header && (
<Grid item xs={12}>
{header.title && <h2>{header.title}</h2>}
</Grid>
)}
{fieldArray.map(
({
name,
label,
props,
gridProps = {},
type,
condition,
conditions,
conditionalProps,
}) => {
// Initialize dynamic props based on watched values
const extra: { [key: string]: any } = {};

// Construct field, take textField by default
const MuiRhfField =
fieldComponentMap[type] || defaultFieldComponent;

if (watch) {
// Union will be used in condition keys
if (condition) {
let conditionHidden = true;

// Retrieve conditions keys
const conditionKeys = Object.keys(condition);

// Retrieve watched values
const conditionWatchedValues = watch(conditionKeys);

// Check conditions, we are actually doing an union of conditions
// If at least one condition has been satisfied, display
conditionKeys.forEach((conditionKey) => {
if (
condition[conditionKey](
conditionWatchedValues[conditionKey]
)
) {
conditionHidden = false;
}
});

if (conditionHidden) {
return null;
}
}

// Intersection will be used in conditions keys
if (conditions) {
let conditionsHidden = false;

// Retrieve conditions keys
const conditionsKeys = Object.keys(conditions);

// Retrieve watched values
const conditionsWatchedValues = watch(conditionsKeys);

// Check conditions, we are actually doing an union of conditions
// All condition needs to be satisfied to display the field
conditionsKeys.forEach((conditionsKey) => {
if (
!conditions[conditionsKey](
conditionsWatchedValues[conditionsKey]
)
) {
conditionsHidden = true;
}
});

if (conditionsHidden) {
return null;
}
}

if (conditionalProps) {
// Retrieve conditions keys
const conditionalKeys = Object.keys(conditionalProps);

// Retrieve watched values
const conditionalWatchedValues = watch(conditionalKeys);

// Check conditions
conditionalKeys.forEach((conditionalKey) => {
const [path, customCondition] = conditionalProps[
conditionalKey
];

// FIXME: Support nested paths
extra[path] = customCondition(
conditionalWatchedValues[conditionalKey]
);
});
}
}
return (
<Grid
key={name || label}
item
xs={4} // Will be overwrite if specified in gridProps
{...gridProps}
>
<MuiRhfField
label={label}
name={name || label}
{...props}
{...extra}
/>
</Grid>
);
}
)}
</Grid>
);
})
)}
</>
);
};

export default MuiRhfForm;
12 changes: 1 addition & 11 deletions src/components/MuiRhfSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,10 @@ import {
FormControl,
Select,
MenuItem,
SelectProps,
FormHelperText,
} from "@material-ui/core";

import { MuiRhfFieldProps } from "~/typings";

type MuiRhfSelectProps = MuiRhfFieldProps & {
defaultValue?: unknown;
select?: SelectProps;
options: {
value: string | number | readonly string[] | undefined;
label: string;
}[];
};
import { MuiRhfSelectProps } from "~/models/fields";

const MuiRhfSelect: React.FC<MuiRhfSelectProps> = ({
control,
Expand Down
9 changes: 2 additions & 7 deletions src/components/MuiRhfTextField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,9 @@ import * as React from "react";

import { Controller } from "react-hook-form";

import { TextField, TextFieldProps } from "@material-ui/core";
import { TextField } from "@material-ui/core";

import { MuiRhfFieldProps } from "~/typings";

type MuiRhfTextFieldProps = MuiRhfFieldProps &
Omit<TextFieldProps, "onChange"> & {
defaultValue?: unknown;
};
import { MuiRhfTextFieldProps } from "~/models/fields";

const MuiRhfTextField: React.FC<MuiRhfTextFieldProps> = ({
control,
Expand Down
9 changes: 8 additions & 1 deletion src/components/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import MuiRhfForm from "./MuiRhfForm";
import MuiRhfTextField from "./MuiRhfTextField";
import MuiRhfSelect from "./MuiRhfSelect";
import MuiRhfCheckbox from "./MuiRhfCheckbox";
import MuiRhfAutocomplete from "./MuiRhfAutocomplete";

export { MuiRhfTextField, MuiRhfSelect, MuiRhfCheckbox, MuiRhfAutocomplete };
export {
MuiRhfForm,
MuiRhfTextField,
MuiRhfSelect,
MuiRhfCheckbox,
MuiRhfAutocomplete,
};
1 change: 1 addition & 0 deletions src/models/fields/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./typing";
40 changes: 40 additions & 0 deletions src/models/fields/typing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Control, FieldErrors } from "react-hook-form";
import { TextFieldProps, SelectProps, CheckboxProps } from "@material-ui/core";

/** Common fields props */
export type MuiRhfFieldProps = {
control: Control;
errors: FieldErrors;
name: string;
label?: string;
};

/** TextField */
export type MuiRhfTextFieldProps = MuiRhfFieldProps &
Omit<TextFieldProps, "onChange"> & {
defaultValue?: unknown;
};

/** Select */
export type MuiRhfSelectProps = MuiRhfFieldProps & {
defaultValue?: unknown;
select?: SelectProps;
options: {
value: string | number | readonly string[] | undefined;
label: string;
}[];
};

/** Checkbox */
export type MuiRhfCheckboxProps = MuiRhfFieldProps &
Omit<CheckboxProps, "defaultValue" | "onChange" | "label"> & {
defaultValue?: unknown;
};

/** Autocomplete */
export type MuiRhfAutocompleteProps = MuiRhfFieldProps & {
defaultValue?: unknown;
textField?: Omit<TextFieldProps, "onChange">;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
autocomplete?: any;
};
16 changes: 16 additions & 0 deletions src/models/form/consts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import {
MuiRhfTextField,
MuiRhfSelect,
MuiRhfCheckbox,
MuiRhfAutocomplete,
} from "~/components";

import { MuiRhfFieldComponentMap } from "./typing";

export const defaultFieldComponent = MuiRhfTextField;
export const fieldComponentMap: MuiRhfFieldComponentMap = {
textField: MuiRhfTextField,
select: MuiRhfSelect,
checkbox: MuiRhfCheckbox,
autocomplete: MuiRhfAutocomplete,
};
2 changes: 2 additions & 0 deletions src/models/form/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./typing";
export * from "./consts";
46 changes: 46 additions & 0 deletions src/models/form/typing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { GridSpacing, GridProps } from "@material-ui/core";
import {
MuiRhfTextFieldProps,
MuiRhfSelectProps,
MuiRhfCheckboxProps,
MuiRhfAutocompleteProps,
} from "~/models/fields";

/** react-hook-form watch */
export type RhfWatch = (names?: string | string[]) => any;

/** Form field key component mapping */
export type MuiRhfFieldComponentMap = {
textField: React.FC<MuiRhfTextFieldProps>;
select: React.FC<MuiRhfSelectProps>;
checkbox: React.FC<MuiRhfCheckboxProps>;
autocomplete: React.FC<MuiRhfAutocompleteProps>;
};

/** Form */
type MuiRhfFormFieldCondition = { [key: string]: (value: unknown) => boolean };
type MuiRhfFormFieldConditionalProps = {
[key: string]: [string, (value: unknown) => unknown];
};

type MuiRhfFormField = {
label?: string;
name?: string;
type: keyof MuiRhfFieldComponentMap;
props: any;
gridProps?: Pick<GridProps, "xs" | "sm" | "md" | "lg" | "xl">;
condition?: MuiRhfFormFieldCondition; // Union
conditions?: MuiRhfFormFieldCondition; // Intersection
conditionalProps?: MuiRhfFormFieldConditionalProps; // Props applied when condition is satisfied
};

type MuiRhfFormHeader = {
title?: string;
};

export type MuiRhfFormProps = {
fields: MuiRhfFormField[][];
headers?: MuiRhfFormHeader[];
spacing?: GridSpacing;
watch?: RhfWatch;
};
8 changes: 0 additions & 8 deletions src/typings/index.ts

This file was deleted.

0 comments on commit ee4e815

Please sign in to comment.