diff --git a/CHANGELOG.md b/CHANGELOG.md index ae754fa684..926f84043e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ should change the heading of the (upcoming) version to include a major version b ## @rjsf/antd - 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/bootstrap-4 - 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/antd/src/index.ts b/packages/antd/src/index.ts index 1fc501f1c7..f5e5953104 100644 --- a/packages/antd/src/index.ts +++ b/packages/antd/src/index.ts @@ -1,15 +1,33 @@ +import { ComponentType } from "react"; +import { FormContextType, RJSFSchema, StrictRJSFSchema } from "@rjsf/utils"; import { FormProps, ThemeProps, withTheme } from "@rjsf/core"; -import Templates from "./templates"; -import Widgets from "./widgets"; +import Templates, { generateTemplates } from "./templates"; +import Widgets, { generateWidgets } from "./widgets"; -export { Templates, Widgets }; +export function generateTheme< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>(): ThemeProps { + return { + templates: generateTemplates(), + widgets: generateWidgets(), + }; +} -export const Theme: ThemeProps = { - templates: Templates, - widgets: Widgets, -}; +const Theme = generateTheme(); -export const Form: React.ComponentType = withTheme(Theme); +export function generateForm< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>(): ComponentType> { + return withTheme(generateTheme()); +} + +const Form = generateForm(); + +export { Form, Templates, Theme, Widgets, generateTemplates, generateWidgets }; export default Form; diff --git a/packages/antd/src/templates/ArrayFieldItemTemplate/index.tsx b/packages/antd/src/templates/ArrayFieldItemTemplate/index.tsx index 0f5dbe4aec..7f920acacd 100644 --- a/packages/antd/src/templates/ArrayFieldItemTemplate/index.tsx +++ b/packages/antd/src/templates/ArrayFieldItemTemplate/index.tsx @@ -1,8 +1,13 @@ import React from "react"; -import { ArrayFieldTemplateItemType } from "@rjsf/utils"; import Button from "antd/lib/button"; import Col from "antd/lib/col"; import Row from "antd/lib/row"; +import { + ArrayFieldTemplateItemType, + FormContextType, + RJSFSchema, + StrictRJSFSchema, +} from "@rjsf/utils"; const BTN_GRP_STYLE = { width: "100%", @@ -12,20 +17,29 @@ const BTN_STYLE = { width: "calc(100% / 3)", }; -const ArrayFieldItemTemplate = ({ - children, - disabled, - hasMoveDown, - hasMoveUp, - hasRemove, - hasToolbar, - index, - onDropIndexClick, - onReorderClick, - readonly, - registry, - uiSchema, -}: 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, + hasMoveDown, + hasMoveUp, + hasRemove, + hasToolbar, + index, + onDropIndexClick, + onReorderClick, + readonly, + registry, + uiSchema, + } = props; const { MoveDownButton, MoveUpButton, RemoveButton } = registry.templates.ButtonTemplates; const { rowGutter = 24, toolbarAlign = "top" } = registry.formContext; @@ -69,6 +83,4 @@ const ArrayFieldItemTemplate = ({ )} ); -}; - -export default ArrayFieldItemTemplate; +} diff --git a/packages/antd/src/templates/ArrayFieldTemplate/index.tsx b/packages/antd/src/templates/ArrayFieldTemplate/index.tsx index 39793e20fa..18f77ecbb7 100644 --- a/packages/antd/src/templates/ArrayFieldTemplate/index.tsx +++ b/packages/antd/src/templates/ArrayFieldTemplate/index.tsx @@ -3,119 +3,144 @@ import { getTemplate, getUiOptions, ArrayFieldTemplateProps, + ArrayFieldTemplateItemType, + FormContextType, + GenericObjectType, + RJSFSchema, + StrictRJSFSchema, } from "@rjsf/utils"; import classNames from "classnames"; import Col from "antd/lib/col"; import Row from "antd/lib/row"; -import { withConfigConsumer } from "antd/lib/config-provider/context"; +import { + ConfigConsumer, + ConfigConsumerProps, +} from "antd/lib/config-provider/context"; const DESCRIPTION_COL_STYLE = { paddingBottom: "8px", }; -// Add in the `prefixCls` element needed by the `withConfigConsumer` HOC -export type AntdArrayFieldTemplateProps = ArrayFieldTemplateProps & { - prefixCls: string; -}; - -const ArrayFieldTemplate = ({ - canAdd, - className, - disabled, - formContext, - idSchema, - items, - onAddClick, - prefixCls, - readonly, - registry, - required, - schema, - title, - uiSchema, -}: AntdArrayFieldTemplateProps) => { - const uiOptions = getUiOptions(uiSchema); - const ArrayFieldDescriptionTemplate = getTemplate( - "ArrayFieldDescriptionTemplate", +/** 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, + className, + disabled, + formContext, + idSchema, + items, + onAddClick, + readonly, registry, - uiOptions - ); - const ArrayFieldItemTemplate = getTemplate<"ArrayFieldItemTemplate">( + required, + schema, + title, + uiSchema, + } = props; + const uiOptions = getUiOptions(uiSchema); + 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 }, } = registry.templates; - const { labelAlign = "right", rowGutter = 24 } = formContext; - - const labelClsBasic = `${prefixCls}-item-label`; - const labelColClassName = classNames( - labelClsBasic, - labelAlign === "left" && `${labelClsBasic}-left` - // labelCol.className, - ); + const { labelAlign = "right", rowGutter = 24 } = + formContext as GenericObjectType; return ( -
- - {(uiOptions.title || title) && ( - - - - )} - {(uiOptions.description || schema.description) && ( - - - - )} - - {items && - items.map(({ key, ...itemProps }) => ( - - ))} - + + {(configProps: ConfigConsumerProps) => { + const { getPrefixCls } = configProps; + const prefixCls = getPrefixCls("form"); + const labelClsBasic = `${prefixCls}-item-label`; + const labelColClassName = classNames( + labelClsBasic, + labelAlign === "left" && `${labelClsBasic}-left` + // labelCol.className, + ); - {canAdd && ( - - - - + return ( +
+ + {(uiOptions.title || title) && ( + + + + )} + {(uiOptions.description || schema.description) && ( + + + + )} + + {items && + items.map( + ({ + key, + ...itemProps + }: ArrayFieldTemplateItemType) => ( + + ) + )} + + {canAdd && ( + + + + + + + + )} - - )} - -
+
+ ); + }} + ); -}; - -export default withConfigConsumer({ - prefixCls: "form", -})(ArrayFieldTemplate); +} diff --git a/packages/antd/src/templates/BaseInputTemplate/index.tsx b/packages/antd/src/templates/BaseInputTemplate/index.tsx index a2f5453236..b4a686525c 100644 --- a/packages/antd/src/templates/BaseInputTemplate/index.tsx +++ b/packages/antd/src/templates/BaseInputTemplate/index.tsx @@ -1,28 +1,46 @@ import React from "react"; -import { getInputProps, WidgetProps } from "@rjsf/utils"; import Input from "antd/lib/input"; import InputNumber from "antd/lib/input-number"; +import { + getInputProps, + FormContextType, + RJSFSchema, + StrictRJSFSchema, + WidgetProps, + GenericObjectType, +} from "@rjsf/utils"; const INPUT_STYLE = { width: "100%", }; -const BaseInputTemplate = ({ - disabled, - formContext, - id, - onBlur, - onChange, - onFocus, - options, - placeholder, - readonly, - schema, - value, - type, -}: WidgetProps) => { - const inputProps = getInputProps(schema, type, options, false); - const { readonlyAsDisabled = true } = formContext; +/** 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 { + disabled, + formContext, + id, + onBlur, + onChange, + onFocus, + options, + placeholder, + readonly, + schema, + value, + type, + } = props; + const inputProps = getInputProps(schema, type, options, false); + const { readonlyAsDisabled = true } = formContext as GenericObjectType; const handleNumberChange = (nextValue: number | null) => onChange(nextValue); @@ -80,6 +98,4 @@ const BaseInputTemplate = ({ )} ); -}; - -export default BaseInputTemplate; +} diff --git a/packages/antd/src/templates/DescriptionField/index.tsx b/packages/antd/src/templates/DescriptionField/index.tsx index 809adf3bea..601c4981f8 100644 --- a/packages/antd/src/templates/DescriptionField/index.tsx +++ b/packages/antd/src/templates/DescriptionField/index.tsx @@ -1,11 +1,23 @@ import React from "react"; -import { DescriptionFieldProps } from "@rjsf/utils"; +import { + DescriptionFieldProps, + FormContextType, + RJSFSchema, + StrictRJSFSchema, +} from "@rjsf/utils"; -const DescriptionField = ({ description, id }: 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 null; } return {description}; -}; - -export default DescriptionField; +} diff --git a/packages/antd/src/templates/ErrorList/index.tsx b/packages/antd/src/templates/ErrorList/index.tsx index 5cd62dc686..27d19c1334 100644 --- a/packages/antd/src/templates/ErrorList/index.tsx +++ b/packages/antd/src/templates/ErrorList/index.tsx @@ -1,12 +1,24 @@ import React from "react"; -import { ErrorListProps } from "@rjsf/utils"; - import Alert from "antd/lib/alert"; import List from "antd/lib/list"; import Space from "antd/lib/space"; import ExclamationCircleOutlined from "@ant-design/icons/ExclamationCircleOutlined"; +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) { const renderErrors = () => ( {errors.map((error, index) => ( @@ -28,6 +40,4 @@ const ErrorList = ({ errors }: ErrorListProps) => { type="error" /> ); -}; - -export default ErrorList; +} diff --git a/packages/antd/src/templates/FieldErrorTemplate/index.tsx b/packages/antd/src/templates/FieldErrorTemplate/index.tsx index e0854a7e26..a9d4904ea3 100644 --- a/packages/antd/src/templates/FieldErrorTemplate/index.tsx +++ b/packages/antd/src/templates/FieldErrorTemplate/index.tsx @@ -1,11 +1,20 @@ import React from "react"; -import { FieldErrorProps } from "@rjsf/utils"; +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/antd/src/templates/FieldTemplate/index.tsx b/packages/antd/src/templates/FieldTemplate/index.tsx index 3d2d98fb92..b672734784 100644 --- a/packages/antd/src/templates/FieldTemplate/index.tsx +++ b/packages/antd/src/templates/FieldTemplate/index.tsx @@ -1,48 +1,65 @@ import React from "react"; -import { FieldTemplateProps } from "@rjsf/utils"; import Form from "antd/lib/form"; - -import { getUiOptions, getTemplate } from "@rjsf/utils"; +import { + FieldTemplateProps, + FormContextType, + RJSFSchema, + StrictRJSFSchema, + getTemplate, + getUiOptions, + GenericObjectType, +} from "@rjsf/utils"; const VERTICAL_LABEL_COL = { span: 24 }; const VERTICAL_WRAPPER_COL = { span: 24 }; -const FieldTemplate = ({ - children, - classNames, - description, - disabled, - displayLabel, - errors, - formContext, - help, - hidden, - id, - label, - onDropPropertyClick, - onKeyChange, - rawErrors, - rawDescription, - rawHelp, - readonly, - registry, - required, - schema, - uiSchema, -}: FieldTemplateProps) => { +/** 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 { + children, + classNames, + description, + disabled, + displayLabel, + errors, + formContext, + help, + hidden, + id, + label, + onDropPropertyClick, + onKeyChange, + rawErrors, + rawDescription, + rawHelp, + readonly, + registry, + required, + schema, + uiSchema, + } = props; const { colon, labelCol = VERTICAL_LABEL_COL, wrapperCol = VERTICAL_WRAPPER_COL, wrapperStyle, - } = formContext; + } = formContext as GenericObjectType; - const uiOptions = getUiOptions(uiSchema); - const WrapIfAdditionalTemplate = getTemplate<"WrapIfAdditionalTemplate">( + const uiOptions = getUiOptions(uiSchema); + const WrapIfAdditionalTemplate = getTemplate< "WrapIfAdditionalTemplate", - registry, - uiOptions - ); + T, + S, + F + >("WrapIfAdditionalTemplate", registry, uiOptions); if (hidden) { return
{children}
; @@ -83,6 +100,4 @@ const FieldTemplate = ({ )} ); -}; - -export default FieldTemplate; +} diff --git a/packages/antd/src/templates/IconButton/index.tsx b/packages/antd/src/templates/IconButton/index.tsx index ec3d8a58b3..e3343ea642 100644 --- a/packages/antd/src/templates/IconButton/index.tsx +++ b/packages/antd/src/templates/IconButton/index.tsx @@ -1,15 +1,29 @@ import React from "react"; -import { IconButtonProps, getUiOptions } from "@rjsf/utils"; import Button, { ButtonProps, ButtonType } from "antd/lib/button"; import ArrowDownOutlined from "@ant-design/icons/ArrowDownOutlined"; import ArrowUpOutlined from "@ant-design/icons/ArrowUpOutlined"; import DeleteOutlined from "@ant-design/icons/DeleteOutlined"; import PlusCircleOutlined from "@ant-design/icons/PlusCircleOutlined"; +import { + getUiOptions, + FormContextType, + IconButtonProps, + RJSFSchema, + StrictRJSFSchema, +} from "@rjsf/utils"; // The `type` for IconButtonProps collides with the `type` for `ButtonProps` so omit it to avoid Typescript issue -export type AntdIconButtonProps = Omit; +export type AntdIconButtonProps< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +> = Omit, "type">; -export default function IconButton(props: AntdIconButtonProps & ButtonProps) { +export default function IconButton< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>(props: AntdIconButtonProps & ButtonProps) { const { iconType = "default", icon, @@ -20,7 +34,11 @@ export default function IconButton(props: AntdIconButtonProps & ButtonProps) { return ); -}; +} diff --git a/packages/antd/src/templates/TitleField/index.tsx b/packages/antd/src/templates/TitleField/index.tsx index 0891ca611e..1cae16ea46 100644 --- a/packages/antd/src/templates/TitleField/index.tsx +++ b/packages/antd/src/templates/TitleField/index.tsx @@ -1,35 +1,33 @@ import React from "react"; import classNames from "classnames"; -import { TitleFieldProps } from "@rjsf/utils"; -import { withConfigConsumer } from "antd/lib/config-provider/context"; +import { + ConfigConsumer, + ConfigConsumerProps, +} from "antd/lib/config-provider/context"; +import { + FormContextType, + TitleFieldProps, + RJSFSchema, + StrictRJSFSchema, +} from "@rjsf/utils"; -// Add in the `prefixCls` element needed by the `withConfigConsumer` HOC -export type AntdTitleFieldProps = TitleFieldProps & { - prefixCls: string; - formContext: object; -}; - -const TitleField = ({ - id, - prefixCls, - required, - registry, - formContext: formContext1, - title, -}: AntdTitleFieldProps) => { +/** 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, required, registry, title }: TitleFieldProps) { const { formContext } = registry; - const { colon = true } = { ...formContext1, ...formContext }; + const { colon = true } = formContext; let labelChildren = title; if (colon && typeof title === "string" && title.trim() !== "") { labelChildren = title.replace(/[::]\s*$/, ""); } - const labelClassName = classNames({ - [`${prefixCls}-item-required`]: required, - [`${prefixCls}-item-no-colon`]: !colon, - }); - const handleLabelClick = () => { if (!id) { return; @@ -44,21 +42,26 @@ const TitleField = ({ }; return title ? ( - - ) : null; -}; + + {(configProps: ConfigConsumerProps) => { + const { getPrefixCls } = configProps; + const prefixCls = getPrefixCls("form"); + const labelClassName = classNames({ + [`${prefixCls}-item-required`]: required, + [`${prefixCls}-item-no-colon`]: !colon, + }); -TitleField.defaultProps = { - formContext: {}, -}; - -export default withConfigConsumer({ prefixCls: "form" })( - TitleField -); + return ( + + ); + }} + + ) : null; +} diff --git a/packages/antd/src/templates/WrapIfAdditionalTemplate/index.tsx b/packages/antd/src/templates/WrapIfAdditionalTemplate/index.tsx index 8fe2dee0fe..fc1e92f7d3 100644 --- a/packages/antd/src/templates/WrapIfAdditionalTemplate/index.tsx +++ b/packages/antd/src/templates/WrapIfAdditionalTemplate/index.tsx @@ -1,13 +1,16 @@ import React from "react"; +import Col from "antd/lib/col"; +import Form from "antd/lib/form"; +import Input from "antd/lib/input"; +import Row from "antd/lib/row"; import { ADDITIONAL_PROPERTY_FLAG, UI_OPTIONS_KEY, + FormContextType, + RJSFSchema, + StrictRJSFSchema, WrapIfAdditionalTemplateProps, } from "@rjsf/utils"; -import Col from "antd/lib/col"; -import Form from "antd/lib/form"; -import Input from "antd/lib/input"; -import Row from "antd/lib/row"; const VERTICAL_LABEL_COL = { span: 24 }; const VERTICAL_WRAPPER_COL = { span: 24 }; @@ -16,20 +19,30 @@ const INPUT_STYLE = { width: "100%", }; -const WrapIfAdditionalTemplate = ({ - children, - classNames, - disabled, - id, - label, - onDropPropertyClick, - onKeyChange, - readonly, - required, - registry, - schema, - uiSchema, -}: 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, + registry, + schema, + uiSchema, + } = props; const { colon, labelCol = VERTICAL_LABEL_COL, @@ -103,6 +116,4 @@ const WrapIfAdditionalTemplate = ({ ); -}; - -export default WrapIfAdditionalTemplate; +} diff --git a/packages/antd/src/templates/index.ts b/packages/antd/src/templates/index.ts index 09297916d1..506f7d7b37 100644 --- a/packages/antd/src/templates/index.ts +++ b/packages/antd/src/templates/index.ts @@ -1,3 +1,10 @@ +import { + FormContextType, + RJSFSchema, + StrictRJSFSchema, + TemplatesType, +} from "@rjsf/utils"; + import ArrayFieldItemTemplate from "./ArrayFieldItemTemplate"; import ArrayFieldTemplate from "./ArrayFieldTemplate"; import BaseInputTemplate from "./BaseInputTemplate"; @@ -15,27 +22,31 @@ import ObjectFieldTemplate from "./ObjectFieldTemplate"; import SubmitButton from "./SubmitButton"; import TitleField from "./TitleField"; import WrapIfAdditionalTemplate from "./WrapIfAdditionalTemplate"; -import { TemplatesType } from "@rjsf/utils"; -const Index: Partial = { - ArrayFieldItemTemplate, - ArrayFieldTemplate: ArrayFieldTemplate as TemplatesType["ArrayFieldTemplate"], - BaseInputTemplate, - ButtonTemplates: { - AddButton, - MoveDownButton, - MoveUpButton, - RemoveButton, - SubmitButton, - }, - DescriptionFieldTemplate: DescriptionField, - ErrorListTemplate: ErrorList, - FieldErrorTemplate, - FieldTemplate, - ObjectFieldTemplate: - ObjectFieldTemplate as TemplatesType["ObjectFieldTemplate"], - TitleFieldTemplate: TitleField as TemplatesType["TitleFieldTemplate"], - 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, + FieldTemplate, + ObjectFieldTemplate, + TitleFieldTemplate: TitleField, + WrapIfAdditionalTemplate, + }; +} -export default Index; +export default generateTemplates(); diff --git a/packages/antd/src/widgets/AltDateTimeWidget/index.tsx b/packages/antd/src/widgets/AltDateTimeWidget/index.tsx index 92a0891a2f..0bd07ff403 100644 --- a/packages/antd/src/widgets/AltDateTimeWidget/index.tsx +++ b/packages/antd/src/widgets/AltDateTimeWidget/index.tsx @@ -1,16 +1,23 @@ import React from "react"; -import { WidgetProps } from "@rjsf/utils"; +import { + FormContextType, + RJSFSchema, + StrictRJSFSchema, + WidgetProps, +} from "@rjsf/utils"; import _AltDateWidget from "../AltDateWidget"; -const AltDateTimeWidget = (props: WidgetProps) => { +export default function AltDateTimeWidget< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>(props: WidgetProps) { const { AltDateWidget } = props.registry.widgets; return ; -}; +} AltDateTimeWidget.defaultProps = { ..._AltDateWidget.defaultProps, showTime: true, }; - -export default AltDateTimeWidget; diff --git a/packages/antd/src/widgets/AltDateWidget/index.tsx b/packages/antd/src/widgets/AltDateWidget/index.tsx index 732336c7e2..83b7a6d51f 100644 --- a/packages/antd/src/widgets/AltDateWidget/index.tsx +++ b/packages/antd/src/widgets/AltDateWidget/index.tsx @@ -1,18 +1,25 @@ import React, { useEffect, useState } from "react"; - +import Button from "antd/lib/button"; +import Col from "antd/lib/col"; +import Row from "antd/lib/row"; import { pad, parseDateString, toDateString, DateObject, + FormContextType, + RJSFSchema, + StrictRJSFSchema, WidgetProps, + GenericObjectType, } from "@rjsf/utils"; -import Button from "antd/lib/button"; -import Col from "antd/lib/col"; -import Row from "antd/lib/row"; -type DateElementProps = Pick< - WidgetProps, +type DateElementProps< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +> = Pick< + WidgetProps, | "id" | "name" | "value" @@ -65,22 +72,27 @@ function dateElementProps( return data; } -const AltDateWidget = ({ - autofocus, - disabled, - formContext, - id, - onBlur, - onChange, - onFocus, - options, - readonly, - registry, - showTime, - value, -}: WidgetProps) => { +export default function AltDateWidget< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>(props: WidgetProps) { + const { + autofocus, + disabled, + formContext, + id, + onBlur, + onChange, + onFocus, + options, + readonly, + registry, + showTime, + value, + } = props; const { SelectWidget } = registry.widgets; - const { rowGutter = 24 } = formContext; + const { rowGutter = 24 } = formContext as GenericObjectType; const [state, setState] = useState(parseDateString(value, showTime)); @@ -118,7 +130,7 @@ const AltDateWidget = ({ onChange(undefined); }; - const renderDateElement = (elemProps: DateElementProps) => ( + const renderDateElement = (elemProps: DateElementProps) => ( ); -}; +} AltDateWidget.defaultProps = { autofocus: false, @@ -203,5 +215,3 @@ AltDateWidget.defaultProps = { readonly: false, showTime: false, }; - -export default AltDateWidget; diff --git a/packages/antd/src/widgets/CheckboxWidget/index.tsx b/packages/antd/src/widgets/CheckboxWidget/index.tsx index a9dab4bc64..8a52c8f1d3 100644 --- a/packages/antd/src/widgets/CheckboxWidget/index.tsx +++ b/packages/antd/src/widgets/CheckboxWidget/index.tsx @@ -1,20 +1,36 @@ import React from "react"; -import { WidgetProps } from "@rjsf/utils"; import Checkbox, { CheckboxChangeEvent } from "antd/lib/checkbox"; +import { + FormContextType, + RJSFSchema, + StrictRJSFSchema, + WidgetProps, + GenericObjectType, +} from "@rjsf/utils"; -const CheckboxWidget = ({ - autofocus, - disabled, - formContext, - id, - label, - onBlur, - onChange, - onFocus, - readonly, - value, -}: WidgetProps) => { - const { readonlyAsDisabled = true } = formContext; +/** 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 { + autofocus, + disabled, + formContext, + id, + label, + onBlur, + onChange, + onFocus, + readonly, + value, + } = props; + const { readonlyAsDisabled = true } = formContext as GenericObjectType; const handleChange = ({ target }: CheckboxChangeEvent) => onChange(target.checked); @@ -45,6 +61,4 @@ const CheckboxWidget = ({ {label} ); -}; - -export default CheckboxWidget; +} diff --git a/packages/antd/src/widgets/CheckboxesWidget/index.tsx b/packages/antd/src/widgets/CheckboxesWidget/index.tsx index e12f87635e..925fb901d8 100644 --- a/packages/antd/src/widgets/CheckboxesWidget/index.tsx +++ b/packages/antd/src/widgets/CheckboxesWidget/index.tsx @@ -1,8 +1,23 @@ import React from "react"; -import { WidgetProps } from "@rjsf/utils"; import Checkbox from "antd/lib/checkbox"; +import { + FormContextType, + WidgetProps, + RJSFSchema, + StrictRJSFSchema, + GenericObjectType, +} from "@rjsf/utils"; -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 +>({ autofocus, disabled, formContext, @@ -13,8 +28,8 @@ const CheckboxesWidget = ({ options, readonly, value, -}: WidgetProps) => { - const { readonlyAsDisabled = true } = formContext; +}: WidgetProps) { + const { readonlyAsDisabled = true } = formContext as GenericObjectType; const { enumOptions, enumDisabled, inline } = options; @@ -62,6 +77,4 @@ const CheckboxesWidget = ({ ))} ) : null; -}; - -export default CheckboxesWidget; +} diff --git a/packages/antd/src/widgets/DateTimeWidget/index.tsx b/packages/antd/src/widgets/DateTimeWidget/index.tsx index 85b6b23a78..b1a42743de 100644 --- a/packages/antd/src/widgets/DateTimeWidget/index.tsx +++ b/packages/antd/src/widgets/DateTimeWidget/index.tsx @@ -1,6 +1,12 @@ import React from "react"; import dayjs from "dayjs"; -import { WidgetProps } from "@rjsf/utils"; +import { + FormContextType, + GenericObjectType, + RJSFSchema, + StrictRJSFSchema, + WidgetProps, +} from "@rjsf/utils"; import DatePicker from "../../components/DatePicker"; @@ -8,18 +14,28 @@ const DATE_PICKER_STYLE = { width: "100%", }; -const DateTimeWidget = ({ - disabled, - formContext, - id, - onBlur, - onChange, - onFocus, - placeholder, - readonly, - value, -}: WidgetProps) => { - const { readonlyAsDisabled = true } = formContext; +/** 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 { + disabled, + formContext, + id, + onBlur, + onChange, + onFocus, + placeholder, + readonly, + value, + } = props; + const { readonlyAsDisabled = true } = formContext as GenericObjectType; const handleChange = (nextValue: any) => onChange(nextValue && nextValue.toISOString()); @@ -45,6 +61,4 @@ const DateTimeWidget = ({ value={value && dayjs(value)} /> ); -}; - -export default DateTimeWidget; +} diff --git a/packages/antd/src/widgets/DateWidget/index.tsx b/packages/antd/src/widgets/DateWidget/index.tsx index 9e6833493e..7ad1db1be0 100644 --- a/packages/antd/src/widgets/DateWidget/index.tsx +++ b/packages/antd/src/widgets/DateWidget/index.tsx @@ -1,6 +1,12 @@ import React from "react"; import dayjs from "dayjs"; -import { WidgetProps } from "@rjsf/utils"; +import { + FormContextType, + RJSFSchema, + StrictRJSFSchema, + WidgetProps, + GenericObjectType, +} from "@rjsf/utils"; import DatePicker from "../../components/DatePicker"; @@ -8,18 +14,28 @@ const DATE_PICKER_STYLE = { width: "100%", }; -const DateWidget = ({ - disabled, - formContext, - id, - onBlur, - onChange, - onFocus, - placeholder, - readonly, - value, -}: WidgetProps) => { - const { readonlyAsDisabled = true } = formContext; +/** 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 { + disabled, + formContext, + id, + onBlur, + onChange, + onFocus, + placeholder, + readonly, + value, + } = props; + const { readonlyAsDisabled = true } = formContext as GenericObjectType; const handleChange = (nextValue: any) => onChange(nextValue && nextValue.format("YYYY-MM-DD")); @@ -45,6 +61,4 @@ const DateWidget = ({ value={value && dayjs(value)} /> ); -}; - -export default DateWidget; +} diff --git a/packages/antd/src/widgets/PasswordWidget/index.tsx b/packages/antd/src/widgets/PasswordWidget/index.tsx index 79da9ad323..960f6ea5e7 100644 --- a/packages/antd/src/widgets/PasswordWidget/index.tsx +++ b/packages/antd/src/widgets/PasswordWidget/index.tsx @@ -1,20 +1,35 @@ import React from "react"; -import { WidgetProps } from "@rjsf/utils"; import Input from "antd/lib/input"; +import { + FormContextType, + RJSFSchema, + StrictRJSFSchema, + WidgetProps, + GenericObjectType, +} from "@rjsf/utils"; -const PasswordWidget = ({ - disabled, - formContext, - id, - onBlur, - onChange, - onFocus, - options, - placeholder, - readonly, - value, -}: WidgetProps) => { - const { readonlyAsDisabled = true } = formContext; +/** The `PasswordWidget` component uses the `BaseInputTemplate` changing the type to `password`. + * + * @param props - The `WidgetProps` for this component + */ +export default function PasswordWidget< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>(props: WidgetProps) { + const { + disabled, + formContext, + id, + onBlur, + onChange, + onFocus, + options, + placeholder, + readonly, + value, + } = props; + const { readonlyAsDisabled = true } = formContext as GenericObjectType; const emptyValue = options.emptyValue || ""; @@ -39,6 +54,4 @@ const PasswordWidget = ({ value={value || ""} /> ); -}; - -export default PasswordWidget; +} diff --git a/packages/antd/src/widgets/RadioWidget/index.tsx b/packages/antd/src/widgets/RadioWidget/index.tsx index 3865ec6ed0..638062d6e1 100644 --- a/packages/antd/src/widgets/RadioWidget/index.tsx +++ b/packages/antd/src/widgets/RadioWidget/index.tsx @@ -1,8 +1,23 @@ import React from "react"; -import { WidgetProps } from "@rjsf/utils"; import Radio, { RadioChangeEvent } from "antd/lib/radio"; +import { + FormContextType, + GenericObjectType, + 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 +>({ autofocus, disabled, formContext, @@ -14,8 +29,8 @@ const RadioWidget = ({ readonly, schema, value, -}: WidgetProps) => { - const { readonlyAsDisabled = true } = formContext; +}: WidgetProps) { + const { readonlyAsDisabled = true } = formContext as GenericObjectType; const { enumOptions, enumDisabled } = options; @@ -55,6 +70,4 @@ const RadioWidget = ({ ))} ); -}; - -export default RadioWidget; +} diff --git a/packages/antd/src/widgets/RangeWidget/index.tsx b/packages/antd/src/widgets/RangeWidget/index.tsx index acca3a4460..f3bc8e2aea 100644 --- a/packages/antd/src/widgets/RangeWidget/index.tsx +++ b/packages/antd/src/widgets/RangeWidget/index.tsx @@ -1,22 +1,39 @@ import React from "react"; -import { rangeSpec, WidgetProps } from "@rjsf/utils"; import Slider from "antd/lib/slider"; +import { + rangeSpec, + FormContextType, + RJSFSchema, + StrictRJSFSchema, + WidgetProps, + GenericObjectType, +} from "@rjsf/utils"; -const RangeWidget = ({ - autofocus, - disabled, - formContext, - id, - onBlur, - onChange, - onFocus, - options, - placeholder, - readonly, - schema, - value, -}: WidgetProps) => { - const { readonlyAsDisabled = true } = formContext; +/** 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 { + autofocus, + disabled, + formContext, + id, + onBlur, + onChange, + onFocus, + options, + placeholder, + readonly, + schema, + value, + } = props; + const { readonlyAsDisabled = true } = formContext as GenericObjectType; const { min, max, step } = rangeSpec(schema); @@ -51,6 +68,4 @@ const RangeWidget = ({ {...extraProps} /> ); -}; - -export default RangeWidget; +} diff --git a/packages/antd/src/widgets/SelectWidget/index.tsx b/packages/antd/src/widgets/SelectWidget/index.tsx index 617fd9026e..909a892d60 100644 --- a/packages/antd/src/widgets/SelectWidget/index.tsx +++ b/packages/antd/src/widgets/SelectWidget/index.tsx @@ -1,15 +1,31 @@ import React from "react"; -import { processSelectValue, WidgetProps } from "@rjsf/utils"; import Select from "antd/lib/select"; +import { + processSelectValue, + FormContextType, + GenericObjectType, + RJSFSchema, + StrictRJSFSchema, + WidgetProps, +} from "@rjsf/utils"; const SELECT_STYLE = { width: "100%", }; -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 +>({ autofocus, disabled, - formContext, + formContext = {} as F, id, multiple, onBlur, @@ -20,19 +36,19 @@ const SelectWidget = ({ readonly, schema, value, -}: WidgetProps) => { - const { readonlyAsDisabled = true } = formContext; +}: WidgetProps) { + const { readonlyAsDisabled = true } = formContext as GenericObjectType; const { enumOptions, enumDisabled } = options; const handleChange = (nextValue: any) => - onChange(processSelectValue(schema, nextValue, options)); + onChange(processSelectValue(schema, nextValue, options)); const handleBlur = () => - onBlur(id, processSelectValue(schema, value, options)); + onBlur(id, processSelectValue(schema, value, options)); const handleFocus = () => - onFocus(id, processSelectValue(schema, value, options)); + onFocus(id, processSelectValue(schema, value, options)); const getPopupContainer = (node: any) => node.parentNode; @@ -74,10 +90,4 @@ const SelectWidget = ({ ))} ); -}; - -SelectWidget.defaultProps = { - formContext: {}, -}; - -export default SelectWidget; +} diff --git a/packages/antd/src/widgets/TextareaWidget/index.tsx b/packages/antd/src/widgets/TextareaWidget/index.tsx index b752a77060..b44fa64d3c 100644 --- a/packages/antd/src/widgets/TextareaWidget/index.tsx +++ b/packages/antd/src/widgets/TextareaWidget/index.tsx @@ -1,12 +1,26 @@ import React from "react"; -import { WidgetProps } from "@rjsf/utils"; import Input from "antd/lib/input"; +import { + FormContextType, + GenericObjectType, + RJSFSchema, + StrictRJSFSchema, + WidgetProps, +} from "@rjsf/utils"; const INPUT_STYLE = { width: "100%", }; -const TextareaWidget = ({ +/** 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 +>({ disabled, formContext, id, @@ -17,8 +31,8 @@ const TextareaWidget = ({ placeholder, readonly, value, -}: WidgetProps) => { - const { readonlyAsDisabled = true } = formContext; +}: WidgetProps) { + const { readonlyAsDisabled = true } = formContext as GenericObjectType; const handleChange = ({ target }: React.ChangeEvent) => onChange(target.value === "" ? options.emptyValue : target.value); @@ -50,6 +64,4 @@ const TextareaWidget = ({ {...extraProps} /> ); -}; - -export default TextareaWidget; +} diff --git a/packages/antd/src/widgets/index.ts b/packages/antd/src/widgets/index.ts index 34c927c032..b9cd29276f 100644 --- a/packages/antd/src/widgets/index.ts +++ b/packages/antd/src/widgets/index.ts @@ -1,3 +1,10 @@ +import { + FormContextType, + RegistryWidgetsType, + RJSFSchema, + StrictRJSFSchema, +} from "@rjsf/utils"; + import AltDateTimeWidget from "./AltDateTimeWidget"; import AltDateWidget from "./AltDateWidget"; import CheckboxesWidget from "./CheckboxesWidget"; @@ -10,18 +17,24 @@ import RangeWidget from "./RangeWidget"; import SelectWidget from "./SelectWidget"; import TextareaWidget from "./TextareaWidget"; -const Widgets = { - AltDateTimeWidget, - AltDateWidget, - CheckboxesWidget, - CheckboxWidget, - DateTimeWidget, - DateWidget, - PasswordWidget, - RadioWidget, - RangeWidget, - SelectWidget, - TextareaWidget, -}; +export function generateWidgets< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>(): RegistryWidgetsType { + return { + AltDateTimeWidget, + AltDateWidget, + CheckboxesWidget, + CheckboxWidget, + DateTimeWidget, + DateWidget, + PasswordWidget, + RadioWidget, + RangeWidget, + SelectWidget, + TextareaWidget, + }; +} -export default Widgets; +export default generateWidgets();