Skip to content

Commit

Permalink
fix: Generic support for Chakra UI (#3327)
Browse files Browse the repository at this point in the history
* Generic support for Chakra UI
- Partially fix #3072
- Extract ChakraIconButton to its own file to abstract away React.memo
- Refactor AltDateWidget to extract DateElement component

* Changes from code review

* Update packages/chakra-ui/src/IconButton/index.ts

Co-authored-by: Heath C <51679588+heath-freenome@users.noreply.github.com>
  • Loading branch information
nickgros and heath-freenome committed Jan 6, 2023
1 parent 51a6517 commit db14094
Show file tree
Hide file tree
Showing 32 changed files with 512 additions and 283 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -33,6 +33,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<
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;
}

0 comments on commit db14094

Please sign in to comment.