Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Generic support for Chakra UI #3327

Merged
merged 4 commits into from Jan 6, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -30,6 +30,7 @@ should change the heading of the (upcoming) version to include a major version b
- 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 `CheckboxWidget` to get the `required` state of the checkbox from the `schemaRequiresTrueValue()` utility function rather than the `required` prop, fixing [#3317](https://github.com/rjsf-team/react-jsonschema-form/issues/3317)
- 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/core
- 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)
Expand Down
29 changes: 17 additions & 12 deletions packages/chakra-ui/src/AddButton/AddButton.tsx
@@ -1,16 +1,21 @@
import React from "react";
import { IconButtonProps } from "@rjsf/utils";
import {
FormContextType,
IconButtonProps,
RJSFSchema,
StrictRJSFSchema,
} from "@rjsf/utils";
import { Button } from "@chakra-ui/react";
import { AddIcon } from "@chakra-ui/icons";

const AddButton: React.ComponentType<IconButtonProps> = ({
uiSchema,
registry,
...props
}) => (
<Button leftIcon={<AddIcon />} {...props}>
Add Item
</Button>
);

export default AddButton;
export default function AddButton<
T = any,
S extends StrictRJSFSchema = RJSFSchema,
F extends FormContextType = any
>({ uiSchema, registry, ...props }: IconButtonProps<T, S, F>) {
return (
<Button leftIcon={<AddIcon />} {...props}>
Add Item
</Button>
);
}
15 changes: 12 additions & 3 deletions packages/chakra-ui/src/AltDateTimeWidget/AltDateTimeWidget.tsx
@@ -1,12 +1,21 @@
import React from "react";

import _AltDateWidget from "../AltDateWidget";
import { WidgetProps } from "@rjsf/utils";
import {
FormContextType,
RJSFSchema,
StrictRJSFSchema,
WidgetProps,
} from "@rjsf/utils";

const AltDateTimeWidget = (props: WidgetProps) => {
function AltDateTimeWidget<
T = any,
S extends StrictRJSFSchema = RJSFSchema,
F extends FormContextType = any
>(props: WidgetProps<T, S, F>) {
const { AltDateWidget } = props.registry.widgets;
return <AltDateWidget {...props} showTime />;
};
}

AltDateTimeWidget.defaultProps = {
..._AltDateWidget.defaultProps,
Expand Down
85 changes: 48 additions & 37 deletions packages/chakra-ui/src/AltDateWidget/AltDateWidget.tsx
@@ -1,8 +1,11 @@
import React, { MouseEvent, useEffect, useState } from "react";
import {
DateObject,
FormContextType,
pad,
parseDateString,
RJSFSchema,
StrictRJSFSchema,
toDateString,
WidgetProps,
} from "@rjsf/utils";
Expand All @@ -16,6 +19,31 @@ const rangeOptions = (start: number, stop: number) => {
return options;
};

function DateElement<
Copy link
Contributor Author

@nickgros nickgros Dec 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This component was originally created within the AltDateWidget component, so I extracted it to prevent potential performance issues where the component may be excessively re-rendered.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should do this for other themes?

T = any,
S extends StrictRJSFSchema = RJSFSchema,
F extends FormContextType = any
>(props: WidgetProps<T, S, F>) {
const { SelectWidget } = props.registry.widgets;
const value = props.value ? props.value : undefined;
return (
<SelectWidget
{...props}
label={""}
className="form-control"
onChange={(elemValue: WidgetProps<T, S, F>) =>
props.select(props.type, elemValue)
}
options={{
enumOptions: rangeOptions(props.range[0], props.range[1]),
}}
placeholder={props.type}
schema={{ type: "integer" } as S}
value={value}
/>
);
}

interface AltDateStateType extends DateObject {
[x: string]: number | undefined;
}
Expand All @@ -26,7 +54,11 @@ const readyForChange = (state: AltDateStateType) => {
);
};

const AltDateWidget = (props: any) => {
function AltDateWidget<
T = any,
S extends StrictRJSFSchema = RJSFSchema,
F extends FormContextType = any
>(props: WidgetProps<T, S, F>) {
const {
autofocus,
disabled,
Expand All @@ -40,7 +72,6 @@ const AltDateWidget = (props: any) => {
showTime,
value,
} = props;
const { SelectWidget } = registry.widgets;
const [state, setState] = useState(parseDateString(value, showTime));
useEffect(() => {
setState(parseDateString(value, showTime));
Expand Down Expand Up @@ -96,47 +127,27 @@ const AltDateWidget = (props: any) => {
return data;
};

const renderDateElement = (elemProps: WidgetProps) => {
const value = elemProps.value ? elemProps.value : undefined;
return (
<SelectWidget
{...elemProps}
label={undefined}
className="form-control"
onChange={(elemValue: WidgetProps) =>
elemProps.select(elemProps.type, elemValue)
}
options={{
enumOptions: rangeOptions(elemProps.range[0], elemProps.range[1]),
}}
placeholder={elemProps.type}
schema={{ type: "integer" }}
value={value}
/>
);
};

return (
<Box>
<Box display="flex" flexWrap="wrap" alignItems="center" justify="center">
{dateElementProps().map((elemProps: any, i) => {
const elemId = id + "_" + elemProps.type;
return (
<Box key={elemId} mr="2" mb="2">
{renderDateElement({
...props,
...elemProps,
autofocus: autofocus && i === 0,
disabled,
id: elemId,
name: id,
onBlur,
onFocus,
readonly,
registry,
select: handleChange,
value: elemProps.value < 0 ? "" : elemProps.value,
})}
<DateElement<T, S, F>
{...props}
{...elemProps}
autofocus={autofocus && i === 0}
disabled={disabled}
id={elemId}
name={id}
onBlur={onBlur}
onFocus={onFocus}
readonly={readonly}
registry={registry}
select={handleChange}
value={elemProps.value < 0 ? "" : elemProps.value}
/>
</Box>
);
})}
Expand All @@ -160,7 +171,7 @@ const AltDateWidget = (props: any) => {
</Box>
</Box>
);
};
}

AltDateWidget.defaultProps = {
autofocus: false,
Expand Down
@@ -1,8 +1,17 @@
import React, { useMemo } from "react";
import { Box, ButtonGroup, HStack } from "@chakra-ui/react";
import { ArrayFieldTemplateItemType } from "@rjsf/utils";
import {
ArrayFieldTemplateItemType,
FormContextType,
RJSFSchema,
StrictRJSFSchema,
} from "@rjsf/utils";

const ArrayFieldItemTemplate = (props: ArrayFieldTemplateItemType) => {
export default function ArrayFieldItemTemplate<
T = any,
S extends StrictRJSFSchema = RJSFSchema,
F extends FormContextType = any
>(props: ArrayFieldTemplateItemType<T, S, F>) {
const {
children,
disabled,
Expand Down Expand Up @@ -69,6 +78,4 @@ const ArrayFieldItemTemplate = (props: ArrayFieldTemplateItemType) => {
)}
</HStack>
);
};

export default ArrayFieldItemTemplate;
}
46 changes: 27 additions & 19 deletions packages/chakra-ui/src/ArrayFieldTemplate/ArrayFieldTemplate.tsx
Expand Up @@ -5,9 +5,16 @@ import {
getUiOptions,
ArrayFieldTemplateItemType,
ArrayFieldTemplateProps,
StrictRJSFSchema,
RJSFSchema,
FormContextType,
} from "@rjsf/utils";

const ArrayFieldTemplate = (props: ArrayFieldTemplateProps) => {
export default function ArrayFieldTemplate<
T = any,
S extends StrictRJSFSchema = RJSFSchema,
F extends FormContextType = any
>(props: ArrayFieldTemplateProps<T, S, F>) {
const {
canAdd,
disabled,
Expand All @@ -21,23 +28,24 @@ const ArrayFieldTemplate = (props: ArrayFieldTemplateProps) => {
schema,
title,
} = props;
const uiOptions = getUiOptions(uiSchema);
const ArrayFieldDescriptionTemplate =
getTemplate<"ArrayFieldDescriptionTemplate">(
"ArrayFieldDescriptionTemplate",
registry,
uiOptions
);
const ArrayFieldItemTemplate = getTemplate<"ArrayFieldItemTemplate">(
const uiOptions = getUiOptions<T, S, F>(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 },
Expand All @@ -62,9 +70,11 @@ const ArrayFieldTemplate = (props: ArrayFieldTemplateProps) => {
<Grid key={`array-item-list-${idSchema.$id}`}>
<GridItem>
{items.length > 0 &&
items.map(({ key, ...itemProps }: ArrayFieldTemplateItemType) => (
<ArrayFieldItemTemplate key={key} {...itemProps} />
))}
items.map(
({ key, ...itemProps }: ArrayFieldTemplateItemType<T, S, F>) => (
<ArrayFieldItemTemplate key={key} {...itemProps} />
)
)}
</GridItem>
{canAdd && (
<GridItem justifySelf={"flex-end"}>
Expand All @@ -82,6 +92,4 @@ const ArrayFieldTemplate = (props: ArrayFieldTemplateProps) => {
</Grid>
</Box>
);
};

export default ArrayFieldTemplate;
}
20 changes: 14 additions & 6 deletions packages/chakra-ui/src/BaseInputTemplate/BaseInputTemplate.tsx
@@ -1,9 +1,19 @@
import * as React from "react";
import { FormControl, FormLabel, Input } from "@chakra-ui/react";
import { getInputProps, WidgetProps } from "@rjsf/utils";
import {
FormContextType,
getInputProps,
RJSFSchema,
StrictRJSFSchema,
WidgetProps,
} from "@rjsf/utils";
import { getChakra } from "../utils";

const BaseInputTemplate = (props: WidgetProps) => {
export default function BaseInputTemplate<
T = any,
S extends StrictRJSFSchema = RJSFSchema,
F extends FormContextType = any
>(props: WidgetProps<T, S, F>) {
const {
id,
type,
Expand All @@ -23,7 +33,7 @@ const BaseInputTemplate = (props: WidgetProps) => {
disabled,
registry,
} = props;
const inputProps = getInputProps(schema, type, options);
const inputProps = getInputProps<T, S, F>(schema, type, options);
const chakraProps = getChakra({ uiSchema });
const { schemaUtils } = registry;

Expand Down Expand Up @@ -78,6 +88,4 @@ const BaseInputTemplate = (props: WidgetProps) => {
) : null}
</FormControl>
);
};

export default BaseInputTemplate;
}
20 changes: 14 additions & 6 deletions packages/chakra-ui/src/CheckboxWidget/CheckboxWidget.tsx
@@ -1,9 +1,19 @@
import React from "react";
import { Checkbox, FormControl, Text } from "@chakra-ui/react";
import { WidgetProps, schemaRequiresTrueValue } from "@rjsf/utils";
import {
WidgetProps,
schemaRequiresTrueValue,
StrictRJSFSchema,
RJSFSchema,
FormContextType,
} from "@rjsf/utils";
import { getChakra } from "../utils";

const CheckboxWidget = (props: WidgetProps) => {
export default function CheckboxWidget<
T = any,
S extends StrictRJSFSchema = RJSFSchema,
F extends FormContextType = any
>(props: WidgetProps<T, S, F>) {
const {
id,
value,
Expand All @@ -20,7 +30,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<S>(schema);

const _onChange = ({
target: { checked },
Expand All @@ -47,6 +57,4 @@ const CheckboxWidget = (props: WidgetProps) => {
</Checkbox>
</FormControl>
);
};

export default CheckboxWidget;
}