diff --git a/CHANGELOG.md b/CHANGELOG.md index ae754fa684..6f1f079b77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,7 @@ should change the heading of the (upcoming) version to include a major version b ## @rjsf/material-ui - Updated the usage of the `ButtonTemplates` to pass the new required `registry` prop, filtering it out in the actual implementations before spreading props, fixing - [#3314](https://github.com/rjsf-team/react-jsonschema-form/issues/3314) - Updated the test for the `CheckboxWidget` validating that the `schema.title` is passed as the label, fixing [#3302](https://github.com/rjsf-team/react-jsonschema-form/issues/3302) +- Updated the theme to accept generic types, exporting `generateXXX` functions for `Form`, `Theme`, `Templates` and `Widgets` to support using the theme with user-specified type generics, partially fixing [#3072](https://github.com/rjsf-team/react-jsonschema-form/issues/3072) ## @rjsf/mui - Updated the usage of the `ButtonTemplates` to pass the new required `registry` prop, filtering it out in the actual implementations before spreading props, fixing - [#3314](https://github.com/rjsf-team/react-jsonschema-form/issues/3314) diff --git a/packages/material-ui/src/AddButton/AddButton.tsx b/packages/material-ui/src/AddButton/AddButton.tsx index 031eb3d3f1..46e909c939 100644 --- a/packages/material-ui/src/AddButton/AddButton.tsx +++ b/packages/material-ui/src/AddButton/AddButton.tsx @@ -1,18 +1,23 @@ import React from "react"; import AddIcon from "@material-ui/icons/Add"; import IconButton from "@material-ui/core/IconButton"; -import { IconButtonProps } from "@rjsf/utils"; +import { + FormContextType, + IconButtonProps, + RJSFSchema, + StrictRJSFSchema, +} from "@rjsf/utils"; -const AddButton: React.ComponentType = ({ - uiSchema, - registry, - ...props -}) => { +/** The `AddButton` renders a button that represent the `Add` action on a form + */ +export default function AddButton< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>({ uiSchema, registry, ...props }: IconButtonProps) { return ( ); -}; - -export default AddButton; +} diff --git a/packages/material-ui/src/ArrayFieldItemTemplate/ArrayFieldItemTemplate.tsx b/packages/material-ui/src/ArrayFieldItemTemplate/ArrayFieldItemTemplate.tsx index 7def41c20d..19b1d17e7d 100644 --- a/packages/material-ui/src/ArrayFieldItemTemplate/ArrayFieldItemTemplate.tsx +++ b/packages/material-ui/src/ArrayFieldItemTemplate/ArrayFieldItemTemplate.tsx @@ -2,9 +2,22 @@ import React, { CSSProperties } from "react"; import Box from "@material-ui/core/Box"; import Grid from "@material-ui/core/Grid"; import Paper from "@material-ui/core/Paper"; -import { ArrayFieldTemplateItemType } from "@rjsf/utils"; +import { + ArrayFieldTemplateItemType, + FormContextType, + RJSFSchema, + StrictRJSFSchema, +} from "@rjsf/utils"; -const ArrayFieldItemTemplate = (props: ArrayFieldTemplateItemType) => { +/** The `ArrayFieldItemTemplate` component is the template used to render an items of an array. + * + * @param props - The `ArrayFieldTemplateItemType` props for the component + */ +export default function ArrayFieldItemTemplate< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>(props: ArrayFieldTemplateItemType) { const { children, disabled, @@ -71,6 +84,4 @@ const ArrayFieldItemTemplate = (props: ArrayFieldTemplateItemType) => { )} ); -}; - -export default ArrayFieldItemTemplate; +} diff --git a/packages/material-ui/src/ArrayFieldTemplate/ArrayFieldTemplate.tsx b/packages/material-ui/src/ArrayFieldTemplate/ArrayFieldTemplate.tsx index 3b2f4c945b..b3b70b20fa 100644 --- a/packages/material-ui/src/ArrayFieldTemplate/ArrayFieldTemplate.tsx +++ b/packages/material-ui/src/ArrayFieldTemplate/ArrayFieldTemplate.tsx @@ -3,13 +3,24 @@ import Box from "@material-ui/core/Box"; import Grid from "@material-ui/core/Grid"; import Paper from "@material-ui/core/Paper"; import { - ArrayFieldTemplateItemType, - ArrayFieldTemplateProps, getTemplate, getUiOptions, + ArrayFieldTemplateProps, + ArrayFieldTemplateItemType, + FormContextType, + RJSFSchema, + StrictRJSFSchema, } from "@rjsf/utils"; -const ArrayFieldTemplate = (props: ArrayFieldTemplateProps) => { +/** The `ArrayFieldTemplate` component is the template used to render all items in an array. + * + * @param props - The `ArrayFieldTemplateItemType` props for the component + */ +export default function ArrayFieldTemplate< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>(props: ArrayFieldTemplateProps) { const { canAdd, disabled, @@ -24,22 +35,23 @@ const ArrayFieldTemplate = (props: ArrayFieldTemplateProps) => { title, } = props; const uiOptions = getUiOptions(uiSchema); - const ArrayFieldDescriptionTemplate = - getTemplate<"ArrayFieldDescriptionTemplate">( - "ArrayFieldDescriptionTemplate", - registry, - uiOptions - ); - const ArrayFieldItemTemplate = getTemplate<"ArrayFieldItemTemplate">( + const ArrayFieldDescriptionTemplate = getTemplate< + "ArrayFieldDescriptionTemplate", + T, + S, + F + >("ArrayFieldDescriptionTemplate", registry, uiOptions); + const ArrayFieldItemTemplate = getTemplate<"ArrayFieldItemTemplate", T, S, F>( "ArrayFieldItemTemplate", registry, uiOptions ); - const ArrayFieldTitleTemplate = getTemplate<"ArrayFieldTitleTemplate">( + const ArrayFieldTitleTemplate = getTemplate< "ArrayFieldTitleTemplate", - registry, - uiOptions - ); + T, + S, + F + >("ArrayFieldTitleTemplate", registry, uiOptions); // Button templates are not overridden in the uiSchema const { ButtonTemplates: { AddButton }, @@ -64,9 +76,11 @@ const ArrayFieldTemplate = (props: ArrayFieldTemplateProps) => { /> {items && - items.map(({ key, ...itemProps }: ArrayFieldTemplateItemType) => ( - - ))} + items.map( + ({ key, ...itemProps }: ArrayFieldTemplateItemType) => ( + + ) + )} {canAdd && ( @@ -86,6 +100,4 @@ const ArrayFieldTemplate = (props: ArrayFieldTemplateProps) => { ); -}; - -export default ArrayFieldTemplate; +} diff --git a/packages/material-ui/src/BaseInputTemplate/BaseInputTemplate.tsx b/packages/material-ui/src/BaseInputTemplate/BaseInputTemplate.tsx index ae0f9f0109..afba47bf77 100644 --- a/packages/material-ui/src/BaseInputTemplate/BaseInputTemplate.tsx +++ b/packages/material-ui/src/BaseInputTemplate/BaseInputTemplate.tsx @@ -1,29 +1,46 @@ import React from "react"; import TextField, { TextFieldProps } from "@material-ui/core/TextField"; -import { getInputProps, WidgetProps } from "@rjsf/utils"; +import { + getInputProps, + FormContextType, + RJSFSchema, + StrictRJSFSchema, + WidgetProps, +} from "@rjsf/utils"; -const BaseInputTemplate = ({ - id, - placeholder, - required, - readonly, - disabled, - type, - label, - value, - onChange, - onBlur, - onFocus, - autofocus, - options, - schema, - uiSchema, - rawErrors = [], - formContext, - registry, - ...textFieldProps -}: WidgetProps) => { - const inputProps = getInputProps(schema, type, options); +/** The `BaseInputTemplate` is the template to use to render the basic `` component for the `core` theme. + * It is used as the template for rendering many of the based widgets that differ by `type` and callbacks only. + * It can be customized/overridden for other themes or individual implementations as needed. + * + * @param props - The `WidgetProps` for this template + */ +export default function BaseInputTemplate< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>(props: WidgetProps) { + const { + id, + placeholder, + required, + readonly, + disabled, + type, + label, + value, + onChange, + onBlur, + onFocus, + autofocus, + options, + schema, + uiSchema, + rawErrors = [], + formContext, + registry, + ...textFieldProps + } = props; + const inputProps = getInputProps(schema, type, options); // Now we need to pull out the step, min, max into an inner `inputProps` for material-ui const { step, min, max, ...rest } = inputProps; const otherProps = { @@ -77,6 +94,4 @@ const BaseInputTemplate = ({ )} ); -}; - -export default BaseInputTemplate; +} diff --git a/packages/material-ui/src/CheckboxWidget/CheckboxWidget.tsx b/packages/material-ui/src/CheckboxWidget/CheckboxWidget.tsx index 6e9a2f0bb1..bb895c2fb6 100644 --- a/packages/material-ui/src/CheckboxWidget/CheckboxWidget.tsx +++ b/packages/material-ui/src/CheckboxWidget/CheckboxWidget.tsx @@ -1,9 +1,24 @@ import React from "react"; import Checkbox from "@material-ui/core/Checkbox"; import FormControlLabel from "@material-ui/core/FormControlLabel"; -import { schemaRequiresTrueValue, WidgetProps } from "@rjsf/utils"; +import { + schemaRequiresTrueValue, + FormContextType, + RJSFSchema, + StrictRJSFSchema, + WidgetProps, +} from "@rjsf/utils"; -const CheckboxWidget = (props: WidgetProps) => { +/** The `CheckBoxWidget` is a widget for rendering boolean properties. + * It is typically used to represent a boolean. + * + * @param props - The `WidgetProps` for this component + */ +export default function CheckboxWidget< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>(props: WidgetProps) { const { schema, id, @@ -19,7 +34,7 @@ const CheckboxWidget = (props: WidgetProps) => { // Because an unchecked checkbox will cause html5 validation to fail, only add // the "required" attribute if the field value must be "true", due to the // "const" or "enum" keywords - const required = schemaRequiresTrueValue(schema); + const required = schemaRequiresTrueValue(schema); const _onChange = (_: any, checked: boolean) => onChange(checked); const _onBlur = ({ @@ -47,6 +62,4 @@ const CheckboxWidget = (props: WidgetProps) => { label={label || ""} /> ); -}; - -export default CheckboxWidget; +} diff --git a/packages/material-ui/src/CheckboxesWidget/CheckboxesWidget.tsx b/packages/material-ui/src/CheckboxesWidget/CheckboxesWidget.tsx index fdb5a2d530..0848816be0 100644 --- a/packages/material-ui/src/CheckboxesWidget/CheckboxesWidget.tsx +++ b/packages/material-ui/src/CheckboxesWidget/CheckboxesWidget.tsx @@ -3,7 +3,12 @@ import Checkbox from "@material-ui/core/Checkbox"; import FormControlLabel from "@material-ui/core/FormControlLabel"; import FormGroup from "@material-ui/core/FormGroup"; import FormLabel from "@material-ui/core/FormLabel"; -import { WidgetProps } from "@rjsf/utils"; +import { + FormContextType, + WidgetProps, + RJSFSchema, + StrictRJSFSchema, +} from "@rjsf/utils"; const selectValue = (value: any, selected: any, all: any) => { const at = all.indexOf(value); @@ -18,7 +23,16 @@ const deselectValue = (value: any, selected: any) => { return selected.filter((v: any) => v !== value); }; -const CheckboxesWidget = ({ +/** The `CheckboxesWidget` is a widget for rendering checkbox groups. + * It is typically used to represent an array of enums. + * + * @param props - The `WidgetProps` for this component + */ +export default function CheckboxesWidget< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>({ schema, label, id, @@ -31,7 +45,7 @@ const CheckboxesWidget = ({ onChange, onBlur, onFocus, -}: WidgetProps) => { +}: WidgetProps) { const { enumOptions, enumDisabled, inline } = options; const _onChange = @@ -88,6 +102,4 @@ const CheckboxesWidget = ({ ); -}; - -export default CheckboxesWidget; +} diff --git a/packages/material-ui/src/DateTimeWidget/DateTimeWidget.tsx b/packages/material-ui/src/DateTimeWidget/DateTimeWidget.tsx index c89802c5ae..ef39a8afc6 100644 --- a/packages/material-ui/src/DateTimeWidget/DateTimeWidget.tsx +++ b/packages/material-ui/src/DateTimeWidget/DateTimeWidget.tsx @@ -1,9 +1,26 @@ import React from "react"; -import { getTemplate, localToUTC, utcToLocal, WidgetProps } from "@rjsf/utils"; +import { + getTemplate, + localToUTC, + utcToLocal, + FormContextType, + RJSFSchema, + StrictRJSFSchema, + WidgetProps, +} from "@rjsf/utils"; -const DateTimeWidget = (props: WidgetProps) => { +/** The `DateTimeWidget` component uses the `BaseInputTemplate` changing the type to `datetime-local` and transforms + * the value to/from utc using the appropriate utility functions. + * + * @param props - The `WidgetProps` for this component + */ +export default function DateTimeWidget< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>(props: WidgetProps) { const { options, registry } = props; - const BaseInputTemplate = getTemplate<"BaseInputTemplate">( + const BaseInputTemplate = getTemplate<"BaseInputTemplate", T, S, F>( "BaseInputTemplate", registry, options @@ -24,6 +41,4 @@ const DateTimeWidget = (props: WidgetProps) => { onChange={onChange} /> ); -}; - -export default DateTimeWidget; +} diff --git a/packages/material-ui/src/DateWidget/DateWidget.tsx b/packages/material-ui/src/DateWidget/DateWidget.tsx index 90ac16f47d..2a96a0e902 100644 --- a/packages/material-ui/src/DateWidget/DateWidget.tsx +++ b/packages/material-ui/src/DateWidget/DateWidget.tsx @@ -1,9 +1,24 @@ import React from "react"; -import { getTemplate, WidgetProps } from "@rjsf/utils"; +import { + getTemplate, + FormContextType, + RJSFSchema, + StrictRJSFSchema, + WidgetProps, +} from "@rjsf/utils"; -const DateWidget = (props: WidgetProps) => { +/** The `DateWidget` component uses the `BaseInputTemplate` changing the type to `date` and transforms + * the value to undefined when it is falsy during the `onChange` handling. + * + * @param props - The `WidgetProps` for this component + */ +export default function DateWidget< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>(props: WidgetProps) { const { options, registry } = props; - const BaseInputTemplate = getTemplate<"BaseInputTemplate">( + const BaseInputTemplate = getTemplate<"BaseInputTemplate", T, S, F>( "BaseInputTemplate", registry, options @@ -17,6 +32,4 @@ const DateWidget = (props: WidgetProps) => { {...props} /> ); -}; - -export default DateWidget; +} diff --git a/packages/material-ui/src/DescriptionField/DescriptionField.tsx b/packages/material-ui/src/DescriptionField/DescriptionField.tsx index 85279e1927..c604e36d93 100644 --- a/packages/material-ui/src/DescriptionField/DescriptionField.tsx +++ b/packages/material-ui/src/DescriptionField/DescriptionField.tsx @@ -1,8 +1,22 @@ import React from "react"; import Typography from "@material-ui/core/Typography"; -import { DescriptionFieldProps } from "@rjsf/utils"; +import { + DescriptionFieldProps, + FormContextType, + RJSFSchema, + StrictRJSFSchema, +} from "@rjsf/utils"; -const DescriptionField = ({ id, description }: DescriptionFieldProps) => { +/** The `DescriptionField` is the template to use to render the description of a field + * + * @param props - The `DescriptionFieldProps` for this component + */ +export default function DescriptionField< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>(props: DescriptionFieldProps) { + const { id, description } = props; if (description) { return ( @@ -12,6 +26,4 @@ const DescriptionField = ({ id, description }: DescriptionFieldProps) => { } return null; -}; - -export default DescriptionField; +} diff --git a/packages/material-ui/src/ErrorList/ErrorList.tsx b/packages/material-ui/src/ErrorList/ErrorList.tsx index 34b0807cef..44d572e1bd 100644 --- a/packages/material-ui/src/ErrorList/ErrorList.tsx +++ b/packages/material-ui/src/ErrorList/ErrorList.tsx @@ -7,9 +7,22 @@ import ListItemIcon from "@material-ui/core/ListItemIcon"; import ListItemText from "@material-ui/core/ListItemText"; import Paper from "@material-ui/core/Paper"; import Typography from "@material-ui/core/Typography"; -import { ErrorListProps } from "@rjsf/utils"; +import { + ErrorListProps, + FormContextType, + RJSFSchema, + StrictRJSFSchema, +} from "@rjsf/utils"; -const ErrorList = ({ errors }: ErrorListProps) => { +/** The `ErrorList` component is the template that renders the all the errors associated with the fields in the `Form` + * + * @param props - The `ErrorListProps` for this component + */ +export default function ErrorList< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>({ errors }: ErrorListProps) { return ( @@ -29,6 +42,4 @@ const ErrorList = ({ errors }: ErrorListProps) => { ); -}; - -export default ErrorList; +} diff --git a/packages/material-ui/src/FieldErrorTemplate/FieldErrorTemplate.tsx b/packages/material-ui/src/FieldErrorTemplate/FieldErrorTemplate.tsx index 4594ad603a..6e1724f82d 100644 --- a/packages/material-ui/src/FieldErrorTemplate/FieldErrorTemplate.tsx +++ b/packages/material-ui/src/FieldErrorTemplate/FieldErrorTemplate.tsx @@ -1,14 +1,23 @@ import React from "react"; -import { FieldErrorProps } from "@rjsf/utils"; import ListItem from "@material-ui/core/ListItem"; import FormHelperText from "@material-ui/core/FormHelperText"; import List from "@material-ui/core/List"; +import { + FieldErrorProps, + FormContextType, + RJSFSchema, + StrictRJSFSchema, +} from "@rjsf/utils"; /** The `FieldErrorTemplate` component renders the errors local to the particular field * * @param props - The `FieldErrorProps` for the errors being rendered */ -export default function FieldErrorTemplate(props: FieldErrorProps) { +export default function FieldErrorTemplate< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>(props: FieldErrorProps) { const { errors = [], idSchema } = props; if (errors.length === 0) { return null; diff --git a/packages/material-ui/src/FieldHelpTemplate/FieldHelpTemplate.tsx b/packages/material-ui/src/FieldHelpTemplate/FieldHelpTemplate.tsx index 3b24ee2d13..fd97735b3e 100644 --- a/packages/material-ui/src/FieldHelpTemplate/FieldHelpTemplate.tsx +++ b/packages/material-ui/src/FieldHelpTemplate/FieldHelpTemplate.tsx @@ -1,12 +1,21 @@ import React from "react"; -import { FieldHelpProps } from "@rjsf/utils"; import FormHelperText from "@material-ui/core/FormHelperText"; +import { + FieldHelpProps, + FormContextType, + RJSFSchema, + StrictRJSFSchema, +} from "@rjsf/utils"; /** The `FieldHelpTemplate` component renders any help desired for a field * * @param props - The `FieldHelpProps` to be rendered */ -export default function FieldHelpTemplate(props: FieldHelpProps) { +export default function FieldHelpTemplate< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>(props: FieldHelpProps) { const { idSchema, help } = props; if (!help) { return null; diff --git a/packages/material-ui/src/FieldTemplate/FieldTemplate.tsx b/packages/material-ui/src/FieldTemplate/FieldTemplate.tsx index 2d53560c95..da74bf87ca 100644 --- a/packages/material-ui/src/FieldTemplate/FieldTemplate.tsx +++ b/packages/material-ui/src/FieldTemplate/FieldTemplate.tsx @@ -1,34 +1,52 @@ import React from "react"; import FormControl from "@material-ui/core/FormControl"; import Typography from "@material-ui/core/Typography"; -import { FieldTemplateProps, getTemplate, getUiOptions } from "@rjsf/utils"; +import { + FieldTemplateProps, + FormContextType, + RJSFSchema, + StrictRJSFSchema, + getTemplate, + getUiOptions, +} from "@rjsf/utils"; -const FieldTemplate = ({ - id, - children, - classNames, - disabled, - displayLabel, - hidden, - label, - onDropPropertyClick, - onKeyChange, - readonly, - required, - rawErrors = [], - errors, - help, - rawDescription, - schema, - uiSchema, - registry, -}: FieldTemplateProps) => { - const uiOptions = getUiOptions(uiSchema); - const WrapIfAdditionalTemplate = getTemplate<"WrapIfAdditionalTemplate">( - "WrapIfAdditionalTemplate", +/** The `FieldTemplate` component is the template used by `SchemaField` to render any field. It renders the field + * content, (label, description, children, errors and help) inside of a `WrapIfAdditional` component. + * + * @param props - The `FieldTemplateProps` for this component + */ +export default function FieldTemplate< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>(props: FieldTemplateProps) { + const { + id, + children, + classNames, + disabled, + displayLabel, + hidden, + label, + onDropPropertyClick, + onKeyChange, + readonly, + required, + rawErrors = [], + errors, + help, + rawDescription, + schema, + uiSchema, registry, - uiOptions - ); + } = props; + const uiOptions = getUiOptions(uiSchema); + const WrapIfAdditionalTemplate = getTemplate< + "WrapIfAdditionalTemplate", + T, + S, + F + >("WrapIfAdditionalTemplate", registry, uiOptions); if (hidden) { return
{children}
; @@ -63,6 +81,4 @@ const FieldTemplate = ({ ); -}; - -export default FieldTemplate; +} diff --git a/packages/material-ui/src/IconButton/IconButton.tsx b/packages/material-ui/src/IconButton/IconButton.tsx index a970423ce5..768ddb9043 100644 --- a/packages/material-ui/src/IconButton/IconButton.tsx +++ b/packages/material-ui/src/IconButton/IconButton.tsx @@ -5,9 +5,18 @@ import IconButton, { import ArrowUpwardIcon from "@material-ui/icons/ArrowUpward"; import ArrowDownwardIcon from "@material-ui/icons/ArrowDownward"; import RemoveIcon from "@material-ui/icons/Remove"; -import { IconButtonProps } from "@rjsf/utils"; +import { + FormContextType, + IconButtonProps, + RJSFSchema, + StrictRJSFSchema, +} from "@rjsf/utils"; -export default function MuiIconButton(props: IconButtonProps) { +export default function MuiIconButton< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>(props: IconButtonProps) { const { icon, color, uiSchema, registry, ...otherProps } = props; return ( (props: IconButtonProps) { return ( (props: IconButtonProps) { return ( (props: IconButtonProps) { const { iconType, ...otherProps } = props; return ( = withTheme(Theme); +export function generateForm< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>(): ComponentType> { + return withTheme(generateTheme()); +} -export default MuiForm; +export default generateForm(); diff --git a/packages/material-ui/src/ObjectFieldTemplate/ObjectFieldTemplate.tsx b/packages/material-ui/src/ObjectFieldTemplate/ObjectFieldTemplate.tsx index 24924b964d..efd0bbe66d 100644 --- a/packages/material-ui/src/ObjectFieldTemplate/ObjectFieldTemplate.tsx +++ b/packages/material-ui/src/ObjectFieldTemplate/ObjectFieldTemplate.tsx @@ -1,37 +1,52 @@ import React from "react"; import Grid from "@material-ui/core/Grid"; import { + FormContextType, ObjectFieldTemplateProps, + RJSFSchema, + StrictRJSFSchema, canExpand, getTemplate, getUiOptions, } from "@rjsf/utils"; -const ObjectFieldTemplate = ({ - description, - title, - properties, - required, - disabled, - readonly, - uiSchema, - idSchema, - schema, - formData, - onAddClick, - registry, -}: ObjectFieldTemplateProps) => { - const uiOptions = getUiOptions(uiSchema); - const TitleFieldTemplate = getTemplate<"TitleFieldTemplate">( +/** The `ObjectFieldTemplate` is the template to use to render all the inner properties of an object along with the + * title and description if available. If the object is expandable, then an `AddButton` is also rendered after all + * the properties. + * + * @param props - The `ObjectFieldTemplateProps` for this component + */ +export default function ObjectFieldTemplate< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>(props: ObjectFieldTemplateProps) { + const { + description, + title, + properties, + required, + disabled, + readonly, + uiSchema, + idSchema, + schema, + formData, + onAddClick, + registry, + } = props; + const uiOptions = getUiOptions(uiSchema); + const TitleFieldTemplate = getTemplate<"TitleFieldTemplate", T, S, F>( "TitleFieldTemplate", registry, uiOptions ); - const DescriptionFieldTemplate = getTemplate<"DescriptionFieldTemplate">( + const DescriptionFieldTemplate = getTemplate< "DescriptionFieldTemplate", - registry, - uiOptions - ); + T, + S, + F + >("DescriptionFieldTemplate", registry, uiOptions); // Button templates are not overridden in the uiSchema const { ButtonTemplates: { AddButton }, @@ -74,7 +89,7 @@ const ObjectFieldTemplate = ({
) )} - {canExpand(schema, uiSchema, formData) && ( + {canExpand(schema, uiSchema, formData) && ( ); -}; - -export default ObjectFieldTemplate; +} diff --git a/packages/material-ui/src/RadioWidget/RadioWidget.tsx b/packages/material-ui/src/RadioWidget/RadioWidget.tsx index 7977f35a99..8ff86aad9e 100644 --- a/packages/material-ui/src/RadioWidget/RadioWidget.tsx +++ b/packages/material-ui/src/RadioWidget/RadioWidget.tsx @@ -3,9 +3,23 @@ import FormControlLabel from "@material-ui/core/FormControlLabel"; import FormLabel from "@material-ui/core/FormLabel"; import Radio from "@material-ui/core/Radio"; import RadioGroup from "@material-ui/core/RadioGroup"; -import { WidgetProps } from "@rjsf/utils"; +import { + FormContextType, + RJSFSchema, + StrictRJSFSchema, + WidgetProps, +} from "@rjsf/utils"; -const RadioWidget = ({ +/** The `RadioWidget` is a widget for rendering a radio group. + * It is typically used with a string property constrained with enum options. + * + * @param props - The `WidgetProps` for this component + */ +export default function RadioWidget< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>({ id, schema, options, @@ -17,7 +31,7 @@ const RadioWidget = ({ onChange, onBlur, onFocus, -}: WidgetProps) => { +}: WidgetProps) { const { enumOptions, enumDisabled } = options; const _onChange = (_: any, value: any) => @@ -70,6 +84,4 @@ const RadioWidget = ({ ); -}; - -export default RadioWidget; +} diff --git a/packages/material-ui/src/RangeWidget/RangeWidget.tsx b/packages/material-ui/src/RangeWidget/RangeWidget.tsx index 4455d88a7b..32e37ec9a5 100644 --- a/packages/material-ui/src/RangeWidget/RangeWidget.tsx +++ b/packages/material-ui/src/RangeWidget/RangeWidget.tsx @@ -1,22 +1,38 @@ import React from "react"; import FormLabel from "@material-ui/core/FormLabel"; import Slider from "@material-ui/core/Slider"; -import { WidgetProps, rangeSpec } from "@rjsf/utils"; +import { + FormContextType, + RJSFSchema, + StrictRJSFSchema, + WidgetProps, + rangeSpec, +} from "@rjsf/utils"; -const RangeWidget = ({ - value, - readonly, - disabled, - onBlur, - onFocus, - options, - schema, - onChange, - required, - label, - id, -}: WidgetProps) => { - const sliderProps = { value, label, id, name: id, ...rangeSpec(schema) }; +/** The `RangeWidget` component uses the `BaseInputTemplate` changing the type to `range` and wrapping the result + * in a div, with the value along side it. + * + * @param props - The `WidgetProps` for this component + */ +export default function RangeWidget< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>(props: WidgetProps) { + const { + value, + readonly, + disabled, + onBlur, + onFocus, + options, + schema, + onChange, + required, + label, + id, + } = props; + const sliderProps = { value, label, id, name: id, ...rangeSpec(schema) }; const _onChange = (_: any, value?: number | number[]) => { onChange(value ? value : options.emptyValue); @@ -42,6 +58,4 @@ const RangeWidget = ({ /> ); -}; - -export default RangeWidget; +} diff --git a/packages/material-ui/src/SelectWidget/SelectWidget.tsx b/packages/material-ui/src/SelectWidget/SelectWidget.tsx index 7e39466e2a..de29063844 100644 --- a/packages/material-ui/src/SelectWidget/SelectWidget.tsx +++ b/packages/material-ui/src/SelectWidget/SelectWidget.tsx @@ -1,9 +1,24 @@ import React from "react"; import MenuItem from "@material-ui/core/MenuItem"; import TextField from "@material-ui/core/TextField"; -import { WidgetProps, processSelectValue } from "@rjsf/utils"; +import { + processSelectValue, + FormContextType, + RJSFSchema, + StrictRJSFSchema, + WidgetProps, +} from "@rjsf/utils"; -const SelectWidget = ({ +/** The `SelectWidget` is a widget for rendering dropdowns. + * It is typically used with string properties constrained with enum options. + * + * @param props - The `WidgetProps` for this component + */ +export default function SelectWidget< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>({ schema, id, options, @@ -18,7 +33,7 @@ const SelectWidget = ({ onBlur, onFocus, rawErrors = [], -}: WidgetProps) => { +}: WidgetProps) { const { enumOptions, enumDisabled } = options; const emptyValue = multiple ? [] : ""; @@ -26,13 +41,13 @@ const SelectWidget = ({ const _onChange = ({ target: { value }, }: React.ChangeEvent<{ name?: string; value: unknown }>) => - onChange(processSelectValue(schema, value, options)); + onChange(processSelectValue(schema, value, options)); const _onBlur = ({ target: { value } }: React.FocusEvent) => - onBlur(id, processSelectValue(schema, value, options)); + onBlur(id, processSelectValue(schema, value, options)); const _onFocus = ({ target: { value }, }: React.FocusEvent) => - onFocus(id, processSelectValue(schema, value, options)); + onFocus(id, processSelectValue(schema, value, options)); return ( ); -}; - -export default SelectWidget; +} diff --git a/packages/material-ui/src/SubmitButton/SubmitButton.tsx b/packages/material-ui/src/SubmitButton/SubmitButton.tsx index 4ad4f11616..231150d231 100644 --- a/packages/material-ui/src/SubmitButton/SubmitButton.tsx +++ b/packages/material-ui/src/SubmitButton/SubmitButton.tsx @@ -1,14 +1,26 @@ import React from "react"; import Box from "@material-ui/core/Box"; import Button from "@material-ui/core/Button"; -import { SubmitButtonProps, getSubmitButtonOptions } from "@rjsf/utils"; +import { + getSubmitButtonOptions, + FormContextType, + RJSFSchema, + StrictRJSFSchema, + SubmitButtonProps, +} from "@rjsf/utils"; -const SubmitButton: React.ComponentType = (props) => { +/** The `SubmitButton` renders a button that represent the `Submit` action on a form + */ +export default function SubmitButton< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>({ uiSchema }: SubmitButtonProps) { const { submitText, norender, - props: submitButtonProps, - } = getSubmitButtonOptions(props.uiSchema); + props: submitButtonProps = {}, + } = getSubmitButtonOptions(uiSchema); if (norender) { return null; } @@ -24,6 +36,4 @@ const SubmitButton: React.ComponentType = (props) => { ); -}; - -export default SubmitButton; +} diff --git a/packages/material-ui/src/Templates/Templates.ts b/packages/material-ui/src/Templates/Templates.ts index ce8c6020ea..357980042a 100644 --- a/packages/material-ui/src/Templates/Templates.ts +++ b/packages/material-ui/src/Templates/Templates.ts @@ -1,3 +1,10 @@ +import { + FormContextType, + RJSFSchema, + StrictRJSFSchema, + TemplatesType, +} from "@rjsf/utils"; + import AddButton from "../AddButton"; import ArrayFieldItemTemplate from "../ArrayFieldItemTemplate"; import ArrayFieldTemplate from "../ArrayFieldTemplate"; @@ -13,23 +20,31 @@ import SubmitButton from "../SubmitButton"; import TitleField from "../TitleField"; import WrapIfAdditionalTemplate from "../WrapIfAdditionalTemplate"; -export default { - ArrayFieldItemTemplate, - ArrayFieldTemplate, - BaseInputTemplate, - ButtonTemplates: { - AddButton, - MoveDownButton, - MoveUpButton, - RemoveButton, - SubmitButton, - }, - DescriptionFieldTemplate: DescriptionField, - ErrorListTemplate: ErrorList, - FieldErrorTemplate, - FieldHelpTemplate, - FieldTemplate, - ObjectFieldTemplate, - TitleFieldTemplate: TitleField, - WrapIfAdditionalTemplate, -}; +export function generateTemplates< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>(): Partial> { + return { + ArrayFieldItemTemplate, + ArrayFieldTemplate, + BaseInputTemplate, + ButtonTemplates: { + AddButton, + MoveDownButton, + MoveUpButton, + RemoveButton, + SubmitButton, + }, + DescriptionFieldTemplate: DescriptionField, + ErrorListTemplate: ErrorList, + FieldErrorTemplate, + FieldHelpTemplate, + FieldTemplate, + ObjectFieldTemplate, + TitleFieldTemplate: TitleField, + WrapIfAdditionalTemplate, + }; +} + +export default generateTemplates(); diff --git a/packages/material-ui/src/TextareaWidget/TextareaWidget.tsx b/packages/material-ui/src/TextareaWidget/TextareaWidget.tsx index 96865fac9f..a5486401e4 100644 --- a/packages/material-ui/src/TextareaWidget/TextareaWidget.tsx +++ b/packages/material-ui/src/TextareaWidget/TextareaWidget.tsx @@ -1,9 +1,23 @@ import React from "react"; -import { getTemplate, WidgetProps } from "@rjsf/utils"; +import { + FormContextType, + RJSFSchema, + StrictRJSFSchema, + WidgetProps, + getTemplate, +} from "@rjsf/utils"; -const TextareaWidget = (props: WidgetProps) => { +/** The `TextareaWidget` is a widget for rendering input fields as textarea. + * + * @param props - The `WidgetProps` for this component + */ +export default function TextareaWidget< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>(props: WidgetProps) { const { options, registry } = props; - const BaseInputTemplate = getTemplate<"BaseInputTemplate">( + const BaseInputTemplate = getTemplate<"BaseInputTemplate", T, S, F>( "BaseInputTemplate", registry, options @@ -15,6 +29,4 @@ const TextareaWidget = (props: WidgetProps) => { } return ; -}; - -export default TextareaWidget; +} diff --git a/packages/material-ui/src/Theme/Theme.tsx b/packages/material-ui/src/Theme/Theme.tsx index ecfcdd5720..1c50874ba2 100644 --- a/packages/material-ui/src/Theme/Theme.tsx +++ b/packages/material-ui/src/Theme/Theme.tsx @@ -1,11 +1,18 @@ +import { FormContextType, RJSFSchema, StrictRJSFSchema } from "@rjsf/utils"; import { ThemeProps } from "@rjsf/core"; -import Templates from "../Templates"; -import Widgets from "../Widgets"; +import { generateTemplates } from "../Templates"; +import { generateWidgets } from "../Widgets"; -const Theme: ThemeProps = { - templates: Templates, - widgets: Widgets, -}; +export function generateTheme< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>(): ThemeProps { + return { + templates: generateTemplates(), + widgets: generateWidgets(), + }; +} -export default Theme; +export default generateTheme(); diff --git a/packages/material-ui/src/TitleField/TitleField.tsx b/packages/material-ui/src/TitleField/TitleField.tsx index b24f4ea47d..622376ad86 100644 --- a/packages/material-ui/src/TitleField/TitleField.tsx +++ b/packages/material-ui/src/TitleField/TitleField.tsx @@ -2,15 +2,26 @@ import React from "react"; import Box from "@material-ui/core/Box"; import Divider from "@material-ui/core/Divider"; import Typography from "@material-ui/core/Typography"; -import { TitleFieldProps } from "@rjsf/utils"; +import { + FormContextType, + TitleFieldProps, + RJSFSchema, + StrictRJSFSchema, +} from "@rjsf/utils"; -const TitleField = ({ id, title }: TitleFieldProps) => { +/** The `TitleField` is the template to use to render the title of a field + * + * @param props - The `TitleFieldProps` for this component + */ +export default function TitleField< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>({ id, title }: TitleFieldProps) { return ( {title} ); -}; - -export default TitleField; +} diff --git a/packages/material-ui/src/Widgets/Widgets.ts b/packages/material-ui/src/Widgets/Widgets.ts index 24ecef0178..58456c00cc 100644 --- a/packages/material-ui/src/Widgets/Widgets.ts +++ b/packages/material-ui/src/Widgets/Widgets.ts @@ -1,3 +1,10 @@ +import { + FormContextType, + RegistryWidgetsType, + RJSFSchema, + StrictRJSFSchema, +} from "@rjsf/utils"; + import CheckboxWidget from "../CheckboxWidget/CheckboxWidget"; import CheckboxesWidget from "../CheckboxesWidget/CheckboxesWidget"; import DateWidget from "../DateWidget/DateWidget"; @@ -7,13 +14,21 @@ import RangeWidget from "../RangeWidget/RangeWidget"; import SelectWidget from "../SelectWidget/SelectWidget"; import TextareaWidget from "../TextareaWidget/TextareaWidget"; -export default { - CheckboxWidget, - CheckboxesWidget, - DateWidget, - DateTimeWidget, - RadioWidget, - RangeWidget, - SelectWidget, - TextareaWidget, -}; +export function generateWidgets< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>(): RegistryWidgetsType { + return { + CheckboxWidget, + CheckboxesWidget, + DateWidget, + DateTimeWidget, + RadioWidget, + RangeWidget, + SelectWidget, + TextareaWidget, + }; +} + +export default generateWidgets(); diff --git a/packages/material-ui/src/WrapIfAdditionalTemplate/WrapIfAdditionalTemplate.tsx b/packages/material-ui/src/WrapIfAdditionalTemplate/WrapIfAdditionalTemplate.tsx index b063e29c57..47bc64464f 100644 --- a/packages/material-ui/src/WrapIfAdditionalTemplate/WrapIfAdditionalTemplate.tsx +++ b/packages/material-ui/src/WrapIfAdditionalTemplate/WrapIfAdditionalTemplate.tsx @@ -5,23 +5,36 @@ import InputLabel from "@material-ui/core/InputLabel"; import Input from "@material-ui/core/Input"; import { ADDITIONAL_PROPERTY_FLAG, + FormContextType, + RJSFSchema, + StrictRJSFSchema, WrapIfAdditionalTemplateProps, } from "@rjsf/utils"; -const WrapIfAdditionalTemplate = ({ - children, - classNames, - disabled, - id, - label, - onDropPropertyClick, - onKeyChange, - readonly, - required, - schema, - uiSchema, - registry, -}: WrapIfAdditionalTemplateProps) => { +/** The `WrapIfAdditional` component is used by the `FieldTemplate` to rename, or remove properties that are + * part of an `additionalProperties` part of a schema. + * + * @param props - The `WrapIfAdditionalProps` for this component + */ +export default function WrapIfAdditionalTemplate< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>(props: WrapIfAdditionalTemplateProps) { + const { + children, + classNames, + disabled, + id, + label, + onDropPropertyClick, + onKeyChange, + readonly, + required, + schema, + uiSchema, + registry, + } = props; // Button templates are not overridden in the uiSchema const { RemoveButton } = registry.templates.ButtonTemplates; const keyLabel = `${label} Key`; // i18n ? @@ -76,6 +89,4 @@ const WrapIfAdditionalTemplate = ({ ); -}; - -export default WrapIfAdditionalTemplate; +} diff --git a/packages/material-ui/src/index.ts b/packages/material-ui/src/index.ts index 710586d10e..bcd29442d3 100644 --- a/packages/material-ui/src/index.ts +++ b/packages/material-ui/src/index.ts @@ -1,8 +1,8 @@ import MuiForm from "./MuiForm/MuiForm"; -export { default as Form } from "./MuiForm"; -export { default as Templates } from "./Templates"; -export { default as Theme } from "./Theme"; -export { default as Widgets } from "./Widgets"; +export { default as Form, generateForm } from "./MuiForm"; +export { default as Templates, generateTemplates } from "./Templates"; +export { default as Theme, generateTheme } from "./Theme"; +export { default as Widgets, generateWidgets } from "./Widgets"; export default MuiForm;